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