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 = 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;
139 destangle = delta + 2 * delta2 * phasepos;
140 destangle = vectoangles(destangle);
141 destangle_x = -destangle_x; // flip up / down orientation
142
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;
152 }
153 if(nexttick < this.animstate_endtime)
154 veloc = nextpos - this.owner.origin;
155 else
156 veloc = this.finaldest - this.owner.origin;
157 veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
158
159 this.owner.velocity = veloc;
160 this.nextthink = nexttick;
161 }
162 else
163 {
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;
169 delete(this);
170 getthink(own)(own);
171 }
172}
173
175{
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
179
180 //setorigin(controller, org); // don't link to the world
181 controller.origin = org;
182 control -= org;
183 destin -= org;
184
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)
188}
189
191{
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
195
196 //setorigin(controller, org); // don't link to the world
197 controller.origin = org;
198 destin -= org;
199
200 controller.destvec = destin; // end point
201 controller.destvec2 = '0 0 0';
202}
203
204void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
205{
206 float traveltime;
207 entity controller;
208
209 if (!tspeed)
210 objerror (this, "No speed is defined!");
211
212 this.think1 = func;
213 this.finaldest = tdest;
215
216 switch(tspeedtype)
217 {
218 default:
219 case TSPEED_START:
220 traveltime = 2 * vlen(tcontrol - this.origin) / tspeed;
221 break;
222 case TSPEED_END:
223 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
224 break;
225 case TSPEED_LINEAR:
226 traveltime = vlen(tdest - this.origin) / tspeed;
227 break;
228 case TSPEED_TIME:
229 traveltime = tspeed;
230 break;
231 }
232
233 if (traveltime < 0.1) // useless anim
234 {
235 this.velocity = '0 0 0';
236 this.nextthink = this.ltime + 0.1;
237 return;
238 }
239
240 // delete the previous controller, otherwise changing movement midway is glitchy
241 if (this.move_controller != NULL)
242 {
243 delete(this.move_controller);
244 }
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;
257 controller.think1 = getthink(this);
258
259 // the thinking is now done by the controller
260 setthink(this, SUB_NullThink); // for PushMove
261 this.nextthink = this.ltime + traveltime;
262
263 // invoke controller
264 getthink(controller)(controller);
265}
266
267void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
268{
269 vector delta;
270 float traveltime;
271
272 if (!tspeed)
273 objerror (this, "No speed is defined!");
274
275 this.think1 = func;
276 this.finaldest = tdest;
278
279 if (tdest == this.origin)
280 {
281 this.velocity = '0 0 0';
282 this.nextthink = this.ltime + 0.1;
283 return;
284 }
285
286 delta = tdest - this.origin;
287
288 switch(tspeedtype)
289 {
290 default:
291 case TSPEED_START:
292 case TSPEED_END:
293 case TSPEED_LINEAR:
294 traveltime = vlen (delta) / tspeed;
295 break;
296 case TSPEED_TIME:
297 traveltime = tspeed;
298 break;
299 }
300
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.
303 if (traveltime <= 0)
304 traveltime = 0.001;
305
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?
311 {
312 this.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
313 this.nextthink = this.ltime + traveltime;
314 return;
315 }
316
317 // now just run like a bezier curve...
318 SUB_CalcMove_Bezier(this, (this.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
319}
320
321void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
322{
323 SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func);
324}
325
326/*
327=============
328SUB_CalcAngleMove
329
330calculate this.avelocity and this.nextthink to reach destangle from
331this.angles rotating
332
333The calling function should make sure this.setthink is valid
334===============
335*/
337{
338 // After rotating, set angle to exact final angle
339 this.angles = this.finalangle;
340 this.avelocity = '0 0 0';
341 this.nextthink = -1;
342 if (this.think1 && this.think1 != SUB_CalcAngleMoveDone) // avoid endless loops
343 this.think1 (this);
344}
345
346// FIXME: I fixed this function only for rotation around the main axes
347void SUB_CalcAngleMove (entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
348{
349 if (!tspeed)
350 objerror (this, "No speed is defined!");
351
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;
357 float traveltime;
358
359 switch(tspeedtype)
360 {
361 default:
362 case TSPEED_START:
363 case TSPEED_END:
364 case TSPEED_LINEAR:
365 traveltime = vlen (delta) / tspeed;
366 break;
367 case TSPEED_TIME:
368 traveltime = tspeed;
369 break;
370 }
371
372 this.think1 = func;
373 this.finalangle = destangle;
375
376 if (traveltime < 0.1)
377 {
378 this.avelocity = '0 0 0';
379 this.nextthink = this.ltime + 0.1;
380 return;
381 }
382
383 this.avelocity = delta * (1 / traveltime);
384 this.nextthink = this.ltime + traveltime;
385}
386
387void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
388{
389 SUB_CalcAngleMove (ent, destangle, tspeedtype, tspeed, func);
390}
391
392#ifdef SVQC
394{
395 if(e.angles.x != 0 || e.angles.z != 0 || e.avelocity.x != 0 || e.avelocity.z != 0) // "weird" rotation
396 {
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)
401 );
402 e.mins = -e.maxs;
403 }
404 else if(e.angles.y != 0 || e.avelocity.y != 0) // yaw only is a bit better
405 {
406 e.maxs_x = vlen(
407 '1 0 0' * max(-e.mins.x, e.maxs.x) +
408 '0 1 0' * max(-e.mins.y, e.maxs.y)
409 );
410 e.maxs_y = e.maxs.x;
411 e.mins_x = -e.maxs.x;
412 e.mins_y = -e.maxs.x;
413 }
414 if(e.scale)
415 setsize(e, RoundPerfectVector(e.mins * e.scale), RoundPerfectVector(e.maxs * e.scale));
416 else
417 setsize(e, e.mins, e.maxs);
418}
419
420void SetBrushEntityModel(entity this, bool with_lod)
421{
422 // Ensure .solid is set correctly before calling this (for area grid linking/unlinking)
423
424 if(this.model != "")
425 {
426 precache_model(this.model);
427 if(this.mins != '0 0 0' || this.maxs != '0 0 0')
428 {
429 vector mi = this.mins;
430 vector ma = this.maxs;
431 _setmodel(this, this.model); // no precision needed
432 setsize(this, mi, ma);
433 }
434 else
435 _setmodel(this, this.model); // no precision needed
436 if(with_lod)
438 }
439 setorigin(this, this.origin);
441}
442
443bool LOD_customize(entity this, entity client)
444{
446 {
447 int d = autocvar_loddebug;
448 if(d == 1)
449 this.modelindex = this.lodmodelindex0;
450 else if(d == 2 || !this.lodmodelindex2)
451 this.modelindex = this.lodmodelindex1;
452 else // if(d == 3)
453 this.modelindex = this.lodmodelindex2;
454 return true;
455 }
456
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;
463 else
464 this.modelindex = this.lodmodelindex2;
465
466 return true;
467}
468
470{
471 this.modelindex = this.lodmodelindex0;
472}
473
475{
476 entity e;
477
478 if(!this.loddistance1)
479 this.loddistance1 = 1000;
480 if(!this.loddistance2)
481 this.loddistance2 = 2000;
482 this.lodmodelindex0 = this.modelindex;
483
484 if(this.lodtarget1 != "")
485 {
486 e = find(NULL, targetname, this.lodtarget1);
487 if(e)
488 {
489 this.lodmodel1 = e.model;
490 delete(e);
491 }
492 }
493 if(this.lodtarget2 != "")
494 {
495 e = find(NULL, targetname, this.lodtarget2);
496 if(e)
497 {
498 this.lodmodel2 = e.model;
499 delete(e);
500 }
501 }
502
503 if(autocvar_loddebug < 0)
504 {
505 this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
506 }
507
508 if(this.lodmodel1 != "" && fexists(this.lodmodel1))
509 {
510 vector mi, ma;
511 mi = this.mins;
512 ma = this.maxs;
513
514 precache_model(this.lodmodel1);
515 _setmodel(this, this.lodmodel1);
516 this.lodmodelindex1 = this.modelindex;
517
518 if(this.lodmodel2 != "" && fexists(this.lodmodel2))
519 {
520 precache_model(this.lodmodel2);
521 _setmodel(this, this.lodmodel2);
522 this.lodmodelindex2 = this.modelindex;
523 }
524
525 this.modelindex = this.lodmodelindex0;
526 setsize(this, mi, ma);
527 }
528
529 if(this.lodmodelindex1)
530 if (!getSendEntity(this))
532}
533
534/*
535================
536InitTrigger
537================
538*/
539
541{
542 if(this.movedir != '0 0 0')
543 this.movedir = normalize(this.movedir);
544 else
545 {
546 makevectors(this.angles);
547 this.movedir = v_forward;
548 }
549
550 this.angles = '0 0 0';
551}
552
554{
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.
557 SetMovedir(this);
558 this.solid = SOLID_TRIGGER;
559 SetBrushEntityModel(this, false);
561 this.modelindex = 0;
562 this.model = "";
563}
564
566{
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.
569 SetMovedir(this);
570 this.solid = SOLID_BSP;
571 SetBrushEntityModel(this, false);
572 set_movetype(this, MOVETYPE_NONE); // why was this PUSH? -div0
573// this.modelindex = 0;
574 this.model = "";
575}
576
578{
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);
584 if(this.modelindex == 0)
585 {
586 objerror(this, "InitMovingBrushTrigger: no brushes found!");
587 return false;
588 }
589 return true;
590}
591#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
#define IS_CLIENT(s)
Definition player.qh:242
#define PHYS_INPUT_FRAMETIME
Definition player.qh:255
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:159
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
float ltime
Definition net.qc:10
#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:540
void ApplyMinMaxScaleAngles(entity e)
Definition subs.qc:393
bool InitMovingBrushTrigger(entity this)
Definition subs.qc:577
void SUB_CalcMove_controller_setbezier(entity controller, vector org, vector control, vector destin)
Definition subs.qc:174
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:190
float friction
Definition subs.qc:23
void SUB_CalcAngleMoveEnt(entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func)
Definition subs.qc:387
void SetBrushEntityModel(entity this, bool with_lod)
Definition subs.qc:420
void SUB_Friction(entity this)
Definition subs.qc:24
void InitSolidBSPTrigger(entity this)
Definition subs.qc:565
bool LOD_customize(entity this, entity client)
Definition subs.qc:443
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:347
void LOD_uncustomize(entity this)
Definition subs.qc:469
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:321
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:474
void SUB_CalcMove(entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
Definition subs.qc:267
void InitTrigger(entity this)
Definition subs.qc:553
void SUB_CalcMove_Bezier(entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func)
Definition subs.qc:204
void SUB_CalcAngleMoveDone(entity this)
Definition subs.qc:336
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:206
ERASEABLE vector NearestPointOnBox(entity box, vector org)
Definition vector.qh:178
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2209