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