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