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