]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_jumppads.qc
Merge branch 'Mario/qc_updates' into TimePath/csqc_prediction
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_jumppads.qc
1 #include "t_jumppads.qh"
2
3 #ifdef SVQC
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 .vector dest;
249 #ifdef SVQC
250 void trigger_push_link();
251 void trigger_push_updatelink();
252 #endif
253 void trigger_push_findtarget()
254 {
255         entity t;
256         vector org;
257
258         // first calculate a typical start point for the jump
259         org = (self.absmin + self.absmax) * 0.5;
260         org.z = self.absmax.z - PL_MIN_z;
261
262         if (self.target)
263         {
264                 float n = 0;
265                 for(t = world; (t = find(t, targetname, self.target)); )
266                 {
267                         ++n;
268 #ifdef SVQC
269                         entity e = spawn();
270                         setorigin(e, org);
271                         setsize(e, PL_MIN, PL_MAX);
272                         e.velocity = trigger_push_calculatevelocity(org, t, self.height);
273                         tracetoss(e, e);
274                         if(e.movetype == MOVETYPE_NONE)
275                                 waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
276                         remove(e);
277 #endif
278                 }
279
280                 if(!n)
281                 {
282                         // no dest!
283 #ifdef SVQC
284                         objerror ("Jumppad with nonexistant target");
285 #endif
286                         return;
287                 }
288                 else if(n == 1)
289                 {
290                         // exactly one dest - bots love that
291                         self.enemy = find(world, targetname, self.target);
292                 }
293                 else
294                 {
295                         // have to use random selection every single time
296                         self.enemy = world;
297                 }
298         }
299 #ifdef SVQC
300         else
301         {
302                 entity e = spawn();
303                 setorigin(e, org);
304                 setsize(e, PL_MIN, PL_MAX);
305                 e.velocity = self.movedir;
306                 tracetoss(e, e);
307                 waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
308                 remove(e);
309         }
310
311         trigger_push_link();
312         defer(0.1, trigger_push_updatelink);
313 #endif
314 }
315
316 #ifdef SVQC
317 float trigger_push_send(entity to, float sf)
318 {
319         WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH);
320         WriteByte(MSG_ENTITY, sf);
321
322         if(sf & 1)
323         {
324                 WriteString(MSG_ENTITY, self.target);
325                 WriteByte(MSG_ENTITY, self.team);
326                 WriteInt24_t(MSG_ENTITY, self.spawnflags);
327                 WriteByte(MSG_ENTITY, self.active);
328                 WriteByte(MSG_ENTITY, self.warpzone_isboxy);
329                 WriteByte(MSG_ENTITY, self.height);
330                 WriteByte(MSG_ENTITY, self.scale);
331                 WriteCoord(MSG_ENTITY, self.origin_x);
332                 WriteCoord(MSG_ENTITY, self.origin_y);
333                 WriteCoord(MSG_ENTITY, self.origin_z);
334
335                 WriteCoord(MSG_ENTITY, self.mins_x);
336                 WriteCoord(MSG_ENTITY, self.mins_y);
337                 WriteCoord(MSG_ENTITY, self.mins_z);
338                 WriteCoord(MSG_ENTITY, self.maxs_x);
339                 WriteCoord(MSG_ENTITY, self.maxs_y);
340                 WriteCoord(MSG_ENTITY, self.maxs_z);
341
342                 WriteCoord(MSG_ENTITY, self.movedir_x);
343                 WriteCoord(MSG_ENTITY, self.movedir_y);
344                 WriteCoord(MSG_ENTITY, self.movedir_z);
345
346                 WriteCoord(MSG_ENTITY, self.angles_x);
347                 WriteCoord(MSG_ENTITY, self.angles_y);
348                 WriteCoord(MSG_ENTITY, self.angles_z);
349         }
350
351         if(sf & 2)
352         {
353                 WriteByte(MSG_ENTITY, self.team);
354                 WriteByte(MSG_ENTITY, self.active);
355         }
356
357         return true;
358 }
359
360 void trigger_push_updatelink()
361 {
362         self.SendFlags |= 1;
363 }
364
365 void trigger_push_link()
366 {
367         Net_LinkEntity(self, false, 0, trigger_push_send);
368 }
369 #endif
370 #ifdef SVQC
371 /*
372  * ENTITY PARAMETERS:
373  *
374  *   target:  target of jump
375  *   height:  the absolute value is the height of the highest point of the jump
376  *            trajectory above the higher one of the player and the target.
377  *            the sign indicates whether the highest point is INSIDE (positive)
378  *            or OUTSIDE (negative) of the jump trajectory. General rule: use
379  *            positive values for targets mounted on the floor, and use negative
380  *            values to target a point on the ceiling.
381  *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
382  */
383 void spawnfunc_trigger_push()
384 {
385         SetMovedir ();
386
387         EXACTTRIGGER_INIT;
388
389         self.active = ACTIVE_ACTIVE;
390         self.use = trigger_push_use;
391         self.touch = trigger_push_touch;
392
393         // normal push setup
394         if (!self.speed)
395                 self.speed = 1000;
396         self.movedir = self.movedir * self.speed * 10;
397
398         if (!self.noise)
399                 self.noise = "misc/jumppad.wav";
400         precache_sound (self.noise);
401
402         // this must be called to spawn the teleport waypoints for bots
403         InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET);
404 }
405
406
407 float target_push_send(entity to, float sf)
408 {
409         WriteByte(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH);
410
411         WriteByte(MSG_ENTITY, self.cnt);
412         WriteString(MSG_ENTITY, self.targetname);
413         WriteCoord(MSG_ENTITY, self.origin_x);
414         WriteCoord(MSG_ENTITY, self.origin_y);
415         WriteCoord(MSG_ENTITY, self.origin_z);
416
417         return true;
418 }
419
420 void target_push_link()
421 {
422         Net_LinkEntity(self, false, 0, target_push_send);
423         self.SendFlags |= 1; // update
424 }
425
426 void spawnfunc_target_push() { target_push_link(); }
427 void spawnfunc_info_notnull() { target_push_link(); }
428 void spawnfunc_target_position() { target_push_link(); }
429
430 #endif
431
432 #ifdef CSQC
433 void trigger_push_draw()
434 {
435         float dt = time - self.move_time;
436         self.move_time = time;
437         if(dt <= 0) { return; }
438
439         trigger_touch_generic(trigger_push_touch);
440 }
441
442 void ent_trigger_push()
443 {
444         float sf = ReadByte();
445
446         if(sf & 1)
447         {
448                 self.classname = "jumppad";
449                 self.target = strzone(ReadString());
450                 float mytm = ReadByte(); if(mytm) { self.team = mytm - 1; }
451                 self.spawnflags = ReadInt24_t();
452                 self.active = ReadByte();
453                 self.warpzone_isboxy = ReadByte();
454                 self.height = ReadByte();
455                 self.scale = ReadByte();
456                 self.origin_x = ReadCoord();
457                 self.origin_y = ReadCoord();
458                 self.origin_z = ReadCoord();
459                 setorigin(self, self.origin);
460                 self.mins_x = ReadCoord();
461                 self.mins_y = ReadCoord();
462                 self.mins_z = ReadCoord();
463                 self.maxs_x = ReadCoord();
464                 self.maxs_y = ReadCoord();
465                 self.maxs_z = ReadCoord();
466                 setsize(self, self.mins, self.maxs);
467                 self.movedir_x = ReadCoord();
468                 self.movedir_y = ReadCoord();
469                 self.movedir_z = ReadCoord();
470                 self.angles_x = ReadCoord();
471                 self.angles_y = ReadCoord();
472                 self.angles_z = ReadCoord();
473
474                 self.solid = SOLID_TRIGGER;
475                 //self.draw = trigger_push_draw;
476                 self.drawmask = MASK_ENGINE;
477                 self.move_time = time;
478                 //self.touch = trigger_push_touch;
479                 trigger_push_findtarget();
480         }
481
482         if(sf & 2)
483         {
484                 self.team = ReadByte();
485                 self.active = ReadByte();
486         }
487 }
488
489 void ent_target_push()
490 {
491         self.classname = "push_target";
492         self.cnt = ReadByte();
493         self.targetname = strzone(ReadString());
494         self.origin_x = ReadCoord();
495         self.origin_y = ReadCoord();
496         self.origin_z = ReadCoord();
497         setorigin(self, self.origin);
498
499         self.drawmask = MASK_ENGINE;
500 }
501 #endif