]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/triggers/trigger/jumppads.qc
Move more stuff around
[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                 WriteByte(MSG_ENTITY, self.team);
324                 WriteInt24_t(MSG_ENTITY, self.spawnflags);
325                 WriteByte(MSG_ENTITY, self.active);
326                 WriteByte(MSG_ENTITY, self.height);
327
328                 trigger_common_write(true);
329         }
330
331         if(sf & 2)
332         {
333                 WriteByte(MSG_ENTITY, self.team);
334                 WriteByte(MSG_ENTITY, self.active);
335         }
336
337         return true;
338 }
339
340 void trigger_push_updatelink()
341 {
342         self.SendFlags |= 1;
343 }
344
345 void trigger_push_link()
346 {
347         Net_LinkEntity(self, false, 0, trigger_push_send);
348 }
349 #endif
350 #ifdef SVQC
351 /*
352  * ENTITY PARAMETERS:
353  *
354  *   target:  target of jump
355  *   height:  the absolute value is the height of the highest point of the jump
356  *            trajectory above the higher one of the player and the target.
357  *            the sign indicates whether the highest point is INSIDE (positive)
358  *            or OUTSIDE (negative) of the jump trajectory. General rule: use
359  *            positive values for targets mounted on the floor, and use negative
360  *            values to target a point on the ceiling.
361  *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
362  */
363 void spawnfunc_trigger_push()
364 {
365         SetMovedir ();
366
367         EXACTTRIGGER_INIT;
368
369         self.active = ACTIVE_ACTIVE;
370         self.use = trigger_push_use;
371         self.touch = trigger_push_touch;
372
373         // normal push setup
374         if (!self.speed)
375                 self.speed = 1000;
376         self.movedir = self.movedir * self.speed * 10;
377
378         if (!self.noise)
379                 self.noise = "misc/jumppad.wav";
380         precache_sound (self.noise);
381
382         // this must be called to spawn the teleport waypoints for bots
383         InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET);
384 }
385
386
387 float target_push_send(entity to, float sf)
388 {
389         WriteByte(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH);
390
391         WriteByte(MSG_ENTITY, self.cnt);
392         WriteString(MSG_ENTITY, self.targetname);
393         WriteCoord(MSG_ENTITY, self.origin_x);
394         WriteCoord(MSG_ENTITY, self.origin_y);
395         WriteCoord(MSG_ENTITY, self.origin_z);
396
397         return true;
398 }
399
400 void target_push_link()
401 {
402         Net_LinkEntity(self, false, 0, target_push_send);
403         self.SendFlags |= 1; // update
404 }
405
406 void spawnfunc_target_push() { target_push_link(); }
407 void spawnfunc_info_notnull() { target_push_link(); }
408 void spawnfunc_target_position() { target_push_link(); }
409
410 #endif
411
412 #ifdef CSQC
413
414 void ent_trigger_push()
415 {
416         float sf = ReadByte();
417
418         if(sf & 1)
419         {
420                 self.classname = "jumppad";
421                 int mytm = ReadByte(); if(mytm) { self.team = mytm - 1; }
422                 self.spawnflags = ReadInt24_t();
423                 self.active = ReadByte();
424                 self.height = ReadByte();
425
426                 trigger_common_read(true);
427
428                 self.entremove = trigger_remove_generic;
429                 self.solid = SOLID_TRIGGER;
430                 self.draw = trigger_draw_generic;
431                 self.trigger_touch = trigger_push_touch;
432                 self.drawmask = MASK_NORMAL;
433                 self.move_time = time;
434                 trigger_push_findtarget();
435         }
436
437         if(sf & 2)
438         {
439                 self.team = ReadByte();
440                 self.active = ReadByte();
441         }
442 }
443
444 void target_push_remove()
445 {
446         if(self.classname)
447                 strunzone(self.classname);
448         self.classname = string_null;
449
450         if(self.targetname)
451                 strunzone(self.targetname);
452         self.targetname = string_null;
453 }
454
455 void ent_target_push()
456 {
457         self.classname = "push_target";
458         self.cnt = ReadByte();
459         self.targetname = strzone(ReadString());
460         self.origin_x = ReadCoord();
461         self.origin_y = ReadCoord();
462         self.origin_z = ReadCoord();
463         setorigin(self, self.origin);
464
465         self.drawmask = MASK_NORMAL;
466         self.entremove = target_push_remove;
467 }
468 #endif