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