alias addtolist "qc_cmd_svmenu addtolist ${* ?}" // Add a string to a cvar
alias dumpcommands "qc_cmd_svmenu dumpcommands ${* ?}" // Dump all commands on the program to *_cmd_dump.txt
alias maplist "qc_cmd_svmenu maplist ${* ?}" // Automatic control of maplist
+alias nextframe "qc_cmd_svmenu nextframe ${* ?}" // do something next frame
alias removefromlist "qc_cmd_svmenu removefromlist ${* ?}" // Remove a string from a cvar
alias rpn "qc_cmd_svmenu rpn ${* ?}" // RPN calculator
//alias settemp "qc_cmd_svmenu settemp ${* ?}" // Temporarily set a value to a cvar which is restored later
// rcon server commands
// ======================
rcon_secure 1
-set rcon_restricted_commands "restart fraglimit chmap gotomap endmatch reducematchtime extendmatchtime allready kick kickban \"sv_cmd bans\" \"sv_cmd unban *\" status \"sv_cmd teamstatus\" movetoauto movetored movetoblue movetoyellow movetopink"
\ No newline at end of file
+set rcon_restricted_commands "restart fraglimit chmap gotomap endmatch reducematchtime extendmatchtime allready kick kickban \"sv_cmd bans\" \"sv_cmd unban *\" status \"sv_cmd teamstatus\" movetoauto movetored movetoblue movetoyellow movetopink"
seta cl_forcemyplayermodel "" "set to the model file name you want to show yourself as (requires server to have sv_use_csqc_players 1; does not affect how enemies look with cl_forceplayermodels)"
seta cl_forcemyplayerskin 0 "set to the skin number you want to show yourself as (requires server to have sv_use_csqc_players 1; does not affect how enemies look with cl_forceplayermodels)"
seta cl_forcemyplayercolors 0 "set to the color value (encoding is same as _cl_color) for your own player model (requires server to have sv_use_csqc_players 1, and is ignored in teamplay; does not affect how enemies look with cl_forceplayermodels)"
+seta cl_predictionerrorcompensation 0 "try to compensate for prediction errors and reduce preceived lag (requires server to have sv_use_csqc_players 1)"
// debug cvars for keyhunt attaching
set _angles "0 0 0"
{
if not(isdemo())
{
- localcmd("\n_cl_hook_gamestart ", MapInfo_Type_ToString(gametype), "\n");
+ if(!(calledhooks & HOOK_START))
+ localcmd("\n_cl_hook_gamestart ", MapInfo_Type_ToString(gametype), "\n");
calledhooks |= HOOK_START;
}
}
vector vf_size, vf_min;
float a;
+ execute_next_frame();
+
++framecount;
hud = getstati(STAT_HUD);
// Return value should be 1 if CSQC handled the command, otherwise return 0 to have the engine handle it.
return FALSE;
-}
\ No newline at end of file
+}
}
}
+void GenericCommand_nextframe(float request, float arguments, string command)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ queue_to_execute_next_frame(substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
+ return;
+ }
+
+ default:
+ case CMD_REQUEST_USAGE:
+ {
+ print(strcat("\nUsage:^3 ", GetProgramCommandPrefix(), " nextframe command..."));
+ print(" Where command will be executed next frame of this VM\n");
+ return;
+ }
+ }
+}
+
void GenericCommand_removefromlist(float request, float argc)
{
switch(request)
GENERIC_COMMAND("addtolist", GenericCommand_addtolist(request, arguments), "Add a string to a cvar") \
GENERIC_COMMAND("dumpcommands", GenericCommand_dumpcommands(request), "Dump all commands on the program to *_cmd_dump.txt") \
GENERIC_COMMAND("maplist", GenericCommand_maplist(request, arguments), "Automatic control of maplist") \
+ GENERIC_COMMAND("nextframe", GenericCommand_nextframe(request, arguments, command), "Execute the given command next frame of this VM") \
GENERIC_COMMAND("removefromlist", GenericCommand_removefromlist(request, arguments), "Remove a string from a cvar") \
GENERIC_COMMAND("rpn", GenericCommand_rpn(request, arguments, command), "RPN calculator") \
GENERIC_COMMAND("settemp", GenericCommand_settemp(request, arguments), "Temporarily set a value to a cvar which is restored later") \
// used by common/command/generic.qc:GenericCommand_dumpcommands to list all commands into a .txt file
#define CMD_Write(s) fputs(fh, s)
#define CMD_Write_Alias(execute,command,description) CMD_Write(sprintf("alias %-20s \"%-13s %-20s ${* ?}\" // %s\n", command, execute, command, description))
-void GenericCommand_macro_write_aliases(float fh);
\ No newline at end of file
+void GenericCommand_macro_write_aliases(float fh);
e.skeleton_bones_index = e.modelindex;
}
#endif
+
+string to_execute_next_frame;
+void execute_next_frame()
+{
+ if(to_execute_next_frame)
+ {
+ localcmd("\n", to_execute_next_frame, "\n");
+ strunzone(to_execute_next_frame);
+ to_execute_next_frame = string_null;
+ }
+}
+void queue_to_execute_next_frame(string s)
+{
+ if(to_execute_next_frame)
+ {
+ s = strcat(s, "\n", to_execute_next_frame);
+ strunzone(to_execute_next_frame);
+ }
+ to_execute_next_frame = strzone(s);
+}
#ifdef CSQC
float ReadApproxPastTime();
#endif
+
+// execute-stuff-next-frame subsystem
+void execute_next_frame();
+void queue_to_execute_next_frame(string s);
vector csqcplayer_origin, csqcplayer_velocity;
float csqcplayer_sequence, player_pmflags;
float csqcplayer_moveframe;
-vector csqcplayer_predictionerror;
+vector csqcplayer_predictionerroro;
+vector csqcplayer_predictionerrorv;
float csqcplayer_predictionerrortime;
+float csqcplayer_predictionerrorfactor;
-vector CSQCPlayer_GetPredictionError()
+vector CSQCPlayer_GetPredictionErrorO()
{
- if(!autocvar_cl_predictionerrorcompensation)
+ if(time >= csqcplayer_predictionerrortime)
+ return '0 0 0';
+ return csqcplayer_predictionerroro * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
+}
+
+vector CSQCPlayer_GetPredictionErrorV()
+{
+ if(time >= csqcplayer_predictionerrortime)
return '0 0 0';
- if(time < csqcplayer_predictionerrortime)
- return csqcplayer_predictionerror * (csqcplayer_predictionerrortime - time) * autocvar_cl_predictionerrorcompensation;
- return '0 0 0';
+ return csqcplayer_predictionerrorv * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
}
-void CSQCPlayer_SetPredictionError(vector v)
+void CSQCPlayer_SetPredictionError(vector o, vector v)
{
if(!autocvar_cl_predictionerrorcompensation)
+ {
+ csqcplayer_predictionerrorfactor = 0;
return;
- csqcplayer_predictionerror = (csqcplayer_predictionerrortime - time) * autocvar_cl_predictionerrorcompensation * csqcplayer_predictionerror + v;
- csqcplayer_predictionerrortime = time + 1.0 / autocvar_cl_predictionerrorcompensation;
+ }
+
+ // error too big to compensate, we LIKELY hit a teleport or a
+ // jumppad, or it's a jump time disagreement that'll get fixed
+ // next frame
+ if(vlen(o) > 32 || vlen(v) > 128)
+ return;
+
+ csqcplayer_predictionerroro = CSQCPlayer_GetPredictionErrorO() + o;
+ csqcplayer_predictionerrorv = CSQCPlayer_GetPredictionErrorV() + v;
+ csqcplayer_predictionerrorfactor = autocvar_cl_predictionerrorcompensation / ticrate;
+ csqcplayer_predictionerrortime = time + 1.0 / csqcplayer_predictionerrorfactor;
}
void CSQCPlayer_Unpredict()
csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
}
-void CSQCPlayer_PredictTo(float endframe)
+void CSQCPlayer_PredictTo(float endframe, float apply_error)
{
CSQCPlayer_Unpredict();
+ if(apply_error)
+ {
+ self.origin += CSQCPlayer_GetPredictionErrorO();
+ self.velocity += CSQCPlayer_GetPredictionErrorV();
+ }
CSQCPlayer_SetMinsMaxs();
csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
{
vector o, v;
o = self.origin;
+ v = v0;
csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
- CSQCPlayer_PredictTo(servercommandframe + 1);
- CSQCPlayer_SetPredictionError(o - self.origin);
+ CSQCPlayer_PredictTo(servercommandframe + 1, FALSE);
+ CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v);
self.origin = o;
- self.velocity = v0;
+ self.velocity = v;
// get crouch state from the server
if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
CSQCPlayer_SavePrediction();
}
- CSQCPlayer_PredictTo(clientcommandframe + 1);
+ CSQCPlayer_PredictTo(clientcommandframe + 1, TRUE);
CSQCPlayer_SetMinsMaxs();
float t;
float realFrametime;
+ execute_next_frame();
+
menuMouseMode = cvar("menu_mouse_absolute");
if (anim)
if((++attempting, !CheatsAllowed(i,argc,fr))) \
break
+void spawnfunc_info_autoscreenshot()
+{
+ // empty spawnfunc just so this entity can exist
+}
+
float CheatImpulse(float i)
{
BEGIN_CHEAT_FUNCTION();
break;
case CHIMPULSE_TELEPORT:
IS_CHEAT(i, 0, 0);
+ if(self.movetype == MOVETYPE_NOCLIP)
+ {
+ e = find(world, classname, "info_autoscreenshot");
+ if(e)
+ {
+ sprint(self, "Emergency teleport used info_autoscreenshot location\n");
+ setorigin(self, e.origin);
+ self.angles = e.angles;
+ remove(e);
+ // should we? self.angles_x = -self.angles_x;
+ self.fixangle = TRUE;
+ self.velocity = '0 0 0';
+ DID_CHEAT();
+ break;
+ }
+ }
if(MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((gamestart_sv_cheats >= 2) ? 100000 : 100), 1024, 256))
{
+ sprint(self, "Emergency teleport used random location\n");
self.angles_x = -self.angles_x;
self.fixangle = TRUE;
self.velocity = '0 0 0';
return (get_weaponinfo(wpn)).items & IT_AMMO;
}
+.float savenextthink;
void thrown_wep_think()
{
- self.solid = SOLID_TRIGGER;
self.owner = world;
- SUB_SetFade(self, time + 20, 1);
+ float timeleft = self.savenextthink - time;
+ if(timeleft > 1)
+ SUB_SetFade(self, self.savenextthink - 1, 1);
+ else if(timeleft > 0)
+ SUB_SetFade(self, time, timeleft);
+ else
+ SUB_VanishOrRemove(self);
}
// returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
wep.flags |= FL_TOSSED;
wep.colormap = own.colormap;
+ if(W_WeaponBit(wpn) & WEPBIT_SUPERWEAPONS)
+ {
+ if(own.items & IT_UNLIMITED_SUPERWEAPONS)
+ {
+ wep.superweapons_finished = time + autocvar_g_balance_superweapons_time;
+ }
+ else
+ {
+ float superweapons = 1;
+ for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+ if(own.weapons & WEPBIT_SUPERWEAPONS & W_WeaponBit(i))
+ ++superweapons;
+ if(superweapons <= 1)
+ {
+ wep.superweapons_finished = own.superweapons_finished;
+ own.superweapons_finished = 0;
+ }
+ else
+ {
+ float timeleft = own.superweapons_finished - time;
+ float weptimeleft = timeleft / superweapons;
+ wep.superweapons_finished = time + weptimeleft;
+ own.superweapons_finished -= weptimeleft;
+ }
+ }
+ }
+
wa = W_AmmoItemCode(wpn);
if(wa == 0)
{
return string_null;
wep.glowmod = own.weaponentity_glowmod;
wep.think = thrown_wep_think;
- wep.nextthink = time + 0.5;
+ wep.savenextthink = wep.nextthink;
+ wep.nextthink = min(wep.nextthink, time + 0.5);
+ wep.pickup_anyway = TRUE; // these are ALWAYS pickable
return "";
}
else
}
wep.glowmod = own.weaponentity_glowmod;
wep.think = thrown_wep_think;
- wep.nextthink = time + 0.5;
+ wep.savenextthink = wep.nextthink;
+ wep.nextthink = min(wep.nextthink, time + 0.5);
wep.pickup_anyway = TRUE; // these are ALWAYS pickable
return s;
}
wb = W_WeaponBit(w);
if(!wb)
return 0;
- if(wb & WEPBIT_SUPERWEAPONS) // can't throw a superweapon, they don't work
- return 0;
wa = W_AmmoItemCode(w);
if(start_weapons & wb)
{
{
float argc = tokenize_console(command);
+ // for the mutator hook system
+ cmd_name = strtolower(argv(0));
+ cmd_argc = argc;
+ cmd_string = command;
+
// Guide for working with argc arguments by example:
// argc: 1 - 2 - 3 - 4
// argv: 0 - 1 - 2 - 3
S: "hostname" of the server
C: number of "unpure" cvar changes
U: UDP port number of the server
+ D: duration of the match
P: player ID of an existing player; this also sets the owner for all following "n", "e" and "t" lines (lower case!)
n: nickname of the player (optional)
t: team ID
url_fputs(fh, sprintf("S %s\n", cvar_string("hostname")));
url_fputs(fh, sprintf("C %d\n", cvar_purechanges_count));
url_fputs(fh, sprintf("U %d\n", cvar("port")));
+ url_fputs(fh, sprintf("D %f\n", max(0, time - game_starttime)));
for(p = playerstats_last; (pn = db_get(playerstats_db, sprintf("%s:*", p))) != ""; p = pn)
{
url_fputs(fh, sprintf("P %s\n", p));
entity SelectSpawnPoint (float anypoint);
void StartFrame (void)
{
+ execute_next_frame();
+
remove = remove_unsafely; // not during spawning!
serverprevtime = servertime;
servertime = time;
e.spawnshieldtime = 1;
}
- if (e.strength_finished || e.invincible_finished)
+ if (e.items & (IT_STRENGTH | IT_INVINCIBLE))
e.effects |= EF_ADDITIVE | EF_FULLBRIGHT;
if (autocvar_g_nodepthtestitems)
e.effects |= EF_NODEPTHTEST;
if (self.owner == other)
return;
+ if (self.classname == "droppedweapon")
+ {
+ self.strength_finished = max(0, self.strength_finished - time);
+ self.invincible_finished = max(0, self.invincible_finished - time);
+ self.superweapons_finished = max(0, self.superweapons_finished - time);
+ }
+
if(!Item_GiveTo(self, other))
- return;
+ {
+ if (self.classname == "droppedweapon")
+ {
+ // undo what we did above
+ self.strength_finished += time;
+ self.invincible_finished += time;
+ self.superweapons_finished += time;
+ return;
+ }
+ }
other.last_pickup = time;
{
Item_Show(self, !self.state);
setorigin (self, self.origin);
- self.think = SUB_Null;
- self.nextthink = 0;
- if((self.flags & FL_POWERUP) | (self.weapons & WEPBIT_SUPERWEAPONS)) // do not spawn powerups initially!
- Item_ScheduleInitialRespawn(self);
+ if(self.classname != "droppedweapon")
+ {
+ self.think = SUB_Null;
+ self.nextthink = 0;
+
+ if((self.flags & FL_POWERUP) | (self.weapons & WEPBIT_SUPERWEAPONS)) // do not spawn powerups initially!
+ Item_ScheduleInitialRespawn(self);
+ }
}
void Item_FindTeam()
float commodity_pickupevalfunc(entity player, entity item)
{
- float c, i, need_shells, need_nails, need_rockets, need_cells;
+ float c, i, need_shells, need_nails, need_rockets, need_cells, need_fuel;
entity wi;
c = 0;
need_rockets = TRUE;
else if(wi.items & IT_CELLS)
need_cells = TRUE;
+ else if(wi.items & IT_FUEL)
+ need_cells = TRUE;
}
// TODO: figure out if the player even has the weapon this ammo is for?
if (item.ammo_cells)
if (player.ammo_cells < g_pickup_cells_max)
c = c + max(0, 1 - player.ammo_cells / g_pickup_cells_max);
+ if (need_fuel)
+ if (item.ammo_fuel)
+ if (player.ammo_fuel < g_pickup_fuel_max)
+ c = c + max(0, 1 - player.ammo_fuel / g_pickup_fuel_max);
if (item.armorvalue)
if (player.armorvalue < item.max_armorvalue)
c = c + max(0, 1 - player.armorvalue / item.max_armorvalue);
self.reset = SUB_Remove;
// it's a dropped weapon
self.movetype = MOVETYPE_TOSS;
+
// Savage: remove thrown items after a certain period of time ("garbage collection")
self.think = RemoveItem;
- self.nextthink = time + 60;
+ self.nextthink = time + 20;
+
+ if(self.strength_finished || self.invincible_finished || self.superweapons_finished)
+ /*
+ if(self.items == 0)
+ if(self.weapons == (self.weapons & WEPBIT_SUPERWEAPONS)) // only superweapons
+ if(self.ammo_nails == 0)
+ if(self.ammo_cells == 0)
+ if(self.ammo_rockets == 0)
+ if(self.ammo_shells == 0)
+ if(self.ammo_fuel == 0)
+ if(self.health == 0)
+ if(self.armorvalue == 0)
+ */
+ {
+ // if item is worthless after a timer, have it expire then
+ self.nextthink = max(self.strength_finished, self.invincible_finished, self.superweapons_finished);
+ }
+
// don't drop if in a NODROP zone (such as lava)
traceline(self.origin, self.origin, MOVE_NORMAL, self);
if (trace_dpstartcontents & DPCONTENTS_NODROP)
self.colormap = 1024; // color shirt=0 pants=0 grey
}
- Item_Show(self, 1);
self.state = 0;
if(self.team)
{
remove (self);
}
-entity FindLaserTarget(entity e, float dist_variance, float dot_variance)
-{
- entity head, selected;
- vector dir;
- float dist, maxdist,// bestdist,
- dot,// bestdot,
- points, bestpoints;
- //bestdist = 9999;
- //bestdot = -2;
- bestpoints = 0;
- maxdist = 800;
- selected = world;
-
- makevectors(e.angles);
-
- head = find(world, classname, "laser_target");
- while(head)
- {
- points = 0;
- dir = normalize(head.origin - self.origin);
- dot = dir * v_forward;
- dist = vlen(head.origin - self.origin);
- if(dist > maxdist)
- dist = maxdist;
-
- // gain points for being in front
- points = points + ((dot+1)*0.5) * 500
- * (1 + crandom()*dot_variance);
- // gain points for being close away
- points = points + (1 - dist/maxdist) * 1000
- * (1 + crandom()*dot_variance);
-
- traceline(e.origin, head.origin, TRUE, self);
- if(trace_fraction < 1)
- {
- points = 0;
- }
-
- if(points > bestpoints)//random() > 0.5)//
- {
- bestpoints = points;
- selected = head;
- }
-
- head = find(head, classname, "laser_target");
- }
-
- //bprint(selected.realowner.netname);
- //bprint("\n");
- return selected;
-}
-
void W_Rocket_RemoteExplode()
{
if(self.realowner.deadflag == DEAD_NO)
{
if(thisdir * goaldir > maxturn_cos)
return goaldir;
+ if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
+ return thisdir; // refuse to guide (better than letting a numerical error happen)
float f, m2;
vector v;
// solve:
v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
return normalize(thisdir + goaldir * v_y); // the larger solution!
}
+// assume thisdir == -goaldir:
+// f == -1
+// v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
+// (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
+// x^2 - 2 * x + 1 = 0
+// (x - 1)^2 = 0
+// x = 1
+// normalize(thisdir + goaldir)
+// normalize(0)
void W_Rocket_Think (void)
{