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 = 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;
90 solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist"
91 // ALWAYS solvable because jumpheight >= zdist
92 if(!solution.z)
93 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)
94 if(zdist == 0)
95 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)
96
97 float flighttime;
98 if(zdist < 0)
99 {
100 // down-jump
101 if(ht < 0)
102 {
103 // almost straight line type
104 // jump apex is before the jump
105 // we must take the larger one
106 flighttime = solution.y;
107 }
108 else
109 {
110 // regular jump
111 // jump apex is during the jump
112 // we must take the larger one too
113 flighttime = solution.y;
114 }
115 }
116 else
117 {
118 // up-jump
119 if(ht < 0)
120 {
121 // almost straight line type
122 // jump apex is after the jump
123 // we must take the smaller one
124 flighttime = solution.x;
125 }
126 else
127 {
128 // regular jump
129 // jump apex is during the jump
130 // we must take the larger one
131 flighttime = solution.y;
132 }
133 }
134 vs = sdist / flighttime;
135
136 // finally calculate the velocity
137 return sdir * vs + '0 0 1' * vz;
138}
139
140vector trigger_push_velocity_calculatevelocity(entity this, vector org, entity tgt, float speed, float count, entity pushed_entity, bool already_pushed)
141{
142 bool is_playerdir_xy = boolean(this.spawnflags & PUSH_VELOCITY_PLAYERDIR_XY);
143 bool is_add_xy = boolean(this.spawnflags & PUSH_VELOCITY_ADD_XY);
144 bool is_playerdir_z = boolean(this.spawnflags & PUSH_VELOCITY_PLAYERDIR_Z);
145 bool is_add_z = boolean(this.spawnflags & PUSH_VELOCITY_ADD_Z);
146 bool is_bidirectional_xy = boolean(this.spawnflags & PUSH_VELOCITY_BIDIRECTIONAL_XY);
147 bool is_bidirectional_z = boolean(this.spawnflags & PUSH_VELOCITY_BIDIRECTIONAL_Z);
148 bool is_clamp_negative_adds = boolean(this.spawnflags & PUSH_VELOCITY_CLAMP_NEGATIVE_ADDS);
149
150 vector sdir = normalize(vec2(pushed_entity.velocity));
151 float zdir = pushed_entity.velocity.z;
152 if(zdir != 0) zdir = copysign(1, zdir);
153
154 vector vs_tgt = '0 0 0';
155 float vz_tgt = 0;
156 if (!is_playerdir_xy || !is_playerdir_z)
157 {
158 vector vel_tgt = trigger_push_calculatevelocity(org, tgt, 0, pushed_entity);
159 vs_tgt = vec2(vel_tgt);
160 vz_tgt = vel_tgt.z;
161
162 // bidirectional jump pads do not play nicely with xonotic's jump pad targets
163 if (is_bidirectional_xy)
164 {
165 if (normalize(vs_tgt) * sdir < 0)
166 {
167 vs_tgt *= -1;
168 }
169 }
170
171 if (is_bidirectional_z)
172 {
173 if (signbit(vz_tgt) != signbit(zdir))
174 {
175 vz_tgt *= -1;
176 }
177 }
178 }
179
180 vector vs;
181 if (is_playerdir_xy)
182 {
183 vs = sdir * speed;
184 }
185 else
186 {
187 vs = vs_tgt;
188 }
189
190 float vz;
191 if (is_playerdir_z)
192 {
193 vz = zdir * count;
194 }
195 else
196 {
197 vz = vz_tgt;
198 }
199
200 if (is_add_xy)
201 {
202 vector vs_add = vec2(pushed_entity.velocity);
203 if (already_pushed)
204 {
205 vs = vs_add;
206 }
207 else
208 {
209 vs += vs_add;
210
211 if (is_clamp_negative_adds)
212 {
213 if ((normalize(vs) * sdir) < 0)
214 {
215 vs = '0 0 0';
216 }
217 }
218 }
219 }
220
221 if (is_add_z)
222 {
223 float vz_add = pushed_entity.velocity.z;
224 if (already_pushed)
225 {
226 vz = vz_add;
227 }
228 else
229 {
230 vz += vz_add;
231
232 if (is_clamp_negative_adds)
233 {
234 if (signbit(vz) != signbit(zdir))
235 {
236 vz = 0;
237 }
238 }
239 }
240 }
241
242 return vs + '0 0 1' * vz;
243}
244
245#ifdef SVQC
247{
248 bool found = false;
249 IL_EACH(g_moveables, it.last_pushed == this,
250 {
251 if(!WarpZoneLib_ExactTrigger_Touch(this, it, false))
252 it.last_pushed = NULL;
253 else
254 found = true;
255 });
256
257 if(found)
258 this.nextthink = time;
259 else
260 setthink(this, func_null);
261}
262#endif
263
264bool jumppad_push(entity this, entity targ, bool is_velocity_pad)
265{
266 if (!isPushable(targ))
267 return false;
268
269 vector org = targ.origin;
270
272 org = (this.absmin + this.absmax) * 0.5;
273
274 bool already_pushed = false;
275 if(is_velocity_pad) // remember velocity jump pads
276 {
277 if(this == targ.last_pushed || (targ.last_pushed && !STAT(Q3COMPAT, targ))) // if q3compat is active overwrite last stored jump pad, otherwise ignore
278 {
279 already_pushed = true;
280 }
281 else
282 {
283 targ.last_pushed = this; // may be briefly out of sync between client and server if client prediction is toggled
284
285 #ifdef SVQC
287 this.nextthink = time;
288 #endif
289 }
290 }
291
292 if(this.enemy)
293 {
294 if(!is_velocity_pad)
295 {
296 targ.velocity = trigger_push_calculatevelocity(org, this.enemy, this.height, targ);
297 }
298 else
299 {
300 targ.velocity = trigger_push_velocity_calculatevelocity(this, org, this.enemy, this.speed, this.count, targ, already_pushed);
301 }
302 }
303 else if(this.target && this.target != "")
304 {
305 entity e;
307 for(e = NULL; (e = find(e, targetname, this.target)); )
308 {
309 if(e.cnt)
310 RandomSelection_AddEnt(e, e.cnt, 1);
311 else
312 RandomSelection_AddEnt(e, 1, 1);
313 }
314 if(!is_velocity_pad)
315 {
317 }
318 else
319 {
320 targ.velocity = trigger_push_velocity_calculatevelocity(this, org, RandomSelection_chosen_ent, this.speed, this.count, targ, already_pushed);
321 }
322 }
323 else
324 {
325 if(!is_velocity_pad)
326 {
327 targ.velocity = this.movedir;
328 }
329 else
330 {
331#ifdef SVQC
332 objerror (this, "Jumppad with no target");
333#endif
334 return false;
335 }
336 }
337
338 if(!is_velocity_pad) UNSET_ONGROUND(targ);
339
340#ifdef CSQC
341 if (targ.flags & FL_PROJECTILE)
342 {
343 targ.angles = vectoangles (targ.velocity);
344 switch(targ.move_movetype)
345 {
346 case MOVETYPE_FLY:
348 targ.gravity = 1;
349 break;
352 targ.gravity = 1;
353 break;
354 }
355 }
356#endif
357
358#ifdef SVQC
359 if (IS_PLAYER(targ))
360 {
361 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
362 targ.oldvelocity = targ.velocity;
363
364 // prevent sound spam when a player hits the jumppad more than once
365 // or when a dead player gets stuck in the jumppad for some reason
366 if(!already_pushed && this.pushltime < time && !(IS_DEAD(targ) && targ.velocity == '0 0 0'))
367 {
368 if (Q3COMPAT_COMMON && this.classname == "target_push")
369 this.pushltime = time + 1.5;
370 else
371 {
372 // flash when activated
373 Send_Effect(EFFECT_JUMPPAD, targ.origin, targ.velocity, 1);
374 this.pushltime = time + 0.2;
375 }
376 _sound (targ, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
377 }
378
379 if(IS_REAL_CLIENT(targ) || IS_BOT_CLIENT(targ))
380 {
381 bool found = false;
382 for(int i = 0; i < targ.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
383 if(targ.(jumppadsused[i]) == this)
384 found = true;
385 if(!found)
386 {
387 targ.(jumppadsused[targ.jumppadcount % NUM_JUMPPADSUSED]) = this;
388 targ.jumppadcount = targ.jumppadcount + 1;
389 }
390
391 if(IS_REAL_CLIENT(targ))
392 {
393 if(this.message)
394 centerprint(targ, this.message);
395 }
396 else
397 {
398 targ.lastteleporttime = time;
399 targ.lastteleport_origin = targ.origin;
400 }
401
402 if (!IS_DEAD(targ))
404 }
405 else
406 targ.jumppadcount = 1;
407
408 // reset tracking of who pushed you into a hazard (for kill credit)
409 targ.pushltime = 0;
410 targ.istypefrag = 0;
411 }
412
413 if(this.enemy.target)
414 SUB_UseTargets(this.enemy, targ, this);
415
416 if (targ.flags & FL_PROJECTILE)
417 {
418 targ.angles = vectoangles (targ.velocity);
419 targ.com_phys_gravity_factor = 1;
420 switch(targ.move_movetype)
421 {
422 case MOVETYPE_FLY:
424 targ.gravity = 1;
425 break;
428 targ.gravity = 1;
429 break;
430 }
432 }
433#endif
434
435 return true;
436}
437
439{
440 if (this.active == ACTIVE_NOT)
441 return;
442
443 if(this.team)
444 if(((this.spawnflags & INVERT_TEAMS) == 0) == (DIFF_TEAM(this, toucher)))
445 return;
446
448
449 noref bool success = jumppad_push(this, toucher, false);
450
451#ifdef SVQC
452 if (success && (this.spawnflags & PUSH_ONCE))
453 {
454 settouch(this, func_null);
455 setthink(this, SUB_Remove);
456 this.nextthink = time;
457 }
458#endif
459}
460
462{
463 if (this.active == ACTIVE_NOT)
464 return;
465
466 if(this.team && DIFF_TEAM(this, toucher))
467 return;
468
470
471 jumppad_push(this, toucher, true);
472}
473
474#ifdef SVQC
475void trigger_push_link(entity this);
477bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org)
478{
479 setorigin(tracetest_ent, org);
480 tracetoss(tracetest_ent, tracetest_ent);
482 return false;
483
484 if (!jp.height)
485 {
486 // since tracetoss starting from jumppad's origin often fails when target
487 // is very close to real destination, start it directly from target's
488 // origin instead
489 vector ofs = '0 0 0';
490 if (vdist(vec2(tracetest_ent.velocity), <, autocvar_sv_maxspeed))
491 ofs = stepheightvec;
492
493 tracetest_ent.velocity.z = 0;
494 setorigin(tracetest_ent, targ.origin + ofs);
495 tracetoss(tracetest_ent, tracetest_ent);
496 if (trace_startsolid && ofs.z)
497 {
498 setorigin(tracetest_ent, targ.origin + ofs * 0.5);
499 tracetoss(tracetest_ent, tracetest_ent);
500 if (trace_startsolid && ofs.z)
501 {
502 setorigin(tracetest_ent, targ.origin);
503 tracetoss(tracetest_ent, tracetest_ent);
505 return false;
506 }
507 }
508 }
509 tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent);
510 return true;
511}
512
514{
515 setorigin(tracetest_ent, org);
516 tracetoss(tracetest_ent, tracetest_ent);
517
519 return false;
520 if (trace_ent == item)
521 return true;
522
523 tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent);
524
525 if (trace_ent == item)
526 return true;
527
528 return false;
529}
530#endif
531
532#ifdef SVQC
533// get the push entity in cases where a non-push entity acts as the trigger of a jump pad
535{
536 // triggers could point to several pusher but each pusher would overwrite the effect of the previous one
537 // this means only one pusher has to be considered
538 // we still have to traverse the target list though since entities other than pushers could be in it
540 if(this.classname != "trigger_push")
541 {
542 for(entity t = ftoe(maxclients); (t = find(t, targetname, this.target)); )
543 {
544 if(t.classname == "target_push")
545 {
546 pusher = t;
547 break;
548 }
549 }
550
551 if(!pusher) objerror (this, "Jump pad with non-existent target");
552 }
553
554 if(pusher)
555 return pusher; // if the target field points to a target_push entity, return it
556 else
557 return this; // otherwise, return the original entity
558}
559
561{
562 // calculate a typical start point for the jump
563 vector org = (this.absmin + this.absmax) * 0.5;
564 org.z = this.absmax.z - PL_MIN_CONST.z - 7;
565 return org;
566}
567
569{
571 entity pusher = get_pusher_from_trigger(this); // function might get called from a non-push trigger entity
572
573 float grav = PHYS_GRAVITY(NULL);
574
575 entity t = pusher.enemy;
576 if (t)
577 {
578 entity e = spawn();
579 setsize(e, PL_MIN_CONST, PL_MAX_CONST);
582 vector v2 = trigger_push_calculatevelocity(endpos, t, pusher.height, e);
583 delete(e);
584 return (v.z + v2.z) / grav;
585 }
586 else if (!(pusher.target && pusher.target != ""))
587 {
588 if (!this.team)
589 {
590 vector v = pusher.movedir;
591
592 float t = v.z / grav;
593 float jump_height = 1/2 * grav * (t ** 2);
594 float remaining_height = org.z + jump_height - endpos.z;
595 float v2_z = sqrt(2 * grav * remaining_height);
596
597 return (v.z + v2_z) / grav;
598 }
599 }
600 return 0;
601}
602#endif
603
604// if (item != NULL) returns true if the item can be reached by using this jumppad, false otherwise
605// if (item == NULL) tests jumppad's trajectory and eventually spawns waypoints for it (return value doesn't matter)
606// NOTE: for simplicity's sake it improperly tests jumppad bboxes instead of bmodels
608{
609#ifdef SVQC
611 entity pusher = get_pusher_from_trigger(this); // function might get called from a non-push trigger entity
612#elif defined(CSQC)
613 entity pusher = this; // client-side code never calls this on a non-push entity
614#endif
615
616 if (pusher.target)
617 {
618 int n = 0;
619#ifdef SVQC
620 vector vel = '0 0 0';
621#endif
622 for(entity t = NULL; (t = find(t, targetname, pusher.target)); )
623 {
624 ++n;
625#ifdef SVQC
626 if(t.move_movetype != MOVETYPE_NONE)
627 continue;
628
629 // bots can't tell teamed jumppads from normal ones
630 if (this.team)
631 continue;
632
633 entity e = spawn();
634 setsize(e, PL_MIN_CONST, PL_MAX_CONST);
636 e.velocity = trigger_push_calculatevelocity(org, t, pusher.height, e);
637
638 vel = e.velocity;
639 vector best_target = '0 0 0';
640 vector best_org = '0 0 0';
641 vector best_vel = '0 0 0';
642 bool valid_best_target = false;
643 if (item)
644 {
646 {
647 delete(e);
648 return false;
649 }
650 }
651 else
652 {
653 // optimization: if horizontal velocity is 0 then target is not good since the trajectory
654 // will definitely go back to the jumppad (horizontal velocity of best_vel can't be 0 anyway)
655 if ((e.velocity.x != 0 || e.velocity.y != 0)
657 {
658 best_target = trace_endpos;
659 best_org = org;
660 best_vel = e.velocity;
661 valid_best_target = true;
662 }
663 }
664
665 vector new_org;
666 vector dist = t.origin - org;
667 if (dist.x || dist.y) // if not perfectly vertical
668 {
669 // test trajectory with different starting points, sometimes the trajectory
670 // starting from the jumppad origin can't reach the real destination
671 // and destination waypoint ends up near the jumppad itself
672 vector flatdir = normalize(dist - eZ * dist.z);
673 vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y));
674 new_org = org + ofs;
675
676 LABEL(new_test)
677 e.velocity = trigger_push_calculatevelocity(new_org, t, pusher.height, e);
678 if (item)
679 {
680 if (!trigger_push_testorigin_for_item(e, item, new_org))
681 {
682 delete(e);
683 return false;
684 }
685 }
686 else
687 {
688 vel = e.velocity;
689 if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed))
690 e.velocity = autocvar_sv_maxspeed * flatdir;
691 if (trigger_push_testorigin(e, t, pusher, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50))
692 {
693 best_target = trace_endpos;
694 best_org = new_org;
695 best_vel = vel;
696 valid_best_target = true;
697 }
698 }
699 if (ofs && new_org != org - ofs)
700 {
701 new_org = org - ofs;
702 goto new_test;
703 }
704 }
705
706 if (item)
707 {
708 delete(e);
709 return true;
710 }
711
712 if (valid_best_target)
713 {
714 if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST)))
715 {
716 float velxy = vlen(vec2(best_vel));
717 float cost = vlen(vec2(t.origin - best_org)) / velxy;
718 if(velxy < autocvar_sv_maxspeed)
719 velxy = autocvar_sv_maxspeed;
720 cost += vlen(vec2(best_target - t.origin)) / velxy;
721 waypoint_spawnforteleporter(this, best_target, cost, e);
722 }
723 }
724 delete(e);
725#endif
726 }
727
728 if(item)
729 return false;
730
731 if(!n)
732 {
733 // no dest!
734#ifdef SVQC
735 objerror (this, "Jumppad with nonexistant target");
736#endif
737 return false;
738 }
739 else if(n == 1)
740 {
741 // exactly one dest - bots love that
742 if (!this.team)
743 pusher.enemy = find(NULL, targetname, pusher.target);
744 else // bots can't tell teamed jumppads from normal ones
745 pusher.enemy = NULL;
746 }
747 else
748 {
749 // have to use random selection every single time
750 pusher.enemy = NULL;
751 }
752
753 }
754#ifdef SVQC
755 else
756 {
757 if (!this.team)
758 {
759 entity e = spawn();
760 setsize(e, PL_MIN_CONST, PL_MAX_CONST);
762 setorigin(e, org);
763 e.velocity = pusher.movedir;
764 tracetoss(e, e);
765 if (item)
766 {
767 bool r = (trace_ent == item);
768 delete(e);
769 return r;
770 }
773 delete(e);
774 }
775 else if (item)
776 return false;
777 }
778
780#endif
781 return true;
782}
783
785{
786 trigger_push_test(this, NULL);
787}
788
789#ifdef SVQC
790float trigger_push_send(entity this, entity to, float sf)
791{
792 WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH);
793
795 WriteInt24_t(MSG_ENTITY, this.spawnflags);
798
799 trigger_common_write(this, true);
800
801 return true;
802}
803
804float trigger_push_velocity_send(entity this, entity to, float sf)
805{
806 WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH_VELOCITY);
807
809 WriteInt24_t(MSG_ENTITY, this.spawnflags);
813
814 trigger_common_write(this, true);
815
816 return true;
817}
818
820{
822}
823
828
833
834/*
835 * ENTITY PARAMETERS:
836 *
837 * target: target of jump
838 * height: the absolute value is the height of the highest point of the jump
839 * trajectory above the higher one of the player and the target.
840 * the sign indicates whether the highest point is INSIDE (positive)
841 * or OUTSIDE (negative) of the jump trajectory. General rule: use
842 * positive values for targets mounted on the floor, and use negative
843 * values to target a point on the ceiling.
844 * movedir: if target is not set, movedir * speed * 10 is the velocity to be reached.
845 */
846spawnfunc(trigger_push)
847{
848 SetMovedir(this);
849
852 this.active = ACTIVE_ACTIVE;
853 this.use = trigger_push_use;
855
856 // normal push setup
857 if (!this.speed)
858 this.speed = 1000;
859 this.movedir = this.movedir * this.speed * 10;
860
861 if (!this.noise)
862 this.noise = "misc/jumppad.wav";
863 precache_sound (this.noise);
864
865 trigger_push_link(this); // link it now
866
867 IL_PUSH(g_jumppads, this);
868
869 // this must be called to spawn the teleport waypoints for bots
871}
872
873/*
874 * ENTITY PARAMETERS:
875 *
876 * target: this points to the target_position to which the player will jump.
877 * speed: XY speed for player-directional velocity pads - either sets or adds to the player's horizontal velocity.
878 * count: Z speed for player-directional velocity pads - either sets or adds to the player's vertical velocity.
879 */
880spawnfunc(trigger_push_velocity)
881{
884 this.active = ACTIVE_ACTIVE;
885 this.use = trigger_push_use;
887
888 // normal push setup
889 if (!this.noise)
890 this.noise = "misc/jumppad.wav";
891 precache_sound (this.noise);
892
893 trigger_push_velocity_link(this); // link it now
894}
895
896
897bool target_push_send(entity this, entity to, float sf)
898{
899 WriteHeader(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH);
900
901 WriteByte(MSG_ENTITY, this.cnt);
903 WriteVector(MSG_ENTITY, this.origin);
904
905 WriteAngleVector(MSG_ENTITY, this.angles);
906
907 return true;
908}
909
910void target_push_use(entity this, entity actor, entity trigger)
911{
912 if(trigger.classname == "trigger_push" || trigger == this)
913 return; // WTF, why is this a thing
914
915 jumppad_push(this, actor, false);
916}
917
919{
921 Net_LinkEntity(this, false, 0, target_push_send);
922 //this.SendFlags |= 1; // update
923}
924
926{
927 this.mangle = this.angles;
928 setorigin(this, this.origin);
929 target_push_link(this);
930}
931
932spawnfunc(target_push)
933{
934 target_push_init(this); // normal push target behaviour can be combined with a legacy pusher?
935 this.use = target_push_use;
936
937 if(this.target && this.target != "") // Q3 or old style Nexuiz pusher
938 {
939 for (entity trigger_ent = findchain(target, this.targetname); trigger_ent; trigger_ent = trigger_ent.chain)
940 {
941 // add the triggering entity to the g_jumppads list
942 // multiple jump pads can use the same target_push
943 if(!IL_CONTAINS(g_jumppads, trigger_ent))
944 {
945 IL_PUSH(g_jumppads, trigger_ent);
947 }
948 }
949 }
950 else // Q3 .angles and .speed pusher
951 {
952 if (!this.speed)
953 this.speed = 1000;
954 SetMovedir(this); // this clears .angles so it must be after target_push_init()
955 this.movedir *= this.speed;
956 }
957
958 if (!this.noise)
959 {
961 this.noise = "sound/misc/windfly.wav"; // Q3 mappers provide this, it's not in pak0
962 else
963 this.noise = "misc/jumppad.wav";
964 }
965 precache_sound (this.noise);
966}
967
968spawnfunc(info_notnull)
969{
970 target_push_init(this);
971}
972spawnfunc(target_position)
973{
974 target_push_init(this);
975}
976
977#elif defined(CSQC)
978
979NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH, bool isnew)
980{
981 int mytm = ReadByte(); if(mytm) { this.team = mytm - 1; }
982 this.spawnflags = ReadInt24_t();
983 this.active = ReadByte();
984 this.height = ReadCoord();
985
986 trigger_common_read(this, true);
987
988 this.entremove = trigger_remove_generic;
989 this.solid = SOLID_TRIGGER;
991 this.move_time = time;
992 defer(this, 0.25, trigger_push_findtarget);
993
994 return true;
995}
996
997NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH_VELOCITY, bool isnew)
998{
999 int mytm = ReadByte(); if(mytm) { this.team = mytm - 1; }
1000 this.spawnflags = ReadInt24_t();
1001 this.active = ReadByte();
1002 this.speed = ReadCoord();
1003 this.count = ReadCoord();
1004
1005 trigger_common_read(this, true);
1006
1007 this.entremove = trigger_remove_generic;
1008 this.solid = SOLID_TRIGGER;
1010 this.move_time = time;
1011
1012 return true;
1013}
1014
1015void target_push_remove(entity this)
1016{
1017 // strfree(this.classname);
1018 strfree(this.targetname);
1019}
1020
1021NET_HANDLE(ENT_CLIENT_TARGET_PUSH, bool isnew)
1022{
1023 this.cnt = ReadByte();
1024 this.targetname = strzone(ReadString());
1025 this.origin = ReadVector();
1026
1027 this.angles = ReadAngleVector();
1028
1029 return = true;
1030
1031 setorigin(this, this.origin);
1032
1033 this.drawmask = MASK_NORMAL;
1034 this.entremove = target_push_remove;
1035}
1036#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:124
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:790
vector trigger_push_get_start_point(entity this)
Definition jumppads.qc:560
void target_push_link(entity this)
Definition jumppads.qc:918
bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org)
Definition jumppads.qc:477
bool trigger_push_testorigin_for_item(entity tracetest_ent, entity item, vector org)
Definition jumppads.qc:513
void trigger_push_use(entity this, entity actor, entity trigger)
Definition jumppads.qc:6
void target_push_init(entity this)
Definition jumppads.qc:925
void trigger_push_findtarget(entity this)
Definition jumppads.qc:784
bool trigger_push_test(entity this, entity item)
Definition jumppads.qc:607
bool jumppad_push(entity this, entity targ, bool is_velocity_pad)
Definition jumppads.qc:264
entity get_pusher_from_trigger(entity this)
Definition jumppads.qc:534
float trigger_push_get_push_time(entity this, vector endpos)
Definition jumppads.qc:568
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:897
void trigger_push_link(entity this)
Definition jumppads.qc:824
void trigger_push_velocity_think(entity this)
Definition jumppads.qc:246
float trigger_push_velocity_send(entity this, entity to, float sf)
Definition jumppads.qc:804
void trigger_push_updatelink(entity this)
Definition jumppads.qc:819
void trigger_push_velocity_link(entity this)
Definition jumppads.qc:829
vector trigger_push_velocity_calculatevelocity(entity this, vector org, entity tgt, float speed, float count, entity pushed_entity, bool already_pushed)
Definition jumppads.qc:140
void trigger_push_velocity_touch(entity this, entity toucher)
Definition jumppads.qc:461
void target_push_use(entity this, entity actor, entity trigger)
Definition jumppads.qc:910
void trigger_push_touch(entity this, entity toucher)
Definition jumppads.qc:438
#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:118
#define NET_HANDLE(id, param)
Definition net.qh:15
const int MSG_ENTITY
Definition net.qh:115
#define ReadVector()
Definition net.qh:367
int ReadInt24_t()
Definition net.qh:355
#define ReadAngleVector()
Definition net.qh:369
#define WriteHeader(to, id)
Definition net.qh:221
#define REGISTER_NET_LINKED(id)
Definition net.qh:55
void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
Definition net.qh:123
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:226
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:540
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:2225
IntrusiveList g_moveables
Definition world.qh:157