5 #include "../common/triggers/teleporters.qh"
7 #include "mutators/_mod.qh"
9 #include "weapons/tracing.qh"
11 #include "../common/constants.qh"
12 #include "../common/deathtypes/all.qh"
13 #include "../common/util.qh"
15 #include <common/physics/player.qh>
17 #include "../common/monsters/_mod.qh"
19 #include <common/weapons/_all.qh>
21 #include "../common/triggers/subs.qh"
23 #include "../common/triggers/func/breakable.qh"
25 #include "../lib/csqcmodel/sv_model.qh"
27 #include "../lib/warpzone/anglestransform.qh"
28 #include "../lib/warpzone/util_server.qh"
30 void CopyBody(entity this, float keepvelocity);
34 float CheatImpulse(entity this, int imp) { return 0; }
35 float CheatCommand(entity this, int argc) { return 0; }
36 float CheatFrame(entity this) { return 0; }
37 void CheatInit() { cheatcount_total = world.cheatcount; }
38 void CheatShutdown() { }
39 void Drag_MoveDrag(entity from, entity to) { }
44 float gamestart_sv_cheats;
50 gamestart_sv_cheats = autocvar_sv_cheats;
57 float CheatsAllowed(entity this, float i, float argc, float fr) // the cheat gets passed as argument for possible future ACL checking
59 // dead people cannot cheat
62 if(gamestart_sv_cheats < 2 && !IS_PLAYER(this))
66 if(i == CHIMPULSE_CLONE_MOVING.impulse || i == CHIMPULSE_CLONE_STANDING.impulse)
67 if(this.lip < sv_clones)
75 if(gamestart_sv_cheats && autocvar_sv_cheats)
78 // if we get here, player is not allowed to cheat. Log it.
80 bprintf("Player %s^7 tried to use cheat 'impulse %d'\n", this.netname, i);
82 bprintf("Player %s^7 tried to use cheat '%s'\n", this.netname, argv(0));
84 bprintf("Player %s^7 tried to use cheat frame %d\n", this.netname, fr);
86 bprintf("Player %s^7 tried to use an unknown cheat\n", this.netname);
91 #define BEGIN_CHEAT_FUNCTION() \
92 float cheating, attempting; \
93 cheating = 0; attempting = 0
96 #define ADD_CHEATS(e,n) \
97 cheatcount_total += n; \
99 #define END_CHEAT_FUNCTION() \
100 ADD_CHEATS(this, cheating); \
102 #define IS_CHEAT(ent,i,argc,fr) \
103 if((++attempting, !CheatsAllowed(ent,i,argc,fr))) \
106 float num_autoscreenshot;
107 void info_autoscreenshot_findtarget(entity this)
110 e = find(NULL, targetname, this.target);
113 objerror(this, "Missing target. FAIL!");
116 vector a = vectoangles(e.origin - this.origin);
117 a.x = -a.x; // don't ask
120 // we leave Rick Roll alone
122 spawnfunc(info_autoscreenshot)
124 if(++num_autoscreenshot > autocvar_g_max_info_autoscreenshot)
126 objerror(this, "Too many info_autoscreenshot entitites. FAIL!");
129 if(this.target != "")
130 InitializeEntity(this, info_autoscreenshot_findtarget, INITPRIO_FINDTARGET);
131 // this one just has to exist
134 bool autocvar_g_allow_checkpoints;
135 float CheatImpulse(entity this, int imp)
137 BEGIN_CHEAT_FUNCTION();
142 case CHIMPULSE_SPEEDRUN_INIT.impulse: // deploy personal waypoint
143 // shared with regular waypoint init, so this is not a cheat by itself
146 this.personal = new(personal_wp);
148 this.personal.origin = this.origin;
149 this.personal.v_angle = this.v_angle;
150 this.personal.velocity = this.velocity;
151 this.personal.ammo_rockets = this.ammo_rockets;
152 this.personal.ammo_nails = this.ammo_nails;
153 this.personal.ammo_cells = this.ammo_cells;
154 this.personal.ammo_plasma = this.ammo_plasma;
155 this.personal.ammo_shells = this.ammo_shells;
156 this.personal.ammo_fuel = this.ammo_fuel;
157 this.personal.health = this.health;
158 this.personal.armorvalue = this.armorvalue;
159 this.personal.weapons = this.weapons;
160 this.personal.items = this.items;
161 this.personal.pauserotarmor_finished = this.pauserotarmor_finished;
162 this.personal.pauserothealth_finished = this.pauserothealth_finished;
163 this.personal.pauserotfuel_finished = this.pauserotfuel_finished;
164 this.personal.pauseregen_finished = this.pauseregen_finished;
165 this.personal.strength_finished = this.strength_finished;
166 this.personal.invincible_finished = this.invincible_finished;
167 this.personal.teleport_time = time;
168 break; // this part itself doesn't cheat, so let's not count this
169 case CHIMPULSE_CLONE_MOVING.impulse:
170 IS_CHEAT(this, imp, 0, 0);
171 makevectors (this.v_angle);
172 this.velocity = this.velocity + v_forward * 300;
175 this.velocity = this.velocity - v_forward * 300;
178 case CHIMPULSE_CLONE_STANDING.impulse:
179 IS_CHEAT(this, imp, 0, 0);
184 case CHIMPULSE_GIVE_ALL.impulse:
185 IS_CHEAT(this, imp, 0, 0);
186 CheatCommand(this, tokenize_console("give all"));
187 break; // already counted as cheat
188 case CHIMPULSE_SPEEDRUN.impulse:
189 if(!autocvar_g_allow_checkpoints)
190 IS_CHEAT(this, imp, 0, 0);
193 this.speedrunning = true;
194 tracebox(this.personal.origin, this.mins, this.maxs, this.personal.origin, MOVE_WORLDONLY, this);
197 sprint(this, "Cannot move there, cheater - only waypoints set using g_waypointsprite_personal work\n");
201 // Abort speedrun, teleport back
202 setorigin(this, this.personal.origin);
203 this.oldvelocity = this.velocity = this.personal.velocity;
204 this.angles = this.personal.v_angle;
205 this.fixangle = true;
207 MUTATOR_CALLHOOK(AbortSpeedrun, this);
210 this.ammo_rockets = this.personal.ammo_rockets;
211 this.ammo_nails = this.personal.ammo_nails;
212 this.ammo_cells = this.personal.ammo_cells;
213 this.ammo_plasma = this.personal.ammo_plasma;
214 this.ammo_shells = this.personal.ammo_shells;
215 this.ammo_fuel = this.personal.ammo_fuel;
216 this.health = this.personal.health;
217 this.armorvalue = this.personal.armorvalue;
218 this.weapons = this.personal.weapons;
219 this.items = this.personal.items;
220 this.pauserotarmor_finished = time + this.personal.pauserotarmor_finished - this.personal.teleport_time;
221 this.pauserothealth_finished = time + this.personal.pauserothealth_finished - this.personal.teleport_time;
222 this.pauserotfuel_finished = time + this.personal.pauserotfuel_finished - this.personal.teleport_time;
223 this.pauseregen_finished = time + this.personal.pauseregen_finished - this.personal.teleport_time;
224 this.strength_finished = time + this.personal.strength_finished - this.personal.teleport_time;
225 this.invincible_finished = time + this.personal.invincible_finished - this.personal.teleport_time;
227 if(!autocvar_g_allow_checkpoints)
232 sprint(this, "UR DEAD AHAHAH))\n");
234 sprint(this, "No waypoint set, cheater (use g_waypointsprite_personal to set one)\n");
236 case CHIMPULSE_TELEPORT.impulse:
237 IS_CHEAT(this, imp, 0, 0);
238 if(this.move_movetype == MOVETYPE_NOCLIP)
240 e = find(NULL, classname, "info_autoscreenshot");
243 sprint(this, "Emergency teleport used info_autoscreenshot location\n");
244 setorigin(this, e.origin);
245 this.angles = e.angles;
247 // should we? this.angles_x = -this.angles_x;
248 this.fixangle = true;
249 this.velocity = '0 0 0';
254 if(MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((gamestart_sv_cheats < 2) ? 100 : 100000), 384, 384))
256 sprint(this, "Emergency teleport used random location\n");
257 this.angles_x = -this.angles.x;
258 this.fixangle = true;
259 this.velocity = '0 0 0';
263 sprint(this, "Emergency teleport could not find a good location, forget it!\n");
265 case CHIMPULSE_R00T.impulse:
266 IS_CHEAT(this, imp, 0, 0);
267 RandomSelection_Init();
268 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it) && DIFF_TEAM(it, this), LAMBDA(RandomSelection_AddEnt(it, 1, 1)));
269 if(RandomSelection_chosen_ent)
270 e = RandomSelection_chosen_ent;
274 Send_Effect(EFFECT_ROCKET_EXPLODE, e.origin, '0 0 0', 1);
275 sound(e, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
278 setorigin(e2, e.origin);
279 RadiusDamage(e2, this, 1000, 0, 128, NULL, NULL, 500, DEATH_CHEAT.m_id, e);
282 LOG_INFO("404 Sportsmanship not found.\n");
287 END_CHEAT_FUNCTION();
290 void DragBox_Think(entity this);
292 float CheatCommand(entity this, int argc)
294 BEGIN_CHEAT_FUNCTION();
302 case "pointparticles":
303 IS_CHEAT(this, 0, argc, 0);
308 // origin (0..1, on crosshair line)
312 crosshair_trace(this);
313 start = (1-f) * this.origin + f * trace_endpos;
316 Send_Effect_(argv(1), start, end, f);
320 sprint(this, "Usage: sv_cheats 1; restart; cmd pointparticles effectname position(0..1) velocityvector multiplier\n");
322 case "trailparticles":
323 IS_CHEAT(this, 0, argc, 0);
328 effectnum = _particleeffectnum(argv(1));
329 W_SetupShot(this, weaponentities[0], false, false, SND_Null, CH_WEAPON_A, 0);
330 traceline(w_shotorg, w_shotorg + w_shotdir * max_shot_distance, MOVE_NORMAL, this);
331 __trailparticles(this, effectnum, w_shotorg, trace_endpos);
335 sprint(this, "Usage: sv_cheats 1; restart; cmd trailparticles effectname\n");
338 IS_CHEAT(this, 0, argc, 0);
344 W_SetupShot(this, weaponentities[0], false, false, SND_Null, CH_WEAPON_A, 0);
345 traceline(w_shotorg, w_shotorg + w_shotdir * 2048, MOVE_NORMAL, this);
346 if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) || trace_fraction == 1)
348 sprint(this, "cannot make stuff there (bad surface)\n");
353 e.model = strzone(argv(1));
354 e.mdl = "rocket_explode";
356 setorigin(e, trace_endpos);
357 e.effects = EF_NOMODELFLAGS;
360 e.angles = fixedvectoangles2(trace_plane_normal, v_forward);
361 e.angles = AnglesTransform_ApplyToAngles(e.angles, '-90 0 0'); // so unrotated models work
363 spawnfunc_func_breakable(e);
367 tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_NORMAL, e);
371 sprint(this, "cannot make stuff there (no space)\n");
381 sprint(this, "Usage: sv_cheats 1; restart; cmd make models/... 0/1/2\n");
384 IS_CHEAT(this, 0, argc, 0);
387 race_ImposePenaltyTime(this, stof(argv(1)), argv(2));
391 sprint(this, "Usage: sv_cheats 1; restart; cmd penalty 5.0 AHAHAHAHAHAHAH))\n");
393 case "dragbox_spawn": {
394 IS_CHEAT(this, 0, argc, 0);
395 entity e = new(dragbox_box);
396 setthink(e, DragBox_Think);
398 e.solid = -1; // black
399 setmodel(e, MDL_Null); // network it
401 e.cnt = stof(argv(1));
403 e.cnt = max(0, drag_lastcnt);
405 e.aiment = new(dragbox_corner_1);
407 setmodel(e.aiment, MDL_MARKER);
409 setsize(e.aiment, '0 0 0', '0 0 0');
411 setorigin(e.aiment, stov(argv(2)));
414 crosshair_trace(this);
415 setorigin(e.aiment, trace_endpos);
418 e.enemy = new(dragbox_corner_2);
420 setmodel(e.enemy, MDL_MARKER);
422 setsize(e.enemy, '0 0 0', '0 0 0');
423 end = normalize(this.origin + this.view_ofs - e.aiment.origin);
424 end.x = (end.x > 0) * 2 - 1;
425 end.y = (end.y > 0) * 2 - 1;
426 end.z = (end.z > 0) * 2 - 1;
428 setorigin(e.enemy, stov(argv(3)));
430 setorigin(e.enemy, e.aiment.origin + 32 * end);
432 e.killindicator = new(drag_digit);
433 e.killindicator.owner = e;
434 setattachment(e.killindicator, e, "");
435 setorigin(e.killindicator, '0 0 -8');
436 e.killindicator.killindicator = new(drag_digit);
437 e.killindicator.killindicator.owner = e;
438 setattachment(e.killindicator.killindicator, e, "");
439 setorigin(e.killindicator.killindicator, '0 0 8');
443 case "dragpoint_spawn": {
444 IS_CHEAT(this, 0, argc, 0);
445 entity e = new(dragpoint);
446 setthink(e, DragBox_Think);
448 e.solid = 0; // nothing special
449 setmodel(e, MDL_MARKER);
450 setsize(e, STAT(PL_MIN, this), STAT(PL_MAX, this));
453 e.cnt = stof(argv(1));
455 e.cnt = drag_lastcnt;
457 setorigin(e, stov(argv(2)));
460 crosshair_trace(this);
461 setorigin(e, trace_endpos + normalize(this.origin + this.view_ofs - trace_endpos));
462 move_out_of_solid(e);
465 e.killindicator = new(drag_digit);
466 e.killindicator.owner = e;
467 setattachment(e.killindicator, e, "");
468 setorigin(e.killindicator, '0 0 40');
469 e.killindicator.killindicator = new(drag_digit);
470 e.killindicator.killindicator.owner = e;
471 setattachment(e.killindicator.killindicator, e, "");
472 setorigin(e.killindicator.killindicator, '0 0 56');
477 IS_CHEAT(this, 0, argc, 0);
478 RandomSelection_Init();
479 crosshair_trace(this);
480 for(entity e = NULL; (e = find(e, classname, "dragbox_box")); )
481 RandomSelection_AddEnt(e, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - trace_endpos));
482 for(entity e = NULL; (e = find(e, classname, "dragpoint")); )
483 RandomSelection_AddEnt(e, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - trace_endpos));
484 if(RandomSelection_chosen_ent)
486 delete(RandomSelection_chosen_ent.killindicator.killindicator);
487 delete(RandomSelection_chosen_ent.killindicator);
488 if(RandomSelection_chosen_ent.aiment)
489 delete(RandomSelection_chosen_ent.aiment);
490 if(RandomSelection_chosen_ent.enemy)
491 delete(RandomSelection_chosen_ent.enemy);
492 delete(RandomSelection_chosen_ent);
497 IS_CHEAT(this, 0, argc, 0);
500 RandomSelection_Init();
501 crosshair_trace(this);
502 for(entity e = NULL; (e = find(e, classname, "dragbox_box")); )
503 RandomSelection_AddEnt(e, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - trace_endpos));
504 for(entity e = NULL; (e = find(e, classname, "dragpoint")); )
505 RandomSelection_AddEnt(e, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - trace_endpos));
506 if(RandomSelection_chosen_ent)
508 if(substring(argv(1), 0, 1) == "*")
509 RandomSelection_chosen_ent.cnt = drag_lastcnt = RandomSelection_chosen_ent.cnt + stof(substring(argv(1), 1, -1));
511 RandomSelection_chosen_ent.cnt = drag_lastcnt = stof(argv(1));
516 sprint(this, "Usage: sv_cheats 1; restart; cmd dragbox_setcnt cnt\n");
519 IS_CHEAT(this, 0, argc, 0);
522 f = fopen(argv(1), FILE_WRITE);
523 fputs(f, "cmd drag_clear\n");
524 for(entity e = NULL; (e = find(e, classname, "dragbox_box")); )
526 fputs(f, strcat("cmd dragbox_spawn ", ftos(e.cnt), " \"", vtos(e.aiment.origin), "\" \"", vtos(e.enemy.origin), "\"\n"));
528 for(entity e = NULL; (e = find(e, classname, "dragpoint")); )
530 fputs(f, strcat("cmd dragpoint_spawn ", ftos(e.cnt), " \"", vtos(e.origin), "\"\n"));
536 sprint(this, "Usage: sv_cheats 1; restart; cmd dragbox_save filename\n");
538 case "drag_saveraceent":
539 IS_CHEAT(this, 0, argc, 0);
542 f = fopen(argv(1), FILE_WRITE);
543 for(entity e = NULL; (e = find(e, classname, "dragbox_box")); )
546 fputs(f, "\"classname\" \"trigger_race_checkpoint\"\n");
547 fputs(f, strcat("\"origin\" \"", ftos(e.absmin.x), " ", ftos(e.absmin.y), " ", ftos(e.absmin.z), "\"\n"));
548 fputs(f, strcat("\"maxs\" \"", ftos(e.absmax.x - e.absmin.x), " ", ftos(e.absmax.y - e.absmin.y), " ", ftos(e.absmax.z - e.absmin.z), "\"\n"));
549 fputs(f, strcat("\"cnt\" \"", ftos(e.cnt), "\"\n"));
550 fputs(f, strcat("\"targetname\" \"checkpoint", ftos(e.cnt), "\"\n"));
553 for(entity e = NULL; (e = find(e, classname, "dragpoint")); )
557 for(entity ent = NULL; (ent = find(ent, classname, "dragbox_box")); )
559 if(e.cnt <= 0 && ent.cnt == 0 || e.cnt == ent.cnt)
561 start = start + ent.origin;
565 start *= 1 / effectnum;
567 fputs(f, "\"classname\" \"info_player_race\"\n");
568 fputs(f, strcat("\"angle\" \"", ftos(vectoyaw(start - e.origin)), "\"\n"));
569 fputs(f, strcat("\"origin\" \"", ftos(e.origin.x), " ", ftos(e.origin.y), " ", ftos(e.origin.z), "\"\n"));
572 fputs(f, "\"target\" \"checkpoint0\"\n");
573 fputs(f, "\"race_place\" \"0\"\n");
577 fputs(f, "\"target\" \"checkpoint0\"\n");
578 fputs(f, "\"race_place\" \"-1\"\n");
582 fputs(f, strcat("\"target\" \"checkpoint", ftos(e.cnt), "\"\n"));
585 // these need race_place
588 for(entity ent = NULL; (ent = find(ent, classname, "dragpoint")); )
591 if(vlen2(ent.origin - start) < vlen2(e.origin - start))
593 else if(vlen2(ent.origin - start) == vlen2(e.origin - start) && etof(ent) < etof(e))
596 fputs(f, strcat("\"race_place\" \"", ftos(effectnum), "\"\n"));
605 sprint(this, "Usage: sv_cheats 1; restart; cmd dragbox_save filename\n");
608 IS_CHEAT(this, 0, argc, 0);
609 for(entity e = NULL; (e = find(e, classname, "dragbox_box")); )
611 for(entity e = NULL; (e = find(e, classname, "dragbox_corner_1")); )
613 for(entity e = NULL; (e = find(e, classname, "dragbox_corner_2")); )
615 for(entity e = NULL; (e = find(e, classname, "dragpoint")); )
617 for(entity e = NULL; (e = find(e, classname, "drag_digit")); )
622 IS_CHEAT(this, 0, argc, 0);
623 BITXOR_ASSIGN(this.flags, FL_GODMODE);
624 if(this.flags & FL_GODMODE)
626 sprint(this, "godmode ON\n");
630 sprint(this, "godmode OFF\n");
633 IS_CHEAT(this, 0, argc, 0);
634 BITXOR_ASSIGN(this.flags, FL_NOTARGET);
635 if(this.flags & FL_NOTARGET)
637 sprint(this, "notarget ON\n");
641 sprint(this, "notarget OFF\n");
644 IS_CHEAT(this, 0, argc, 0);
645 if(this.move_movetype != MOVETYPE_NOCLIP)
647 set_movetype(this, MOVETYPE_NOCLIP);
648 sprint(this, "noclip ON\n");
653 set_movetype(this, MOVETYPE_WALK);
654 sprint(this, "noclip OFF\n");
658 IS_CHEAT(this, 0, argc, 0);
659 if(this.move_movetype != MOVETYPE_FLY)
661 set_movetype(this, MOVETYPE_FLY);
662 sprint(this, "flymode ON\n");
667 set_movetype(this, MOVETYPE_WALK);
668 sprint(this, "flymode OFF\n");
672 IS_CHEAT(this, 0, argc, 0);
673 if(GiveItems(this, 1, argc))
677 IS_CHEAT(this, 0, argc, 0);
680 SUB_UseTargets(e, this, NULL);
685 IS_CHEAT(this, 0, argc, 0);
687 e2.killtarget = argv(1);
688 SUB_UseTargets(e2, this, NULL);
692 case "teleporttotarget":
693 IS_CHEAT(this, 0, argc, 0);
694 entity ent = new(cheattriggerteleport);
695 setorigin(ent, ent.origin);
696 ent.target = argv(1);
697 teleport_findtarget(ent);
700 Simple_TeleportPlayer(ent, this);
707 END_CHEAT_FUNCTION();
710 float Drag(entity this, float force_allow_pick, float ischeat);
711 void Drag_Begin(entity dragger, entity draggee, vector touchpoint);
712 void Drag_Finish(entity dragger);
713 float Drag_IsDraggable(entity draggee);
714 float Drag_MayChangeAngles(entity draggee);
715 void Drag_MoveForward(entity dragger);
716 void Drag_SetSpeed(entity dragger, float s);
717 void Drag_MoveBackward(entity dragger);
718 void Drag_Update(entity dragger);
719 float Drag_CanDrag(entity dragger);
720 float Drag_IsDragging(entity dragger);
721 void Drag_MoveDrag(entity from, entity to);
724 float CheatFrame(entity this)
726 BEGIN_CHEAT_FUNCTION();
728 // Dragging can be used as either a cheat, or a function for some objects. If sv_cheats is active,
729 // the cheat dragging is used (unlimited pickup range and any entity can be carried). If sv_cheats
730 // is disabled, normal dragging is used (limited pickup range and only dragable objects can be carried),
731 // grabbing itself no longer being accounted as cheating.
736 if(this.maycheat || (gamestart_sv_cheats && autocvar_sv_cheats))
738 // use cheat dragging if cheats are enabled
739 //if(Drag_IsDragging(this))
740 //crosshair_trace_plusvisibletriggers(this);
741 Drag(this, true, true);
745 Drag(this, false, false); // execute dragging
750 END_CHEAT_FUNCTION();
761 .float dragspeed; // speed of mouse wheel action
762 .float dragdistance; // distance of dragentity's draglocalvector from view_ofs
763 .vector draglocalvector; // local attachment vector of the dragentity
764 .float draglocalangle;
769 float Drag(entity this, float force_allow_pick, float ischeat)
771 BEGIN_CHEAT_FUNCTION();
773 // returns true when an entity has been picked up
774 // If pick is true, the object can also be picked up if it's not being held already
775 // If pick is false, only keep dragging the object if it's already being held
780 if(Drag_IsDragging(this))
782 if(PHYS_INPUT_BUTTON_DRAG(this))
784 if(this.impulse == 10 || this.impulse == 15 || this.impulse == 18)
786 Drag_MoveForward(this);
789 else if(this.impulse == 12 || this.impulse == 16 || this.impulse == 19)
791 Drag_MoveBackward(this);
794 else if(this.impulse >= 1 && this.impulse <= 9)
796 Drag_SetSpeed(this, this.impulse - 1);
798 else if(this.impulse == 14)
800 Drag_SetSpeed(this, 9);
813 if(Drag_CanDrag(this))
814 if(PHYS_INPUT_BUTTON_DRAG(this))
816 crosshair_trace_plusvisibletriggers(this);
817 entity e = trace_ent;
818 float pick = force_allow_pick;
821 // pick is true if the object can be picked up. While an object is being carried, the Drag() function
822 // must execute for it either way, otherwise it would cause bugs if it went out of the player's trace.
823 // This also makes sure that an object can only pe picked up if in range, but does not get dropped if
824 // it goes out of range while slinging it around.
826 if(vdist(this.origin - e.origin, <=, autocvar_g_grab_range))
830 case 0: // can't grab
832 case 1: // owner can grab
833 if(e.owner == this || e.realowner == this)
836 case 2: // owner and team mates can grab
837 if(SAME_TEAM(e.owner, this) || SAME_TEAM(e.realowner, this) || e.team == this.team)
840 case 3: // anyone can grab
850 if(Drag_IsDraggable(e))
853 IS_CHEAT(this, 0, 0, CHRAME_DRAG);
855 Drag_Finish(e.draggedby);
857 detach_sameorigin(e);
858 Drag_Begin(this, e, trace_endpos);
870 void Drag_Begin(entity dragger, entity draggee, vector touchpoint)
874 draggee.dragmovetype = draggee.move_movetype;
875 draggee.draggravity = draggee.gravity;
876 set_movetype(draggee, MOVETYPE_WALK);
877 draggee.gravity = 0.00001;
878 UNSET_ONGROUND(draggee);
879 draggee.draggedby = dragger;
881 dragger.dragentity = draggee;
883 dragger.dragdistance = vlen(touchpoint - dragger.origin - dragger.view_ofs);
884 dragger.draglocalangle = draggee.angles.y - dragger.v_angle.y;
885 touchpoint = touchpoint - gettaginfo(draggee, 0);
886 tagscale = pow(vlen(v_forward), -2);
887 dragger.draglocalvector_x = touchpoint * v_forward * tagscale;
888 dragger.draglocalvector_y = touchpoint * v_right * tagscale;
889 dragger.draglocalvector_z = touchpoint * v_up * tagscale;
891 dragger.dragspeed = 64;
894 void Drag_Finish(entity dragger)
897 draggee = dragger.dragentity;
899 dragger.dragentity = NULL;
900 draggee.draggedby = NULL;
901 set_movetype(draggee, draggee.dragmovetype);
902 draggee.gravity = draggee.draggravity;
904 switch(draggee.move_movetype)
909 case MOVETYPE_FLYMISSILE:
910 case MOVETYPE_BOUNCE:
911 case MOVETYPE_BOUNCEMISSILE:
912 case MOVETYPE_PHYSICS:
915 draggee.velocity = '0 0 0';
919 if((draggee.flags & FL_ITEM) && (vdist(draggee.velocity, <, 32)))
921 draggee.velocity = '0 0 0';
922 SET_ONGROUND(draggee); // floating items are FUN
926 float Drag_IsDraggable(entity draggee)
928 // TODO add more checks for bad stuff here
931 if(draggee.classname == "func_bobbing")
933 if(draggee.classname == "door") // FIXME find out why these must be excluded, or work around the problem (trying to drag these causes like 4 fps)
935 if(draggee.classname == "plat")
937 if(draggee.classname == "func_button")
939 // if(draggee.model == "")
943 if(IS_OBSERVER(draggee))
945 if(draggee.classname == "exteriorweaponentity")
947 if(draggee.classname == "weaponentity")
953 float Drag_MayChangeAngles(entity draggee)
955 // TODO add more checks for bad stuff here
956 if(substring(draggee.model, 0, 1) == "*")
961 void Drag_MoveForward(entity dragger)
963 dragger.dragdistance += dragger.dragspeed;
966 void Drag_SetSpeed(entity dragger, float s)
968 dragger.dragspeed = pow(2, s);
971 void Drag_MoveBackward(entity dragger)
973 dragger.dragdistance = max(0, dragger.dragdistance - dragger.dragspeed);
976 void Drag_Update(entity dragger)
978 vector curorigin, neworigin, goodvelocity;
982 draggee = dragger.dragentity;
983 UNSET_ONGROUND(draggee);
985 curorigin = gettaginfo(draggee, 0);
986 curorigin = curorigin + v_forward * dragger.draglocalvector.x + v_right * dragger.draglocalvector.y + v_up * dragger.draglocalvector.z;
987 makevectors(dragger.v_angle);
988 neworigin = dragger.origin + dragger.view_ofs + v_forward * dragger.dragdistance;
989 goodvelocity = (neworigin - curorigin) * (1 / frametime);
991 while(draggee.angles.y - dragger.v_angle.y - dragger.draglocalangle > 180)
992 dragger.draglocalangle += 360;
993 while(draggee.angles.y - dragger.v_angle.y - dragger.draglocalangle <= -180)
994 dragger.draglocalangle -= 360;
996 f = min(frametime * 10, 1);
997 draggee.velocity = draggee.velocity * (1 - f) + goodvelocity * f;
999 if(Drag_MayChangeAngles(draggee))
1000 draggee.angles_y = draggee.angles.y * (1 - f) + (dragger.v_angle.y + dragger.draglocalangle) * f;
1002 draggee.ltime = max(servertime + serverframetime, draggee.ltime); // fixes func_train breakage
1004 vector vecs = '0 0 0';
1005 .entity weaponentity = weaponentities[0]; // TODO: unhardcode
1006 if(dragger.(weaponentity).movedir.x > 0)
1007 vecs = dragger.(weaponentity).movedir;
1009 vector dv = v_right * -vecs_y + v_up * vecs_z;
1011 te_lightning1(draggee, dragger.origin + dragger.view_ofs + dv, curorigin);
1014 float Drag_CanDrag(entity dragger)
1016 return (!IS_DEAD(dragger)) || (IS_PLAYER(dragger));
1019 float Drag_IsDragging(entity dragger)
1021 if(!dragger.dragentity)
1023 if(wasfreed(dragger.dragentity) || dragger.dragentity.draggedby != dragger)
1025 dragger.dragentity = NULL;
1028 if(!Drag_CanDrag(dragger) || !Drag_IsDraggable(dragger.dragentity))
1030 Drag_Finish(dragger);
1036 void Drag_MoveDrag(entity from, entity to)
1040 to.draggedby = from.draggedby;
1041 to.draggedby.dragentity = to;
1042 from.draggedby = NULL;
1046 void DragBox_Think(entity this)
1048 if(this.aiment && this.enemy)
1050 this.origin_x = (this.aiment.origin.x + this.enemy.origin.x) * 0.5;
1051 this.origin_y = (this.aiment.origin.y + this.enemy.origin.y) * 0.5;
1052 this.origin_z = (this.aiment.origin.z + this.enemy.origin.z) * 0.5;
1053 this.maxs_x = fabs(this.aiment.origin.x - this.enemy.origin.x) * 0.5;
1054 this.maxs_y = fabs(this.aiment.origin.y - this.enemy.origin.y) * 0.5;
1055 this.maxs_z = fabs(this.aiment.origin.z - this.enemy.origin.z) * 0.5;
1056 this.mins = -1 * this.maxs;
1057 setorigin(this, this.origin);
1058 setsize(this, this.mins, this.maxs); // link edict
1061 if(this.cnt == -1) // actually race_place -1
1063 // show "10 10" for qualifying spawns
1064 setmodel(this.killindicator, MDL_NUM(10));
1065 setmodel(this.killindicator.killindicator, MDL_NUM(10));
1067 else if(this.cnt == -2) // actually race_place 0
1069 // show "10 0" for loser spawns
1070 setmodel(this.killindicator, MDL_NUM(10));
1071 setmodel(this.killindicator.killindicator, MDL_NUM(0));
1075 setmodel(this.killindicator, MDL_NUM(this.cnt % 10));
1076 setmodel(this.killindicator.killindicator, MDL_NUM(floor(this.cnt / 10)));
1079 this.nextthink = time;