]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_jumppads.qc
3db56c6ee03ce74738e07ccdd7d571aca1fa6022
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_jumppads.qc
1 #if defined(CSQC)
2 #elif defined(MENUQC)
3 #elif defined(SVQC)
4         #include "../dpdefs/progsdefs.qh"
5     #include "../dpdefs/dpextensions.qh"
6     #include "../warpzonelib/util_server.qh"
7     #include "../common/constants.qh"
8     #include "../common/util.qh"
9     #include "../common/animdecide.qh"
10     #include "../common/weapons/weapons.qh"
11     #include "weapons/csqcprojectile.qh"
12     #include "constants.qh"
13     #include "defs.qh"
14 #endif
15
16 const float PUSH_ONCE                   = 1;
17 const float PUSH_SILENT         = 2;
18
19 .float pushltime;
20 .float istypefrag;
21 .float height;
22
23 void() SUB_UseTargets;
24
25 float trigger_push_calculatevelocity_flighttime;
26
27 void trigger_push_use()
28 {
29         if(teamplay)
30                 self.team = activator.team;
31 }
32
33 /*
34         trigger_push_calculatevelocity
35
36         Arguments:
37           org - origin of the object which is to be pushed
38           tgt - target entity (can be either a point or a model entity; if it is
39                 the latter, its midpoint is used)
40           ht  - jump height, measured from the higher one of org and tgt's midpoint
41
42         Returns: velocity for the jump
43         the global trigger_push_calculatevelocity_flighttime is set to the total
44         jump time
45  */
46
47 vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
48 {
49         float grav, sdist, zdist, vs, vz, jumpheight;
50         vector sdir, torg;
51
52         torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
53
54         grav = autocvar_sv_gravity;
55         if(other.gravity)
56                 grav *= other.gravity;
57
58         zdist = torg.z - org.z;
59         sdist = vlen(torg - org - zdist * '0 0 1');
60         sdir = normalize(torg - org - zdist * '0 0 1');
61
62         // how high do we need to push the player?
63         jumpheight = fabs(ht);
64         if(zdist > 0)
65                 jumpheight = jumpheight + zdist;
66
67         /*
68                 STOP.
69
70                 You will not understand the following equations anyway...
71                 But here is what I did to get them.
72
73                 I used the functions
74
75                   s(t) = t * vs
76                   z(t) = t * vz - 1/2 grav t^2
77
78                 and solved for:
79
80                   s(ti) = sdist
81                   z(ti) = zdist
82                   max(z, ti) = jumpheight
83
84                 From these three equations, you will find the three parameters vs, vz
85                 and ti.
86          */
87
88         // push him so high...
89         vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)!
90
91         // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump!
92         if(ht < 0)
93                 if(zdist < 0)
94                         vz = -vz;
95
96         vector solution;
97         solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist"
98         // ALWAYS solvable because jumpheight >= zdist
99         if(!solution.z)
100                 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)
101         if(zdist == 0)
102                 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)
103
104         if(zdist < 0)
105         {
106                 // down-jump
107                 if(ht < 0)
108                 {
109                         // almost straight line type
110                         // jump apex is before the jump
111                         // we must take the larger one
112                         trigger_push_calculatevelocity_flighttime = solution.y;
113                 }
114                 else
115                 {
116                         // regular jump
117                         // jump apex is during the jump
118                         // we must take the larger one too
119                         trigger_push_calculatevelocity_flighttime = solution.y;
120                 }
121         }
122         else
123         {
124                 // up-jump
125                 if(ht < 0)
126                 {
127                         // almost straight line type
128                         // jump apex is after the jump
129                         // we must take the smaller one
130                         trigger_push_calculatevelocity_flighttime = solution.x;
131                 }
132                 else
133                 {
134                         // regular jump
135                         // jump apex is during the jump
136                         // we must take the larger one
137                         trigger_push_calculatevelocity_flighttime = solution.y;
138                 }
139         }
140         vs = sdist / trigger_push_calculatevelocity_flighttime;
141
142         // finally calculate the velocity
143         return sdir * vs + '0 0 1' * vz;
144 }
145
146 void trigger_push_touch()
147 {
148         if (self.active == ACTIVE_NOT)
149                 return;
150
151         if (!isPushable(other))
152                 return;
153
154         if(self.team)
155                 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
156                         return;
157
158         EXACTTRIGGER_TOUCH;
159
160         if(self.enemy)
161         {
162                 other.velocity = trigger_push_calculatevelocity(other.origin, self.enemy, self.height);
163         }
164         else if(self.target)
165         {
166                 entity e;
167                 RandomSelection_Init();
168                 for(e = world; (e = find(e, targetname, self.target)); )
169                 {
170                         if(e.cnt)
171                                 RandomSelection_Add(e, 0, string_null, e.cnt, 1);
172                         else
173                                 RandomSelection_Add(e, 0, string_null, 1, 1);
174                 }
175                 other.velocity = trigger_push_calculatevelocity(other.origin, RandomSelection_chosen_ent, self.height);
176         }
177         else
178         {
179                 other.velocity = self.movedir;
180         }
181
182         other.flags &= ~FL_ONGROUND;
183
184         if (IS_PLAYER(other))
185         {
186                 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
187                 other.oldvelocity = other.velocity;
188
189                 if(self.pushltime < time)  // prevent "snorring" sound when a player hits the jumppad more than once
190                 {
191                         // flash when activated
192                         pointparticles(particleeffectnum("jumppad_activate"), other.origin, other.velocity, 1);
193                         sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
194                         self.pushltime = time + 0.2;
195                 }
196                 if(IS_REAL_CLIENT(other) || IS_BOT_CLIENT(other))
197                 {
198                         float i;
199                         float found;
200                         found = false;
201                         for(i = 0; i < other.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
202                                 if(other.(jumppadsused[i]) == self)
203                                         found = true;
204                         if(!found)
205                         {
206                                 other.(jumppadsused[other.jumppadcount % NUM_JUMPPADSUSED]) = self;
207                                 other.jumppadcount = other.jumppadcount + 1;
208                         }
209
210                         if(IS_REAL_CLIENT(other))
211                         {
212                                 if(self.message)
213                                         centerprint(other, self.message);
214                         }
215                         else
216                                 other.lastteleporttime = time;
217
218                         if (other.deadflag == DEAD_NO)
219                                 animdecide_setaction(other, ANIMACTION_JUMP, true);
220                 }
221                 else
222                         other.jumppadcount = true;
223
224                 // reset tracking of who pushed you into a hazard (for kill credit)
225                 other.pushltime = 0;
226                 other.istypefrag = 0;
227         }
228
229         if(self.enemy.target)
230         {
231                 entity oldself;
232                 oldself = self;
233                 activator = other;
234                 self = self.enemy;
235                 SUB_UseTargets();
236                 self = oldself;
237         }
238
239         if (other.flags & FL_PROJECTILE)
240         {
241                 other.angles = vectoangles (other.velocity);
242                 switch(other.movetype)
243                 {
244                         case MOVETYPE_FLY:
245                                 other.movetype = MOVETYPE_TOSS;
246                                 other.gravity = 1;
247                                 break;
248                         case MOVETYPE_BOUNCEMISSILE:
249                                 other.movetype = MOVETYPE_BOUNCE;
250                                 other.gravity = 1;
251                                 break;
252                 }
253                 UpdateCSQCProjectile(other);
254         }
255
256         if (self.spawnflags & PUSH_ONCE)
257         {
258                 self.touch = func_null;
259                 self.think = SUB_Remove;
260                 self.nextthink = time;
261         }
262 }
263
264 .vector dest;
265 void trigger_push_findtarget()
266 {
267         entity e, t;
268         vector org;
269
270         // first calculate a typical start point for the jump
271         org = (self.absmin + self.absmax) * 0.5;
272         org_z = self.absmax.z - PL_MIN_z;
273
274         if (self.target)
275         {
276                 float n;
277                 n = 0;
278                 for(t = world; (t = find(t, targetname, self.target)); )
279                 {
280                         ++n;
281                         e = spawn();
282                         setorigin(e, org);
283                         setsize(e, PL_MIN, PL_MAX);
284                         e.velocity = trigger_push_calculatevelocity(org, t, self.height);
285                         tracetoss(e, e);
286                         if(e.movetype == MOVETYPE_NONE)
287                                 waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
288                         remove(e);
289                 }
290
291                 if(n == 0)
292                 {
293                         // no dest!
294                         objerror ("Jumppad with nonexistant target");
295                         return;
296                 }
297                 else if(n == 1)
298                 {
299                         // exactly one dest - bots love that
300                         self.enemy = find(world, targetname, self.target);
301                 }
302                 else
303                 {
304                         // have to use random selection every single time
305                         self.enemy = world;
306                 }
307         }
308         else
309         {
310                 e = spawn();
311                 setorigin(e, org);
312                 setsize(e, PL_MIN, PL_MAX);
313                 e.velocity = self.movedir;
314                 tracetoss(e, e);
315                 waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
316                 remove(e);
317         }
318 }
319
320 /*
321  * ENTITY PARAMETERS:
322  *
323  *   target:  target of jump
324  *   height:  the absolute value is the height of the highest point of the jump
325  *            trajectory above the higher one of the player and the target.
326  *            the sign indicates whether the highest point is INSIDE (positive)
327  *            or OUTSIDE (negative) of the jump trajectory. General rule: use
328  *            positive values for targets mounted on the floor, and use negative
329  *            values to target a point on the ceiling.
330  *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
331  */
332 void spawnfunc_trigger_push()
333 {
334         SetMovedir ();
335
336         EXACTTRIGGER_INIT;
337
338         self.active = ACTIVE_ACTIVE;
339         self.use = trigger_push_use;
340         self.touch = trigger_push_touch;
341
342         // normal push setup
343         if (!self.speed)
344                 self.speed = 1000;
345         self.movedir = self.movedir * self.speed * 10;
346
347         if (!self.noise)
348                 self.noise = "misc/jumppad.wav";
349         precache_sound (self.noise);
350
351         // this must be called to spawn the teleport waypoints for bots
352         InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET);
353 }
354
355 void spawnfunc_target_push() {}
356 void spawnfunc_info_notnull() {}
357 void spawnfunc_target_position() {}