]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mapobjects/teleporters.qc
Merge branch 'master' into Mario/status_effects_extended
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mapobjects / teleporters.qc
1 #include "teleporters.qh"
2
3 #if defined(CSQC)
4 #elif defined(MENUQC)
5 #elif defined(SVQC)
6         #include <common/constants.qh>
7         #include <common/deathtypes/all.qh>
8         #include <common/gamemodes/_mod.qh>
9         #include <common/mapobjects/subs.qh>
10         #include <common/stats.qh>
11         #include <common/turrets/sv_turrets.qh>
12         #include <common/util.qh>
13         #include <common/vehicles/all.qh>
14         #include <common/weapons/_all.qh>
15         #include <lib/warpzone/common.qh>
16         #include <lib/warpzone/server.qh>
17         #include <lib/warpzone/util_server.qh>
18         #include <server/anticheat.qh>
19         #include <server/weapons/csqcprojectile.qh>
20 #endif
21
22 #ifdef SVQC
23 float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax)
24 {
25         if (IS_PLAYER(player) && !IS_DEAD(player))
26         {
27                 TDEATHLOOP(org)
28                 {
29                 #ifdef SVQC
30                         if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
31                 #endif
32                                 if(IS_PLAYER(head))
33                                         if(!IS_DEAD(head))
34                                                 return 1;
35                 }
36         }
37         return 0;
38 }
39
40 void trigger_teleport_link(entity this);
41
42 void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax)
43 {
44         TDEATHLOOP(player.origin)
45         {
46                 if (IS_PLAYER(player) && GetResource(player, RES_HEALTH) >= 1)
47                 {
48                         if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
49                         {
50                                 if(IS_PLAYER(head))
51                                         if(GetResource(head, RES_HEALTH) >= 1)
52                                                 ++tdeath_hit;
53                                 Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, DMG_NOWEP, head.origin, '0 0 0');
54                         }
55                 }
56                 else // dead bodies and monsters gib themselves instead of telefragging
57                         Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, DMG_NOWEP, telefragger.origin, '0 0 0');
58         }
59 }
60
61 void spawn_tdeath(vector v0, entity e, vector v)
62 {
63         tdeath(e, e, e, '0 0 0', '0 0 0');
64 }
65 #endif
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 #ifdef SVQC
80         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
81         {
82                 if(teleporter.pushltime < time) // only show one teleport effect per teleporter per 0.2 seconds, for better fps
83                 {
84                         if(tflags & TELEPORT_FLAG_SOUND)
85                         {
86                                 string thesound = SND(TELEPORT);
87                                 if(teleporter.noise != "")
88                                 {
89                                         RandomSelection_Init();
90                                         FOREACH_WORD(teleporter.noise, true,
91                                         {
92                                                 RandomSelection_AddString(it, 1, 1);
93                                         });
94                                         thesound = RandomSelection_chosen_string;
95                                 }
96                                 _sound (player, CH_TRIGGER, thesound, VOL_BASE, ATTEN_NORM);
97                         }
98                         if(tflags & TELEPORT_FLAG_PARTICLES)
99                         {
100                                 Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1);
101                                 Send_Effect(EFFECT_TELEPORT, to + v_forward * 32, '0 0 0', 1);
102                         }
103                         teleporter.pushltime = time + 0.2;
104                 }
105         }
106 #endif
107
108         // Relocate the player
109         // assuming to allows PL_MIN to PL_MAX box and some more
110 #ifdef SVQC
111         from = player.origin;
112         setorigin(player, to);
113         player.oldorigin = to; // don't undo the teleport by unsticking
114         player.angles = to_angles;
115         if (IS_BOT_CLIENT(player))
116         {
117                 player.v_angle = player.angles;
118                 bot_aim_reset(player);
119         }
120         player.fixangle = true;
121         player.velocity = to_velocity;
122         BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
123
124         makevectors(player.angles);
125         Reset_ArcBeam(player, v_forward);
126         UpdateCSQCProjectileAfterTeleport(player);
127         UpdateItemAfterTeleport(player);
128 #elif defined(CSQC)
129         from = player.origin;
130         setorigin(player, to);
131         player.angles = to_angles;
132         player.velocity = to_velocity;
133         UNSET_ONGROUND(player);
134         player.iflags |= IFLAG_TELEPORTED | IFLAG_V_ANGLE | IFLAG_ANGLES;
135         player.csqcmodel_teleported = 1;
136         player.v_angle = to_angles;
137
138         if(player == csqcplayer) // not for anything but the main player
139         {
140                 setproperty(VF_ANGLES, player.angles);
141                 setproperty(VF_CL_VIEWANGLES, player.angles);
142         }
143 #endif
144
145 #ifdef SVQC
146         if(IS_PLAYER(player))
147         {
148                 if((tflags & TELEPORT_FLAG_TDEATH) && player.takedamage && !IS_DEAD(player)
149                         && !g_race && !g_cts && (autocvar_g_telefrags || (tflags & TELEPORT_FLAG_FORCE_TDEATH))
150                         && !(round_handler_IsActive() && !round_handler_IsRoundStarted()))
151                 {
152                         tdeath(player, teleporter, telefragger, telefragmin, telefragmax);
153                 }
154
155                 // player no longer is on ground
156                 UNSET_ONGROUND(player);
157
158                 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
159                 player.oldvelocity = player.velocity;
160
161                 // reset tracking of who pushed you into a hazard (for kill credit)
162                 if(teleporter.owner)
163                 {
164                         player.pusher = teleporter.owner;
165                         player.pushltime = time + autocvar_g_maxpushtime;
166                         player.istypefrag = PHYS_INPUT_BUTTON_CHAT(player);
167                 }
168                 else
169                 {
170                         player.pushltime = 0;
171                         player.istypefrag = 0;
172                 }
173
174                 player.lastteleporttime = time;
175                 player.lastteleport_origin = from;
176         }
177 #endif
178 }
179
180 entity Simple_TeleportPlayer(entity teleporter, entity player)
181 {
182         vector locout;
183         entity e = NULL;
184
185         // Find the output teleporter
186         if(teleporter.enemy)
187         {
188                 e = teleporter.enemy;
189         }
190         else
191         {
192                 // sorry CSQC, random stuff ain't gonna happen
193 #ifdef SVQC
194                 RandomSelection_Init();
195                 FOREACH_ENTITY_STRING(targetname, teleporter.target,
196                 {
197                         bool p = true;
198                         if(STAT(TELEPORT_TELEFRAG_AVOID, player))
199                         {
200                         #ifdef SVQC
201                                 locout = it.origin + '0 0 1' * (1 - player.mins.z - 24);
202                         #elif defined(CSQC)
203                                 locout = it.origin + '0 0 1' * (1 - player.mins.z - 24);
204                         #endif
205                                 if(check_tdeath(player, locout, '0 0 0', '0 0 0'))
206                                         p = false;
207                         }
208                         RandomSelection_AddEnt(it, (it.cnt ? it.cnt : 1), p);
209                 });
210                 e = RandomSelection_chosen_ent;
211 #endif
212         }
213
214 #ifdef SVQC
215         if(!e) { sprint(player, "Teleport destination vanished. Sorry... please complain to the mapper.\n"); }
216 #elif defined(CSQC)
217         if(!e) { LOG_INFO("Teleport destination could not be found from CSQC."); }
218 #endif
219
220         makevectors(e.mangle);
221
222         if(e.speed)
223                 if(vdist(player.velocity, >, e.speed))
224                         player.velocity = normalize(player.velocity) * max(0, e.speed);
225
226         if(STAT(TELEPORT_MAXSPEED, player))
227                 if(vdist(player.velocity, >, STAT(TELEPORT_MAXSPEED, player)))
228                         player.velocity = normalize(player.velocity) * max(0, STAT(TELEPORT_MAXSPEED, player));
229
230         locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
231
232         TeleportPlayer(teleporter, player, locout, e.mangle, v_forward * vlen(player.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
233
234         return e;
235 }
236
237 void teleport_findtarget(entity this)
238 {
239         bool istrigger = (this.solid == SOLID_TRIGGER);
240
241         int n = 0;
242         for(entity e = NULL; (e = find(e, targetname, this.target)); )
243         {
244                 ++n;
245 #ifdef SVQC
246                 if(e.move_movetype == MOVETYPE_NONE)
247                 {
248                         entity tracetest_ent = spawn();
249                         setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
250                         tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
251                         waypoint_spawnforteleporter(this, e.origin, 0, tracetest_ent);
252                         delete(tracetest_ent);
253                 }
254                 if(e.classname != "info_teleport_destination")
255                         LOG_INFO("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work.");
256 #endif
257         }
258
259         if(n == 0)
260         {
261                 // no dest!
262                 objerror (this, "Teleporter with nonexistent target");
263                 return;
264         }
265         else if(n == 1)
266         {
267                 // exactly one dest - bots love that
268                 this.enemy = find(NULL, targetname, this.target);
269         }
270         else
271         {
272                 // have to use random selection every single time
273                 this.enemy = NULL;
274         }
275
276         // now enable touch
277         if(istrigger)
278                 settouch(this, Teleport_Touch);
279 #ifdef SVQC
280         if(istrigger)
281                 trigger_teleport_link(this);
282 #endif
283 }
284
285 entity Teleport_Find(vector mi, vector ma)
286 {
287         IL_EACH(g_teleporters, WarpZoneLib_BoxTouchesBrush(mi, ma, it, NULL),
288         {
289                 return it;
290         });
291         return NULL;
292 }
293
294 void WarpZone_PostTeleportPlayer_Callback(entity pl)
295 {
296 #ifdef SVQC
297         makevectors(pl.angles);
298         Reset_ArcBeam(pl, v_forward);
299         UpdateCSQCProjectileAfterTeleport(pl);
300         UpdateItemAfterTeleport(pl);
301     if (IS_PLAYER(pl)) anticheat_fixangle(pl);
302 #endif
303         // "disown" projectiles after teleport
304         if(pl.owner)
305         if(pl.owner == pl.realowner)
306         {
307         #ifdef SVQC
308                 if(!(pl.flags & FL_PROJECTILE))
309         #elif defined(CSQC)
310                 if(!(pl.flags & BIT(15))) // FL_PROJECTILE
311         #endif
312                         LOG_INFO("A non-projectile got through a warpzone and its owner cleared. It's a ", pl.classname, ".");
313                 pl.owner = NULL;
314         }
315         if(IS_PLAYER(pl))
316         {
317                 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
318         #ifdef SVQC
319                 pl.oldvelocity = pl.velocity;
320         #endif
321         }
322 }