3 void SUB_NullThink(entity this) { }
5 void SUB_CalcMoveDone(entity this);
6 void SUB_CalcAngleMoveDone(entity this);
12 // if anything breaks, tell the mapper to fix their map! info_null is meant to remove itself immediately.
20 Applies some friction to this
24 void SUB_Friction (entity this)
26 this.nextthink = time;
28 this.velocity = this.velocity * (1 - frametime * this.friction);
35 Makes client invisible or removes non-client
38 void SUB_VanishOrRemove (entity ent)
57 void SUB_SetFade_Think (entity this)
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);
67 this.nextthink = time;
74 Fade ent out when time >= vanish_time
77 void SUB_SetFade(entity ent, float vanish_time, float fading_time)
81 ent.fade_rate = 1/fading_time;
82 setthink(ent, SUB_SetFade_Think);
83 ent.nextthink = vanish_time;
90 calculate this.velocity and this.nextthink to reach dest from
91 this.origin traveling at speed
94 void SUB_CalcMoveDone(entity this)
96 // After moving, set origin to exact final destination
98 setorigin (this, this.finaldest);
99 this.velocity = '0 0 0';
101 if (this.think1 && this.think1 != SUB_CalcMoveDone)
105 void SUB_CalcMovePause(entity this)
107 if (!this.move_controller)
109 this.move_controller.animstate_starttime += frametime;
110 this.move_controller.animstate_endtime += frametime;
113 .float platmovetype_turn;
114 void SUB_CalcMove_controller_think (entity this)
124 delta = this.destvec;
125 delta2 = this.destvec2;
126 if(time < this.animstate_endtime)
128 nexttick = time + PHYS_INPUT_FRAMETIME;
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)
136 if(this.owner.platmovetype_turn)
139 destangle = delta + 2 * delta2 * phasepos;
140 destangle = vectoangles(destangle);
141 destangle_x = -destangle_x; // flip up / down orientation
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;
153 if(nexttick < this.animstate_endtime)
154 veloc = nextpos - this.owner.origin;
156 veloc = this.finaldest - this.owner.origin;
157 veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
159 this.owner.velocity = veloc;
160 this.nextthink = nexttick;
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;
174 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector destin)
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
180 //setorigin(controller, org); // don't link to the world
181 controller.origin = org;
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)
190 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector destin)
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
196 //setorigin(controller, org); // don't link to the world
197 controller.origin = org;
200 controller.destvec = destin; // end point
201 controller.destvec2 = '0 0 0';
204 void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
210 objerror (this, "No speed is defined!");
213 this.finaldest = tdest;
214 setthink(this, SUB_CalcMoveDone);
220 traveltime = 2 * vlen(tcontrol - this.origin) / tspeed;
223 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
226 traveltime = vlen(tdest - this.origin) / tspeed;
233 if (traveltime < 0.1) // useless anim
235 this.velocity = '0 0 0';
236 this.nextthink = this.ltime + 0.1;
240 // delete the previous controller, otherwise changing movement midway is glitchy
241 if (this.move_controller != NULL)
243 delete(this.move_controller);
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);
259 // the thinking is now done by the controller
260 setthink(this, SUB_NullThink); // for PushMove
261 this.nextthink = this.ltime + traveltime;
264 getthink(controller)(controller);
267 void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
273 objerror (this, "No speed is defined!");
276 this.finaldest = tdest;
277 setthink(this, SUB_CalcMoveDone);
279 if (tdest == this.origin)
281 this.velocity = '0 0 0';
282 this.nextthink = this.ltime + 0.1;
286 delta = tdest - this.origin;
294 traveltime = vlen (delta) / tspeed;
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.
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?
312 this.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
313 this.nextthink = this.ltime + traveltime;
317 // now just run like a bezier curve...
318 SUB_CalcMove_Bezier(this, (this.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
321 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
323 SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func);
330 calculate this.avelocity and this.nextthink to reach destangle from
333 The calling function should make sure this.setthink is valid
336 void SUB_CalcAngleMoveDone(entity this)
338 // After rotating, set angle to exact final angle
339 this.angles = this.finalangle;
340 this.avelocity = '0 0 0';
342 if (this.think1 && this.think1 != SUB_CalcAngleMoveDone) // avoid endless loops
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)
350 objerror (this, "No speed is defined!");
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;
365 traveltime = vlen (delta) / tspeed;
373 this.finalangle = destangle;
374 setthink(this, SUB_CalcAngleMoveDone);
376 if (traveltime < 0.1)
378 this.avelocity = '0 0 0';
379 this.nextthink = this.ltime + 0.1;
383 this.avelocity = delta * (1 / traveltime);
384 this.nextthink = this.ltime + traveltime;
387 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
389 SUB_CalcAngleMove (ent, destangle, tspeedtype, tspeed, func);
393 void ApplyMinMaxScaleAngles(entity e)
395 if(e.angles.x != 0 || e.angles.z != 0 || e.avelocity.x != 0 || e.avelocity.z != 0) // "weird" rotation
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)
404 else if(e.angles.y != 0 || e.avelocity.y != 0) // yaw only is a bit better
407 '1 0 0' * max(-e.mins.x, e.maxs.x) +
408 '0 1 0' * max(-e.mins.y, e.maxs.y)
411 e.mins_x = -e.maxs.x;
412 e.mins_y = -e.maxs.x;
415 setsize(e, RoundPerfectVector(e.mins * e.scale), RoundPerfectVector(e.maxs * e.scale));
417 setsize(e, e.mins, e.maxs);
420 void SetBrushEntityModel(entity this, bool with_lod)
422 // Ensure .solid is set correctly before calling this (for area grid linking/unlinking)
426 precache_model(this.model);
427 if(this.mins != '0 0 0' || this.maxs != '0 0 0')
429 vector mi = this.mins;
430 vector ma = this.maxs;
431 _setmodel(this, this.model); // no precision needed
432 setsize(this, mi, ma);
435 _setmodel(this, this.model); // no precision needed
437 InitializeEntity(this, LODmodel_attach, INITPRIO_FINDTARGET);
439 setorigin(this, this.origin);
440 ApplyMinMaxScaleAngles(this);
443 bool LOD_customize(entity this, entity client)
445 if(autocvar_loddebug)
447 int d = autocvar_loddebug;
449 this.modelindex = this.lodmodelindex0;
450 else if(d == 2 || !this.lodmodelindex2)
451 this.modelindex = this.lodmodelindex1;
453 this.modelindex = this.lodmodelindex2;
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;
464 this.modelindex = this.lodmodelindex2;
469 void LOD_uncustomize(entity this)
471 this.modelindex = this.lodmodelindex0;
474 void LODmodel_attach(entity this)
478 if(!this.loddistance1)
479 this.loddistance1 = 1000;
480 if(!this.loddistance2)
481 this.loddistance2 = 2000;
482 this.lodmodelindex0 = this.modelindex;
484 if(this.lodtarget1 != "")
486 e = find(NULL, targetname, this.lodtarget1);
489 this.lodmodel1 = e.model;
493 if(this.lodtarget2 != "")
495 e = find(NULL, targetname, this.lodtarget2);
498 this.lodmodel2 = e.model;
503 if(autocvar_loddebug < 0)
505 this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
508 if(this.lodmodel1 != "" && fexists(this.lodmodel1))
514 precache_model(this.lodmodel1);
515 _setmodel(this, this.lodmodel1);
516 this.lodmodelindex1 = this.modelindex;
518 if(this.lodmodel2 != "" && fexists(this.lodmodel2))
520 precache_model(this.lodmodel2);
521 _setmodel(this, this.lodmodel2);
522 this.lodmodelindex2 = this.modelindex;
525 this.modelindex = this.lodmodelindex0;
526 setsize(this, mi, ma);
529 if(this.lodmodelindex1)
530 if (!getSendEntity(this))
531 SetCustomizer(this, LOD_customize, LOD_uncustomize);
540 void SetMovedir(entity this)
542 if(this.movedir != '0 0 0')
543 this.movedir = normalize(this.movedir);
546 makevectors(this.angles);
547 this.movedir = v_forward;
550 this.angles = '0 0 0';
553 void InitTrigger(entity this)
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.
558 this.solid = SOLID_TRIGGER;
559 SetBrushEntityModel(this, false);
560 set_movetype(this, MOVETYPE_NONE);
565 void InitSolidBSPTrigger(entity this)
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.
570 this.solid = SOLID_BSP;
571 SetBrushEntityModel(this, false);
572 set_movetype(this, MOVETYPE_NONE); // why was this PUSH? -div0
573 // this.modelindex = 0;
577 bool InitMovingBrushTrigger(entity this)
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)
586 objerror(this, "InitMovingBrushTrigger: no brushes found!");