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