Xonotic
server.qc
Go to the documentation of this file.
1 #include "server.qh"
2 
3 #include "common.qh"
4 #if defined(CSQC)
5 #elif defined(MENUQC)
6 #elif defined(SVQC)
7  #include <common/constants.qh>
8  #include <common/net_linked.qh>
11  #include <common/util.qh>
12  #include <common/weapons/_all.qh>
13  #include <common/stats.qh>
14  #include <server/utils.qh>
15  #include <server/weapons/common.qh>
16 #endif
17 
18 #ifdef SVQC
19 bool autocvar_sv_warpzone_allow_selftarget;
20 #endif
21 
22 #ifdef WARPZONELIB_KEEPDEBUG
23 #define WARPZONELIB_REMOVEHACK
24 #endif
25 
26 // for think function
31 
32 // for all entities
37 
38 #define WarpZone_StoreProjectileData(e_) MACRO_BEGIN \
39  entity e = e_; \
40  e.warpzone_oldorigin = e.origin; \
41  e.warpzone_oldvelocity = e.velocity; \
42  e.warpzone_oldangles = e.angles; \
43  MACRO_END
44 
45 void WarpZone_TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity)
46 {
47 #ifdef SVQC
48  player.lastteleport_origin = player.origin;
49  player.lastteleporttime = time;
50 #endif
51  setorigin(player, to); // NOTE: this also aborts the move, when this is called by touch
52  player.angles = to_angles;
53 #ifdef SVQC
54  player.oldorigin = to; // for DP's unsticking
55  player.fixangle = true;
56  if (IS_BOT_CLIENT(player))
57  {
58  // FIXME find a way to smooth view's angles change for bots too
59  player.v_angle = player.angles;
60  bot_aim_reset(player);
61  }
62 #endif
63  player.velocity = to_velocity;
64 
65  BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
66 
67  if(IS_PLAYER(player))
68  BITCLR_ASSIGN(player.flags, FL_ONGROUND);
69 
71 }
72 
73 #ifdef SVQC
74 bool WarpZone_Teleported_Send(entity this, entity to, int sf)
75 {
76  WriteHeader(MSG_ENTITY, ENT_CLIENT_WARPZONE_TELEPORTED);
77  WriteVector(MSG_ENTITY, this.angles);
78  return true;
79 }
80 #endif
81 
82 float WarpZone_Teleport(entity wz, entity player, float f0, float f1)
83 {
84  vector o0, a0, v0, o1, a1, v1, o10;
85 
86  o0 = player.origin + player.view_ofs;
87  v0 = player.velocity;
88  a0 = player.angles;
89 
90  o10 = o1 = WarpZone_TransformOrigin(wz, o0);
91  v1 = WarpZone_TransformVelocity(wz, v0);
92  if (!IS_NOT_A_CLIENT(player))
93  a1 = WarpZone_TransformVAngles(wz, PHYS_INPUT_ANGLES(player));
94  else
95  a1 = WarpZone_TransformAngles(wz, a0);
96 
97  if(f0 != 0 || f1 != 0)
98  {
99  // retry last move but behind the warpzone!
100  // we must first go back as far as we can, then forward again, to not cause double touch events!
101 
102  tracebox(o1 - player.view_ofs + v1 * frametime * f1, player.mins, player.maxs, o1 - player.view_ofs + v1 * frametime * f0, MOVE_WORLDONLY, player);
103  {
104  entity own;
105  own = player.owner;
106  player.owner = NULL;
107  tracebox(trace_endpos, player.mins, player.maxs, o1 - player.view_ofs + v1 * frametime * f1, MOVE_NORMAL, player); // this should get us through the warpzone
108  player.owner = own;
109  }
110  o1 = trace_endpos + player.view_ofs;
111 
112  float d, dv, md;
113  md = max(vlen(player.mins), vlen(player.maxs));
114  d = WarpZone_TargetPlaneDist(wz, o1);
115  dv = WarpZone_TargetPlaneDist(wz, v1);
116  if(d < 0)
117  o1 = o1 - v1 * (d / dv);
118  }
119 
120  // put him out of solid
121  tracebox(o1 - player.view_ofs, player.mins, player.maxs, o1 - player.view_ofs, MOVE_NOMONSTERS, player);
122  if(trace_startsolid)
123  {
124  setorigin(player, o1 - player.view_ofs);
125  if(WarpZoneLib_MoveOutOfSolid(player))
126  {
127  o1 = player.origin + player.view_ofs;
128  setorigin(player, o0 - player.view_ofs);
129  }
130  else
131  {
132  LOG_INFO("would have to put player in solid, won't do that");
133  setorigin(player, o0 - player.view_ofs);
134  return 0;
135  }
136  }
137 
138  // do the teleport
139  WarpZone_RefSys_Add(player, wz);
140  WarpZone_TeleportPlayer(wz, player, o1 - player.view_ofs, a1, v1);
142  player.warpzone_teleport_time = time;
143  player.warpzone_teleport_finishtime = time;
144  player.warpzone_teleport_zone = wz;
145 
146 #ifdef SVQC
147  // prevent further teleports back
148  float dt = (o1 - o10) * v1 * (1 / (v1 * v1));
149  if(dt < PHYS_INPUT_FRAMETIME)
150  player.warpzone_teleport_finishtime += PHYS_INPUT_FRAMETIME - dt;
151 #endif
152 
153 #ifndef WARPZONE_USE_FIXANGLE
154  #ifdef SVQC
155  if(IS_VEHICLE(player) && player.owner)
156  player = player.owner; // hax
157  if(IS_PLAYER(player))
158  {
159  // instead of fixangle, send the transform to the client for smoother operation
160  player.fixangle = false;
161 
162  entity ts = new(warpzone_teleported);
163  setmodel(ts, MDL_Null);
164  setSendEntity(ts, WarpZone_Teleported_Send);
165  ts.SendFlags = 0xFFFFFF;
166  ts.drawonlytoclient = player;
167  setthink(ts, SUB_Remove);
168  ts.nextthink = time + 1;
169  ts.owner = player;
170  ts.enemy = wz;
171  ts.effects = EF_NODEPTHTEST;
172  ts.angles = wz.warpzone_transform;
173  }
174  #elif defined(CSQC)
175  setproperty(VF_CL_VIEWANGLES, WarpZone_TransformVAngles(wz, getpropertyvec(VF_CL_VIEWANGLES)));
176  //if(checkextension("DP_CSQC_ROTATEMOVES"))
177  //CL_RotateMoves(wz.warpzone_transform);
178  #endif
179 #endif
180 
181  return 1;
182 }
183 
184 void WarpZone_Touch(entity this, entity toucher)
185 {
186  if(toucher.classname == "trigger_warpzone")
187  return;
188 
189  if(time <= toucher.warpzone_teleport_finishtime) // already teleported this frame
190  return;
191 
192  // FIXME needs a better check to know what is safe to teleport and what not
193  if(toucher.move_movetype == MOVETYPE_NONE || toucher.move_movetype == MOVETYPE_FOLLOW || toucher.tag_entity)
194  return;
195 
196  if(WarpZoneLib_ExactTrigger_Touch(this, toucher))
197  return;
198 
199  if(WarpZone_PlaneDist(this, toucher.origin + toucher.view_ofs) >= 0) // wrong side of the trigger_warpzone (don't teleport yet)
200  return;
201 
202  float f;
203  // number of frames we need to go back:
204  // dist = 16*sqrt(2) qu
205  // dist ~ 24 qu
206  // 24 qu = v*t
207  // 24 qu = v*frametime*n
208  // n = 24 qu/(v*frametime)
209  // for clients go only one frame though, may be too irritating otherwise
210  // but max 0.25 sec = 0.25/frametime frames
211  // 24/(0.25/frametime)
212  // 96*frametime
213  float d;
214  d = 24 + max(vlen(toucher.mins), vlen(toucher.maxs));
215  if(IS_NOT_A_CLIENT(toucher))
216  f = -d / bound(frametime * d * 1, frametime * vlen(toucher.velocity), d);
217  else
218  f = -1;
219  if(WarpZone_Teleport(this, toucher, f, 0))
220  {
221 #ifdef SVQC
222  SUB_UseTargets_SkipTargets(this, toucher, toucher, BIT(1) | BIT(3)); // use toucher too?
223  SUB_UseTargets_SkipTargets(this.enemy, toucher, toucher, BIT(1) | BIT(2)); // use toucher too?
224 #endif
225  }
226  else
227  {
228  LOG_TRACE("WARPZONE FAIL AHAHAHAHAH))");
229  }
230 }
231 
232 #ifdef SVQC
233 bool WarpZone_Send(entity this, entity to, int sendflags)
234 {
235  WriteHeader(MSG_ENTITY, ENT_CLIENT_WARPZONE);
236 
237  // we must send this flag for clientside to match properly too
238  int f = 0;
239  if(this.warpzone_isboxy)
240  BITSET_ASSIGN(f, 1);
241  if(this.warpzone_fadestart)
242  BITSET_ASSIGN(f, 2);
243  if(this.origin != '0 0 0')
244  BITSET_ASSIGN(f, 4);
245  WriteByte(MSG_ENTITY, f);
246 
247  // we need THESE to render the warpzone (and cull properly)...
248  if(f & 4)
249  {
250  WriteCoord(MSG_ENTITY, this.origin.x);
251  WriteCoord(MSG_ENTITY, this.origin.y);
252  WriteCoord(MSG_ENTITY, this.origin.z);
253  }
254 
255  WriteShort(MSG_ENTITY, this.modelindex);
256  WriteCoord(MSG_ENTITY, this.mins.x);
257  WriteCoord(MSG_ENTITY, this.mins.y);
258  WriteCoord(MSG_ENTITY, this.mins.z);
259  WriteCoord(MSG_ENTITY, this.maxs.x);
260  WriteCoord(MSG_ENTITY, this.maxs.y);
261  WriteCoord(MSG_ENTITY, this.maxs.z);
262  WriteByte(MSG_ENTITY, bound(1, this.scale * 16, 255));
263 
264  // we need THESE to calculate the proper transform
265  WriteCoord(MSG_ENTITY, this.warpzone_origin.x);
266  WriteCoord(MSG_ENTITY, this.warpzone_origin.y);
267  WriteCoord(MSG_ENTITY, this.warpzone_origin.z);
268  WriteCoord(MSG_ENTITY, this.warpzone_angles.x);
269  WriteCoord(MSG_ENTITY, this.warpzone_angles.y);
270  WriteCoord(MSG_ENTITY, this.warpzone_angles.z);
271  WriteCoord(MSG_ENTITY, this.warpzone_targetorigin.x);
272  WriteCoord(MSG_ENTITY, this.warpzone_targetorigin.y);
273  WriteCoord(MSG_ENTITY, this.warpzone_targetorigin.z);
274  WriteCoord(MSG_ENTITY, this.warpzone_targetangles.x);
275  WriteCoord(MSG_ENTITY, this.warpzone_targetangles.y);
276  WriteCoord(MSG_ENTITY, this.warpzone_targetangles.z);
277 
278  if(f & 2)
279  {
280  WriteShort(MSG_ENTITY, this.warpzone_fadestart);
281  WriteShort(MSG_ENTITY, this.warpzone_fadeend);
282  }
283 
284  return true;
285 }
286 
287 bool WarpZone_Camera_Send(entity this, entity to, int sendflags)
288 {
289  int f = 0;
290  WriteHeader(MSG_ENTITY, ENT_CLIENT_WARPZONE_CAMERA);
291 
292  if(this.warpzone_fadestart)
293  BITSET_ASSIGN(f, 2);
294  if(this.origin != '0 0 0')
295  BITSET_ASSIGN(f, 4);
296  WriteByte(MSG_ENTITY, f);
297 
298  // we need THESE to render the warpzone (and cull properly)...
299  if(f & 4)
300  {
301  WriteCoord(MSG_ENTITY, this.origin.x);
302  WriteCoord(MSG_ENTITY, this.origin.y);
303  WriteCoord(MSG_ENTITY, this.origin.z);
304  }
305 
306  WriteShort(MSG_ENTITY, this.modelindex);
307  WriteCoord(MSG_ENTITY, this.mins.x);
308  WriteCoord(MSG_ENTITY, this.mins.y);
309  WriteCoord(MSG_ENTITY, this.mins.z);
310  WriteCoord(MSG_ENTITY, this.maxs.x);
311  WriteCoord(MSG_ENTITY, this.maxs.y);
312  WriteCoord(MSG_ENTITY, this.maxs.z);
313  WriteByte(MSG_ENTITY, bound(1, this.scale * 16, 255));
314 
315  // we need THESE to calculate the proper transform
316  WriteCoord(MSG_ENTITY, this.enemy.origin.x);
317  WriteCoord(MSG_ENTITY, this.enemy.origin.y);
318  WriteCoord(MSG_ENTITY, this.enemy.origin.z);
319  WriteCoord(MSG_ENTITY, this.enemy.angles.x);
320  WriteCoord(MSG_ENTITY, this.enemy.angles.y);
321  WriteCoord(MSG_ENTITY, this.enemy.angles.z);
322 
323  if(f & 2)
324  {
325  WriteShort(MSG_ENTITY, this.warpzone_fadestart);
326  WriteShort(MSG_ENTITY, this.warpzone_fadeend);
327  }
328 
329  return true;
330 }
331 
332 #ifdef WARPZONELIB_KEEPDEBUG
333 float WarpZone_CheckProjectileImpact(entity player)
334 {
335  vector o0, v0;
336 
337  o0 = player.origin + player.view_ofs;
338  v0 = player.velocity;
339 
340  // if we teleported shortly before, abort
341  if(time <= player.warpzone_teleport_finishtime + 0.1)
342  return 0;
343 
344  // if player hit a warpzone, abort
345  entity wz;
346  wz = WarpZone_Find(o0 + player.mins, o0 + player.maxs);
347  if(!wz)
348  return 0;
349 
350 #ifdef WARPZONELIB_REMOVEHACK
351  LOG_INFO("impactfilter found something - and it no longer gets handled correctly - please tell divVerent whether anything behaves broken now");
352 #else
353  LOG_INFO("impactfilter found something - and it even gets handled correctly - please tell divVerent that this code apparently gets triggered again");
354 #endif
355  LOG_INFO("Entity type: ", player.classname);
356  LOG_INFO("Origin: ", vtos(player.origin));
357  LOG_INFO("Velocity: ", vtos(player.velocity));
358 
359 #ifdef WARPZONELIB_REMOVEHACK
360  return 0;
361 #else
362  // retry previous move
363  setorigin(player, player.warpzone_oldorigin);
364  player.velocity = player.warpzone_oldvelocity;
365  if(WarpZone_Teleport(wz, player, 0, 1))
366  {
367  SUB_UseTargets_SkipTargets(wz, player, player, BIT(1) | BIT(3));
368  SUB_UseTargets_SkipTargets(wz.enemy, player, player, BIT(1) | BIT(2));
369  }
370  else
371  {
372  setorigin(player, o0 - player.view_ofs);
373  player.velocity = v0;
374  }
375 
376  return +1;
377 #endif
378 }
379 #endif
380 #endif
381 
383 {
384  if(toucher.classname == "trigger_warpzone")
385  return true;
386 
387  // no further impacts if we teleported this frame!
388  // this is because even if we did teleport, the engine still may raise
389  // touch events for the previous location
390  // engine now aborts moves on teleport, so this SHOULD not happen any more
391  // but if this is called from TouchAreaGrid of the projectile moving,
392  // then this won't do
393  if(time == this.warpzone_teleport_time)
394  return true;
395 
396 #ifdef SVQC
397 #ifdef WARPZONELIB_KEEPDEBUG
398  // this SEEMS to not happen at the moment, but if it did, it would be more reliable
399  {
400  float save_dpstartcontents;
401  float save_dphitcontents;
402  float save_dphitq3surfaceflags;
403  string save_dphittexturename;
404  float save_allsolid;
405  float save_startsolid;
406  float save_fraction;
407  vector save_endpos;
408  vector save_plane_normal;
409  float save_plane_dist;
410  entity save_ent;
411  float save_inopen;
412  float save_inwater;
413  save_dpstartcontents = trace_dpstartcontents;
414  save_dphitcontents = trace_dphitcontents;
415  save_dphitq3surfaceflags = trace_dphitq3surfaceflags;
416  save_dphittexturename = trace_dphittexturename;
417  save_allsolid = trace_allsolid;
418  save_startsolid = trace_startsolid;
419  save_fraction = trace_fraction;
420  save_endpos = trace_endpos;
421  save_plane_normal = trace_plane_normal;
422  save_plane_dist = trace_plane_dist;
423  save_ent = trace_ent;
424  save_inopen = trace_inopen;
425  save_inwater = trace_inwater;
426  float f = WarpZone_CheckProjectileImpact(this);
427  if (f) return (f > 0);
428  trace_dpstartcontents = save_dpstartcontents;
429  trace_dphitcontents = save_dphitcontents;
430  trace_dphitq3surfaceflags = save_dphitq3surfaceflags;
431  trace_dphittexturename = save_dphittexturename;
432  trace_allsolid = save_allsolid;
433  trace_startsolid = save_startsolid;
434  trace_fraction = save_fraction;
435  trace_endpos = save_endpos;
436  trace_plane_normal = save_plane_normal;
437  trace_plane_dist = save_plane_dist;
438  trace_ent = save_ent;
439  trace_inopen = save_inopen;
440  trace_inwater = save_inwater;
441  }
442 #endif
443 
445  return true;
446 #endif
447 
448  return false;
449 }
450 
451 #ifdef SVQC
452 
453 void WarpZone_InitStep_FindOriginTarget(entity this)
454 {
455  if(this.killtarget != "")
456  {
457  this.aiment = find(NULL, targetname, this.killtarget);
458  if(this.aiment == NULL)
459  {
460  error("Warp zone with nonexisting killtarget");
461  return;
462  }
463  this.killtarget = string_null;
464  }
465 }
466 
467 void WarpZonePosition_InitStep_FindTarget(entity this)
468 {
469  if(this.target == "")
470  {
471  error("Warp zone position with no target");
472  return;
473  }
474  this.enemy = find(NULL, targetname, this.target);
475  if(this.enemy == NULL)
476  {
477  error("Warp zone position with nonexisting target");
478  return;
479  }
480  if(this.enemy.aiment)
481  {
482  // already is positioned
483  error("Warp zone position targeting already oriented warpzone");
484  return;
485  }
486  this.enemy.aiment = this;
487 }
488 
489 void WarpZoneCamera_Think(entity this)
490 {
491  if(this.warpzone_save_origin != this.origin
492  || this.warpzone_save_angles != this.angles
493  || this.warpzone_save_eorigin != this.enemy.origin
494  || this.warpzone_save_eangles != this.enemy.angles)
495  {
496  WarpZone_Camera_SetUp(this, this.enemy.origin, this.enemy.angles);
497  this.warpzone_save_origin = this.origin;
498  this.warpzone_save_angles = this.angles;
499  this.warpzone_save_eorigin = this.enemy.origin;
500  this.warpzone_save_eangles = this.enemy.angles;
501  }
502  this.nextthink = time;
503 }
504 
505 void WarpZoneCamera_InitStep_FindTarget(entity this)
506 {
507  entity e;
508  float i;
509  if(this.target == "")
510  {
511  error("Camera with no target");
512  return;
513  }
514  this.enemy = NULL;
515  for(e = NULL, i = 0; (e = find(e, targetname, this.target)); )
516  if(random() * ++i < 1)
517  this.enemy = e;
518  if(this.enemy == NULL)
519  {
520  error("Camera with nonexisting target");
521  return;
522  }
524  WarpZone_Camera_SetUp(this, this.enemy.origin, this.enemy.angles);
525  this.SendFlags = 0xFFFFFF;
526  if(this.spawnflags & 1)
527  {
528  setthink(this, WarpZoneCamera_Think);
529  this.nextthink = time;
530  }
531  else
532  this.nextthink = 0;
533 }
534 
535 void WarpZone_InitStep_UpdateTransform(entity this)
536 {
537  vector org, ang, norm, point;
538  float area;
539  vector tri, a, b, c, n;
540  float i_s, i_t, n_t;
541  string tex;
542 
543  org = this.origin;
544  if(org == '0 0 0')
545  org = 0.5 * (this.mins + this.maxs);
546 
547  norm = point = '0 0 0';
548  area = 0;
549  for(i_s = 0; ; ++i_s)
550  {
551  tex = getsurfacetexture(this, i_s);
552  if (!tex)
553  break; // this is beyond the last one
554  if(tex == "textures/common/trigger" || tex == "trigger")
555  continue;
556  n_t = getsurfacenumtriangles(this, i_s);
557  for(i_t = 0; i_t < n_t; ++i_t)
558  {
559  tri = getsurfacetriangle(this, i_s, i_t);
560  a = getsurfacepoint(this, i_s, tri.x);
561  b = getsurfacepoint(this, i_s, tri.y);
562  c = getsurfacepoint(this, i_s, tri.z);
563  n = cross(c - a, b - a);
564  area = area + vlen(n);
565  norm = norm + n;
566  point = point + vlen(n) * (a + b + c);
567  }
568  }
569  if(area > 0)
570  {
571  norm = norm * (1 / area);
572  point = point * (1 / (3 * area));
573  if(vdist(norm, <, 0.99))
574  {
575  LOG_INFO("trigger_warpzone near ", vtos(this.aiment.origin), " is nonplanar. BEWARE.");
576  area = 0; // no autofixing in this case
577  }
578  norm = normalize(norm);
579  }
580 
581  ang = '0 0 0';
582  if(this.aiment)
583  {
584  org = this.aiment.origin;
585  ang = this.aiment.angles;
586  if(area > 0)
587  {
588  org = org - ((org - point) * norm) * norm; // project to plane
589  vector forward, right, up;
590  MAKE_VECTORS(ang, forward, right, up);
591  if(norm * forward < 0)
592  {
593  LOG_INFO("Position target of trigger_warpzone near ", vtos(this.aiment.origin), " points into trigger_warpzone. BEWARE.");
594  norm = -1 * norm;
595  }
596  ang = vectoangles2(norm, up); // keep rotation, but turn exactly against plane
597  ang.x = -ang.x;
598  if(norm * forward < 0.99)
599  LOG_INFO("trigger_warpzone near ", vtos(this.aiment.origin), " has been turned to match plane orientation (", vtos(this.aiment.angles), " -> ", vtos(ang));
600  if(vdist(org - this.aiment.origin, >, 0.5))
601  LOG_INFO("trigger_warpzone near ", vtos(this.aiment.origin), " has been moved to match the plane (", vtos(this.aiment.origin), " -> ", vtos(org), ").");
602  }
603  }
604  else if(area > 0)
605  {
606  org = point;
607  ang = vectoangles(norm);
608  ang.x = -ang.x;
609  }
610  else
611  error("cannot infer origin/angles for this warpzone, please use a killtarget or a trigger_warpzone_position");
612 
613  this.warpzone_origin = org;
614  this.warpzone_angles = ang;
615 }
616 
617 void WarpZone_InitStep_ClearTarget(entity this)
618 {
619  if(this.enemy)
620  this.enemy.enemy = NULL;
621  this.enemy = NULL;
622 }
623 
624 void WarpZone_InitStep_FindTarget(entity this)
625 {
626  float i;
627  entity e, e2;
628 
629  if(this.enemy)
630  return;
631 
632  // this way only one of the two ents needs to target
633  if(this.target != "")
634  {
635  if(!autocvar_sv_warpzone_allow_selftarget)
636  this.enemy = this; // so the if(!e.enemy) check also skips this, saves one IF
637 
638  e2 = NULL;
639  for(e = NULL, i = 0; (e = find(e, targetname, this.target)); )
640  if(!e.enemy)
641  if(e.classname == this.classname) // possibly non-warpzones may use the same targetname!
642  if(random() * ++i < 1)
643  e2 = e;
644  if(!e2)
645  {
646  this.enemy = NULL;
647  error("Warpzone with non-existing target");
648  return;
649  }
650  this.enemy = e2;
651  e2.enemy = this;
652  }
653 }
654 
655 void WarpZone_Think(entity this);
656 void WarpZone_InitStep_FinalizeTransform(entity this)
657 {
658  if(!this.enemy || this.enemy.enemy != this)
659  {
660  error("Invalid warp zone detected. Killed.");
661  return;
662  }
663 
665  WarpZone_SetUp(this, this.warpzone_origin, this.warpzone_angles, this.enemy.warpzone_origin, this.enemy.warpzone_angles);
666  settouch(this, WarpZone_Touch);
667  this.SendFlags = 0xFFFFFF;
668  if(this.spawnflags & 1)
669  {
670  setthink(this, WarpZone_Think);
671  this.nextthink = time;
672  }
673  else
674  this.nextthink = 0;
675 }
676 
677 float warpzone_initialized;
678 //entity warpzone_first;
679 entity warpzone_position_first;
680 entity warpzone_camera_first;
681 .entity warpzone_next;
682 spawnfunc(misc_warpzone_position)
683 {
684  // "target", "angles", "origin"
685  this.warpzone_next = warpzone_position_first;
686  warpzone_position_first = this;
687 }
688 spawnfunc(trigger_warpzone_position)
689 {
690  spawnfunc_misc_warpzone_position(this);
691 }
692 spawnfunc(trigger_warpzone)
693 {
694  // warp zone entities must have:
695  // "killtarget" pointing to a target_position with a direction arrow
696  // that points AWAY from the warp zone, and that is inside
697  // the warp zone trigger
698  // "target" pointing to an identical warp zone at another place in
699  // the map, with another killtarget to designate its
700  // orientation
701 
702  if(!this.scale)
703  this.scale = this.modelscale;
704  if(!this.scale)
705  this.scale = 1;
706  string m;
707  m = this.model;
709  if(m != "")
710  {
711  precache_model(m);
712  _setmodel(this, m); // no precision needed
713  }
714  setorigin(this, this.origin);
715  if(this.scale)
716  setsize(this, this.mins * this.scale, this.maxs * this.scale);
717  else
718  setsize(this, this.mins, this.maxs);
719  setSendEntity(this, WarpZone_Send);
720  this.SendFlags = 0xFFFFFF;
722  this.warpzone_next = warpzone_first;
723  warpzone_first = this;
724 
725  IL_PUSH(g_warpzones, this);
726 }
727 spawnfunc(func_camera)
728 {
729  if(!this.scale)
730  this.scale = this.modelscale;
731  if(!this.scale)
732  this.scale = 1;
733  if(this.model != "")
734  {
735  precache_model(this.model);
736  _setmodel(this, this.model); // no precision needed
737  }
738  setorigin(this, this.origin);
739  if(this.scale)
740  setsize(this, this.mins * this.scale, this.maxs * this.scale);
741  else
742  setsize(this, this.mins, this.maxs);
743  if(!this.solid)
744  this.solid = SOLID_BSP;
745  else if(this.solid < 0)
746  this.solid = SOLID_NOT;
747  setSendEntity(this, WarpZone_Camera_Send);
748  this.SendFlags = 0xFFFFFF;
749  this.warpzone_next = warpzone_camera_first;
750  warpzone_camera_first = this;
751 }
752 void WarpZones_Reconnect()
753 {
754  for(entity e = warpzone_first; e; e = e.warpzone_next)
755  WarpZone_InitStep_ClearTarget(e);
756  for(entity e = warpzone_first; e; e = e.warpzone_next)
757  WarpZone_InitStep_FindTarget(e);
758  for(entity e = warpzone_camera_first; e; e = e.warpzone_next)
759  WarpZoneCamera_InitStep_FindTarget(e);
760  for(entity e = warpzone_first; e; e = e.warpzone_next)
761  WarpZone_InitStep_FinalizeTransform(e);
762 }
763 
764 void WarpZone_Think(entity this)
765 {
766  if(this.warpzone_save_origin != this.origin
767  || this.warpzone_save_angles != this.angles
768  || this.warpzone_save_eorigin != this.enemy.origin
769  || this.warpzone_save_eangles != this.enemy.angles)
770  {
771  WarpZone_InitStep_UpdateTransform(this);
772  WarpZone_InitStep_UpdateTransform(this.enemy);
773  WarpZone_InitStep_FinalizeTransform(this);
774  WarpZone_InitStep_FinalizeTransform(this.enemy);
775  this.warpzone_save_origin = this.origin;
776  this.warpzone_save_angles = this.angles;
777  this.warpzone_save_eorigin = this.enemy.origin;
778  this.warpzone_save_eangles = this.enemy.angles;
779  }
780  this.nextthink = time;
781 }
782 
783 void WarpZone_StartFrame()
784 {
785  if (!warpzone_initialized)
786  {
787  warpzone_initialized = true;
788  for(entity e = warpzone_first; e; e = e.warpzone_next)
789  WarpZone_InitStep_FindOriginTarget(e);
790  for(entity e = warpzone_position_first; e; e = e.warpzone_next)
791  WarpZonePosition_InitStep_FindTarget(e);
792  for(entity e = warpzone_first; e; e = e.warpzone_next)
793  WarpZone_InitStep_UpdateTransform(e);
794  WarpZones_Reconnect();
796  }
797 
799  {
800  IL_EACH(g_projectiles, true,
801  {
803  });
804  }
805 
806 
807  FOREACH_CLIENT(true,
808  {
810  WarpZone_StoreProjectileData(it); // TODO: not actually needed
811 
812  if(IS_OBSERVER(it) || it.solid == SOLID_NOT)
813  if(IS_CLIENT(it)) // we don't care about it being a bot
814  {
815  // warpzones
817  entity e = WarpZone_Find(it.origin + it.mins, it.origin + it.maxs);
818  if (e)
819  if (!WarpZoneLib_ExactTrigger_Touch(e, it))
820  if (WarpZone_PlaneDist(e, it.origin + it.view_ofs) <= 0)
821  WarpZone_Teleport(e, it, -1, 0); // NOT triggering targets by this!
822  }
823 
824  // teleporters
825  if(it.teleportable)
826  {
827  entity ent = Teleport_Find(it.origin + it.mins, it.origin + it.maxs);
828  if (ent)
829  if (!WarpZoneLib_ExactTrigger_Touch(ent, it))
830  Simple_TeleportPlayer(ent, it); // NOT triggering targets by this!
831  }
832  }
833  });
834 }
835 
836 .float warpzone_reconnecting;
837 bool visible_to_some_client(entity ent)
838 {
839  FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && checkpvs(it.origin + it.view_ofs, ent),
840  {
841  return true;
842  });
843  return false;
844 }
845 void trigger_warpzone_reconnect_use(entity this, entity actor, entity trigger)
846 {
847  // NOTE: this matches for target, not targetname, but of course
848  // targetname must be set too on the other entities
849  for(entity e = warpzone_first; e; e = e.warpzone_next)
850  e.warpzone_reconnecting = ((this.target == "" || e.target == this.target) && !((this.spawnflags & 1) && (visible_to_some_client(e) || visible_to_some_client(e.enemy))));
851  for(entity e = warpzone_camera_first; e; e = e.warpzone_next)
852  e.warpzone_reconnecting = ((this.target == "" || e.target == this.target) && !((this.spawnflags & 1) && visible_to_some_client(e)));
853  for(entity e = warpzone_first; e; e = e.warpzone_next)
854  if(e.warpzone_reconnecting)
855  WarpZone_InitStep_ClearTarget(e);
856  for(entity e = warpzone_first; e; e = e.warpzone_next)
857  if(e.warpzone_reconnecting)
858  WarpZone_InitStep_FindTarget(e);
859  for(entity e = warpzone_camera_first; e; e = e.warpzone_next)
860  if(e.warpzone_reconnecting)
861  WarpZoneCamera_InitStep_FindTarget(e);
862  for(entity e = warpzone_first; e; e = e.warpzone_next)
863  if(e.warpzone_reconnecting || e.enemy.warpzone_reconnecting)
864  WarpZone_InitStep_FinalizeTransform(e);
865 }
866 
867 spawnfunc(trigger_warpzone_reconnect)
868 {
869  this.use = trigger_warpzone_reconnect_use;
870 }
871 
872 spawnfunc(target_warpzone_reconnect)
873 {
874  spawnfunc_trigger_warpzone_reconnect(this); // both names make sense here :(
875 }
876 
877 void WarpZone_PlayerPhysics_FixVAngle(entity this)
878 {
879 #ifndef WARPZONE_DONT_FIX_VANGLE
880  if(IS_REAL_CLIENT(this))
881  if(this.v_angle.z <= 360) // if not already adjusted
882  if(time - CS(this).ping * 0.001 < this.warpzone_teleport_time)
883  {
885  this.v_angle_z += 720; // mark as adjusted
886  }
887 #endif
888 }
889 
890 #endif
const float SOLID_NOT
Definition: csprogsdefs.qc:244
#define IL_EACH(this, cond, body)
float MOVETYPE_NONE
Definition: progsdefs.qc:246
string string_null
Definition: nil.qh:9
float trace_plane_dist
Definition: csprogsdefs.qc:39
vector warpzone_save_eorigin
Definition: server.qc:29
string killtarget
Definition: subs.qh:48
float warpzone_fadestart
Definition: common.qh:21
float trace_dphitq3surfaceflags
float WarpZone_Teleport(entity wz, entity player, float f0, float f1)
Definition: server.qc:82
const float VF_CL_VIEWANGLES
Definition: csprogsdefs.qc:194
void WarpZoneLib_ExactTrigger_Init(entity this)
Definition: util_server.qc:12
#define IS_CLIENT(v)
Definition: utils.qh:13
float modelindex
Definition: csprogsdefs.qc:91
void bot_aim_reset(entity this)
Definition: aim.qc:170
float FL_ONGROUND
Definition: progsdefs.qc:240
bool WarpZoneLib_MoveOutOfSolid(entity e)
Definition: common.qc:826
bool WarpZoneLib_ExactTrigger_Touch(entity this, entity toucher)
Definition: common.qc:798
float trace_dphitcontents
entity() spawn
vector WarpZone_TransformOrigin(entity wz, vector v)
Definition: common.qc:499
void SUB_UseTargets_SkipTargets(entity this, entity actor, entity trigger, int skiptargets)
Definition: triggers.qc:368
const float MOVE_NORMAL
Definition: csprogsdefs.qc:252
ClientState CS(Client this)
Definition: state.qh:47
#define FOREACH_CLIENT(cond, body)
Definition: utils.qh:49
vector v_angle
Definition: progsdefs.qc:161
vector maxs
Definition: csprogsdefs.qc:113
#define IS_OBSERVER(v)
Definition: utils.qh:11
float checkpvs(vector viewpos, entity viewee)
entity Teleport_Find(vector mi, vector ma)
Definition: teleporters.qc:285
vector warpzone_oldorigin
Definition: server.qc:33
vector warpzone_origin
Definition: common.qh:14
entity to
Definition: self.qh:96
spawnfunc(info_player_attacker)
Definition: sv_assault.qc:283
origin
Definition: ent_cs.qc:114
#define WarpZone_StoreProjectileData(e_)
Definition: server.qc:38
bool WarpZone_Projectile_Touch_ImpactFilter_Callback(entity this, entity toucher)
Definition: common.qc:150
float ping
Definition: main.qh:138
const float EF_NODEPTHTEST
Definition: csprogsdefs.qc:304
float warpzone_warpzones_exist
Definition: common.qh:9
entity warpzone_teleport_zone
Definition: server.qc:36
float effects
Definition: csprogsdefs.qc:111
float spawnflags
Definition: progsdefs.qc:191
void WarpZone_PostInitialize_Callback()
Definition: main.qc:456
vector warpzone_targetangles
Definition: common.qh:18
string model
Definition: csprogsdefs.qc:108
float WarpZone_PlaneDist(entity wz, vector v)
Definition: common.qc:489
#define BITSET_ASSIGN(a, b)
Definition: common.qh:104
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1
Definition: all.inc:654
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
const int EF_TELEPORT_BIT
entity trace_ent
Definition: csprogsdefs.qc:40
vector warpzone_save_origin
Definition: server.qc:27
vector warpzone_oldangles
Definition: server.qc:33
#define setmodel(this, m)
Definition: model.qh:26
float warpzone_fadeend
Definition: common.qh:22
string trace_dphittexturename
float trace_inopen
Definition: csprogsdefs.qc:41
void WarpZone_TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity)
Definition: server.qc:45
entity Simple_TeleportPlayer(entity teleporter, entity player)
Definition: teleporters.qc:180
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition: bits.qh:8
#define BITCLR_ASSIGN(a, b)
Definition: common.qh:101
entity enemy
Definition: sv_ctf.qh:143
const float MOVE_NOMONSTERS
Definition: csprogsdefs.qc:253
vector mins
Definition: csprogsdefs.qc:113
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
vector WarpZone_TransformAngles(entity wz, vector v)
Definition: common.qc:509
void WarpZone_SetUp(entity e, vector my_org, vector my_ang, vector other_org, vector other_ang)
Definition: common.qc:66
vector WarpZone_TransformVAngles(entity wz, vector ang)
Definition: common.qc:514
float warpzone_teleport_finishtime
Definition: server.qc:35
float warpzone_teleport_time
Definition: server.qc:34
entity WarpZone_Find(vector mi, vector ma)
Definition: common.qc:157
void WarpZone_RefSys_Add(entity me, entity wz)
Definition: common.qc:714
#define setSendEntity(e, f)
Definition: self.qh:97
#define NULL
Definition: post.qh:17
float frametime
Definition: csprogsdefs.qc:17
float trace_dpstartcontents
#define LOG_INFO(...)
Definition: log.qh:70
vector warpzone_save_eangles
Definition: server.qc:30
float WarpZone_TargetPlaneDist(entity wz, vector v)
Definition: common.qc:494
vector trace_endpos
Definition: csprogsdefs.qc:37
#define IS_NOT_A_CLIENT(v)
was: (clienttype(v) == CLIENTTYPE_NOTACLIENT)
Definition: utils.qh:19
float nextthink
Definition: csprogsdefs.qc:121
vector warpzone_targetorigin
Definition: common.qh:17
vector warpzone_oldvelocity
Definition: server.qc:33
float scale
Definition: projectile.qc:14
vector WarpZone_TransformVelocity(entity wz, vector v)
Definition: common.qc:504
vector(float skel, float bonenum) _skel_get_boneabs_hidden
#define IS_VEHICLE(v)
Definition: utils.qh:22
IntrusiveList g_projectiles
Definition: common.qh:46
vector warpzone_angles
Definition: common.qh:15
const float SOLID_BSP
Definition: csprogsdefs.qc:248
void WarpZone_Camera_SetUp(entity e, vector my_org, vector my_ang)
Definition: common.qc:95
IntrusiveList g_warpzones
Definition: common.qh:6
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
float MOVETYPE_FOLLOW
#define LOG_TRACE(...)
Definition: log.qh:81
void WarpZone_PostTeleportPlayer_Callback(entity pl)
Definition: teleporters.qc:294
float warpzone_isboxy
Definition: common.qh:12
float trace_inwater
Definition: csprogsdefs.qc:42
string targetname
Definition: progsdefs.qc:194
entity aiment
Definition: movetypes.qh:90
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition: utils.qh:15
float warpzone_cameras_exist
Definition: common.qh:10
#define cross(a, b)
Definition: vector.qh:25
entity int sendflags
Definition: self.qh:96
setorigin(ent, v)
#define setthink(e, f)
vector trace_plane_normal
Definition: csprogsdefs.qc:38
float WarpZone_Projectile_Touch(entity this, entity toucher)
Definition: server.qc:382
vector angles
Definition: csprogsdefs.qc:104
#define use
Definition: csprogsdefs.qh:50
float trace_startsolid
Definition: csprogsdefs.qc:35
void WarpZone_Touch(entity this, entity toucher)
Definition: server.qc:184
string target
Definition: progsdefs.qc:193
vector warpzone_save_angles
Definition: server.qc:28
#define BITXOR_ASSIGN(a, b)
Definition: common.qh:107
float MOVE_WORLDONLY
float trace_allsolid
Definition: csprogsdefs.qc:34
float time
Definition: csprogsdefs.qc:16
float modelscale
Definition: models.qh:3
float trace_fraction
Definition: csprogsdefs.qc:36
#define IS_PLAYER(v)
Definition: utils.qh:9
float solid
Definition: csprogsdefs.qc:99