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