]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_jumppads.qc
Broken jumppad prediction
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_jumppads.qc
1 .float height;
2
3 #ifdef CSQC
4 .float active;
5 .string target;
6 .string targetname;
7 .float jpad_time;
8 #define ACTIVE_NOT              0
9 #define ACTIVE_ACTIVE   1
10 #define ACTIVE_IDLE     2
11 #define ACTIVE_BUSY     2
12 #define ACTIVE_TOGGLE   3
13 #endif
14
15 #ifdef SVQC
16
17 const float PUSH_ONCE = 1;
18 const float PUSH_SILENT = 2;
19
20 .float pushltime;
21 .float istypefrag;
22
23 void() SUB_UseTargets;
24
25 void trigger_push_use()
26 {
27         if(teamplay)
28         {
29                 self.team = activator.team;
30                 self.SendFlags |= 2;
31         }
32 }
33 #endif
34
35 float trigger_push_calculatevelocity_flighttime;
36
37 /*
38         trigger_push_calculatevelocity
39
40         Arguments:
41           org - origin of the object which is to be pushed
42           tgt - target entity (can be either a point or a model entity; if it is
43                 the latter, its midpoint is used)
44           ht  - jump height, measured from the higher one of org and tgt's midpoint
45
46         Returns: velocity for the jump
47         the global trigger_push_calculatevelocity_flighttime is set to the total
48         jump time
49  */
50
51 vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
52 {
53         float grav, sdist, zdist, vs, vz, jumpheight;
54         vector sdir, torg;
55
56         torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
57
58         grav = PHYS_GRAVITY;
59         if(PHYS_ENTGRAVITY(other))
60                 grav *= PHYS_ENTGRAVITY(other);
61
62         zdist = torg_z - org_z;
63         sdist = vlen(torg - org - zdist * '0 0 1');
64         sdir = normalize(torg - org - zdist * '0 0 1');
65
66         // how high do we need to push the player?
67         jumpheight = fabs(ht);
68         if(zdist > 0)
69                 jumpheight = jumpheight + zdist;
70
71         /*
72                 STOP.
73
74                 You will not understand the following equations anyway...
75                 But here is what I did to get them.
76
77                 I used the functions
78
79                   s(t) = t * vs
80                   z(t) = t * vz - 1/2 grav t^2
81
82                 and solved for:
83
84                   s(ti) = sdist
85                   z(ti) = zdist
86                   max(z, ti) = jumpheight
87
88                 From these three equations, you will find the three parameters vs, vz
89                 and ti.
90          */
91
92         // push him so high...
93         vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)!
94
95         // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump!
96         if(ht < 0)
97                 if(zdist < 0)
98                         vz = -vz;
99
100         vector solution;
101         solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist"
102         // ALWAYS solvable because jumpheight >= zdist
103         if(!solution_z)
104                 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)
105         if(zdist == 0)
106                 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)
107
108         if(zdist < 0)
109         {
110                 // down-jump
111                 if(ht < 0)
112                 {
113                         // almost straight line type
114                         // jump apex is before the jump
115                         // we must take the larger one
116                         trigger_push_calculatevelocity_flighttime = solution_y;
117                 }
118                 else
119                 {
120                         // regular jump
121                         // jump apex is during the jump
122                         // we must take the larger one too
123                         trigger_push_calculatevelocity_flighttime = solution_y;
124                 }
125         }
126         else
127         {
128                 // up-jump
129                 if(ht < 0)
130                 {
131                         // almost straight line type
132                         // jump apex is after the jump
133                         // we must take the smaller one
134                         trigger_push_calculatevelocity_flighttime = solution_x;
135                 }
136                 else
137                 {
138                         // regular jump
139                         // jump apex is during the jump
140                         // we must take the larger one
141                         trigger_push_calculatevelocity_flighttime = solution_y;
142                 }
143         }
144         vs = sdist / trigger_push_calculatevelocity_flighttime;
145
146         // finally calculate the velocity
147         return sdir * vs + '0 0 1' * vz;
148 }
149
150 void PM_ClientMovement_Move();
151 void trigger_push_touch()
152 {
153         if (self.active == ACTIVE_NOT)
154                 return;
155
156 #ifdef SVQC
157         if (!isPushable(other))
158                 return;
159 #endif
160
161         if(self.team)
162                 if(((self.spawnflags & 4) == 0) == (DIFF_TEAM(self, other)))
163                         return;
164
165         EXACTTRIGGER_TOUCH;
166
167         if(self.enemy)
168         {
169                 other.velocity = trigger_push_calculatevelocity(other.origin, self.enemy, self.height);
170         }
171         else if(self.target)
172         {
173                 entity e;
174                 RandomSelection_Init();
175                 for(e = world; (e = find(e, targetname, self.target)); )
176                 {
177                         if(e.cnt)
178                                 RandomSelection_Add(e, 0, string_null, e.cnt, 1);
179                         else
180                                 RandomSelection_Add(e, 0, string_null, 1, 1);
181                 }
182                 other.velocity = trigger_push_calculatevelocity(other.origin, RandomSelection_chosen_ent, self.height);
183         }
184         else
185         {
186                 other.velocity = self.movedir;
187         }
188
189         UNSET_ONGROUND(other);
190
191 #ifdef SVQC
192         if (IS_PLAYER(other))
193         {
194                 // reset tracking of oldvelocity for impact damage (sudden velocity changes)
195                 other.oldvelocity = other.velocity;
196
197                 if(self.pushltime < time)  // prevent "snorring" sound when a player hits the jumppad more than once
198                 {
199                         // flash when activated
200                         pointparticles(particleeffectnum("jumppad_activate"), other.origin, other.velocity, 1);
201                         sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
202                         self.pushltime = time + 0.2;
203                 }
204
205                 if(IS_REAL_CLIENT(other) || IS_BOT_CLIENT(other))
206                 {
207                         float i;
208                         float found;
209                         found = FALSE;
210                         for(i = 0; i < other.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
211                                 if(other.(jumppadsused[i]) == self)
212                                         found = TRUE;
213                         if(!found)
214                         {
215                                 other.(jumppadsused[mod(other.jumppadcount, NUM_JUMPPADSUSED)]) = self;
216                                 other.jumppadcount = other.jumppadcount + 1;
217                         }
218
219                         if(IS_REAL_CLIENT(other))
220                         {
221                                 if(self.message)
222                                         centerprint(other, self.message);
223                         }
224                         else
225                                 other.lastteleporttime = time;
226
227                         if (other.deadflag == DEAD_NO)
228                                 animdecide_setaction(other, ANIMACTION_JUMP, TRUE);
229                 }
230                 else
231                         other.jumppadcount = TRUE;
232
233                 // reset tracking of who pushed you into a hazard (for kill credit)
234                 other.pushltime = 0;
235                 other.istypefrag = 0;
236         }
237
238         if(self.enemy.target)
239         {
240                 entity oldself;
241                 oldself = self;
242                 activator = other;
243                 self = self.enemy;
244                 SUB_UseTargets();
245                 self = oldself;
246         }
247
248         if (other.flags & FL_PROJECTILE)
249         {
250                 other.angles = vectoangles (other.velocity);
251                 switch(other.movetype)
252                 {
253                         case MOVETYPE_FLY:
254                                 other.movetype = MOVETYPE_TOSS;
255                                 other.gravity = 1;
256                                 break;
257                         case MOVETYPE_BOUNCEMISSILE:
258                                 other.movetype = MOVETYPE_BOUNCE;
259                                 other.gravity = 1;
260                                 break;
261                 }
262                 UpdateCSQCProjectile(other);
263         }
264
265         if (self.spawnflags & PUSH_ONCE)
266         {
267                 self.touch = func_null;
268                 self.think = SUB_Remove;
269                 self.nextthink = time;
270         }
271 #endif
272 }
273
274 #ifdef SVQC
275 float trigger_push_send(entity to, float sf)
276 {
277         WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH);
278         WriteByte(MSG_ENTITY, sf);
279
280         if(sf & 1)
281         {
282                 WriteString(MSG_ENTITY, self.target);
283                 WriteByte(MSG_ENTITY, self.team);
284                 WriteByte(MSG_ENTITY, self.active);
285                 WriteByte(MSG_ENTITY, self.warpzone_isboxy);
286                 WriteByte(MSG_ENTITY, self.height);
287                 WriteByte(MSG_ENTITY, self.scale);
288                 WriteCoord(MSG_ENTITY, self.origin_x);
289                 WriteCoord(MSG_ENTITY, self.origin_y);
290                 WriteCoord(MSG_ENTITY, self.origin_z);
291
292                 WriteCoord(MSG_ENTITY, self.mins_x);
293                 WriteCoord(MSG_ENTITY, self.mins_y);
294                 WriteCoord(MSG_ENTITY, self.mins_z);
295                 WriteCoord(MSG_ENTITY, self.maxs_x);
296                 WriteCoord(MSG_ENTITY, self.maxs_y);
297                 WriteCoord(MSG_ENTITY, self.maxs_z);
298
299                 WriteCoord(MSG_ENTITY, self.movedir_x);
300                 WriteCoord(MSG_ENTITY, self.movedir_y);
301                 WriteCoord(MSG_ENTITY, self.movedir_z);
302
303                 WriteCoord(MSG_ENTITY, self.angles_x);
304                 WriteCoord(MSG_ENTITY, self.angles_y);
305                 WriteCoord(MSG_ENTITY, self.angles_z);
306         }
307
308         if(sf & 2)
309         {
310                 WriteByte(MSG_ENTITY, self.team);
311                 WriteByte(MSG_ENTITY, self.active);
312         }
313
314         return TRUE;
315 }
316
317 void trigger_push_updatelink()
318 {
319         self.SendFlags |= 1;
320 }
321
322 void trigger_push_link()
323 {
324         Net_LinkEntity(self, FALSE, 0, trigger_push_send);
325 }
326 #endif
327
328 .vector dest;
329 void trigger_push_findtarget()
330 {
331         entity t;
332         vector org;
333
334         // first calculate a typical start point for the jump
335         org = (self.absmin + self.absmax) * 0.5;
336         org_z = self.absmax_z - PL_MIN_z;
337
338         if (self.target)
339         {
340                 float n = 0;
341                 for(t = world; (t = find(t, targetname, self.target)); )
342                 {
343                         ++n;
344 #ifdef SVQC
345                         entity e = spawn();
346                         setorigin(e, org);
347                         setsize(e, PL_MIN, PL_MAX);
348                         e.velocity = trigger_push_calculatevelocity(org, t, self.height);
349                         tracetoss(e, e);
350                         if(e.movetype == MOVETYPE_NONE)
351                                 waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
352                         remove(e);
353 #endif
354                 }
355
356                 if(!n)
357                 {
358                         // no dest!
359 #ifdef SVQC
360                         objerror ("Jumppad with nonexistant target");
361 #endif
362                         return;
363                 }
364                 else if(n == 1)
365                 {
366                         // exactly one dest - bots love that
367                         self.enemy = find(world, targetname, self.target);
368                 }
369                 else
370                 {
371                         // have to use random selection every single time
372                         self.enemy = world;
373                 }
374         }
375 #ifdef SVQC
376         else
377         {
378                 entity e = spawn();
379                 setorigin(e, org);
380                 setsize(e, PL_MIN, PL_MAX);
381                 e.velocity = self.movedir;
382                 tracetoss(e, e);
383                 waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
384                 remove(e);
385         }
386
387         trigger_push_link();
388         defer(0.1, trigger_push_updatelink);
389 #endif
390 }
391
392 #ifdef SVQC
393 /*
394  * ENTITY PARAMETERS:
395  *
396  *   target:  target of jump
397  *   height:  the absolute value is the height of the highest point of the jump
398  *            trajectory above the higher one of the player and the target.
399  *            the sign indicates whether the highest point is INSIDE (positive)
400  *            or OUTSIDE (negative) of the jump trajectory. General rule: use
401  *            positive values for targets mounted on the floor, and use negative
402  *            values to target a point on the ceiling.
403  *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
404  */
405 void spawnfunc_trigger_push()
406 {
407         SetMovedir ();
408
409         EXACTTRIGGER_INIT;
410
411         self.active = ACTIVE_ACTIVE;
412         self.use = trigger_push_use;
413         self.touch = trigger_push_touch;
414
415         // normal push setup
416         if (!self.speed)
417                 self.speed = 1000;
418         self.movedir = self.movedir * self.speed * 10;
419
420         if (!self.noise)
421                 self.noise = "misc/jumppad.wav";
422         precache_sound (self.noise);
423
424         // this must be called to spawn the teleport waypoints for bots
425         InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET);
426 }
427
428
429 float target_push_send(entity to, float sf)
430 {
431         WriteByte(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH);
432
433         WriteByte(MSG_ENTITY, self.cnt);
434         WriteString(MSG_ENTITY, self.targetname);
435         WriteCoord(MSG_ENTITY, self.origin_x);
436         WriteCoord(MSG_ENTITY, self.origin_y);
437         WriteCoord(MSG_ENTITY, self.origin_z);
438
439         return TRUE;
440 }
441
442 void target_push_link()
443 {
444         Net_LinkEntity(self, FALSE, 0, target_push_send);
445         self.SendFlags |= 1; // update
446 }
447
448 void spawnfunc_target_push() { target_push_link(); }
449 void spawnfunc_info_notnull() { target_push_link(); }
450 void spawnfunc_target_position() { target_push_link(); }
451
452 #endif
453
454 #ifdef CSQC
455 void trigger_push_draw()
456 {
457         /*float dt = time - self.move_time;
458         self.move_time = time;
459         if(dt <= 0)
460                 return;*/
461
462         tracebox(self.origin, self.mins, self.maxs, self.origin, MOVE_NORMAL, self);
463
464         //if(trace_fraction < 1)
465         if(trace_ent)
466         if(time >= trace_ent.jpad_time)
467         {
468                 other = trace_ent;
469                 trigger_push_touch();
470                 trace_ent.jpad_time = time + 0.2;
471         }
472 }
473
474 void ent_trigger_push()
475 {
476         float sf = ReadByte();
477
478         if(sf & 1)
479         {
480                 self.classname = "jumppad";
481                 self.target = strzone(ReadString());
482                 float mytm = ReadByte(); if(mytm) { self.team = mytm - 1; }
483                 self.active = ReadByte();
484                 self.warpzone_isboxy = ReadByte();
485                 self.height = ReadByte();
486                 self.scale = ReadByte();
487                 self.origin_x = ReadCoord();
488                 self.origin_y = ReadCoord();
489                 self.origin_z = ReadCoord();
490                 setorigin(self, self.origin);
491                 self.mins_x = ReadCoord();
492                 self.mins_y = ReadCoord();
493                 self.mins_z = ReadCoord();
494                 self.maxs_x = ReadCoord();
495                 self.maxs_y = ReadCoord();
496                 self.maxs_z = ReadCoord();
497                 setsize(self, self.mins, self.maxs);
498                 self.movedir_x = ReadCoord();
499                 self.movedir_y = ReadCoord();
500                 self.movedir_z = ReadCoord();
501                 self.angles_x = ReadCoord();
502                 self.angles_y = ReadCoord();
503                 self.angles_z = ReadCoord();
504
505                 self.solid = SOLID_TRIGGER;
506                 //self.draw = trigger_push_draw;
507                 self.drawmask = MASK_NORMAL;
508                 self.move_time = time;
509                 //self.touch = trigger_push_touch;
510                 trigger_push_findtarget();
511         }
512
513         if(sf & 2)
514         {
515                 self.team = ReadByte();
516                 self.active = ReadByte();
517         }
518 }
519
520 void ent_target_push()
521 {
522         self.classname = "push_target";
523         self.cnt = ReadByte();
524         self.targetname = strzone(ReadString());
525         self.origin_x = ReadCoord();
526         self.origin_y = ReadCoord();
527         self.origin_z = ReadCoord();
528         setorigin(self, self.origin);
529
530         self.drawmask = MASK_NORMAL;
531 }
532 #endif