2 void SUB_NullThink(entity this) { }
4 void SUB_CalcMoveDone(entity this);
5 void SUB_CalcAngleMoveDone(entity this);
11 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
19 Applies some friction to this
23 void SUB_Friction (entity this)
25 this.nextthink = time;
27 this.velocity = this.velocity * (1 - frametime * this.friction);
34 Makes client invisible or removes non-client
37 void SUB_VanishOrRemove (entity ent)
56 void SUB_SetFade_Think (entity this)
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);
66 this.nextthink = time;
73 Fade 'ent' out when time >= 'when'
76 void SUB_SetFade (entity ent, float when, float fading_time)
78 ent.fade_rate = 1/fading_time;
79 setthink(ent, SUB_SetFade_Think);
87 calculate this.velocity and this.nextthink to reach dest from
88 this.origin traveling at speed
91 void SUB_CalcMoveDone(entity this)
93 // After moving, set origin to exact final destination
95 setorigin (this, this.finaldest);
96 this.velocity = '0 0 0';
98 if (this.think1 && this.think1 != SUB_CalcMoveDone)
102 .float platmovetype_turn;
103 void SUB_CalcMove_controller_think (entity this)
113 delta = this.destvec;
114 delta2 = this.destvec2;
115 if(time < this.animstate_endtime)
117 nexttick = time + PHYS_INPUT_FRAMETIME;
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)
125 if(this.owner.platmovetype_turn)
128 destangle = delta + 2 * delta2 * phasepos;
129 destangle = vectoangles(destangle);
130 destangle_x = -destangle_x; // flip up / down orientation
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;
142 if(nexttick < this.animstate_endtime)
143 veloc = nextpos - this.owner.origin;
145 veloc = this.finaldest - this.owner.origin;
146 veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
148 this.owner.velocity = veloc;
149 this.nextthink = nexttick;
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;
163 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector destin)
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
169 setorigin(controller, org);
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)
178 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector destin)
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
184 setorigin(controller, org);
187 controller.destvec = destin; // end point
188 controller.destvec2 = '0 0 0';
191 void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
197 objerror (this, "No speed is defined!");
200 this.finaldest = tdest;
201 setthink(this, SUB_CalcMoveDone);
207 traveltime = 2 * vlen(tcontrol - this.origin) / tspeed;
210 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
213 traveltime = vlen(tdest - this.origin) / tspeed;
220 if (traveltime < 0.1) // useless anim
222 this.velocity = '0 0 0';
223 this.nextthink = this.ltime + 0.1;
227 // delete the previous controller, otherwise changing movement midway is glitchy
228 if (this.move_controller != NULL)
230 delete(this.move_controller);
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);
245 // the thinking is now done by the controller
246 setthink(this, SUB_NullThink); // for PushMove
247 this.nextthink = this.ltime + traveltime;
250 getthink(controller)(controller);
253 void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
259 objerror (this, "No speed is defined!");
262 this.finaldest = tdest;
263 setthink(this, SUB_CalcMoveDone);
265 if (tdest == this.origin)
267 this.velocity = '0 0 0';
268 this.nextthink = this.ltime + 0.1;
272 delta = tdest - this.origin;
280 traveltime = vlen (delta) / tspeed;
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?
293 this.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
294 this.nextthink = this.ltime + traveltime;
298 // now just run like a bezier curve...
299 SUB_CalcMove_Bezier(this, (this.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
302 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
304 SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func);
311 calculate this.avelocity and this.nextthink to reach destangle from
314 The calling function should make sure this.setthink is valid
317 void SUB_CalcAngleMoveDone(entity this)
319 // After rotating, set angle to exact final angle
320 this.angles = this.finalangle;
321 this.avelocity = '0 0 0';
323 if (this.think1 && this.think1 != SUB_CalcAngleMoveDone) // avoid endless loops
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)
331 objerror (this, "No speed is defined!");
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;
346 traveltime = vlen (delta) / tspeed;
354 this.finalangle = destangle;
355 setthink(this, SUB_CalcAngleMoveDone);
357 if (traveltime < 0.1)
359 this.avelocity = '0 0 0';
360 this.nextthink = this.ltime + 0.1;
364 this.avelocity = delta * (1 / traveltime);
365 this.nextthink = this.ltime + traveltime;
368 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
370 SUB_CalcAngleMove (ent, destangle, tspeedtype, tspeed, func);
374 void ApplyMinMaxScaleAngles(entity e)
376 if(e.angles.x != 0 || e.angles.z != 0 || e.avelocity.x != 0 || e.avelocity.z != 0) // "weird" rotation
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)
385 else if(e.angles.y != 0 || e.avelocity.y != 0) // yaw only is a bit better
388 '1 0 0' * max(-e.mins.x, e.maxs.x) +
389 '0 1 0' * max(-e.mins.y, e.maxs.y)
392 e.mins_x = -e.maxs.x;
393 e.mins_y = -e.maxs.x;
396 setsize(e, e.mins * e.scale, e.maxs * e.scale);
398 setsize(e, e.mins, e.maxs);
401 void SetBrushEntityModel(entity this)
405 precache_model(this.model);
406 if(this.mins != '0 0 0' || this.maxs != '0 0 0')
408 vector mi = this.mins;
409 vector ma = this.maxs;
410 _setmodel(this, this.model); // no precision needed
411 setsize(this, mi, ma);
414 _setmodel(this, this.model); // no precision needed
415 InitializeEntity(this, LODmodel_attach, INITPRIO_FINDTARGET);
417 setorigin(this, this.origin);
418 ApplyMinMaxScaleAngles(this);
421 void SetBrushEntityModelNoLOD(entity this)
425 precache_model(this.model);
426 if(this.mins != '0 0 0' || this.maxs != '0 0 0')
428 vector mi = this.mins;
429 vector ma = this.maxs;
430 _setmodel(this, this.model); // no precision needed
431 setsize(this, mi, ma);
434 _setmodel(this, this.model); // no precision needed
436 setorigin(this, this.origin);
437 ApplyMinMaxScaleAngles(this);
440 bool LOD_customize(entity this, entity client)
442 if(autocvar_loddebug)
444 int d = autocvar_loddebug;
446 this.modelindex = this.lodmodelindex0;
447 else if(d == 2 || !this.lodmodelindex2)
448 this.modelindex = this.lodmodelindex1;
450 this.modelindex = this.lodmodelindex2;
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;
461 this.modelindex = this.lodmodelindex2;
466 void LOD_uncustomize(entity this)
468 this.modelindex = this.lodmodelindex0;
471 void LODmodel_attach(entity this)
475 if(!this.loddistance1)
476 this.loddistance1 = 1000;
477 if(!this.loddistance2)
478 this.loddistance2 = 2000;
479 this.lodmodelindex0 = this.modelindex;
481 if(this.lodtarget1 != "")
483 e = find(NULL, targetname, this.lodtarget1);
486 this.lodmodel1 = e.model;
490 if(this.lodtarget2 != "")
492 e = find(NULL, targetname, this.lodtarget2);
495 this.lodmodel2 = e.model;
500 if(autocvar_loddebug < 0)
502 this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
505 if(this.lodmodel1 != "")
511 precache_model(this.lodmodel1);
512 _setmodel(this, this.lodmodel1);
513 this.lodmodelindex1 = this.modelindex;
515 if(this.lodmodel2 != "")
517 precache_model(this.lodmodel2);
518 _setmodel(this, this.lodmodel2);
519 this.lodmodelindex2 = this.modelindex;
522 this.modelindex = this.lodmodelindex0;
523 setsize(this, mi, ma);
526 if(this.lodmodelindex1)
527 if (!getSendEntity(this))
528 SetCustomizer(this, LOD_customize, LOD_uncustomize);
537 void SetMovedir(entity this)
539 if(this.movedir != '0 0 0')
540 this.movedir = normalize(this.movedir);
543 makevectors(this.angles);
544 this.movedir = v_forward;
547 this.angles = '0 0 0';
550 void InitTrigger(entity this)
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.
555 this.solid = SOLID_TRIGGER;
556 SetBrushEntityModelNoLOD(this);
557 set_movetype(this, MOVETYPE_NONE);
562 void InitSolidBSPTrigger(entity this)
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.
567 this.solid = SOLID_BSP;
568 SetBrushEntityModelNoLOD(this);
569 set_movetype(this, MOVETYPE_NONE); // why was this PUSH? -div0
570 // this.modelindex = 0;
574 bool InitMovingBrushTrigger(entity this)
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)
583 objerror(this, "InitMovingBrushTrigger: no brushes found!");