Step 5: complete
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / triggers / subs.qc
1 void SUB_NullThink(entity this) { }
2
3 void SUB_CalcMoveDone(entity this);
4 void SUB_CalcAngleMoveDone(entity this);
5
6 /*
7 ==================
8 SUB_Friction
9
10 Applies some friction to this
11 ==================
12 */
13 .float friction;
14 void SUB_Friction (entity this)
15 {
16         this.SUB_NEXTTHINK = time;
17         if(this.SUB_FLAGS & FL_ONGROUND)
18                 this.SUB_VELOCITY = this.SUB_VELOCITY * (1 - frametime * this.friction);
19 }
20
21 /*
22 ==================
23 SUB_VanishOrRemove
24
25 Makes client invisible or removes non-client
26 ==================
27 */
28 void SUB_VanishOrRemove (entity ent)
29 {
30         if (IS_CLIENT(ent))
31         {
32                 // vanish
33                 ent.alpha = -1;
34                 ent.effects = 0;
35 #ifdef SVQC
36                 ent.glow_size = 0;
37                 ent.pflags = 0;
38 #endif
39         }
40         else
41         {
42                 // remove
43                 remove (ent);
44         }
45 }
46
47 void SUB_SetFade_Think (entity this)
48 {
49         if(this.alpha == 0)
50                 this.alpha = 1;
51         SUB_THINK(this, SUB_SetFade_Think);
52         this.SUB_NEXTTHINK = time;
53         this.alpha -= frametime * this.fade_rate;
54         if (this.alpha < 0.01)
55                 SUB_VanishOrRemove(this);
56         else
57                 this.SUB_NEXTTHINK = time;
58 }
59
60 /*
61 ==================
62 SUB_SetFade
63
64 Fade 'ent' out when time >= 'when'
65 ==================
66 */
67 void SUB_SetFade (entity ent, float when, float fading_time)
68 {
69         ent.fade_rate = 1/fading_time;
70         SUB_THINK(ent, SUB_SetFade_Think);
71         ent.SUB_NEXTTHINK = when;
72 }
73
74 /*
75 =============
76 SUB_CalcMove
77
78 calculate this.SUB_VELOCITY and this.SUB_NEXTTHINK to reach dest from
79 this.SUB_ORIGIN traveling at speed
80 ===============
81 */
82 void SUB_CalcMoveDone(entity this)
83 {
84         // After moving, set origin to exact final destination
85
86         SUB_SETORIGIN (this, this.finaldest);
87         this.SUB_VELOCITY = '0 0 0';
88         this.SUB_NEXTTHINK = -1;
89         if (this.think1)
90                 this.think1 (this);
91 }
92
93 .float platmovetype_turn;
94 void SUB_CalcMove_controller_think (entity this)
95 {
96         float traveltime;
97         float phasepos;
98         float nexttick;
99         vector delta;
100         vector delta2;
101         vector veloc;
102         vector angloc;
103         vector nextpos;
104         delta = this.destvec;
105         delta2 = this.destvec2;
106         if(time < this.animstate_endtime)
107         {
108                 nexttick = time + PHYS_INPUT_FRAMETIME;
109
110                 traveltime = this.animstate_endtime - this.animstate_starttime;
111                 phasepos = (nexttick - this.animstate_starttime) / traveltime; // range: [0, 1]
112                 phasepos = cubic_speedfunc(this.platmovetype_start, this.platmovetype_end, phasepos);
113                 nextpos = this.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
114                 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
115
116                 if(this.owner.platmovetype_turn)
117                 {
118                         vector destangle;
119                         destangle = delta + 2 * delta2 * phasepos;
120                         destangle = vectoangles(destangle);
121                         destangle_x = -destangle_x; // flip up / down orientation
122
123                         // take the shortest distance for the angles
124                         vector v = SUB_ANGLES(this.owner);
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                         SUB_ANGLES(this.owner) = v;
129                         angloc = destangle - SUB_ANGLES(this.owner);
130                         angloc = angloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
131                         this.owner.SUB_AVELOCITY = angloc;
132                 }
133                 if(nexttick < this.animstate_endtime)
134                         veloc = nextpos - this.owner.SUB_ORIGIN;
135                 else
136                         veloc = this.finaldest - this.owner.SUB_ORIGIN;
137                 veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
138
139                 this.owner.SUB_VELOCITY = veloc;
140                 this.nextthink = nexttick;
141         }
142         else
143         {
144                 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
145                 entity own = this.owner;
146                 SUB_THINK(own, this.think1);
147                 remove(this);
148                 WITHSELF(own, SUB_THUNK(own)(own));
149         }
150 }
151
152 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector destin)
153 {
154         // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
155         // 2 * control * t - 2 * control * t * t + destin * t * t
156         // 2 * control * t + (destin - 2 * control) * t * t
157
158         setorigin(controller, org);
159         control -= org;
160         destin -= org;
161
162         controller.destvec = 2 * control; // control point
163         controller.destvec2 = destin - 2 * control; // quadratic part required to reach end point
164         // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (destin - control)
165 }
166
167 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector destin)
168 {
169         // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
170         // 2 * control * t - 2 * control * t * t + destin * t * t
171         // 2 * control * t + (destin - 2 * control) * t * t
172
173         setorigin(controller, org);
174         destin -= org;
175
176         controller.destvec = destin; // end point
177         controller.destvec2 = '0 0 0';
178 }
179
180 float TSPEED_TIME = -1;
181 float TSPEED_LINEAR = 0;
182 float TSPEED_START = 1;
183 float TSPEED_END = 2;
184 // TODO average too?
185
186 void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
187 {
188         float   traveltime;
189         entity controller;
190
191         if (!tspeed)
192                 objerror ("No speed is defined!");
193
194         this.think1 = func;
195         this.finaldest = tdest;
196         SUB_THINK(this, SUB_CalcMoveDone);
197
198         switch(tspeedtype)
199         {
200                 default:
201                 case TSPEED_START:
202                         traveltime = 2 * vlen(tcontrol - this.SUB_ORIGIN) / tspeed;
203                         break;
204                 case TSPEED_END:
205                         traveltime = 2 * vlen(tcontrol - tdest)       / tspeed;
206                         break;
207                 case TSPEED_LINEAR:
208                         traveltime = vlen(tdest - this.SUB_ORIGIN)        / tspeed;
209                         break;
210                 case TSPEED_TIME:
211                         traveltime = tspeed;
212                         break;
213         }
214
215         if (traveltime < 0.1) // useless anim
216         {
217                 this.SUB_VELOCITY = '0 0 0';
218                 this.SUB_NEXTTHINK = this.SUB_LTIME + 0.1;
219                 return;
220         }
221
222         controller = new(SUB_CalcMove_controller);
223         controller.owner = this;
224         controller.platmovetype = this.platmovetype;
225         controller.platmovetype_start = this.platmovetype_start;
226         controller.platmovetype_end = this.platmovetype_end;
227         SUB_CalcMove_controller_setbezier(controller, this.SUB_ORIGIN, tcontrol, tdest);
228         controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
229         controller.animstate_starttime = time;
230         controller.animstate_endtime = time + traveltime;
231         setthink(controller, SUB_CalcMove_controller_think);
232         controller.think1 = SUB_THUNK(this);
233
234         // the thinking is now done by the controller
235         SUB_THINK(this, SUB_NullThink); // for PushMove
236         this.SUB_NEXTTHINK = this.SUB_LTIME + traveltime;
237
238         // invoke controller
239         WITHSELF(controller, getthink(controller)(controller));
240 }
241
242 void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
243 {
244         vector  delta;
245         float   traveltime;
246
247         if (!tspeed)
248                 objerror ("No speed is defined!");
249
250         this.think1 = func;
251         this.finaldest = tdest;
252         SUB_THINK(this, SUB_CalcMoveDone);
253
254         if (tdest == this.SUB_ORIGIN)
255         {
256                 this.SUB_VELOCITY = '0 0 0';
257                 this.SUB_NEXTTHINK = this.SUB_LTIME + 0.1;
258                 return;
259         }
260
261         delta = tdest - this.SUB_ORIGIN;
262
263         switch(tspeedtype)
264         {
265                 default:
266                 case TSPEED_START:
267                 case TSPEED_END:
268                 case TSPEED_LINEAR:
269                         traveltime = vlen (delta) / tspeed;
270                         break;
271                 case TSPEED_TIME:
272                         traveltime = tspeed;
273                         break;
274         }
275
276         // Very short animations don't really show off the effect
277         // of controlled animation, so let's just use linear movement.
278         // Alternatively entities can choose to specify non-controlled movement.
279         // The only currently implemented alternative movement is linear (value 1)
280         if (traveltime < 0.15 || (this.platmovetype_start == 1 && this.platmovetype_end == 1)) // is this correct?
281         {
282                 this.SUB_VELOCITY = delta * (1/traveltime);     // QuakeC doesn't allow vector/float division
283                 this.SUB_NEXTTHINK = this.SUB_LTIME + traveltime;
284                 return;
285         }
286
287         // now just run like a bezier curve...
288         SUB_CalcMove_Bezier(this, (this.SUB_ORIGIN + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
289 }
290
291 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
292 {
293         WITHSELF(ent, SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func));
294 }
295
296 /*
297 =============
298 SUB_CalcAngleMove
299
300 calculate this.SUB_AVELOCITY and this.SUB_NEXTTHINK to reach destangle from
301 this.angles rotating
302
303 The calling function should make sure this.SUB_THINK is valid
304 ===============
305 */
306 void SUB_CalcAngleMoveDone(entity this)
307 {
308         // After rotating, set angle to exact final angle
309         this.angles = this.finalangle;
310         this.SUB_AVELOCITY = '0 0 0';
311         this.SUB_NEXTTHINK = -1;
312         if (this.think1)
313                 this.think1 (this);
314 }
315
316 // FIXME: I fixed this function only for rotation around the main axes
317 void SUB_CalcAngleMove (entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
318 {
319         vector  delta;
320         float   traveltime;
321
322         if (!tspeed)
323                 objerror ("No speed is defined!");
324
325         // take the shortest distance for the angles
326         this.angles_x -= 360 * floor((this.angles_x - destangle_x) / 360 + 0.5);
327         this.angles_y -= 360 * floor((this.angles_y - destangle_y) / 360 + 0.5);
328         this.angles_z -= 360 * floor((this.angles_z - destangle_z) / 360 + 0.5);
329         delta = destangle - this.angles;
330
331         switch(tspeedtype)
332         {
333                 default:
334                 case TSPEED_START:
335                 case TSPEED_END:
336                 case TSPEED_LINEAR:
337                         traveltime = vlen (delta) / tspeed;
338                         break;
339                 case TSPEED_TIME:
340                         traveltime = tspeed;
341                         break;
342         }
343
344         this.think1 = func;
345         this.finalangle = destangle;
346         SUB_THINK(this, SUB_CalcAngleMoveDone);
347
348         if (traveltime < 0.1)
349         {
350                 this.SUB_AVELOCITY = '0 0 0';
351                 this.SUB_NEXTTHINK = this.SUB_LTIME + 0.1;
352                 return;
353         }
354
355         this.SUB_AVELOCITY = delta * (1 / traveltime);
356         this.SUB_NEXTTHINK = this.SUB_LTIME + traveltime;
357 }
358
359 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
360 {
361         WITHSELF(ent, SUB_CalcAngleMove (ent, destangle, tspeedtype, tspeed, func));
362 }