]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/triggers/teleporters.qc
Merge branch 'master' into TimePath/deathtypes
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / triggers / teleporters.qc
1 #include "teleporters.qh"
2
3 #if defined(CSQC)
4 #elif defined(MENUQC)
5 #elif defined(SVQC)
6     #include "../../lib/warpzone/common.qh"
7     #include "../../lib/warpzone/util_server.qh"
8     #include "../../lib/warpzone/server.qh"
9     #include "../constants.qh"
10         #include "../triggers/subs.qh"
11     #include "../util.qh"
12     #include "../../server/weapons/csqcprojectile.qh"
13     #include "../../server/autocvars.qh"
14     #include "../../server/constants.qh"
15     #include "../../server/defs.qh"
16     #include "../deathtypes/all.qh"
17     #include "../turrets/sv_turrets.qh"
18     #include "../vehicles/all.qh"
19     #include "../mapinfo.qh"
20     #include "../../server/anticheat.qh"
21 #endif
22
23 #ifdef SVQC
24
25 float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax)
26 {
27         if (IS_PLAYER(player) && player.health >= 1)
28         {
29                 TDEATHLOOP(org)
30                 {
31                         if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
32                                 if(IS_PLAYER(head))
33                                         if(head.health >= 1)
34                                                 return 1;
35                 }
36         }
37         return 0;
38 }
39
40 void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax)
41 {
42         TDEATHLOOP(player.origin)
43         {
44                 if (IS_PLAYER(player) && player.health >= 1)
45                 {
46                         if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
47                         {
48                                 if(IS_PLAYER(head))
49                                         if(head.health >= 1)
50                                                 ++tdeath_hit;
51                                 Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, head.origin, '0 0 0');
52                         }
53                 }
54                 else // dead bodies and monsters gib themselves instead of telefragging
55                         Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, telefragger.origin, '0 0 0');
56         }
57 }
58
59 void spawn_tdeath(vector v0, entity e, vector v)
60 {
61         tdeath(e, e, e, '0 0 0', '0 0 0');
62 }
63
64 void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags)
65 {SELFPARAM();
66         entity telefragger;
67         vector from;
68
69         if(teleporter.owner)
70                 telefragger = teleporter.owner;
71         else
72                 telefragger = player;
73
74         makevectors (to_angles);
75
76         if(player.teleportable == TELEPORT_NORMAL) // don't play sounds or show particles for anything that isn't a player, maybe change later to block only observers
77         {
78                 if(self.pushltime < time) // only show one teleport effect per teleporter per 0.2 seconds, for better fps
79                 {
80                         if(tflags & TELEPORT_FLAG_SOUND)
81                                 sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM);
82                         if(tflags & TELEPORT_FLAG_PARTICLES)
83                         {
84                                 Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1);
85                                 Send_Effect(EFFECT_TELEPORT, to + v_forward * 32, '0 0 0', 1);
86                         }
87                         self.pushltime = time + 0.2;
88                 }
89         }
90
91         // Relocate the player
92         // assuming to allows PL_MIN to PL_MAX box and some more
93         from = player.origin;
94         setorigin (player, to);
95         player.oldorigin = to; // don't undo the teleport by unsticking
96         player.angles = to_angles;
97         player.fixangle = true;
98         player.velocity = to_velocity;
99         BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
100
101         makevectors(player.angles);
102         Reset_ArcBeam(player, v_forward);
103         UpdateCSQCProjectileAfterTeleport(player);
104
105         if(IS_PLAYER(player))
106         {
107                 if(tflags & TELEPORT_FLAG_TDEATH)
108                         if(player.takedamage && player.deadflag == DEAD_NO && !g_race && !g_cts && (autocvar_g_telefrags || (tflags & TELEPORT_FLAG_FORCE_TDEATH)))
109                                 tdeath(player, teleporter, telefragger, telefragmin, telefragmax);
110
111                 // player no longer is on ground
112                 player.flags &= ~FL_ONGROUND;
113
114                 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
115                 player.oldvelocity = player.velocity;
116
117                 // reset tracking of who pushed you into a hazard (for kill credit)
118                 if(teleporter.owner)
119                 {
120                         player.pusher = teleporter.owner;
121                         player.pushltime = time + autocvar_g_maxpushtime;
122                         player.istypefrag = player.BUTTON_CHAT;
123                 }
124                 else
125                 {
126                         player.pushltime = 0;
127                         player.istypefrag = 0;
128                 }
129
130                 player.lastteleporttime = time;
131         }
132 }
133
134 entity Simple_TeleportPlayer(entity teleporter, entity player)
135 {
136         vector locout;
137         entity e;
138         float p;
139
140         // Find the output teleporter
141         if(teleporter.enemy)
142         {
143                 e = teleporter.enemy;
144         }
145         else
146         {
147                 RandomSelection_Init();
148                 for(e = world; (e = find(e, targetname, teleporter.target)); )
149                 {
150                         p = 1;
151                         if(autocvar_g_telefrags_avoid)
152                         {
153                                 locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
154                                 if(check_tdeath(player, locout, '0 0 0', '0 0 0'))
155                                         p = 0;
156                         }
157                         RandomSelection_Add(e, 0, string_null, (e.cnt ? e.cnt : 1), p);
158                 }
159                 e = RandomSelection_chosen_ent;
160         }
161
162         if(!e) { sprint(player, "Teleport destination vanished. Sorry... please complain to the mapper.\n"); }
163
164         makevectors(e.mangle);
165
166         if(e.speed)
167                 if(vlen(player.velocity) > e.speed)
168                         player.velocity = normalize(player.velocity) * max(0, e.speed);
169
170         if(autocvar_g_teleport_maxspeed)
171                 if(vlen(player.velocity) > autocvar_g_teleport_maxspeed)
172                         player.velocity = normalize(player.velocity) * max(0, autocvar_g_teleport_maxspeed);
173
174         locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
175         TeleportPlayer(teleporter, player, locout, e.mangle, v_forward * vlen(player.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
176
177         return e;
178 }
179
180 void teleport_findtarget (void)
181 {SELFPARAM();
182         entity e;
183         float n;
184
185         n = 0;
186         for(e = world; (e = find(e, targetname, self.target)); )
187         {
188                 ++n;
189                 if(e.movetype == MOVETYPE_NONE)
190                         waypoint_spawnforteleporter(self, e.origin, 0);
191                 if(e.classname != "info_teleport_destination")
192                         LOG_INFO("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work.\n");
193         }
194
195         if(n == 0)
196         {
197                 // no dest!
198                 objerror ("Teleporter with nonexistant target");
199                 return;
200         }
201         else if(n == 1)
202         {
203                 // exactly one dest - bots love that
204                 self.enemy = find(e, targetname, self.target);
205         }
206         else
207         {
208                 // have to use random selection every single time
209                 self.enemy = world;
210         }
211
212         // now enable touch
213         self.touch = Teleport_Touch;
214 }
215
216 entity Teleport_Find(vector mi, vector ma)
217 {
218         entity e;
219         for(e = world; (e = find(e, classname, "trigger_teleport")); )
220                 if(WarpZoneLib_BoxTouchesBrush(mi, ma, e, world))
221                         return e;
222         return world;
223 }
224
225 void WarpZone_PostTeleportPlayer_Callback(entity pl)
226 {SELFPARAM();
227         makevectors(pl.angles);
228         Reset_ArcBeam(pl, v_forward);
229         UpdateCSQCProjectileAfterTeleport(pl);
230         {
231                 WITH(entity, self, pl, anticheat_fixangle());
232         }
233         // "disown" projectiles after teleport
234         if(pl.owner)
235         if(pl.owner == pl.realowner)
236         {
237                 if(!(pl.flags & FL_PROJECTILE))
238                         LOG_INFO("A non-projectile got through a warpzone and its owner cleared. It's a ", pl.classname, ".\n");
239                 pl.owner = world;
240         }
241         if(IS_PLAYER(pl))
242         {
243                 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
244                 pl.oldvelocity = pl.velocity;
245                 // reset teleport time tracking too (or multijump can cause insane speeds)
246                 pl.lastteleporttime = time;
247         }
248 }
249 #endif