Merge branch 'master' into Mario/vaporizer_damage
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_teleporters.qc
1 #include "t_teleporters.qh"
2
3 #if defined(CSQC)
4 #elif defined(MENUQC)
5 #elif defined(SVQC)
6     #include "../warpzonelib/common.qh"
7     #include "../warpzonelib/util_server.qh"
8     #include "../warpzonelib/server.qh"
9     #include "../common/constants.qh"
10     #include "../common/util.qh"
11     #include "weapons/csqcprojectile.qh"
12     #include "autocvars.qh"
13     #include "constants.qh"
14     #include "defs.qh"
15     #include "../common/deathtypes.qh"
16     #include "tturrets/include/turrets_early.qh"
17     #include "vehicles/vehicles_def.qh"
18     #include "../common/mapinfo.qh"
19     #include "anticheat.qh"
20 #endif
21
22 void trigger_teleport_use()
23 {
24         if(teamplay)
25                 self.team = activator.team;
26 }
27
28 float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax)
29 {
30         if (IS_PLAYER(player) && player.health >= 1)
31         {
32                 TDEATHLOOP(org)
33                 {
34                         if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
35                                 if(IS_PLAYER(head))
36                                         if(head.health >= 1)
37                                                 return 1;
38                 }
39         }
40         return 0;
41 }
42
43 void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax)
44 {
45         TDEATHLOOP(player.origin)
46         {
47                 if (IS_PLAYER(player) && player.health >= 1)
48                 {
49                         if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
50                         {
51                                 if(IS_PLAYER(head))
52                                         if(head.health >= 1)
53                                                 ++tdeath_hit;
54                                 Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG, head.origin, '0 0 0');
55                         }
56                 }
57                 else // dead bodies and monsters gib themselves instead of telefragging
58                         Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG, telefragger.origin, '0 0 0');
59         }
60 }
61
62 void spawn_tdeath(vector v0, entity e, vector v)
63 {
64         tdeath(e, e, e, '0 0 0', '0 0 0');
65 }
66
67 void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags)
68 {
69         entity telefragger;
70         vector from;
71
72         if(teleporter.owner)
73                 telefragger = teleporter.owner;
74         else
75                 telefragger = player;
76
77         makevectors (to_angles);
78
79         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
80         {
81                 if(self.pushltime < time) // only show one teleport effect per teleporter per 0.2 seconds, for better fps
82                 {
83                         if(tflags & TELEPORT_FLAG_SOUND)
84                                 sound (player, CH_TRIGGER, "misc/teleport.wav", VOL_BASE, ATTEN_NORM);
85                         if(tflags & TELEPORT_FLAG_PARTICLES)
86                         {
87                                 pointparticles(particleeffectnum("teleport"), player.origin, '0 0 0', 1);
88                                 pointparticles(particleeffectnum("teleport"), to + v_forward * 32, '0 0 0', 1);
89                         }
90                         self.pushltime = time + 0.2;
91                 }
92         }
93
94         // Relocate the player
95         // assuming to allows PL_MIN to PL_MAX box and some more
96         from = player.origin;
97         setorigin (player, to);
98         player.oldorigin = to; // don't undo the teleport by unsticking
99         player.angles = to_angles;
100         player.fixangle = true;
101         player.velocity = to_velocity;
102         BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
103
104         makevectors(player.angles);
105         Reset_ArcBeam(player, v_forward);
106         UpdateCSQCProjectileAfterTeleport(player);
107
108         if(IS_PLAYER(player))
109         {
110                 if(tflags & TELEPORT_FLAG_TDEATH)
111                         if(player.takedamage && player.deadflag == DEAD_NO && !g_race && !g_cts && (autocvar_g_telefrags || (tflags & TELEPORT_FLAG_FORCE_TDEATH)))
112                                 tdeath(player, teleporter, telefragger, telefragmin, telefragmax);
113
114                 // player no longer is on ground
115                 player.flags &= ~FL_ONGROUND;
116
117                 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
118                 player.oldvelocity = player.velocity;
119
120                 // reset tracking of who pushed you into a hazard (for kill credit)
121                 if(teleporter.owner)
122                 {
123                         player.pusher = teleporter.owner;
124                         player.pushltime = time + autocvar_g_maxpushtime;
125                         player.istypefrag = player.BUTTON_CHAT;
126                 }
127                 else
128                 {
129                         player.pushltime = 0;
130                         player.istypefrag = 0;
131                 }
132
133                 player.lastteleporttime = time;
134         }
135 }
136
137 entity Simple_TeleportPlayer(entity teleporter, entity player)
138 {
139         vector locout;
140         entity e;
141         float p;
142
143         // Find the output teleporter
144         if(teleporter.enemy)
145         {
146                 e = teleporter.enemy;
147         }
148         else
149         {
150                 RandomSelection_Init();
151                 for(e = world; (e = find(e, targetname, teleporter.target)); )
152                 {
153                         p = 1;
154                         if(autocvar_g_telefrags_avoid)
155                         {
156                                 locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
157                                 if(check_tdeath(player, locout, '0 0 0', '0 0 0'))
158                                         p = 0;
159                         }
160                         RandomSelection_Add(e, 0, string_null, (e.cnt ? e.cnt : 1), p);
161                 }
162                 e = RandomSelection_chosen_ent;
163         }
164
165         if(!e) { sprint(player, "Teleport destination vanished. Sorry... please complain to the mapper.\n"); }
166
167         makevectors(e.mangle);
168
169         if(e.speed)
170                 if(vlen(player.velocity) > e.speed)
171                         player.velocity = normalize(player.velocity) * max(0, e.speed);
172
173         if(autocvar_g_teleport_maxspeed)
174                 if(vlen(player.velocity) > autocvar_g_teleport_maxspeed)
175                         player.velocity = normalize(player.velocity) * max(0, autocvar_g_teleport_maxspeed);
176
177         locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
178         TeleportPlayer(teleporter, player, locout, e.mangle, v_forward * vlen(player.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
179
180         return e;
181 }
182
183 void Teleport_Touch (void)
184 {
185         entity oldself;
186         string s;
187
188         if (self.active != ACTIVE_ACTIVE)
189                 return;
190
191         if (!other.teleportable)
192                 return;
193
194         if(other.vehicle)
195         if(!other.vehicle.teleportable)
196                 return;
197
198         if(other.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
199                 return;
200
201         if(other.deadflag != DEAD_NO)
202                 return;
203
204         if(self.team)
205                 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
206                         return;
207
208         EXACTTRIGGER_TOUCH;
209
210         if(IS_PLAYER(other))
211                 RemoveGrapplingHook(other);
212
213         entity e;
214         e = Simple_TeleportPlayer(self, other);
215
216         activator = other;
217         s = self.target; self.target = string_null;
218         SUB_UseTargets();
219         if (!self.target) self.target = s;
220
221         oldself = self;
222         self = e;
223         SUB_UseTargets();
224         self = oldself;
225 }
226
227 void spawnfunc_info_teleport_destination (void)
228 {
229         self.classname = "info_teleport_destination";
230
231         self.mangle = self.angles;
232         self.angles = '0 0 0';
233
234         //setorigin (self, self.origin + '0 0 27');     // To fix a mappers' habit as old as Quake
235         setorigin (self, self.origin);
236
237         IFTARGETED
238         {
239         }
240         else
241                 objerror ("^3Teleport destination without a targetname");
242 }
243
244 void spawnfunc_misc_teleporter_dest (void)
245 {
246         spawnfunc_info_teleport_destination();
247 }
248
249 void spawnfunc_target_teleporter (void)
250 {
251         spawnfunc_info_teleport_destination();
252 }
253
254 void teleport_findtarget (void)
255 {
256         entity e;
257         float n;
258
259         n = 0;
260         for(e = world; (e = find(e, targetname, self.target)); )
261         {
262                 ++n;
263                 if(e.movetype == MOVETYPE_NONE)
264                         waypoint_spawnforteleporter(self, e.origin, 0);
265                 if(e.classname != "info_teleport_destination")
266                         print("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work.\n");
267         }
268
269         if(n == 0)
270         {
271                 // no dest!
272                 objerror ("Teleporter with nonexistant target");
273                 return;
274         }
275         else if(n == 1)
276         {
277                 // exactly one dest - bots love that
278                 self.enemy = find(e, targetname, self.target);
279         }
280         else
281         {
282                 // have to use random selection every single time
283                 self.enemy = world;
284         }
285
286         // now enable touch
287         self.touch = Teleport_Touch;
288 }
289
290 entity Teleport_Find(vector mi, vector ma)
291 {
292         entity e;
293         for(e = world; (e = find(e, classname, "trigger_teleport")); )
294                 if(WarpZoneLib_BoxTouchesBrush(mi, ma, e, world))
295                         return e;
296         return world;
297 }
298
299 void spawnfunc_trigger_teleport (void)
300 {
301         self.angles = '0 0 0';
302
303         EXACTTRIGGER_INIT;
304
305         self.active = ACTIVE_ACTIVE;
306
307         self.use = trigger_teleport_use;
308
309         // this must be called to spawn the teleport waypoints for bots
310         InitializeEntity(self, teleport_findtarget, INITPRIO_FINDTARGET);
311
312         if (self.target == "")
313         {
314                 objerror ("Teleporter with no target");
315                 return;
316         }
317
318         self.teleport_next = teleport_first;
319         teleport_first = self;
320 }
321
322 void WarpZone_PostTeleportPlayer_Callback(entity pl)
323 {
324         makevectors(pl.angles);
325         Reset_ArcBeam(pl, v_forward);
326         UpdateCSQCProjectileAfterTeleport(pl);
327         {
328                 entity oldself = self;
329                 self = pl;
330                 anticheat_fixangle();
331                 self = oldself;
332         }
333         // "disown" projectiles after teleport
334         if(pl.owner)
335         if(pl.owner == pl.realowner)
336         {
337                 if(!(pl.flags & FL_PROJECTILE))
338                         print("A non-projectile got through a warpzone and its owner cleared. It's a ", pl.classname, ".\n");
339                 pl.owner = world;
340         }
341         if(IS_PLAYER(pl))
342         {
343                 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
344                 pl.oldvelocity = pl.velocity;
345                 // reset teleport time tracking too (or multijump can cause insane speeds)
346                 pl.lastteleporttime = time;
347         }
348 }