]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mapobjects/subs.qc
Transifex autosync
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mapobjects / subs.qc
1 #include "subs.qh"
2
3 void SUB_NullThink(entity this) { }
4
5 void SUB_CalcMoveDone(entity this);
6 void SUB_CalcAngleMoveDone(entity this);
7
8 #ifdef SVQC
9 spawnfunc(info_null)
10 {
11         delete(this);
12         // if anything breaks, tell the mapper to fix their map! info_null is meant to remove itself immediately.
13 }
14 #endif
15
16 /*
17 ==================
18 SUB_Friction
19
20 Applies some friction to this
21 ==================
22 */
23 .float friction;
24 void SUB_Friction (entity this)
25 {
26         this.nextthink = time;
27         if(IS_ONGROUND(this))
28                 this.velocity = this.velocity * (1 - frametime * this.friction);
29 }
30
31 /*
32 ==================
33 SUB_VanishOrRemove
34
35 Makes client invisible or removes non-client
36 ==================
37 */
38 void SUB_VanishOrRemove (entity ent)
39 {
40         if (IS_CLIENT(ent))
41         {
42                 // vanish
43                 ent.alpha = -1;
44                 ent.effects = 0;
45 #ifdef SVQC
46                 ent.glow_size = 0;
47                 ent.pflags = 0;
48 #endif
49         }
50         else
51         {
52                 // remove
53                 delete(ent);
54         }
55 }
56
57 void SUB_SetFade_Think (entity this)
58 {
59         if(this.alpha == 0)
60                 this.alpha = 1;
61         setthink(this, SUB_SetFade_Think);
62         this.nextthink = time;
63         this.alpha -= frametime * this.fade_rate;
64         if (this.alpha < 0.01)
65                 SUB_VanishOrRemove(this);
66         else
67                 this.nextthink = time;
68 }
69
70 /*
71 ==================
72 SUB_SetFade
73
74 Fade ent out when time >= vanish_time
75 ==================
76 */
77 void SUB_SetFade(entity ent, float vanish_time, float fading_time)
78 {
79         if (fading_time <= 0)
80                 fading_time = 0.01;
81         ent.fade_rate = 1/fading_time;
82         setthink(ent, SUB_SetFade_Think);
83         ent.nextthink = vanish_time;
84 }
85
86 /*
87 =============
88 SUB_CalcMove
89
90 calculate this.velocity and this.nextthink to reach dest from
91 this.origin traveling at speed
92 ===============
93 */
94 void SUB_CalcMoveDone(entity this)
95 {
96         // After moving, set origin to exact final destination
97
98         setorigin (this, this.finaldest);
99         this.velocity = '0 0 0';
100         this.nextthink = -1;
101         if (this.think1 && this.think1 != SUB_CalcMoveDone)
102                 this.think1 (this);
103 }
104
105 void SUB_CalcMovePause(entity this)
106 {
107         if (!this.move_controller)
108                 return;
109         this.move_controller.animstate_starttime += frametime;
110         this.move_controller.animstate_endtime += frametime;
111 }
112
113 .float platmovetype_turn;
114 void SUB_CalcMove_controller_think (entity this)
115 {
116         float traveltime;
117         float phasepos;
118         float nexttick;
119         vector delta;
120         vector delta2;
121         vector veloc;
122         vector angloc;
123         vector nextpos;
124         delta = this.destvec;
125         delta2 = this.destvec2;
126         if(time < this.animstate_endtime)
127         {
128                 nexttick = time + PHYS_INPUT_FRAMETIME;
129
130                 traveltime = this.animstate_endtime - this.animstate_starttime;
131                 phasepos = (nexttick - this.animstate_starttime) / traveltime; // range: [0, 1]
132                 phasepos = cubic_speedfunc(this.platmovetype_start, this.platmovetype_end, phasepos);
133                 nextpos = this.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
134                 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
135
136                 if(this.owner.platmovetype_turn)
137                 {
138                         vector destangle;
139                         destangle = delta + 2 * delta2 * phasepos;
140                         destangle = vectoangles(destangle);
141                         destangle_x = -destangle_x; // flip up / down orientation
142
143                         // take the shortest distance for the angles
144                         vector v = this.owner.angles;
145                         v.x -= 360 * floor((v.x - destangle_x) / 360 + 0.5);
146                         v.y -= 360 * floor((v.y - destangle_y) / 360 + 0.5);
147                         v.z -= 360 * floor((v.z - destangle_z) / 360 + 0.5);
148                         this.owner.angles = v;
149                         angloc = destangle - this.owner.angles;
150                         angloc = angloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
151                         this.owner.avelocity = angloc;
152                 }
153                 if(nexttick < this.animstate_endtime)
154                         veloc = nextpos - this.owner.origin;
155                 else
156                         veloc = this.finaldest - this.owner.origin;
157                 veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
158
159                 this.owner.velocity = veloc;
160                 this.nextthink = nexttick;
161         }
162         else
163         {
164                 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
165                 entity own = this.owner;
166                 setthink(own, this.think1);
167                 // set the owner's reference to this entity to NULL
168                 own.move_controller = NULL;
169                 delete(this);
170                 getthink(own)(own);
171         }
172 }
173
174 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector destin)
175 {
176         // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
177         // 2 * control * t - 2 * control * t * t + destin * t * t
178         // 2 * control * t + (destin - 2 * control) * t * t
179
180         //setorigin(controller, org); // don't link to the world
181         controller.origin = org;
182         control -= org;
183         destin -= org;
184
185         controller.destvec = 2 * control; // control point
186         controller.destvec2 = destin - 2 * control; // quadratic part required to reach end point
187         // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (destin - control)
188 }
189
190 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector destin)
191 {
192         // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
193         // 2 * control * t - 2 * control * t * t + destin * t * t
194         // 2 * control * t + (destin - 2 * control) * t * t
195
196         //setorigin(controller, org); // don't link to the world
197         controller.origin = org;
198         destin -= org;
199
200         controller.destvec = destin; // end point
201         controller.destvec2 = '0 0 0';
202 }
203
204 void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
205 {
206         float   traveltime;
207         entity controller;
208
209         if (!tspeed)
210                 objerror (this, "No speed is defined!");
211
212         this.think1 = func;
213         this.finaldest = tdest;
214         setthink(this, SUB_CalcMoveDone);
215
216         switch(tspeedtype)
217         {
218                 default:
219                 case TSPEED_START:
220                         traveltime = 2 * vlen(tcontrol - this.origin) / tspeed;
221                         break;
222                 case TSPEED_END:
223                         traveltime = 2 * vlen(tcontrol - tdest)       / tspeed;
224                         break;
225                 case TSPEED_LINEAR:
226                         traveltime = vlen(tdest - this.origin)        / tspeed;
227                         break;
228                 case TSPEED_TIME:
229                         traveltime = tspeed;
230                         break;
231         }
232
233         if (traveltime < 0.1) // useless anim
234         {
235                 this.velocity = '0 0 0';
236                 this.nextthink = this.ltime + 0.1;
237                 return;
238         }
239
240         // delete the previous controller, otherwise changing movement midway is glitchy
241         if (this.move_controller != NULL)
242         {
243                 delete(this.move_controller);
244         }
245         controller = new_pure(SUB_CalcMove_controller);
246         set_movetype(controller, MOVETYPE_NONE); // mark the entity as physics driven so that thinking is handled by QC
247         controller.owner = this;
248         this.move_controller = controller;
249         controller.platmovetype = this.platmovetype;
250         controller.platmovetype_start = this.platmovetype_start;
251         controller.platmovetype_end = this.platmovetype_end;
252         SUB_CalcMove_controller_setbezier(controller, this.origin, tcontrol, tdest);
253         controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
254         controller.animstate_starttime = time;
255         controller.animstate_endtime = time + traveltime;
256         setthink(controller, SUB_CalcMove_controller_think);
257         controller.think1 = getthink(this);
258
259         // the thinking is now done by the controller
260         setthink(this, SUB_NullThink); // for PushMove
261         this.nextthink = this.ltime + traveltime;
262
263         // invoke controller
264         getthink(controller)(controller);
265 }
266
267 void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
268 {
269         vector  delta;
270         float   traveltime;
271
272         if (!tspeed)
273                 objerror (this, "No speed is defined!");
274
275         this.think1 = func;
276         this.finaldest = tdest;
277         setthink(this, SUB_CalcMoveDone);
278
279         if (tdest == this.origin)
280         {
281                 this.velocity = '0 0 0';
282                 this.nextthink = this.ltime + 0.1;
283                 return;
284         }
285
286         delta = tdest - this.origin;
287
288         switch(tspeedtype)
289         {
290                 default:
291                 case TSPEED_START:
292                 case TSPEED_END:
293                 case TSPEED_LINEAR:
294                         traveltime = vlen (delta) / tspeed;
295                         break;
296                 case TSPEED_TIME:
297                         traveltime = tspeed;
298                         break;
299         }
300
301         // Q3 implements this fallback for all movers at the end of its InitMover()
302         // If .speed is negative this applies, instead of the mover-specific default speed.
303         if (traveltime <= 0)
304                 traveltime = 0.001;
305
306         // Very short animations don't really show off the effect
307         // of controlled animation, so let's just use linear movement.
308         // Alternatively entities can choose to specify non-controlled movement.
309         // The only currently implemented alternative movement is linear (value 1)
310         if (traveltime < 0.15 || (this.platmovetype_start == 1 && this.platmovetype_end == 1)) // is this correct?
311         {
312                 this.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
313                 this.nextthink = this.ltime + traveltime;
314                 return;
315         }
316
317         // now just run like a bezier curve...
318         SUB_CalcMove_Bezier(this, (this.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
319 }
320
321 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
322 {
323         SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func);
324 }
325
326 /*
327 =============
328 SUB_CalcAngleMove
329
330 calculate this.avelocity and this.nextthink to reach destangle from
331 this.angles rotating
332
333 The calling function should make sure this.setthink is valid
334 ===============
335 */
336 void SUB_CalcAngleMoveDone(entity this)
337 {
338         // After rotating, set angle to exact final angle
339         this.angles = this.finalangle;
340         this.avelocity = '0 0 0';
341         this.nextthink = -1;
342         if (this.think1 && this.think1 != SUB_CalcAngleMoveDone) // avoid endless loops
343                 this.think1 (this);
344 }
345
346 // FIXME: I fixed this function only for rotation around the main axes
347 void SUB_CalcAngleMove (entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
348 {
349         if (!tspeed)
350                 objerror (this, "No speed is defined!");
351
352         // take the shortest distance for the angles
353         this.angles_x -= 360 * floor((this.angles_x - destangle_x) / 360 + 0.5);
354         this.angles_y -= 360 * floor((this.angles_y - destangle_y) / 360 + 0.5);
355         this.angles_z -= 360 * floor((this.angles_z - destangle_z) / 360 + 0.5);
356         vector delta = destangle - this.angles;
357         float traveltime;
358
359         switch(tspeedtype)
360         {
361                 default:
362                 case TSPEED_START:
363                 case TSPEED_END:
364                 case TSPEED_LINEAR:
365                         traveltime = vlen (delta) / tspeed;
366                         break;
367                 case TSPEED_TIME:
368                         traveltime = tspeed;
369                         break;
370         }
371
372         this.think1 = func;
373         this.finalangle = destangle;
374         setthink(this, SUB_CalcAngleMoveDone);
375
376         if (traveltime < 0.1)
377         {
378                 this.avelocity = '0 0 0';
379                 this.nextthink = this.ltime + 0.1;
380                 return;
381         }
382
383         this.avelocity = delta * (1 / traveltime);
384         this.nextthink = this.ltime + traveltime;
385 }
386
387 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
388 {
389         SUB_CalcAngleMove (ent, destangle, tspeedtype, tspeed, func);
390 }
391
392 #ifdef SVQC
393 void ApplyMinMaxScaleAngles(entity e)
394 {
395         if(e.angles.x != 0 || e.angles.z != 0 || e.avelocity.x != 0 || e.avelocity.z != 0) // "weird" rotation
396         {
397                 e.maxs = '1 1 1' * vlen(
398                         '1 0 0' * max(-e.mins.x, e.maxs.x) +
399                         '0 1 0' * max(-e.mins.y, e.maxs.y) +
400                         '0 0 1' * max(-e.mins.z, e.maxs.z)
401                 );
402                 e.mins = -e.maxs;
403         }
404         else if(e.angles.y != 0 || e.avelocity.y != 0) // yaw only is a bit better
405         {
406                 e.maxs_x = vlen(
407                         '1 0 0' * max(-e.mins.x, e.maxs.x) +
408                         '0 1 0' * max(-e.mins.y, e.maxs.y)
409                 );
410                 e.maxs_y = e.maxs.x;
411                 e.mins_x = -e.maxs.x;
412                 e.mins_y = -e.maxs.x;
413         }
414         if(e.scale)
415                 setsize(e, RoundPerfectVector(e.mins * e.scale), RoundPerfectVector(e.maxs * e.scale));
416         else
417                 setsize(e, e.mins, e.maxs);
418 }
419
420 void SetBrushEntityModel(entity this, bool with_lod)
421 {
422         // Ensure .solid is set correctly before calling this (for area grid linking/unlinking)
423
424         if(this.model != "")
425         {
426                 precache_model(this.model);
427                 if(this.mins != '0 0 0' || this.maxs != '0 0 0')
428                 {
429                         vector mi = this.mins;
430                         vector ma = this.maxs;
431                         _setmodel(this, this.model); // no precision needed
432                         setsize(this, mi, ma);
433                 }
434                 else
435                         _setmodel(this, this.model); // no precision needed
436                 if(with_lod)
437                         InitializeEntity(this, LODmodel_attach, INITPRIO_FINDTARGET);
438         }
439         setorigin(this, this.origin);
440         ApplyMinMaxScaleAngles(this);
441 }
442
443 bool LOD_customize(entity this, entity client)
444 {
445         if(autocvar_loddebug)
446         {
447                 int d = autocvar_loddebug;
448                 if(d == 1)
449                         this.modelindex = this.lodmodelindex0;
450                 else if(d == 2 || !this.lodmodelindex2)
451                         this.modelindex = this.lodmodelindex1;
452                 else // if(d == 3)
453                         this.modelindex = this.lodmodelindex2;
454                 return true;
455         }
456
457         // TODO csqc network this so it only gets sent once
458         vector near_point = NearestPointOnBox(this, client.origin);
459         if(vdist(near_point - client.origin, <, this.loddistance1))
460                 this.modelindex = this.lodmodelindex0;
461         else if(!this.lodmodelindex2 || vdist(near_point - client.origin, <, this.loddistance2))
462                 this.modelindex = this.lodmodelindex1;
463         else
464                 this.modelindex = this.lodmodelindex2;
465
466         return true;
467 }
468
469 void LOD_uncustomize(entity this)
470 {
471         this.modelindex = this.lodmodelindex0;
472 }
473
474 void LODmodel_attach(entity this)
475 {
476         entity e;
477
478         if(!this.loddistance1)
479                 this.loddistance1 = 1000;
480         if(!this.loddistance2)
481                 this.loddistance2 = 2000;
482         this.lodmodelindex0 = this.modelindex;
483
484         if(this.lodtarget1 != "")
485         {
486                 e = find(NULL, targetname, this.lodtarget1);
487                 if(e)
488                 {
489                         this.lodmodel1 = e.model;
490                         delete(e);
491                 }
492         }
493         if(this.lodtarget2 != "")
494         {
495                 e = find(NULL, targetname, this.lodtarget2);
496                 if(e)
497                 {
498                         this.lodmodel2 = e.model;
499                         delete(e);
500                 }
501         }
502
503         if(autocvar_loddebug < 0)
504         {
505                 this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
506         }
507
508         if(this.lodmodel1 != "" && fexists(this.lodmodel1))
509         {
510                 vector mi, ma;
511                 mi = this.mins;
512                 ma = this.maxs;
513
514                 precache_model(this.lodmodel1);
515                 _setmodel(this, this.lodmodel1);
516                 this.lodmodelindex1 = this.modelindex;
517
518                 if(this.lodmodel2 != "" && fexists(this.lodmodel2))
519                 {
520                         precache_model(this.lodmodel2);
521                         _setmodel(this, this.lodmodel2);
522                         this.lodmodelindex2 = this.modelindex;
523                 }
524
525                 this.modelindex = this.lodmodelindex0;
526                 setsize(this, mi, ma);
527         }
528
529         if(this.lodmodelindex1)
530                 if (!getSendEntity(this))
531                         SetCustomizer(this, LOD_customize, LOD_uncustomize);
532 }
533
534 /*
535 ================
536 InitTrigger
537 ================
538 */
539
540 void SetMovedir(entity this)
541 {
542         if(this.movedir != '0 0 0')
543                 this.movedir = normalize(this.movedir);
544         else
545         {
546                 makevectors(this.angles);
547                 this.movedir = v_forward;
548         }
549
550         this.angles = '0 0 0';
551 }
552
553 void InitTrigger(entity this)
554 {
555 // trigger angles are used for one-way touches.  An angle of 0 is assumed
556 // to mean no restrictions, so use a yaw of 360 instead.
557         SetMovedir(this);
558         this.solid = SOLID_TRIGGER;
559         SetBrushEntityModel(this, false);
560         set_movetype(this, MOVETYPE_NONE);
561         this.modelindex = 0;
562         this.model = "";
563 }
564
565 void InitSolidBSPTrigger(entity this)
566 {
567 // trigger angles are used for one-way touches.  An angle of 0 is assumed
568 // to mean no restrictions, so use a yaw of 360 instead.
569         SetMovedir(this);
570         this.solid = SOLID_BSP;
571         SetBrushEntityModel(this, false);
572         set_movetype(this, MOVETYPE_NONE); // why was this PUSH? -div0
573 //      this.modelindex = 0;
574         this.model = "";
575 }
576
577 bool InitMovingBrushTrigger(entity this)
578 {
579 // trigger angles are used for one-way touches.  An angle of 0 is assumed
580 // to mean no restrictions, so use a yaw of 360 instead.
581         this.solid = SOLID_BSP;
582         SetBrushEntityModel(this, true);
583         set_movetype(this, MOVETYPE_PUSH);
584         if(this.modelindex == 0)
585         {
586                 objerror(this, "InitMovingBrushTrigger: no brushes found!");
587                 return false;
588         }
589         return true;
590 }
591 #endif