2 void SUB_NullThink(entity this) {}
4 void SUB_CalcMoveDone(entity this);
5 void SUB_CalcAngleMoveDone(entity this);
11 Applies some friction to this
15 void SUB_Friction(entity this)
17 this.nextthink = time;
18 if (IS_ONGROUND(this)) {
19 this.velocity = this.velocity * (1 - frametime * this.friction);
27 Makes client invisible or removes non-client
30 void SUB_VanishOrRemove(entity ent)
46 void SUB_SetFade_Think(entity this)
48 if (this.alpha == 0) {
51 setthink(this, SUB_SetFade_Think);
52 this.nextthink = time;
53 this.alpha -= frametime * this.fade_rate;
54 if (this.alpha < 0.01) {
55 SUB_VanishOrRemove(this);
57 this.nextthink = time;
65 Fade 'ent' out when time >= 'when'
68 void SUB_SetFade(entity ent, float when, float fading_time)
70 ent.fade_rate = 1 / fading_time;
71 setthink(ent, SUB_SetFade_Think);
79 calculate this.velocity and this.nextthink to reach dest from
80 this.origin traveling at speed
83 void SUB_CalcMoveDone(entity this)
85 // After moving, set origin to exact final destination
87 setorigin(this, this.finaldest);
88 this.velocity = '0 0 0';
90 if (this.think1 && this.think1 != SUB_CalcMoveDone) {
95 .float platmovetype_turn;
96 void SUB_CalcMove_controller_think(entity this)
106 delta = this.destvec;
107 delta2 = this.destvec2;
108 if (time < this.animstate_endtime) {
109 nexttick = time + PHYS_INPUT_FRAMETIME;
111 traveltime = this.animstate_endtime - this.animstate_starttime;
112 phasepos = (nexttick - this.animstate_starttime) / traveltime; // range: [0, 1]
113 phasepos = cubic_speedfunc(this.platmovetype_start, this.platmovetype_end, phasepos);
114 nextpos = this.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
115 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
117 if (this.owner.platmovetype_turn) {
119 destangle = delta + 2 * delta2 * phasepos;
120 destangle = vectoangles(destangle);
121 destangle_x = -destangle_x; // flip up / down orientation
123 // take the shortest distance for the angles
124 vector v = this.owner.angles;
125 v.x -= 360 * floor((v.x - destangle_x) / 360 + 0.5);
126 v.y -= 360 * floor((v.y - destangle_y) / 360 + 0.5);
127 v.z -= 360 * floor((v.z - destangle_z) / 360 + 0.5);
128 this.owner.angles = v;
129 angloc = destangle - this.owner.angles;
130 angloc = angloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
131 this.owner.avelocity = angloc;
133 if (nexttick < this.animstate_endtime) {
134 veloc = nextpos - this.owner.origin;
136 veloc = this.finaldest - this.owner.origin;
138 veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
140 this.owner.velocity = veloc;
141 this.nextthink = nexttick;
143 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
144 entity own = this.owner;
145 setthink(own, this.think1);
151 void SUB_CalcMove_controller_setbezier(entity controller, vector org, vector control, vector destin)
153 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
154 // 2 * control * t - 2 * control * t * t + destin * t * t
155 // 2 * control * t + (destin - 2 * control) * t * t
157 setorigin(controller, org);
161 controller.destvec = 2 * control; // control point
162 controller.destvec2 = destin - 2 * control; // quadratic part required to reach end point
163 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (destin - control)
166 void SUB_CalcMove_controller_setlinear(entity controller, vector org, vector destin)
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
172 setorigin(controller, org);
175 controller.destvec = destin; // end point
176 controller.destvec2 = '0 0 0';
179 float TSPEED_TIME = -1;
180 float TSPEED_LINEAR = 0;
181 float TSPEED_START = 1;
182 float TSPEED_END = 2;
185 void SUB_CalcMove_Bezier(entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
191 objerror(this, "No speed is defined!");
195 this.finaldest = tdest;
196 setthink(this, SUB_CalcMoveDone);
198 switch (tspeedtype) {
201 traveltime = 2 * vlen(tcontrol - this.origin) / tspeed;
204 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
207 traveltime = vlen(tdest - this.origin) / tspeed;
214 if (traveltime < 0.1) { // useless anim
215 this.velocity = '0 0 0';
216 this.nextthink = this.ltime + 0.1;
220 controller = new(SUB_CalcMove_controller);
221 controller.owner = this;
222 controller.platmovetype = this.platmovetype;
223 controller.platmovetype_start = this.platmovetype_start;
224 controller.platmovetype_end = this.platmovetype_end;
225 SUB_CalcMove_controller_setbezier(controller, this.origin, tcontrol, tdest);
226 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
227 controller.animstate_starttime = time;
228 controller.animstate_endtime = time + traveltime;
229 setthink(controller, SUB_CalcMove_controller_think);
230 controller.think1 = getthink(this);
232 // the thinking is now done by the controller
233 setthink(this, SUB_NullThink); // for PushMove
234 this.nextthink = this.ltime + traveltime;
237 getthink(controller)(controller);
240 void SUB_CalcMove(entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
246 objerror(this, "No speed is defined!");
250 this.finaldest = tdest;
251 setthink(this, SUB_CalcMoveDone);
253 if (tdest == this.origin) {
254 this.velocity = '0 0 0';
255 this.nextthink = this.ltime + 0.1;
259 delta = tdest - this.origin;
261 switch (tspeedtype) {
266 traveltime = vlen(delta) / tspeed;
273 // Very short animations don't really show off the effect
274 // of controlled animation, so let's just use linear movement.
275 // Alternatively entities can choose to specify non-controlled movement.
276 // The only currently implemented alternative movement is linear (value 1)
277 if (traveltime < 0.15 || (this.platmovetype_start == 1 && this.platmovetype_end == 1)) { // is this correct?
278 this.velocity = delta * (1 / traveltime); // QuakeC doesn't allow vector/float division
279 this.nextthink = this.ltime + traveltime;
283 // now just run like a bezier curve...
284 SUB_CalcMove_Bezier(this, (this.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
287 void SUB_CalcMoveEnt(entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
289 SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func);
296 calculate this.avelocity and this.nextthink to reach destangle from
299 The calling function should make sure this.setthink is valid
302 void SUB_CalcAngleMoveDone(entity this)
304 // After rotating, set angle to exact final angle
305 this.angles = this.finalangle;
306 this.avelocity = '0 0 0';
308 if (this.think1 && this.think1 != SUB_CalcAngleMoveDone) { // avoid endless loops
313 // FIXME: I fixed this function only for rotation around the main axes
314 void SUB_CalcAngleMove(entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
317 objerror(this, "No speed is defined!");
320 // take the shortest distance for the angles
321 this.angles_x -= 360 * floor((this.angles_x - destangle_x) / 360 + 0.5);
322 this.angles_y -= 360 * floor((this.angles_y - destangle_y) / 360 + 0.5);
323 this.angles_z -= 360 * floor((this.angles_z - destangle_z) / 360 + 0.5);
324 vector delta = destangle - this.angles;
327 switch (tspeedtype) {
332 traveltime = vlen(delta) / tspeed;
340 this.finalangle = destangle;
341 setthink(this, SUB_CalcAngleMoveDone);
343 if (traveltime < 0.1) {
344 this.avelocity = '0 0 0';
345 this.nextthink = this.ltime + 0.1;
349 this.avelocity = delta * (1 / traveltime);
350 this.nextthink = this.ltime + traveltime;
353 void SUB_CalcAngleMoveEnt(entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
355 SUB_CalcAngleMove(ent, destangle, tspeedtype, tspeed, func);