]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/movetypes/movetypes.qc
Fix some issues with QC movetypes
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / movetypes / movetypes.qc
1 #include "include.qh"
2 #include "../physics.qh"
3
4 #if defined(CSQC)
5         #include "../../client/defs.qh"
6         #include "../stats.qh"
7         #include "../util.qh"
8         #include "movetypes.qh"
9         #include "../../lib/csqcmodel/common.qh"
10         #include "../../server/t_items.qh"
11 #elif defined(MENUQC)
12 #elif defined(SVQC)
13         #include "../../server/autocvars.qh"
14 #endif
15
16 void _Movetype_WallFriction(entity this, vector stepnormal)  // SV_WallFriction
17 {
18         /*float d, i;
19         vector into, side;
20         makevectors(this.v_angle);
21         d = (stepnormal * v_forward) + 0.5;
22
23         if(d < 0)
24         {
25             i = (stepnormal * this.move_velocity);
26             into = i * stepnormal;
27             side = this.move_velocity - into;
28             this.move_velocity_x = side.x * (1 * d);
29             this.move_velocity_y = side.y * (1 * d);
30         }*/
31 }
32
33 vector planes[MAX_CLIP_PLANES];
34 int _Movetype_FlyMove(entity this, float dt, bool applygravity, vector stepnormal, float stepheight) // SV_FlyMove
35 {
36         int blocked = 0, bumpcount;
37         int i, j, numplanes = 0;
38         float time_left = dt, grav = 0;
39         vector push;
40         vector primal_velocity, original_velocity, restore_velocity;
41
42         for(i = 0; i < MAX_CLIP_PLANES; ++i)
43                 planes[i] = '0 0 0';
44
45         if(applygravity)
46         {
47                 this.move_didgravity = 1;
48                 grav = dt * (PHYS_ENTGRAVITY(this) ? PHYS_ENTGRAVITY(this) : 1) * PHYS_GRAVITY(this);
49
50                 if(!GAMEPLAYFIX_NOGRAVITYONGROUND || !(this.move_flags & FL_ONGROUND))
51                 {
52                         if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
53                                 this.move_velocity_z -= grav * 0.5;
54                         else
55                                 this.move_velocity_z -= grav;
56                 }
57         }
58
59         original_velocity = primal_velocity = restore_velocity = this.move_velocity;
60
61         for(bumpcount = 0;bumpcount < MAX_CLIP_PLANES;bumpcount++)
62         {
63                 if(this.move_velocity == '0 0 0')
64                         break;
65
66                 push = this.move_velocity * time_left;
67                 _Movetype_PushEntity(this, push, true);
68                 if(trace_startsolid)
69                 {
70                         // we got teleported by a touch function
71                         // let's abort the move
72                         blocked |= 8;
73                         break;
74                 }
75
76                 // this code is used by MOVETYPE_WALK and MOVETYPE_STEP and SV_UnstickEntity
77                 // abort move if we're stuck in the world (and didn't make it out)
78                 if(trace_startsolid && trace_allsolid)
79                 {
80                         this.move_velocity = restore_velocity;
81                         return 3;
82                 }
83
84                 if(trace_fraction == 1)
85                         break;
86
87                 float my_trace_fraction = trace_fraction;
88                 vector my_trace_plane_normal = trace_plane_normal;
89
90                 if(trace_plane_normal.z)
91                 {
92                         if(trace_plane_normal.z > 0.7)
93                         {
94                                 // floor
95                                 blocked |= 1;
96
97                                 if(!trace_ent)
98                                 {
99                                         //dprint("_Movetype_FlyMove: !trace_ent\n");
100                                         trace_ent = world;
101                                 }
102
103                                 this.move_flags |= FL_ONGROUND;
104                                 this.move_groundentity = trace_ent;
105                         }
106                 }
107                 else if(stepheight)
108                 {
109                         // step - handle it immediately
110                         vector org = this.move_origin;
111                         vector steppush = '0 0 1' * stepheight;
112
113                         _Movetype_PushEntity(this, steppush, true);
114                         if(trace_startsolid)
115                         {
116                                 blocked |= 8;
117                                 break;
118                         }
119                         _Movetype_PushEntity(this, push, true);
120                         if(trace_startsolid)
121                         {
122                                 blocked |= 8;
123                                 break;
124                         }
125                         float trace2_fraction = trace_fraction;
126                         steppush = '0 0 1' * (org_z - this.move_origin_z);
127                         _Movetype_PushEntity(this, steppush, true);
128                         if(trace_startsolid)
129                         {
130                                 blocked |= 8;
131                                 break;
132                         }
133
134                         // accept the new position if it made some progress...
135                         if(fabs(this.move_origin_x - org_x) >= 0.03125 || fabs(this.move_origin_y - org_y) >= 0.03125)
136                         {
137                                 trace_endpos = this.move_origin;
138                                 time_left *= 1 - trace2_fraction;
139                                 numplanes = 0;
140                                 continue;
141                         }
142                         else
143                                 this.move_origin = org;
144                 }
145                 else
146                 {
147                         // step - return it to caller
148                         blocked |= 2;
149                         // save the trace for player extrafriction
150                         if(stepnormal)
151                                 stepnormal = trace_plane_normal;
152                 }
153
154                 if(my_trace_fraction >= 0.001)
155                 {
156                         // actually covered some distance
157                         original_velocity = this.move_velocity;
158                         numplanes = 0;
159                 }
160
161                 time_left *= 1 - my_trace_fraction;
162
163                 // clipped to another plane
164                 if(numplanes >= MAX_CLIP_PLANES)
165                 {
166                         // this shouldn't really happen
167                         this.move_velocity = '0 0 0';
168                         blocked = 3;
169                         break;
170                 }
171
172                 planes[numplanes] = my_trace_plane_normal;
173                 numplanes++;
174
175                 // modify original_velocity so it parallels all of the clip planes
176                 vector new_velocity = '0 0 0';
177                 for (i = 0;i < numplanes;i++)
178                 {
179                         new_velocity = _Movetype_ClipVelocity(original_velocity, planes[i], 1);
180                         for (j = 0;j < numplanes;j++)
181                         {
182                                 if(j != i)
183                                 {
184                                         // not ok
185                                         if((new_velocity * planes[j]) < 0)
186                                                 break;
187                                 }
188                         }
189                         if(j == numplanes)
190                                 break;
191                 }
192
193                 if(i != numplanes)
194                 {
195                         // go along this plane
196                         this.move_velocity = new_velocity;
197                 }
198                 else
199                 {
200                         // go along the crease
201                         if(numplanes != 2)
202                         {
203                                 this.move_velocity = '0 0 0';
204                                 blocked = 7;
205                                 break;
206                         }
207                         vector dir = cross(planes[0], planes[1]);
208                         // LordHavoc: thanks to taniwha of QuakeForge for pointing out this fix for slowed falling in corners
209                         float ilength = sqrt((dir * dir));
210                         if(ilength)
211                                 ilength = 1.0 / ilength;
212                         dir.x *= ilength;
213                         dir.y *= ilength;
214                         dir.z *= ilength;
215                         float d = (dir * this.move_velocity);
216                         this.move_velocity = dir * d;
217                 }
218
219                 // if current velocity is against the original velocity,
220                 // stop dead to avoid tiny occilations in sloping corners
221                 if((this.move_velocity * primal_velocity) <= 0)
222                 {
223                         this.move_velocity = '0 0 0';
224                         break;
225                 }
226         }
227
228         // LordHavoc: this came from QW and allows you to get out of water more easily
229         if(GAMEPLAYFIX_EASIERWATERJUMP && (this.move_flags & FL_WATERJUMP) && !(blocked & 8))
230                 this.move_velocity = primal_velocity;
231
232         if(applygravity)
233         {
234                 if(!GAMEPLAYFIX_NOGRAVITYONGROUND || !(this.move_flags & FL_ONGROUND))
235                 {
236                         if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
237                                 this.move_velocity_z -= grav * 0.5f;
238                 }
239         }
240
241         return blocked;
242 }
243
244 void _Movetype_CheckVelocity(entity this)  // SV_CheckVelocity
245 {
246         // if(vlen(this.move_velocity) < 0.0001)
247         // this.move_velocity = '0 0 0';
248 }
249
250 bool _Movetype_CheckWater(entity ent)  // SV_CheckWater
251 {
252         vector point = ent.move_origin;
253         point.z += (ent.mins.z + 1);
254
255         int nativecontents = pointcontents(point);
256         if(ent.move_watertype && ent.move_watertype != nativecontents)
257         {
258                 // dprintf("_Movetype_CheckWater(): Original: '%d', New: '%d'\n", ent.move_watertype, nativecontents);
259                 if(ent.contentstransition)
260                         ent.contentstransition(ent.move_watertype, nativecontents);
261         }
262
263         ent.move_waterlevel = 0;
264         ent.move_watertype = CONTENT_EMPTY;
265
266         int supercontents = Mod_Q1BSP_SuperContentsFromNativeContents(nativecontents);
267         if(supercontents & DPCONTENTS_LIQUIDSMASK)
268         {
269                 ent.move_watertype = nativecontents;
270                 ent.move_waterlevel = 1;
271                 point.y = (ent.origin.y + ((ent.mins.z + ent.maxs.y) * 0.5));
272                 if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(point)) & DPCONTENTS_LIQUIDSMASK)
273                 {
274                         ent.move_waterlevel = 2;
275                         point.y = ent.origin.y + ent.view_ofs.y;
276                         if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(point)) & DPCONTENTS_LIQUIDSMASK)
277                                 ent.move_waterlevel = 3;
278                 }
279         }
280
281         return ent.move_waterlevel > 1;
282 }
283
284 void _Movetype_CheckWaterTransition(entity ent)  // SV_CheckWaterTransition
285 {
286         int contents = pointcontents(ent.move_origin);
287
288         if(!ent.move_watertype)
289         {
290                 // just spawned here
291                 if(!autocvar_cl_gameplayfix_fixedcheckwatertransition)
292                 {
293                         ent.move_watertype = contents;
294                         ent.move_waterlevel = 1;
295                         return;
296                 }
297         }
298         else if(ent.move_watertype != contents)
299         {
300                 // dprintf("_Movetype_CheckWaterTransition(): Origin: %s, Direct: '%d', Original: '%d', New: '%d'\n", vtos(ent.move_origin), pointcontents(ent.move_origin), ent.move_watertype, contents);
301                 if(ent.contentstransition)
302                         ent.contentstransition(ent.move_watertype, contents);
303         }
304
305         if(contents <= CONTENT_WATER)
306         {
307                 ent.move_watertype = contents;
308                 ent.move_waterlevel = 1;
309         }
310         else
311         {
312                 ent.move_watertype = CONTENT_EMPTY;
313                 ent.move_waterlevel = (autocvar_cl_gameplayfix_fixedcheckwatertransition ? 0 : contents);
314         }
315 }
316
317 void _Movetype_Impact(entity this, entity oth)  // SV_Impact
318 {
319         entity oldother = other;
320
321         if(this.move_touch)
322         {
323                 other = oth;
324
325                 WITH(entity, self, this, this.move_touch());
326
327                 other = oldother;
328         }
329
330         if(oth.move_touch)
331         {
332                 other = this;
333
334                 WITH(entity, self, oth, oth.move_touch());
335
336                 other = oldother;
337         }
338 }
339
340 void _Movetype_LinkEdict_TouchAreaGrid(entity this)  // SV_LinkEdict_TouchAreaGrid
341 {
342         entity oldother = other;
343
344         for (entity e = findradius(0.5 * (this.absmin + this.absmax), 0.5 * vlen(this.absmax - this.absmin)); e; e = e.chain)
345         {
346                 if(e.move_touch && boxesoverlap(e.absmin, e.absmax, this.absmin, this.absmax))
347                 {
348                         other = this;
349
350                         trace_allsolid = false;
351                         trace_startsolid = false;
352                         trace_fraction = 1;
353                         trace_inwater = false;
354                         trace_inopen = true;
355                         trace_endpos = e.origin;
356                         trace_plane_normal = '0 0 1';
357                         trace_plane_dist = 0;
358                         trace_ent = this;
359
360                         WITH(entity, self, e, e.move_touch());
361                 }
362         }
363
364         other = oldother;
365 }
366
367 void _Movetype_LinkEdict(entity this, bool touch_triggers)  // SV_LinkEdict
368 {
369         vector mi, ma;
370         if(this.solid == SOLID_BSP)
371         {
372                 // TODO set the absolute bbox
373                 mi = this.mins;
374                 ma = this.maxs;
375         }
376         else
377         {
378                 mi = this.mins;
379                 ma = this.maxs;
380         }
381         mi += this.move_origin;
382         ma += this.move_origin;
383
384         if(this.move_flags & FL_ITEM)
385         {
386                 mi.x -= 15;
387                 mi.y -= 15;
388                 mi.z -= 1;
389                 ma.x += 15;
390                 ma.y += 15;
391                 ma.z += 1;
392         }
393         else
394         {
395                 mi.x -= 1;
396                 mi.y -= 1;
397                 mi.z -= 1;
398                 ma.x += 1;
399                 ma.y += 1;
400                 ma.z += 1;
401         }
402
403         this.absmin = mi;
404         this.absmax = ma;
405
406         if(touch_triggers)
407                 _Movetype_LinkEdict_TouchAreaGrid(this);
408 }
409
410 bool _Movetype_TestEntityPosition(entity this, vector ofs)  // SV_TestEntityPosition
411 {
412 //      vector org = this.move_origin + ofs;
413
414         int cont = this.dphitcontentsmask;
415         this.dphitcontentsmask = DPCONTENTS_SOLID;
416         tracebox(this.move_origin, this.mins, this.maxs, this.move_origin, MOVE_NOMONSTERS, this);
417         this.dphitcontentsmask = cont;
418
419         if(trace_startsolid)
420                 return true;
421
422         if(vlen(trace_endpos - this.move_origin) > 0.0001)
423                 this.move_origin = trace_endpos;
424         return false;
425 }
426
427 bool _Movetype_UnstickEntity(entity this)  // SV_UnstickEntity
428 {
429         if(!_Movetype_TestEntityPosition(this, '0 0 0')) return true;
430         if(!_Movetype_TestEntityPosition(this, '-1 0 0')) goto success;
431         if(!_Movetype_TestEntityPosition(this, '1 0 0')) goto success;
432         if(!_Movetype_TestEntityPosition(this, '0 -1 0')) goto success;
433         if(!_Movetype_TestEntityPosition(this, '0 1 0')) goto success;
434         if(!_Movetype_TestEntityPosition(this, '-1 -1 0')) goto success;
435         if(!_Movetype_TestEntityPosition(this, '1 -1 0')) goto success;
436         if(!_Movetype_TestEntityPosition(this, '-1 1 0')) goto success;
437         if(!_Movetype_TestEntityPosition(this, '1 1 0')) goto success;
438         for (int i = 1; i <= 17; ++i)
439         {
440                 if(!_Movetype_TestEntityPosition(this, '0 0 -1' * i)) goto success;
441                 if(!_Movetype_TestEntityPosition(this, '0 0 1' * i)) goto success;
442         }
443         LOG_TRACEF("Can't unstick an entity (edict: %d, classname: %s, origin: %s)\n",
444                 num_for_edict(this), this.classname, vtos(this.move_origin));
445         return false;
446         : success;
447         LOG_TRACEF("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)\n",
448                 num_for_edict(this), this.classname, vtos(this.move_origin));
449         _Movetype_LinkEdict(this, true);
450         return true;
451 }
452
453 vector _Movetype_ClipVelocity(vector vel, vector norm, float f)  // SV_ClipVelocity
454 {
455         vel -= ((vel * norm) * norm) * f;
456
457         if(vel.x > -0.1 && vel.x < 0.1) vel.x = 0;
458         if(vel.y > -0.1 && vel.y < 0.1) vel.y = 0;
459         if(vel.z > -0.1 && vel.z < 0.1) vel.z = 0;
460
461         return vel;
462 }
463
464 void _Movetype_PushEntityTrace(entity this, vector push)
465 {
466         vector end = this.move_origin + push;
467         int type;
468         if(this.move_nomonsters)
469                 type = max(0, this.move_nomonsters);
470         else if(this.move_movetype == MOVETYPE_FLYMISSILE)
471                 type = MOVE_MISSILE;
472         else if(this.solid == SOLID_TRIGGER || this.solid == SOLID_NOT)
473                 type = MOVE_NOMONSTERS;
474         else
475                 type = MOVE_NORMAL;
476
477         tracebox(this.move_origin, this.mins, this.maxs, end, type, this);
478 }
479
480 float _Movetype_PushEntity(entity this, vector push, bool failonstartsolid)  // SV_PushEntity
481 {
482         _Movetype_PushEntityTrace(this, push);
483
484         if(trace_startsolid && failonstartsolid)
485                 return trace_fraction;
486
487         this.move_origin = trace_endpos;
488
489         if(trace_fraction < 1)
490                 if(this.solid >= SOLID_TRIGGER && (!(this.move_flags & FL_ONGROUND) || (this.move_groundentity != trace_ent)))
491                         _Movetype_Impact(this, trace_ent);
492
493         return trace_fraction;
494 }
495
496
497 .float ltime;
498 .void() blocked;
499 // matrix version of makevectors, sets v_forward, v_right and v_up
500 void makevectors_matrix(vector myangles)  // AngleVectorsFLU
501 {
502         v_forward = v_right = v_up = '0 0 0';
503
504         float y = myangles.y * (M_PI * 2 / 360);
505         float sy = sin(y);
506         float cy = cos(y);
507         float p = myangles.x * (M_PI * 2 / 360);
508         float sp = sin(p);
509         float cp = cos(p);
510         if(v_forward)
511         {
512                 v_forward.x = cp * cy;
513                 v_forward.y = cp * sy;
514                 v_forward.z = -sp;
515         }
516         if(v_right || v_up)
517         {
518                 if(myangles.z)
519                 {
520                         float r = myangles.z * (M_PI * 2 / 360);
521                         float sr = sin(r);
522                         float cr = cos(r);
523                         if(v_right)
524                         {
525                                 v_right.x = sr * sp * cy + cr * -sy;
526                                 v_right.y = sr * sp * sy + cr * cy;
527                                 v_right.z = sr * cp;
528                         }
529                         if(v_up)
530                         {
531                                 v_up.x = cr * sp * cy + -sr * -sy;
532                                 v_up.y = cr * sp * sy + -sr * cy;
533                                 v_up.z = cr * cp;
534                         }
535                 }
536                 else
537                 {
538                         if(v_right)
539                         {
540                                 v_right.x = -sy;
541                                 v_right.y = cy;
542                                 v_right.z = 0;
543                         }
544                         if(v_up)
545                         {
546                                 v_up.x = sp * cy;
547                                 v_up.y = sp * sy;
548                                 v_up.z = cp;
549                         }
550                 }
551         }
552 }
553
554 void _Movetype_Physics_Frame(entity this, float movedt)
555 {
556         this.move_didgravity = -1;
557         switch (this.move_movetype)
558         {
559                 case MOVETYPE_PUSH:
560                 case MOVETYPE_FAKEPUSH:
561                         _Movetype_Physics_Pusher(this, movedt);
562                         break;
563                 case MOVETYPE_NONE:
564                         break;
565                 case MOVETYPE_FOLLOW:
566                         _Movetype_Physics_Follow(this);
567                         break;
568                 case MOVETYPE_NOCLIP:
569                         _Movetype_CheckWater(this);
570                         this.move_origin = this.move_origin + TICRATE * this.move_velocity;
571                         this.move_angles = this.move_angles + TICRATE * this.move_avelocity;
572                         _Movetype_LinkEdict(this, false);
573                         break;
574                 case MOVETYPE_STEP:
575                         _Movetype_Physics_Step(this, movedt);
576                         break;
577                 case MOVETYPE_WALK:
578                         _Movetype_Physics_Walk(this, movedt);
579                         break;
580                 case MOVETYPE_TOSS:
581                 case MOVETYPE_BOUNCE:
582                 case MOVETYPE_BOUNCEMISSILE:
583                 case MOVETYPE_FLYMISSILE:
584                 case MOVETYPE_FLY:
585                         _Movetype_Physics_Toss(this, movedt);
586                         break;
587         }
588 }
589
590 void Movetype_Physics_NoMatchServer(entity this)  // optimized
591 {
592         float movedt = time - this.move_time;
593         this.move_time = time;
594
595         _Movetype_Physics_Frame(this, movedt);
596         if(wasfreed(this))
597                 return;
598
599         this.avelocity = this.move_avelocity;
600         this.velocity = this.move_velocity;
601         this.angles = this.move_angles;
602         setorigin(this, this.move_origin);
603 }
604
605 void Movetype_Physics_MatchServer(entity this, bool sloppy)
606 {
607         Movetype_Physics_MatchTicrate(this, TICRATE, sloppy);
608 }
609
610 void Movetype_Physics_MatchTicrate(entity this, float tr, bool sloppy)  // SV_Physics_Entity
611 {
612         if(tr <= 0)
613         {
614                 Movetype_Physics_NoMatchServer(this);
615                 return;
616         }
617
618         float dt = time - this.move_time;
619
620         int n = max(0, floor(dt / tr));
621         dt -= n * tr;
622         this.move_time += n * tr;
623
624         if(!this.move_didgravity)
625                 this.move_didgravity = ((this.move_movetype == MOVETYPE_BOUNCE || this.move_movetype == MOVETYPE_TOSS) && !(this.move_flags & FL_ONGROUND));
626
627         for (int i = 0; i < n; ++i)
628         {
629                 _Movetype_Physics_Frame(this, tr);
630                 if(wasfreed(this))
631                         return;
632         }
633
634         this.avelocity = this.move_avelocity;
635
636         if(dt > 0 && this.move_movetype != MOVETYPE_NONE && !(this.move_flags & FL_ONGROUND))
637         {
638                 // now continue the move from move_time to time
639                 this.velocity = this.move_velocity;
640
641                 if(this.move_didgravity > 0)
642                 {
643                         this.velocity_z -= (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1)
644                             * dt
645                             * (this.gravity ? this.gravity : 1)
646                             * PHYS_GRAVITY(this);
647                 }
648
649                 this.angles = this.move_angles + dt * this.avelocity;
650
651                 if(sloppy || this.move_movetype == MOVETYPE_NOCLIP)
652                 {
653                         setorigin(this, this.move_origin + dt * this.velocity);
654                 }
655                 else
656                 {
657                         _Movetype_PushEntityTrace(this, dt * this.velocity);
658                         if(!trace_startsolid)
659                                 setorigin(this, trace_endpos);
660                 }
661
662                 if(this.move_didgravity > 0 && GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
663                         this.velocity_z -= 0.5 * dt * (this.gravity ? this.gravity : 1) * PHYS_GRAVITY(this);
664         }
665         else
666         {
667                 this.velocity = this.move_velocity;
668                 this.angles = this.move_angles;
669                 setorigin(this, this.move_origin);
670         }
671 }