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