Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
subs.qc
Go to the documentation of this file.
1#include "subs.qh"
2
3void SUB_NullThink(entity this) { }
4
5void SUB_CalcMoveDone(entity this);
7
8#ifdef SVQC
9spawnfunc(info_null)
10{
11 delete(this);
12 // if anything breaks, tell the mapper to fix their map! info_null is meant to remove itself immediately.
13}
14#endif
15
16/*
17==================
18SUB_Friction
19
20Applies some friction to this
21==================
22*/
23.float friction;
25{
26 this.nextthink = time;
27 if(IS_ONGROUND(this))
28 this.velocity *= 1 - frametime * this.friction;
29}
30
31/*
32==================
33SUB_VanishOrRemove
34
35Makes client invisible or removes non-client
36==================
37*/
39{
40 if (IS_CLIENT(ent))
41 {
42 // vanish
43 ent.alpha = -1;
44 ent.effects = 0;
45#ifdef SVQC
46 ent.glow_size = 0;
47 ent.pflags = 0;
48#endif
49 }
50 else
51 {
52 // remove
53 delete(ent);
54 }
55}
56
58{
59 if(this.alpha == 0)
60 this.alpha = 1;
62 this.nextthink = time;
63 this.alpha -= frametime * this.fade_rate;
64 if (this.alpha < 0.01)
66 else
67 this.nextthink = time;
68}
69
70/*
71==================
72SUB_SetFade
73
74Fade ent out when time >= vanish_time
75==================
76*/
77void SUB_SetFade(entity ent, float vanish_time, float fading_time)
78{
79 if (fading_time <= 0)
80 fading_time = 0.01;
81 ent.fade_rate = 1/fading_time;
83 ent.nextthink = vanish_time;
84}
85
86/*
87=============
88SUB_CalcMove
89
90calculate this.velocity and this.nextthink to reach dest from
91this.origin traveling at speed
92===============
93*/
95{
96 // After moving, set origin to exact final destination
97
98 setorigin (this, this.finaldest);
99 this.velocity = '0 0 0';
100 this.nextthink = -1;
101 if (this.think1 && this.think1 != SUB_CalcMoveDone)
102 this.think1 (this);
103}
104
106{
107 if (!this.move_controller)
108 return;
109 this.move_controller.animstate_starttime += frametime;
110 this.move_controller.animstate_endtime += frametime;
111}
112
115{
116 float traveltime;
117 float phasepos;
118 float nexttick;
119 vector delta;
120 vector delta2;
121 vector veloc;
122 vector angloc;
123 vector nextpos;
124 delta = this.destvec;
125 delta2 = this.destvec2;
126 if(time < this.animstate_endtime)
127 {
128 nexttick = time + PHYS_INPUT_FRAMETIME;
129
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)
135
136 if(this.owner.platmovetype_turn)
137 {
138 vector destangle = delta + 2 * delta2 * phasepos;
139 destangle = vectoangles(destangle);
140 destangle_x = -destangle_x; // flip up / down orientation
141
142 // take the shortest distance for the angles
143 vector v = this.owner.angles;
144 v.x -= 360 * floor((v.x - destangle_x) / 360 + 0.5);
145 v.y -= 360 * floor((v.y - destangle_y) / 360 + 0.5);
146 v.z -= 360 * floor((v.z - destangle_z) / 360 + 0.5);
147 this.owner.angles = v;
148 angloc = destangle - this.owner.angles;
149 angloc *= 1 / PHYS_INPUT_FRAMETIME; // so it arrives for the next frame
150 this.owner.avelocity = angloc;
151 }
152 if(nexttick < this.animstate_endtime)
153 veloc = nextpos - this.owner.origin;
154 else
155 veloc = this.finaldest - this.owner.origin;
156 veloc *= 1 / PHYS_INPUT_FRAMETIME; // so it arrives for the next frame
157
158 this.owner.velocity = veloc;
159 this.nextthink = nexttick;
160 }
161 else
162 {
163 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
164 entity own = this.owner;
165 setthink(own, this.think1);
166 // set the owner's reference to this entity to NULL
167 own.move_controller = NULL;
168 delete(this);
169 getthink(own)(own);
170 }
171}
172
174{
175 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
176 // 2 * control * t - 2 * control * t * t + destin * t * t
177 // 2 * control * t + (destin - 2 * control) * t * t
178
179 //setorigin(controller, org); // don't link to the world
180 controller.origin = org;
181 control -= org;
182 destin -= org;
183
184 controller.destvec = 2 * control; // control point
185 controller.destvec2 = destin - 2 * control; // quadratic part required to reach end point
186 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (destin - control)
187}
188
190{
191 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
192 // 2 * control * t - 2 * control * t * t + destin * t * t
193 // 2 * control * t + (destin - 2 * control) * t * t
194
195 //setorigin(controller, org); // don't link to the world
196 controller.origin = org;
197 destin -= org;
198
199 controller.destvec = destin; // end point
200 controller.destvec2 = '0 0 0';
201}
202
203void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
204{
205 float traveltime;
206 entity controller;
207
208 if (!tspeed)
209 objerror (this, "No speed is defined!");
210
211 this.think1 = func;
212 this.finaldest = tdest;
214
215 switch(tspeedtype)
216 {
217 default:
218 case TSPEED_START:
219 traveltime = 2 * vlen(tcontrol - this.origin) / tspeed;
220 break;
221 case TSPEED_END:
222 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
223 break;
224 case TSPEED_LINEAR:
225 traveltime = vlen(tdest - this.origin) / tspeed;
226 break;
227 case TSPEED_TIME:
228 traveltime = tspeed;
229 break;
230 }
231
232 if (traveltime < 0.1) // useless anim
233 {
234 this.velocity = '0 0 0';
235 this.nextthink = this.ltime + 0.1;
236 return;
237 }
238
239 // delete the previous controller, otherwise changing movement midway is glitchy
240 if (this.move_controller != NULL)
241 {
242 delete(this.move_controller);
243 }
244 controller = new_pure(SUB_CalcMove_controller);
245 set_movetype(controller, MOVETYPE_NONE); // mark the entity as physics driven so that thinking is handled by QC
246 controller.owner = this;
247 this.move_controller = controller;
248 controller.platmovetype = this.platmovetype;
249 controller.platmovetype_start = this.platmovetype_start;
250 controller.platmovetype_end = this.platmovetype_end;
251 SUB_CalcMove_controller_setbezier(controller, this.origin, tcontrol, tdest);
252 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
253 controller.animstate_starttime = time;
254 controller.animstate_endtime = time + traveltime;
256 controller.think1 = getthink(this);
257
258 // the thinking is now done by the controller
259 setthink(this, SUB_NullThink); // for PushMove
260 this.nextthink = this.ltime + traveltime;
261
262 // invoke controller
263 getthink(controller)(controller);
264}
265
266void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
267{
268 vector delta;
269 float traveltime;
270
271 if (!tspeed)
272 objerror (this, "No speed is defined!");
273
274 this.think1 = func;
275 this.finaldest = tdest;
277
278 if (tdest == this.origin)
279 {
280 this.velocity = '0 0 0';
281 this.nextthink = this.ltime + 0.1;
282 return;
283 }
284
285 delta = tdest - this.origin;
286
287 switch(tspeedtype)
288 {
289 default:
290 case TSPEED_START:
291 case TSPEED_END:
292 case TSPEED_LINEAR:
293 traveltime = vlen (delta) / tspeed;
294 break;
295 case TSPEED_TIME:
296 traveltime = tspeed;
297 break;
298 }
299
300 // Q3 implements this fallback for all movers at the end of its InitMover()
301 // If .speed is negative this applies, instead of the mover-specific default speed.
302 if (traveltime <= 0)
303 traveltime = 0.001;
304
305 // Very short animations don't really show off the effect
306 // of controlled animation, so let's just use linear movement.
307 // Alternatively entities can choose to specify non-controlled movement.
308 // The only currently implemented alternative movement is linear (value 1)
309 if (traveltime < 0.15 || (this.platmovetype_start == 1 && this.platmovetype_end == 1)) // is this correct?
310 {
311 this.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
312 this.nextthink = this.ltime + traveltime;
313 return;
314 }
315
316 // now just run like a bezier curve...
317 SUB_CalcMove_Bezier(this, (this.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
318}
319
320void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
321{
322 SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func);
323}
324
325/*
326=============
327SUB_CalcAngleMove
328
329calculate this.avelocity and this.nextthink to reach destangle from
330this.angles rotating
331
332The calling function should make sure this.setthink is valid
333===============
334*/
336{
337 // After rotating, set angle to exact final angle
338 this.angles = this.finalangle;
339 this.avelocity = '0 0 0';
340 this.nextthink = -1;
341 if (this.think1 && this.think1 != SUB_CalcAngleMoveDone) // avoid endless loops
342 this.think1 (this);
343}
344
345// FIXME: I fixed this function only for rotation around the main axes
346void SUB_CalcAngleMove (entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
347{
348 if (!tspeed)
349 objerror (this, "No speed is defined!");
350
351 // take the shortest distance for the angles
352 this.angles_x -= 360 * floor((this.angles_x - destangle_x) / 360 + 0.5);
353 this.angles_y -= 360 * floor((this.angles_y - destangle_y) / 360 + 0.5);
354 this.angles_z -= 360 * floor((this.angles_z - destangle_z) / 360 + 0.5);
355 vector delta = destangle - this.angles;
356 float traveltime;
357
358 switch(tspeedtype)
359 {
360 default:
361 case TSPEED_START:
362 case TSPEED_END:
363 case TSPEED_LINEAR:
364 traveltime = vlen (delta) / tspeed;
365 break;
366 case TSPEED_TIME:
367 traveltime = tspeed;
368 break;
369 }
370
371 this.think1 = func;
372 this.finalangle = destangle;
374
375 if (traveltime < 0.1)
376 {
377 this.avelocity = '0 0 0';
378 this.nextthink = this.ltime + 0.1;
379 return;
380 }
381
382 this.avelocity = delta * (1 / traveltime);
383 this.nextthink = this.ltime + traveltime;
384}
385
386void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
387{
388 SUB_CalcAngleMove (ent, destangle, tspeedtype, tspeed, func);
389}
390
391#ifdef SVQC
393{
394 if(e.angles.x != 0 || e.angles.z != 0 || e.avelocity.x != 0 || e.avelocity.z != 0) // "weird" rotation
395 {
396 e.maxs = '1 1 1' * vlen(
397 '1 0 0' * max(-e.mins.x, e.maxs.x) +
398 '0 1 0' * max(-e.mins.y, e.maxs.y) +
399 '0 0 1' * max(-e.mins.z, e.maxs.z)
400 );
401 e.mins = -e.maxs;
402 }
403 else if(e.angles.y != 0 || e.avelocity.y != 0) // yaw only is a bit better
404 {
405 e.maxs_x = vlen(
406 '1 0 0' * max(-e.mins.x, e.maxs.x) +
407 '0 1 0' * max(-e.mins.y, e.maxs.y)
408 );
409 e.maxs_y = e.maxs.x;
410 e.mins_x = -e.maxs.x;
411 e.mins_y = -e.maxs.x;
412 }
413 if(e.scale)
414 setsize(e, RoundPerfectVector(e.mins * e.scale), RoundPerfectVector(e.maxs * e.scale));
415 else
416 setsize(e, e.mins, e.maxs);
417}
418
419void SetBrushEntityModel(entity this, bool with_lod)
420{
421 // Ensure .solid is set correctly before calling this (for area grid linking/unlinking)
422
423 if(this.model != "")
424 {
425 precache_model(this.model);
426 if(this.mins != '0 0 0' || this.maxs != '0 0 0')
427 {
428 vector mi = this.mins;
429 vector ma = this.maxs;
430 _setmodel(this, this.model); // no precision needed
431 setsize(this, mi, ma);
432 }
433 else
434 _setmodel(this, this.model); // no precision needed
435 if(with_lod)
437 }
438 setorigin(this, this.origin);
440}
441
442bool LOD_customize(entity this, entity client)
443{
445 {
446 int d = autocvar_loddebug;
447 if(d == 1)
448 this.modelindex = this.lodmodelindex0;
449 else if(d == 2 || !this.lodmodelindex2)
450 this.modelindex = this.lodmodelindex1;
451 else // if(d == 3)
452 this.modelindex = this.lodmodelindex2;
453 return true;
454 }
455
456 // TODO csqc network this so it only gets sent once
457 vector near_point = NearestPointOnBox(this, client.origin);
458 if(vdist(near_point - client.origin, <, this.loddistance1))
459 this.modelindex = this.lodmodelindex0;
460 else if(!this.lodmodelindex2 || vdist(near_point - client.origin, <, this.loddistance2))
461 this.modelindex = this.lodmodelindex1;
462 else
463 this.modelindex = this.lodmodelindex2;
464
465 return true;
466}
467
469{
470 this.modelindex = this.lodmodelindex0;
471}
472
474{
475 entity e;
476
477 if(!this.loddistance1)
478 this.loddistance1 = 1000;
479 if(!this.loddistance2)
480 this.loddistance2 = 2000;
481 this.lodmodelindex0 = this.modelindex;
482
483 if(this.lodtarget1 != "")
484 {
485 e = find(NULL, targetname, this.lodtarget1);
486 if(e)
487 {
488 this.lodmodel1 = e.model;
489 delete(e);
490 }
491 }
492 if(this.lodtarget2 != "")
493 {
494 e = find(NULL, targetname, this.lodtarget2);
495 if(e)
496 {
497 this.lodmodel2 = e.model;
498 delete(e);
499 }
500 }
501
502 if(autocvar_loddebug < 0)
503 {
504 this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
505 }
506
507 if(this.lodmodel1 != "" && fexists(this.lodmodel1))
508 {
509 vector mi, ma;
510 mi = this.mins;
511 ma = this.maxs;
512
513 precache_model(this.lodmodel1);
514 _setmodel(this, this.lodmodel1);
515 this.lodmodelindex1 = this.modelindex;
516
517 if(this.lodmodel2 != "" && fexists(this.lodmodel2))
518 {
519 precache_model(this.lodmodel2);
520 _setmodel(this, this.lodmodel2);
521 this.lodmodelindex2 = this.modelindex;
522 }
523
524 this.modelindex = this.lodmodelindex0;
525 setsize(this, mi, ma);
526 }
527
528 if(this.lodmodelindex1)
529 if (!getSendEntity(this))
531}
532
533/*
534================
535InitTrigger
536================
537*/
538
540{
541 if(this.movedir != '0 0 0')
542 this.movedir = normalize(this.movedir);
543 else
544 {
545 makevectors(this.angles);
546 this.movedir = v_forward;
547 }
548
549 this.angles = '0 0 0';
550}
551
553{
554// trigger angles are used for one-way touches. An angle of 0 is assumed
555// to mean no restrictions, so use a yaw of 360 instead.
556 SetMovedir(this);
557 this.solid = SOLID_TRIGGER;
558 SetBrushEntityModel(this, false);
560 this.modelindex = 0;
561 this.model = "";
562}
563
565{
566// trigger angles are used for one-way touches. An angle of 0 is assumed
567// to mean no restrictions, so use a yaw of 360 instead.
568 SetMovedir(this);
569 this.solid = SOLID_BSP;
570 SetBrushEntityModel(this, false);
571 set_movetype(this, MOVETYPE_NONE); // why was this PUSH? -div0
572// this.modelindex = 0;
573 this.model = "";
574}
575
577{
578// trigger angles are used for one-way touches. An angle of 0 is assumed
579// to mean no restrictions, so use a yaw of 360 instead.
580 this.solid = SOLID_BSP;
581 SetBrushEntityModel(this, true);
583 if(this.modelindex == 0)
584 {
585 objerror(this, "InitMovingBrushTrigger: no brushes found!");
586 return false;
587 }
588 return true;
589}
590#endif
float animstate_endtime
Definition anim.qh:38
float animstate_starttime
Definition anim.qh:37
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float alpha
Definition items.qc:13
entity owner
Definition main.qh:87
float ltime
Definition net.qh:10
#define IS_CLIENT(s)
Definition player.qh:241
#define PHYS_INPUT_FRAMETIME
Definition player.qh:254
const int INITPRIO_FINDTARGET
Definition constants.qh:96
const float SOLID_TRIGGER
float modelindex
vector avelocity
float frametime
vector mins
vector velocity
float time
vector maxs
float nextthink
vector v_forward
vector origin
const float SOLID_BSP
int lodmodelindex2
float loddistance2
int lodmodelindex1
float loddistance1
int lodmodelindex0
ent angles
Definition ent_cs.qc:121
model
Definition ent_cs.qc:139
angles_y
Definition ent_cs.qc:119
solid
Definition ent_cs.qc:165
ERASEABLE bool fexists(string f)
Definition file.qh:4
void SetCustomizer(entity e, bool(entity this, entity client) customizer, void(entity this) uncustomizer)
Definition net.qh:204
vector movedir
Definition viewloc.qh:18
ERASEABLE float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float spd)
Definition math.qh:126
entity find(entity start,.string field, string match)
float vlen(vector v)
vector vectoangles(vector v)
vector normalize(vector v)
float floor(float f)
float max(float f,...)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_NONE
Definition movetypes.qh:129
const int MOVETYPE_PUSH
Definition movetypes.qh:136
#define IS_ONGROUND(s)
Definition movetypes.qh:16
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:67
#define NULL
Definition post.qh:14
#define makevectors
Definition post.qh:21
#define objerror
Definition pre.qh:8
fade_rate
Definition projectile.qh:14
#define getSendEntity(e)
#define setthink(e, f)
#define getthink(e)
vector
Definition self.qh:92
vector org
Definition self.qh:92
#define spawnfunc(id)
Definition spawnfunc.qh:96
void SUB_CalcMoveDone(entity this)
Definition subs.qc:94
void SetMovedir(entity this)
Definition subs.qc:539
void ApplyMinMaxScaleAngles(entity e)
Definition subs.qc:392
bool InitMovingBrushTrigger(entity this)
Definition subs.qc:576
void SUB_CalcMove_controller_setbezier(entity controller, vector org, vector control, vector destin)
Definition subs.qc:173
void SUB_CalcMove_controller_think(entity this)
Definition subs.qc:114
void SUB_CalcMove_controller_setlinear(entity controller, vector org, vector destin)
Definition subs.qc:189
float friction
Definition subs.qc:23
void SUB_CalcAngleMoveEnt(entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
Definition subs.qc:386
void SetBrushEntityModel(entity this, bool with_lod)
Definition subs.qc:419
void SUB_Friction(entity this)
Definition subs.qc:24
void InitSolidBSPTrigger(entity this)
Definition subs.qc:564
bool LOD_customize(entity this, entity client)
Definition subs.qc:442
void SUB_NullThink(entity this)
Definition subs.qc:3
void SUB_SetFade_Think(entity this)
Definition subs.qc:57
void SUB_CalcAngleMove(entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
Definition subs.qc:346
void LOD_uncustomize(entity this)
Definition subs.qc:468
void SUB_SetFade(entity ent, float vanish_time, float fading_time)
Definition subs.qc:77
void SUB_CalcMoveEnt(entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
Definition subs.qc:320
float platmovetype_turn
Definition subs.qc:113
void SUB_VanishOrRemove(entity ent)
Definition subs.qc:38
void SUB_CalcMovePause(entity this)
Definition subs.qc:105
void LODmodel_attach(entity this)
Definition subs.qc:473
void SUB_CalcMove(entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
Definition subs.qc:266
void InitTrigger(entity this)
Definition subs.qc:552
void SUB_CalcMove_Bezier(entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
Definition subs.qc:203
void SUB_CalcAngleMoveDone(entity this)
Definition subs.qc:335
vector destvec
Definition subs.qh:35
string lodmodel2
Definition subs.qh:126
vector finalangle
Definition subs.qh:30
const int TSPEED_END
Definition subs.qh:73
const int TSPEED_START
Definition subs.qh:72
entity move_controller
Definition subs.qh:68
string lodtarget1
Definition subs.qh:123
float platmovetype_start
Definition subs.qh:44
vector destvec2
Definition subs.qh:36
string lodtarget2
Definition subs.qh:124
float platmovetype_end
Definition subs.qh:44
string lodmodel1
Definition subs.qh:125
int autocvar_loddebug
Definition subs.qh:122
const int TSPEED_TIME
Definition subs.qh:70
vector finaldest
Definition subs.qh:30
const int TSPEED_LINEAR
Definition subs.qh:71
string platmovetype
Definition subs.qh:43
string targetname
Definition triggers.qh:56
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
ERASEABLE vector RoundPerfectVector(vector v)
Definition vector.qh:209
ERASEABLE vector NearestPointOnBox(entity box, vector org)
Definition vector.qh:181
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2230