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
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:29
void SUB_Remove(entity this)
Remove entity.
Definition defer.qh:13
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
ent angles
Definition ent_cs.qc:121
solid
Definition ent_cs.qc:165
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:350
int ReadInt24_t()
Definition net.qh:338
#define ReadAngleVector()
Definition net.qh:352
#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:82
#define EXACTTRIGGER_TOUCH(e, t)
Definition common.qh:115
#define BITSET_ASSIGN(a, b)
Definition common.qh:104
vector movedir
Definition viewloc.qh:18
ERASEABLE vector solve_quadratic(float a, float b, float c)
ax^2 + bx + c = 0
Definition math.qh:304
bool signbit(float e)
Definition mathlib.qc:44
float copysign(float e, float f)
Definition mathlib.qc:224
void WriteString(string data, float dest, float desto)
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)
void WriteByte(float data, float dest, float desto)
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:92
vector org
Definition self.qh:92
entity entity toucher
Definition self.qh:72
#define settouch(e, f)
Definition self.qh:73
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:96
#define strfree(this)
Definition string.qh:59
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:153
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)
#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:46
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:73
#define vec2(...)
Definition vector.qh:90
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2230
IntrusiveList g_moveables
Definition world.qh:157