Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
jumppads.qc
Go to the documentation of this file.
1#include "jumppads.qh"
2// TODO: split target_push and put it in the target folder
3#ifdef SVQC
5
6void trigger_push_use(entity this, entity actor, entity trigger)
7{
8 if(teamplay)
9 {
10 this.team = actor.team;
12 }
13}
14#endif
15
16REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_PUSH)
17REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_PUSH_VELOCITY)
18REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH)
19
20/*
21 trigger_push_calculatevelocity
22
23 Arguments:
24 org - origin of the object which is to be pushed
25 tgt - target entity (can be either a point or a model entity; if it is
26 the latter, its midpoint is used)
27 ht - jump height, measured from the higher one of org and tgt's midpoint
28 pushed_entity - object that is to be pushed
29
30 Returns: velocity for the jump
31 */
33{
34 float grav, sdist, zdist, vs, vz, jumpheight;
35 vector sdir, torg;
36
37 torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
38
39 grav = PHYS_GRAVITY(pushed_entity);
40 if(pushed_entity && pushed_entity.gravity)
41 grav *= pushed_entity.gravity;
42
43 // Q3 has frametime-dependent gravity, but its trigger_push velocity calculation doesn't account for that.
44 // This discrepancy can be simulated accurately which ensures that all entities will arrive
45 // where they would in Q3 with gravity 800 at 125fps, even if entity-specific gravity is applied.
46 // This can be hard-coded because we don't support the Q3 world.gravity field at this time.
47 // See physicsCPMA.cfg for maths and test results.
49 grav /= 750/800; // exact float, unlike 800/750
50
51 zdist = torg.z - org.z;
52 sdist = vlen(torg - org - zdist * '0 0 1');
53 sdir = normalize(torg - org - zdist * '0 0 1');
54
55 // how high do we need to push the player?
56 jumpheight = fabs(ht);
57 if(zdist > 0)
58 jumpheight += zdist;
59
60 /*
61 STOP.
62
63 You will not understand the following equations anyway...
64 But here is what I did to get them.
65
66 I used the functions
67
68 s(t) = t * vs
69 z(t) = t * vz - 1/2 grav t^2
70
71 and solved for:
72
73 s(ti) = sdist
74 z(ti) = zdist
75 max(z, ti) = jumpheight
76
77 From these three equations, you will find the three parameters vs, vz
78 and ti.
79 */
80
81 // push them so high...
82 vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)!
83
84 // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump!
85 if(ht < 0)
86 if(zdist < 0)
87 vz = -vz;
88
89 vector solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist"
90 // ALWAYS solvable because jumpheight >= zdist
91 if(!solution.z)
92 solution_y = solution.x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0)
93 if(zdist == 0)
94 solution_x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually)
95
96 float flighttime;
97 if(zdist < 0)
98 {
99 // down-jump
100 if(ht < 0)
101 {
102 // almost straight line type
103 // jump apex is before the jump
104 // we must take the larger one
105 flighttime = solution.y;
106 }
107 else
108 {
109 // regular jump
110 // jump apex is during the jump
111 // we must take the larger one too
112 flighttime = solution.y;
113 }
114 }
115 else
116 {
117 // up-jump
118 if(ht < 0)
119 {
120 // almost straight line type
121 // jump apex is after the jump
122 // we must take the smaller one
123 flighttime = solution.x;
124 }
125 else
126 {
127 // regular jump
128 // jump apex is during the jump
129 // we must take the larger one
130 flighttime = solution.y;
131 }
132 }
133 vs = sdist / flighttime;
134
135 // finally calculate the velocity
136 return sdir * vs + '0 0 1' * vz;
137}
138
139vector trigger_push_velocity_calculatevelocity(entity this, vector org, entity tgt, float speed, float count, entity pushed_entity, bool already_pushed)
140{
141 bool is_playerdir_xy = boolean(this.spawnflags & PUSH_VELOCITY_PLAYERDIR_XY);
142 bool is_add_xy = boolean(this.spawnflags & PUSH_VELOCITY_ADD_XY);
143 bool is_playerdir_z = boolean(this.spawnflags & PUSH_VELOCITY_PLAYERDIR_Z);
144 bool is_add_z = boolean(this.spawnflags & PUSH_VELOCITY_ADD_Z);
145 bool is_bidirectional_xy = boolean(this.spawnflags & PUSH_VELOCITY_BIDIRECTIONAL_XY);
146 bool is_bidirectional_z = boolean(this.spawnflags & PUSH_VELOCITY_BIDIRECTIONAL_Z);
147 bool is_clamp_negative_adds = boolean(this.spawnflags & PUSH_VELOCITY_CLAMP_NEGATIVE_ADDS);
148
149 vector sdir = normalize(vec2(pushed_entity.velocity));
150 float zdir = pushed_entity.velocity.z;
151 if(zdir != 0) zdir = copysign(1, zdir);
152
153 vector vs_tgt = '0 0 0';
154 float vz_tgt = 0;
155 if (!is_playerdir_xy || !is_playerdir_z)
156 {
157 vector vel_tgt = trigger_push_calculatevelocity(org, tgt, 0, pushed_entity);
158 vs_tgt = vec2(vel_tgt);
159 vz_tgt = vel_tgt.z;
160
161 // bidirectional jump pads do not play nicely with xonotic's jump pad targets
162 if (is_bidirectional_xy)
163 {
164 if (normalize(vs_tgt) * sdir < 0)
165 {
166 vs_tgt *= -1;
167 }
168 }
169
170 if (is_bidirectional_z)
171 {
172 if (signbit(vz_tgt) != signbit(zdir))
173 {
174 vz_tgt *= -1;
175 }
176 }
177 }
178
179 vector vs;
180 if (is_playerdir_xy)
181 {
182 vs = sdir * speed;
183 }
184 else
185 {
186 vs = vs_tgt;
187 }
188
189 float vz;
190 if (is_playerdir_z)
191 {
192 vz = zdir * count;
193 }
194 else
195 {
196 vz = vz_tgt;
197 }
198
199 if (is_add_xy)
200 {
201 vector vs_add = vec2(pushed_entity.velocity);
202 if (already_pushed)
203 {
204 vs = vs_add;
205 }
206 else
207 {
208 vs += vs_add;
209
210 if (is_clamp_negative_adds)
211 {
212 if ((normalize(vs) * sdir) < 0)
213 {
214 vs = '0 0 0';
215 }
216 }
217 }
218 }
219
220 if (is_add_z)
221 {
222 float vz_add = pushed_entity.velocity.z;
223 if (already_pushed)
224 {
225 vz = vz_add;
226 }
227 else
228 {
229 vz += vz_add;
230
231 if (is_clamp_negative_adds)
232 {
233 if (signbit(vz) != signbit(zdir))
234 {
235 vz = 0;
236 }
237 }
238 }
239 }
240
241 return vs + '0 0 1' * vz;
242}
243
244#ifdef SVQC
246{
247 bool found = false;
248 IL_EACH(g_moveables, it.last_pushed == this,
249 {
250 if(!WarpZoneLib_ExactTrigger_Touch(this, it, false))
251 it.last_pushed = NULL;
252 else
253 found = true;
254 });
255
256 if(found)
257 this.nextthink = time;
258 else
259 setthink(this, func_null);
260}
261#endif
262
263bool jumppad_push(entity this, entity targ, bool is_velocity_pad)
264{
265 if (!isPushable(targ))
266 return false;
267
268 vector org = targ.origin;
269
271 org = (this.absmin + this.absmax) * 0.5;
272
273 bool already_pushed = false;
274 if(is_velocity_pad) // remember velocity jump pads
275 {
276 if(this == targ.last_pushed || (targ.last_pushed && !STAT(Q3COMPAT, targ))) // if q3compat is active overwrite last stored jump pad, otherwise ignore
277 {
278 already_pushed = true;
279 }
280 else
281 {
282 targ.last_pushed = this; // may be briefly out of sync between client and server if client prediction is toggled
283
284 #ifdef SVQC
286 this.nextthink = time;
287 #endif
288 }
289 }
290
291 if(this.enemy)
292 {
293 if(!is_velocity_pad)
294 {
295 targ.velocity = trigger_push_calculatevelocity(org, this.enemy, this.height, targ);
296 }
297 else
298 {
299 targ.velocity = trigger_push_velocity_calculatevelocity(this, org, this.enemy, this.speed, this.count, targ, already_pushed);
300 }
301 }
302 else if(this.target && this.target != "")
303 {
304 entity e;
306 for(e = NULL; (e = find(e, targetname, this.target)); )
307 {
308 if(e.cnt)
309 RandomSelection_AddEnt(e, e.cnt, 1);
310 else
311 RandomSelection_AddEnt(e, 1, 1);
312 }
313 if(!is_velocity_pad)
314 {
316 }
317 else
318 {
319 targ.velocity = trigger_push_velocity_calculatevelocity(this, org, RandomSelection_chosen_ent, this.speed, this.count, targ, already_pushed);
320 }
321 }
322 else
323 {
324 if(!is_velocity_pad)
325 {
326 targ.velocity = this.movedir;
327 }
328 else
329 {
330#ifdef SVQC
331 objerror (this, "Jumppad with no target");
332#endif
333 return false;
334 }
335 }
336
337 if(!is_velocity_pad) UNSET_ONGROUND(targ);
338
339#ifdef CSQC
340 if (targ.flags & FL_PROJECTILE)
341 {
342 targ.angles = vectoangles (targ.velocity);
343 switch(targ.move_movetype)
344 {
345 case MOVETYPE_FLY:
347 targ.gravity = 1;
348 break;
351 targ.gravity = 1;
352 break;
353 }
354 }
355#endif
356
357#ifdef SVQC
358 if (IS_PLAYER(targ))
359 {
360 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
361 targ.oldvelocity = targ.velocity;
362
363 // prevent sound spam when a player hits the jumppad more than once
364 // or when a dead player gets stuck in the jumppad for some reason
365 if(!already_pushed && this.pushltime < time && !(IS_DEAD(targ) && targ.velocity == '0 0 0'))
366 {
367 if (Q3COMPAT_COMMON && this.classname == "target_push")
368 this.pushltime = time + 1.5;
369 else
370 {
371 // flash when activated
372 Send_Effect(EFFECT_JUMPPAD, targ.origin, targ.velocity, 1);
373 this.pushltime = time + 0.2;
374 }
375 _sound (targ, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
376 }
377
378 if(IS_REAL_CLIENT(targ) || IS_BOT_CLIENT(targ))
379 {
380 bool found = false;
381 for(int i = 0; i < targ.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
382 if(targ.(jumppadsused[i]) == this)
383 found = true;
384 if(!found)
385 {
386 targ.(jumppadsused[targ.jumppadcount % NUM_JUMPPADSUSED]) = this;
387 ++targ.jumppadcount;
388 }
389
390 if(IS_REAL_CLIENT(targ))
391 {
392 if(this.message)
393 centerprint(targ, this.message);
394 }
395 else
396 {
397 targ.lastteleporttime = time;
398 targ.lastteleport_origin = targ.origin;
399 }
400
401 if (!IS_DEAD(targ))
403 }
404 else
405 targ.jumppadcount = 1;
406
407 // reset tracking of who pushed you into a hazard (for kill credit)
408 targ.pushltime = 0;
409 targ.istypefrag = 0;
410 }
411
412 if(this.enemy.target)
413 SUB_UseTargets(this.enemy, targ, this);
414
415 if (targ.flags & FL_PROJECTILE)
416 {
417 targ.angles = vectoangles (targ.velocity);
418 targ.com_phys_gravity_factor = 1;
419 switch(targ.move_movetype)
420 {
421 case MOVETYPE_FLY:
423 targ.gravity = 1;
424 break;
427 targ.gravity = 1;
428 break;
429 }
431 }
432#endif
433
434 return true;
435}
436
438{
439 if (this.active == ACTIVE_NOT)
440 return;
441
442 if(this.team)
443 if(((this.spawnflags & INVERT_TEAMS) == 0) == (DIFF_TEAM(this, toucher)))
444 return;
445
447
448 noref bool success = jumppad_push(this, toucher, false);
449
450#ifdef SVQC
451 if (success && (this.spawnflags & PUSH_ONCE))
452 {
453 settouch(this, func_null);
454 setthink(this, SUB_Remove);
455 this.nextthink = time;
456 }
457#endif
458}
459
461{
462 if (this.active == ACTIVE_NOT)
463 return;
464
465 if(this.team && DIFF_TEAM(this, toucher))
466 return;
467
469
470 jumppad_push(this, toucher, true);
471}
472
473#ifdef SVQC
474void trigger_push_link(entity this);
476bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org)
477{
478 setorigin(tracetest_ent, org);
479 tracetoss(tracetest_ent, tracetest_ent);
481 return false;
482
483 if (!jp.height)
484 {
485 // since tracetoss starting from jumppad's origin often fails when target
486 // is very close to real destination, start it directly from target's
487 // origin instead
488 vector ofs = '0 0 0';
489 if (vdist(vec2(tracetest_ent.velocity), <, autocvar_sv_maxspeed))
490 ofs = stepheightvec;
491
492 tracetest_ent.velocity.z = 0;
493 setorigin(tracetest_ent, targ.origin + ofs);
494 tracetoss(tracetest_ent, tracetest_ent);
495 if (trace_startsolid && ofs.z)
496 {
497 setorigin(tracetest_ent, targ.origin + ofs * 0.5);
498 tracetoss(tracetest_ent, tracetest_ent);
499 if (trace_startsolid && ofs.z)
500 {
501 setorigin(tracetest_ent, targ.origin);
502 tracetoss(tracetest_ent, tracetest_ent);
504 return false;
505 }
506 }
507 }
508 tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent);
509 return true;
510}
511
513{
514 setorigin(tracetest_ent, org);
515 tracetoss(tracetest_ent, tracetest_ent);
516
518 return false;
519 if (trace_ent == item)
520 return true;
521
522 tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent);
523
524 if (trace_ent == item)
525 return true;
526
527 return false;
528}
529#endif
530
531#ifdef SVQC
532// get the push entity in cases where a non-push entity acts as the trigger of a jump pad
534{
535 // triggers could point to several pusher but each pusher would overwrite the effect of the previous one
536 // this means only one pusher has to be considered
537 // we still have to traverse the target list though since entities other than pushers could be in it
539 if(this.classname != "trigger_push")
540 {
541 for(entity t = ftoe(maxclients); (t = find(t, targetname, this.target)); )
542 {
543 if(t.classname == "target_push")
544 {
545 pusher = t;
546 break;
547 }
548 }
549
550 if(!pusher) objerror (this, "Jump pad with non-existent target");
551 }
552
553 if(pusher)
554 return pusher; // if the target field points to a target_push entity, return it
555 else
556 return this; // otherwise, return the original entity
557}
558
560{
561 // calculate a typical start point for the jump
562 vector org = (this.absmin + this.absmax) * 0.5;
563 org.z = this.absmax.z - PL_MIN_CONST.z - 7;
564 return org;
565}
566
568{
570 entity pusher = get_pusher_from_trigger(this); // function might get called from a non-push trigger entity
571
572 float grav = PHYS_GRAVITY(NULL);
573
574 entity t = pusher.enemy;
575 if (t)
576 {
577 entity e = spawn();
578 setsize(e, PL_MIN_CONST, PL_MAX_CONST);
581 vector v2 = trigger_push_calculatevelocity(endpos, t, pusher.height, e);
582 delete(e);
583 return (v.z + v2.z) / grav;
584 }
585 else if (!(pusher.target && pusher.target != ""))
586 {
587 if (!this.team)
588 {
589 vector v = pusher.movedir;
590
591 float t = v.z / grav;
592 float jump_height = 1/2 * grav * (t ** 2);
593 float remaining_height = org.z + jump_height - endpos.z;
594 float v2_z = sqrt(2 * grav * remaining_height);
595
596 return (v.z + v2_z) / grav;
597 }
598 }
599 return 0;
600}
601#endif
602
603// if (item != NULL) returns true if the item can be reached by using this jumppad, false otherwise
604// if (item == NULL) tests jumppad's trajectory and eventually spawns waypoints for it (return value doesn't matter)
605// NOTE: for simplicity's sake it improperly tests jumppad bboxes instead of bmodels
607{
608#ifdef SVQC
610 entity pusher = get_pusher_from_trigger(this); // function might get called from a non-push trigger entity
611#elif defined(CSQC)
612 entity pusher = this; // client-side code never calls this on a non-push entity
613#endif
614
615 if (pusher.target)
616 {
617 int n = 0;
618#ifdef SVQC
619 vector vel = '0 0 0';
620#endif
621 for(entity t = NULL; (t = find(t, targetname, pusher.target)); )
622 {
623 ++n;
624#ifdef SVQC
625 if(t.move_movetype != MOVETYPE_NONE)
626 continue;
627
628 // bots can't tell teamed jumppads from normal ones
629 if (this.team)
630 continue;
631
632 entity e = spawn();
633 setsize(e, PL_MIN_CONST, PL_MAX_CONST);
635 e.velocity = trigger_push_calculatevelocity(org, t, pusher.height, e);
636
637 vel = e.velocity;
638 vector best_target = '0 0 0';
639 vector best_org = '0 0 0';
640 vector best_vel = '0 0 0';
641 bool valid_best_target = false;
642 if (item)
643 {
645 {
646 delete(e);
647 return false;
648 }
649 }
650 else
651 {
652 // optimization: if horizontal velocity is 0 then target is not good since the trajectory
653 // will definitely go back to the jumppad (horizontal velocity of best_vel can't be 0 anyway)
654 if ((e.velocity.x != 0 || e.velocity.y != 0)
656 {
657 best_target = trace_endpos;
658 best_org = org;
659 best_vel = e.velocity;
660 valid_best_target = true;
661 }
662 }
663
664 vector new_org;
665 vector dist = t.origin - org;
666 if (dist.x || dist.y) // if not perfectly vertical
667 {
668 // test trajectory with different starting points, sometimes the trajectory
669 // starting from the jumppad origin can't reach the real destination
670 // and destination waypoint ends up near the jumppad itself
671 vector flatdir = normalize(dist - eZ * dist.z);
672 vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y));
673 new_org = org + ofs;
674
675 LABEL(new_test)
676 e.velocity = trigger_push_calculatevelocity(new_org, t, pusher.height, e);
677 if (item)
678 {
679 if (!trigger_push_testorigin_for_item(e, item, new_org))
680 {
681 delete(e);
682 return false;
683 }
684 }
685 else
686 {
687 vel = e.velocity;
688 if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed))
689 e.velocity = autocvar_sv_maxspeed * flatdir;
690 if (trigger_push_testorigin(e, t, pusher, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50))
691 {
692 best_target = trace_endpos;
693 best_org = new_org;
694 best_vel = vel;
695 valid_best_target = true;
696 }
697 }
698 if (ofs && new_org != org - ofs)
699 {
700 new_org = org - ofs;
701 goto new_test;
702 }
703 }
704
705 if (item)
706 {
707 delete(e);
708 return true;
709 }
710
711 if (valid_best_target)
712 {
713 if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST)))
714 {
715 float velxy = vlen(vec2(best_vel));
716 float cost = vlen(vec2(t.origin - best_org)) / velxy;
717 if(velxy < autocvar_sv_maxspeed)
718 velxy = autocvar_sv_maxspeed;
719 cost += vlen(vec2(best_target - t.origin)) / velxy;
720 waypoint_spawnforteleporter(this, best_target, cost, e);
721 }
722 }
723 delete(e);
724#endif
725 }
726
727 if(item)
728 return false;
729
730 if(!n)
731 {
732 // no dest!
733#ifdef SVQC
734 objerror (this, "Jumppad with nonexistant target");
735#endif
736 return false;
737 }
738 else if(n == 1)
739 {
740 // exactly one dest - bots love that
741 if (!this.team)
742 pusher.enemy = find(NULL, targetname, pusher.target);
743 else // bots can't tell teamed jumppads from normal ones
744 pusher.enemy = NULL;
745 }
746 else
747 {
748 // have to use random selection every single time
749 pusher.enemy = NULL;
750 }
751
752 }
753#ifdef SVQC
754 else
755 {
756 if (!this.team)
757 {
758 entity e = spawn();
759 setsize(e, PL_MIN_CONST, PL_MAX_CONST);
761 setorigin(e, org);
762 e.velocity = pusher.movedir;
763 tracetoss(e, e);
764 if (item)
765 {
766 bool r = (trace_ent == item);
767 delete(e);
768 return r;
769 }
772 delete(e);
773 }
774 else if (item)
775 return false;
776 }
777
779#endif
780 return true;
781}
782
784{
785 trigger_push_test(this, NULL);
786}
787
788#ifdef SVQC
789float trigger_push_send(entity this, entity to, float sf)
790{
791 WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH);
792
794 WriteInt24_t(MSG_ENTITY, this.spawnflags);
797
798 trigger_common_write(this, true);
799
800 return true;
801}
802
803float trigger_push_velocity_send(entity this, entity to, float sf)
804{
805 WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH_VELOCITY);
806
808 WriteInt24_t(MSG_ENTITY, this.spawnflags);
812
813 trigger_common_write(this, true);
814
815 return true;
816}
817
819{
821}
822
827
832
833/*
834 * ENTITY PARAMETERS:
835 *
836 * target: target of jump
837 * height: the absolute value is the height of the highest point of the jump
838 * trajectory above the higher one of the player and the target.
839 * the sign indicates whether the highest point is INSIDE (positive)
840 * or OUTSIDE (negative) of the jump trajectory. General rule: use
841 * positive values for targets mounted on the floor, and use negative
842 * values to target a point on the ceiling.
843 * movedir: if target is not set, movedir * speed * 10 is the velocity to be reached.
844 */
845spawnfunc(trigger_push)
846{
847 SetMovedir(this);
848
851 this.active = ACTIVE_ACTIVE;
852 this.use = trigger_push_use;
854
855 // normal push setup
856 if (!this.speed)
857 this.speed = 1000;
858 this.movedir *= this.speed * 10;
859
860 if (!this.noise)
861 this.noise = "misc/jumppad.wav";
862 precache_sound (this.noise);
863
864 trigger_push_link(this); // link it now
865
866 IL_PUSH(g_jumppads, this);
867
868 // this must be called to spawn the teleport waypoints for bots
870}
871
872/*
873 * ENTITY PARAMETERS:
874 *
875 * target: this points to the target_position to which the player will jump.
876 * speed: XY speed for player-directional velocity pads - either sets or adds to the player's horizontal velocity.
877 * count: Z speed for player-directional velocity pads - either sets or adds to the player's vertical velocity.
878 */
879spawnfunc(trigger_push_velocity)
880{
883 this.active = ACTIVE_ACTIVE;
884 this.use = trigger_push_use;
886
887 // normal push setup
888 if (!this.noise)
889 this.noise = "misc/jumppad.wav";
890 precache_sound (this.noise);
891
892 trigger_push_velocity_link(this); // link it now
893}
894
895
896bool target_push_send(entity this, entity to, float sf)
897{
898 WriteHeader(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH);
899
900 WriteByte(MSG_ENTITY, this.cnt);
902 WriteVector(MSG_ENTITY, this.origin);
903
904 WriteAngleVector(MSG_ENTITY, this.angles);
905
906 return true;
907}
908
909void target_push_use(entity this, entity actor, entity trigger)
910{
911 if(trigger.classname == "trigger_push" || trigger == this)
912 return; // WTF, why is this a thing
913
914 jumppad_push(this, actor, false);
915}
916
918{
920 Net_LinkEntity(this, false, 0, target_push_send);
921 //this.SendFlags |= 1; // update
922}
923
925{
926 this.mangle = this.angles;
927 setorigin(this, this.origin);
928 target_push_link(this);
929}
930
931spawnfunc(target_push)
932{
933 target_push_init(this); // normal push target behaviour can be combined with a legacy pusher?
934 this.use = target_push_use;
935
936 if(this.target && this.target != "") // Q3 or old style Nexuiz pusher
937 {
938 for (entity trigger_ent = findchain(target, this.targetname); trigger_ent; trigger_ent = trigger_ent.chain)
939 {
940 // add the triggering entity to the g_jumppads list
941 // multiple jump pads can use the same target_push
942 if(!IL_CONTAINS(g_jumppads, trigger_ent))
943 {
944 IL_PUSH(g_jumppads, trigger_ent);
946 }
947 }
948 }
949 else // Q3 .angles and .speed pusher
950 {
951 if (!this.speed)
952 this.speed = 1000;
953 SetMovedir(this); // this clears .angles so it must be after target_push_init()
954 this.movedir *= this.speed;
955 }
956
957 if (!this.noise)
958 {
960 this.noise = "sound/misc/windfly.wav"; // Q3 mappers provide this, it's not in pak0
961 else
962 this.noise = "misc/jumppad.wav";
963 }
964 precache_sound (this.noise);
965}
966
967spawnfunc(info_notnull)
968{
969 target_push_init(this);
970}
971spawnfunc(target_position)
972{
973 target_push_init(this);
974}
975
976#elif defined(CSQC)
977
978NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH, bool isnew)
979{
980 int mytm = ReadByte(); if(mytm) { this.team = mytm - 1; }
981 this.spawnflags = ReadInt24_t();
982 this.active = ReadByte();
983 this.height = ReadCoord();
984
985 trigger_common_read(this, true);
986
987 this.entremove = trigger_remove_generic;
988 this.solid = SOLID_TRIGGER;
990 this.move_time = time;
991 defer(this, 0.25, trigger_push_findtarget);
992
993 return true;
994}
995
996NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH_VELOCITY, bool isnew)
997{
998 int mytm = ReadByte(); if(mytm) { this.team = mytm - 1; }
999 this.spawnflags = ReadInt24_t();
1000 this.active = ReadByte();
1001 this.speed = ReadCoord();
1002 this.count = ReadCoord();
1003
1004 trigger_common_read(this, true);
1005
1006 this.entremove = trigger_remove_generic;
1007 this.solid = SOLID_TRIGGER;
1009 this.move_time = time;
1010
1011 return true;
1012}
1013
1014void target_push_remove(entity this)
1015{
1016 // strfree(this.classname);
1017 strfree(this.targetname);
1018}
1019
1020NET_HANDLE(ENT_CLIENT_TARGET_PUSH, bool isnew)
1021{
1022 this.cnt = ReadByte();
1023 this.targetname = strzone(ReadString());
1024 this.origin = ReadVector();
1025
1026 this.angles = ReadAngleVector();
1027
1028 return = true;
1029
1030 setorigin(this, this.origin);
1031
1032 this.drawmask = MASK_NORMAL;
1033 this.entremove = target_push_remove;
1034}
1035#endif
void animdecide_setaction(entity e, float action, float restart)
const int ANIMACTION_JUMP
void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent)
float height
Definition bobbing.qc:3
#define boolean(value)
Definition bool.qh:9
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
#define ReadString
float cnt
Definition powerups.qc:24
string message
Definition powerups.qc:19
float count
Definition powerups.qc:22
int team
Definition main.qh:188
int spawnflags
Definition ammo.qh:15
#define IS_DEAD(s)
Definition player.qh:244
float autocvar_sv_maxspeed
Definition player.qh:53
#define IS_PLAYER(s)
Definition player.qh:242
#define Q3COMPAT_COMMON
Definition stats.qh:370
#define LABEL(id)
Definition compiler.qh:34
const vector PL_MIN_CONST
Definition constants.qh:56
const int FL_PROJECTILE
Definition constants.qh:85
const int INITPRIO_FINDTARGET
Definition constants.qh:96
const vector PL_MAX_CONST
Definition constants.qh:55
string classname
float drawmask
float maxclients
entity trace_ent
float DPCONTENTS_BOTCLIP
const float SOLID_TRIGGER
float DPCONTENTS_SOLID
const float MASK_NORMAL
float DPCONTENTS_BODY
float effects
float DPCONTENTS_PLAYERCLIP
float time
vector trace_endpos
float trace_startsolid
float nextthink
vector absmax
const float EF_NODEPTHTEST
vector origin
float solid
vector absmin
#define spawn
#define use
void UpdateCSQCProjectile(entity e)
void defer(entity this, float fdelay, void(entity) func)
Execute func() after time + fdelay.
Definition defer.qh:26
void SUB_Remove(entity this)
Remove entity.
Definition defer.qh:12
const int ACTIVE_NOT
Definition defs.qh:36
const int INVERT_TEAMS
Definition defs.qh:10
const int SF_TRIGGER_INIT
Definition defs.qh:22
int active
Definition defs.qh:34
const int ACTIVE_ACTIVE
Definition defs.qh:37
const int SF_TRIGGER_UPDATE
Definition defs.qh:23
float speed
Definition dynlight.qc:9
void Send_Effect(entity eff, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:120
WriteString(chan, ent.netname)
ent angles
Definition ent_cs.qc:146
WriteByte(chan, ent.angles.y/DEC_FACTOR)
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
ERASEABLE bool IL_CONTAINS(IntrusiveList this, entity it)
#define IL_EACH(this, cond, body)
float trigger_push_send(entity this, entity to, float sf)
Definition jumppads.qc:789
vector trigger_push_get_start_point(entity this)
Definition jumppads.qc:559
void target_push_link(entity this)
Definition jumppads.qc:917
bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org)
Definition jumppads.qc:476
bool trigger_push_testorigin_for_item(entity tracetest_ent, entity item, vector org)
Definition jumppads.qc:512
void trigger_push_use(entity this, entity actor, entity trigger)
Definition jumppads.qc:6
void target_push_init(entity this)
Definition jumppads.qc:924
void trigger_push_findtarget(entity this)
Definition jumppads.qc:783
bool trigger_push_test(entity this, entity item)
Definition jumppads.qc:606
bool jumppad_push(entity this, entity targ, bool is_velocity_pad)
Definition jumppads.qc:263
entity get_pusher_from_trigger(entity this)
Definition jumppads.qc:533
float trigger_push_get_push_time(entity this, vector endpos)
Definition jumppads.qc:567
vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity)
Definition jumppads.qc:32
bool target_push_send(entity this, entity to, float sf)
Definition jumppads.qc:896
void trigger_push_link(entity this)
Definition jumppads.qc:823
void trigger_push_velocity_think(entity this)
Definition jumppads.qc:245
float trigger_push_velocity_send(entity this, entity to, float sf)
Definition jumppads.qc:803
void trigger_push_updatelink(entity this)
Definition jumppads.qc:818
void trigger_push_velocity_link(entity this)
Definition jumppads.qc:828
vector trigger_push_velocity_calculatevelocity(entity this, vector org, entity tgt, float speed, float count, entity pushed_entity, bool already_pushed)
Definition jumppads.qc:139
void trigger_push_velocity_touch(entity this, entity toucher)
Definition jumppads.qc:460
void target_push_use(entity this, entity actor, entity trigger)
Definition jumppads.qc:909
void trigger_push_touch(entity this, entity toucher)
Definition jumppads.qc:437
#define PUSH_VELOCITY_PLAYERDIR_Z
Definition jumppads.qh:10
const int PUSH_ONCE
Definition jumppads.qh:4
#define PUSH_VELOCITY_ADD_Z
Definition jumppads.qh:11
void SUB_UseTargets(entity this, entity actor, entity trigger)
Definition triggers.qc:344
#define PUSH_VELOCITY_PLAYERDIR_XY
Definition jumppads.qh:8
#define PUSH_VELOCITY_CLAMP_NEGATIVE_ADDS
Definition jumppads.qh:14
const int NUM_JUMPPADSUSED
Definition jumppads.qh:27
#define Q3_TARGET_PUSH_JUMPPAD
Definition jumppads.qh:16
IntrusiveList g_jumppads
Definition jumppads.qh:18
#define PUSH_VELOCITY_BIDIRECTIONAL_XY
Definition jumppads.qh:12
#define PUSH_VELOCITY_BIDIRECTIONAL_Z
Definition jumppads.qh:13
#define PUSH_VELOCITY_ADD_XY
Definition jumppads.qh:9
float pushltime
Definition jumppads.qh:21
const int PUSH_STATIC
Definition jumppads.qh:6
entity pusher
Definition laser.qc:57
int SendFlags
Definition net.qh:159
#define NET_HANDLE(id, param)
Definition net.qh:15
const int MSG_ENTITY
Definition net.qh:156
#define ReadVector()
Definition net.qh:349
int ReadInt24_t()
Definition net.qh:337
#define ReadAngleVector()
Definition net.qh:351
#define WriteHeader(to, id)
Definition net.qh:265
#define REGISTER_NET_LINKED(id)
Definition net.qh:91
void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
Definition net.qh:167
int ReadByte()
#define STAT(...)
Definition stats.qh:94
#define EXACTTRIGGER_TOUCH(e, t)
Definition common.qh:118
#define BITSET_ASSIGN(a, b)
Definition common.qh:107
vector movedir
Definition viewloc.qh:18
ERASEABLE vector solve_quadratic(float a, float b, float c)
ax^2 + bx + c = 0
Definition math.qh:312
bool signbit(float e)
Definition mathlib.qc:40
float copysign(float e, float f)
Definition mathlib.qc:220
entity find(entity start,.string field, string match)
float vlen(vector v)
vector vectoangles(vector v)
float sqrt(float f)
string precache_sound(string sample)
void WriteCoord(float data, float dest, float desto)
void centerprint(string text,...)
float min(float f,...)
vector normalize(vector v)
float fabs(float f)
string strzone(string s)
#define ftoe(i)
Definition misc.qh:26
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_NONE
Definition movetypes.qh:129
#define PHYS_GRAVITY(s)
Definition movetypes.qh:53
const int MOVETYPE_BOUNCEMISSILE
Definition movetypes.qh:140
float move_time
Definition movetypes.qh:77
#define UNSET_ONGROUND(s)
Definition movetypes.qh:18
const int MOVETYPE_FLY
Definition movetypes.qh:134
const int MOVETYPE_TOSS
Definition movetypes.qh:135
const int MOVETYPE_BOUNCE
Definition movetypes.qh:139
vector stepheightvec
Definition navigation.qh:11
var void func_null()
#define NULL
Definition post.qh:14
#define objerror
Definition pre.qh:8
ERASEABLE void RandomSelection_Init()
Definition random.qc:4
#define RandomSelection_AddEnt(e, weight, priority)
Definition random.qh:14
entity RandomSelection_chosen_ent
Definition random.qh:5
#define setthink(e, f)
vector
Definition self.qh:96
vector org
Definition self.qh:96
entity entity toucher
Definition self.qh:76
#define settouch(e, f)
Definition self.qh:77
const int CH_TRIGGER
Definition sound.qh:12
const float VOL_BASE
Definition sound.qh:36
#define _sound(e, c, s, v, a)
Definition sound.qh:43
const float ATTEN_NORM
Definition sound.qh:30
#define spawnfunc(id)
Definition spawnfunc.qh:107
#define strfree(this)
Definition string.qh:57
void SetMovedir(entity this)
Definition subs.qc:539
string noise
Definition subs.qh:83
vector mangle
Definition subs.qh:51
entity enemy
Definition sv_ctf.qh:152
vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity)
Definition jumppads.qc:32
bool teamplay
Definition teams.qh:59
#define DIFF_TEAM(a, b)
Definition teams.qh:242
void trigger_link(entity this, bool(entity this, entity to, int sendflags) sendfunc)
Definition triggers.qc:104
bool isPushable(entity e)
Definition triggers.qc:3
void trigger_common_write(entity this, bool withtarget)
Definition triggers.qc:110
void trigger_remove_generic(entity this)
string targetname
Definition triggers.qh:56
void trigger_common_read(entity this, bool withtarget)
string target
Definition triggers.qh:55
void WarpZoneLib_ExactTrigger_Init(entity this, bool unsetmodel)
Definition util_server.qc:8
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt().
Definition vector.qh:8
const vector eZ
Definition vector.qh:45
ERASEABLE float boxesoverlap(vector m1, vector m2, vector m3, vector m4)
Requires that m2>m1 in all coordinates, and that m4>m3.
Definition vector.qh:72
#define vec2(...)
Definition vector.qh:95
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2229
IntrusiveList g_moveables
Definition world.qh:157