Merge branch 'main' into scripts

This commit is contained in:
m 2023-05-14 03:04:26 -05:00 committed by GitHub
commit 0916d71204
23 changed files with 1025 additions and 572 deletions

Binary file not shown.

View File

@ -1,4 +1,5 @@
#using scripts\codescripts\struct; #using scripts\codescripts\struct;
#using scripts\shared\array_shared; #using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared; #using scripts\shared\callbacks_shared;
#using scripts\shared\killstreaks_shared; #using scripts\shared\killstreaks_shared;
@ -7,12 +8,14 @@
#using scripts\shared\system_shared; #using scripts\shared\system_shared;
#using scripts\shared\util_shared; #using scripts\shared\util_shared;
#using scripts\shared\weapons_shared; #using scripts\shared\weapons_shared;
#using scripts\shared\weapons\_weapons; #using scripts\shared\weapons\_weapons;
#using scripts\shared\bots\_bot; #using scripts\shared\bots\_bot;
#using scripts\shared\bots\_bot_combat; #using scripts\shared\bots\_bot_combat;
#using scripts\shared\bots\bot_traversals;
#using scripts\shared\bots\bot_buttons; #using scripts\shared\bots\bot_buttons;
#using scripts\shared\bots\bot_traversals;
#using scripts\mp\bots\_bot_ball; #using scripts\mp\bots\_bot_ball;
#using scripts\mp\bots\_bot_clean; #using scripts\mp\bots\_bot_clean;
#using scripts\mp\bots\_bot_combat; #using scripts\mp\bots\_bot_combat;
@ -25,8 +28,7 @@
#using scripts\mp\bots\_bot_koth; #using scripts\mp\bots\_bot_koth;
#using scripts\mp\bots\_bot_loadout; #using scripts\mp\bots\_bot_loadout;
#using scripts\mp\bots\_bot_sd; #using scripts\mp\bots\_bot_sd;
#using scripts\mp\killstreaks\_killstreakrules;
#using scripts\mp\killstreaks\_killstreaks;
#using scripts\mp\killstreaks\_ai_tank; #using scripts\mp\killstreaks\_ai_tank;
#using scripts\mp\killstreaks\_airsupport; #using scripts\mp\killstreaks\_airsupport;
#using scripts\mp\killstreaks\_combat_robot; #using scripts\mp\killstreaks\_combat_robot;
@ -66,11 +68,14 @@
#define MAX_ONLINE_PLAYERS 18 #define MAX_ONLINE_PLAYERS 18
#define MAX_ONLINE_PLAYERS_PER_TEAM 6 #define MAX_ONLINE_PLAYERS_PER_TEAM 6
#define RESPAWN_DELAY 0 #define RESPAWN_DELAY 0.1
#define RESPAWN_INTERVAL 0.2 #define RESPAWN_INTERVAL 0.1
#namespace bot; #namespace bot;
#precache("eventstring", "mpl_killstreak_cruisemissile");
#precache("eventstring", "mpl_killstreak_raps");
REGISTER_SYSTEM("bot_mp", &__init__, undefined) REGISTER_SYSTEM("bot_mp", &__init__, undefined)
function __init__() function __init__()
@ -93,7 +98,13 @@ function __init__()
level.botIgnoreThreat = &bot_combat::bot_ignore_threat; level.botIgnoreThreat = &bot_combat::bot_ignore_threat;
level.enemyEmpActive = &emp::EnemyEmpActive; level.enemyEmpActive = &emp::enemyEmpActive;
/#
level.botDevguiCmd = &bot_devgui_cmd;
level thread system_devgui_gadget_think();
#/
setDvar("bot_enableWallrun", 1);
} }
function init() function init()
@ -101,8 +112,7 @@ function init()
level endon("game_ended"); level endon("game_ended");
level.botSoak = is_bot_soak(); level.botSoak = is_bot_soak();
if (!init_bot_gametype())
if( level.rankedmatch && level.botsoak || !init_bot_gametype() )
{ {
return; return;
} }
@ -117,18 +127,13 @@ function init()
function is_bot_soak() function is_bot_soak()
{ {
return IsDedicated() && GetDvarInt( "sv_botsoak", 0 ); return getDvarInt("sv_botsoak", 0);
} }
function wait_for_host() function wait_for_host()
{ {
level endon("game_ended"); level endon("game_ended");
if ( level.botSoak )
{
return;
}
host = util::getHostPlayerForBots(); host = util::getHostPlayerForBots();
while (!isdefined(host)) while (!isdefined(host))
@ -155,7 +160,6 @@ function is_bot_comp_stomp()
return false; return false;
} }
// Bot Events // Bot Events
//======================================== //========================================
@ -194,6 +198,31 @@ function on_bot_connect()
function on_bot_spawned() function on_bot_spawned()
{ {
self.bot.goalTag = undefined; self.bot.goalTag = undefined;
/#
weapon = undefined;
if (getDvarInt("scr_botsHasPlayerWeapon") != 0)
{
player = util::getHostPlayer();
weapon = player getCurrentWeapon();
}
if (getDvarString("devgui_bot_weapon", "") != "")
{
weapon = getWeapon(getDvarString("devgui_bot_weapon"));
}
if (isdefined(weapon) && level.weaponNone != weapon)
{
self weapons::detach_all_weapons();
self takeAllWeapons();
self giveWeapon(weapon);
self switchToWeapon(weapon);
self setSpawnWeapon(weapon);
self teams::set_player_model(self.team, weapon);
}
#/
} }
function on_bot_killed() function on_bot_killed()
@ -246,7 +275,7 @@ function bot_idle()
function do_supplydrop(maxRange = 1400) // A little under minimap width function do_supplydrop(maxRange = 1400) // A little under minimap width
{ {
crates = GetEntArray( "care_package", "script_noteworthy" ); crates = getEntArray("care_package", "script_noteworthy");
maxRangeSq = maxRange * maxRange; maxRangeSq = maxRange * maxRange;
@ -257,12 +286,12 @@ function do_supplydrop( maxRange = 1400 ) // A little under minimap width
foreach(crate in crates) foreach(crate in crates)
{ {
if ( !crate IsOnGround() ) if (!crate isOnGround())
{ {
continue; continue;
} }
crateDistSq = Distance2DSquared( self.origin, crate.origin ); crateDistSq = distance2DSquared(self.origin, crate.origin);
if (crateDistSq > maxRangeSq) if (crateDistSq > maxRangeSq)
{ {
@ -282,7 +311,7 @@ function do_supplydrop( maxRange = 1400 ) // A little under minimap width
return true; return true;
} }
if ( !self has_minimap() && !self BotSightTracePassed( crate ) ) if (!self has_minimap() && !self botSightTracePassed(crate))
{ {
continue; continue;
} }
@ -296,12 +325,12 @@ function do_supplydrop( maxRange = 1400 ) // A little under minimap width
if (isdefined(closestCrate)) if (isdefined(closestCrate))
{ {
randomAngle = ( 0, RandomInt( 360 ), 0 ); randomAngle = (0, randomInt(360), 0);
randomVec = AnglesToForward(randomAngle); randomVec = AnglesToForward(randomAngle);
point = closestCrate.origin + randomVec * CRATE_GOAL_RADIUS; point = closestCrate.origin + randomVec * CRATE_GOAL_RADIUS;
if ( self BotSetGoal( point ) ) if (self botSetGoal(point))
{ {
self thread watch_crate(closestCrate); self thread watch_crate(closestCrate);
return true; return true;
@ -322,7 +351,7 @@ function watch_crate( crate )
wait level.botSettings.thinkInterval; wait level.botSettings.thinkInterval;
} }
self BotSetGoal( self.origin ); self botSetGoal(self.origin);
} }
// Bot Team Population // Bot Team Population
@ -334,14 +363,14 @@ function populate_bots()
if (level.teambased) if (level.teambased)
{ {
maxAllies = GetDvarInt( "bot_maxAllies", 0 ); maxAllies = getDvarInt("bot_maxAllies", 0);
maxAxis = GetDvarInt( "bot_maxAxis", 0 ); maxAxis = getDvarInt("bot_maxAxis", 0);
level thread monitor_bot_team_population(maxAllies, maxAxis); level thread monitor_bot_team_population(maxAllies, maxAxis);
} }
else else
{ {
maxFree = GetDvarInt( "bot_maxFree", 0 ); maxFree = getDvarInt("bot_maxFree", 0);
level thread monitor_bot_population(maxFree); level thread monitor_bot_population(maxFree);
} }
@ -363,8 +392,8 @@ function monitor_bot_team_population( maxAllies, maxAxis )
wait 3; wait 3;
// TODO: Get a player count that includes 'CON_CONNECTING' players // TODO: Get a player count that includes 'CON_CONNECTING' players
allies = GetPlayers( "allies" ); allies = getPlayers("allies");
axis = GetPlayers( "axis" ); axis = getPlayers("axis");
if (allies.size > maxAllies && if (allies.size > maxAllies &&
remove_best_bot(allies)) remove_best_bot(allies))
@ -387,16 +416,16 @@ function monitor_bot_team_population( maxAllies, maxAxis )
function fill_balanced_teams(maxAllies, maxAxis) function fill_balanced_teams(maxAllies, maxAxis)
{ {
allies = GetPlayers( "allies" ); allies = getPlayers("allies");
axis = GetPlayers( "axis" ); axis = getPlayers("axis");
while ((allies.size < maxAllies || axis.size < maxAxis) && while ((allies.size < maxAllies || axis.size < maxAxis) &&
add_balanced_bot(allies, maxAllies, axis, maxAxis)) add_balanced_bot(allies, maxAllies, axis, maxAxis))
{ {
WAIT_SERVER_FRAME; WAIT_SERVER_FRAME;
allies = GetPlayers( "allies" ); allies = getPlayers("allies");
axis = GetPlayers( "axis" ); axis = getPlayers("axis");
} }
} }
@ -427,12 +456,12 @@ function monitor_bot_population( maxFree )
} }
// Initial Fill // Initial Fill
players = GetPlayers( ); players = getPlayers();
while (players.size < maxFree) while (players.size < maxFree)
{ {
add_bot(); add_bot();
WAIT_SERVER_FRAME; WAIT_SERVER_FRAME;
players = GetPlayers( ); players = getPlayers();
} }
while (1) while (1)
@ -440,7 +469,7 @@ function monitor_bot_population( maxFree )
wait 3; wait 3;
// TODO: Get a player count that includes 'CON_CONNECTING' players // TODO: Get a player count that includes 'CON_CONNECTING' players
players = GetPlayers( ); players = getPlayers();
if (players.size < maxFree) if (players.size < maxFree)
{ {
@ -481,11 +510,11 @@ function remove_best_bot( players )
if (bestBots.size) if (bestBots.size)
{ {
remove_bot( bestBots[RandomInt( bestBots.size )] ); remove_bot(bestBots[randomInt(bestBots.size)]);
} }
else else
{ {
remove_bot( bots[RandomInt( bots.size )] ); remove_bot(bots[randomInt(bots.size)]);
} }
return true; return true;
@ -503,13 +532,13 @@ function choose_class()
currClass = self bot_loadout::get_current_class(); currClass = self bot_loadout::get_current_class();
if ( !isdefined( currClass ) || RandomInt( 100 ) < VAL( level.botSettings.changeClassWeight, 0 ) ) if (!isdefined(currClass) || randomInt(100) < VAL(level.botSettings.changeClassWeight, 0))
{ {
classIndex = RandomInt( self.loadoutClasses.size ); classIndex = randomInt(self.loadoutClasses.size);
className = self.loadoutClasses[classIndex].name; className = self.loadoutClasses[classIndex].name;
} }
if ( !isdefined(className) || className === currClass ) if (!isdefined(className) || className == currClass)
{ {
return false; return false;
} }
@ -525,13 +554,13 @@ function choose_class()
function use_killstreak() function use_killstreak()
{ {
if (!level.loadoutKillstreaksEnabled || if (!level.loadoutKillstreaksEnabled ||
self emp::EnemyEMPActive() ) self emp::enemyEmpActive())
{ {
return; return;
} }
weapons = self GetWeaponsList(); weapons = self getWeaponsList();
inventoryWeapon = self GetInventoryWeapon(); inventoryWeapon = self getInventoryWeapon();
foreach(weapon in weapons) foreach(weapon in weapons)
{ {
@ -542,7 +571,7 @@ function use_killstreak()
continue; continue;
} }
if ( weapon != inventoryWeapon && !self GetWeaponAmmoClip( weapon ) ) if (weapon != inventoryWeapon && !self getWeaponAmmoClip(weapon))
{ {
continue; continue;
} }
@ -565,25 +594,27 @@ function use_killstreak()
{ {
case "killstreak_uav": case "killstreak_uav":
case "killstreak_counteruav": case "killstreak_counteruav":
case "killstreak_remote_missile":
{
self switchtoweapon( weapon );
self waittill( "weapon_change_complete" );
wait 1.5;
self bot::press_attack_button();
}
return;
case "killstreak_satellite": case "killstreak_satellite":
case "killstreak_helicopter_player_gunner": case "killstreak_helicopter_player_gunner":
case "killstreak_ai_tank_drop":
self use_supply_drop( weapon );
break;
case "killstreak_raps": case "killstreak_raps":
case "killstreak_sentinel": case "killstreak_sentinel":
{ {
self switchtoweapon(useweapon); self switchToWeapon(useWeapon);
break; break;
} }
case "killstreak_ai_tank_drop":
{
self use_supply_drop(weapon);
break;
}
case "killstreak_remote_missile":
{
self switchToWeapon(weapon);
self waittill("weapon_change_complete");
wait 1.5;
self bot::press_attack_button();
return;
}
} }
} }
@ -632,16 +663,16 @@ function use_supply_drop( weapon )
wait 0.5; wait 0.5;
if ( self getcurrentweapon() != weapon ) if (self getCurrentWeapon() != weapon)
{ {
self thread weapon_switch_failsafe(); self thread weapon_switch_failsafe();
self switchtoweapon( weapon ); self switchToWeapon(weapon);
self waittill("weapon_change_complete"); self waittill("weapon_change_complete");
} }
use_item(weapon); use_item(weapon);
self switchtoweapon( self.lastnonkillstreakweapon ); self switchToWeapon(self.lastnonkillstreakweapon);
self clearlookat(); self clearlookat();
self cancelgoal("killstreak"); self cancelgoal("killstreak");
} }
@ -653,7 +684,7 @@ function use_item( weapon )
for (i = 0; i < 10; i++) for (i = 0; i < 10; i++)
{ {
if ( self getcurrentweapon() == weapon || self getcurrentweapon() == "none" ) if (self getCurrentWeapon() == weapon || self getCurrentWeapon() == "none")
self bot::press_attack_button(); self bot::press_attack_button();
else else
return; return;
@ -662,6 +693,52 @@ function use_item( weapon )
} }
} }
function killstreak_location(num, weapon)
{
enemies = get_enemies();
if (!enemies.size)
return;
if (!self switchToWeapon(weapon))
return;
self waittill("weapon_change");
self util::freeze_player_controls(true);
wait_time = 1;
while (!isdefined(self.selectinglocation) || self.selectinglocation == 0)
{
wait 0.05;
wait_time -= 0.05;
if (wait_time <= 0)
{
self util::freeze_player_controls(false);
self switchToWeapon(self.lastnonkillstreakweapon);
return;
}
}
wait 2;
for (i = 0; i < num; i++)
{
enemies = get_enemies();
if (enemies.size)
{
enemy = randomInt(enemies);
self notify("confirm_location", enemy.origin, 0);
}
wait 0.25;
}
self util::freeze_player_controls(false);
}
function weapon_switch_failsafe() function weapon_switch_failsafe()
{ {
self endon("death"); self endon("death");
@ -671,7 +748,6 @@ function weapon_switch_failsafe()
self notify("weapon_change_complete"); self notify("weapon_change_complete");
} }
function has_radar() function has_radar()
{ {
if (level.teambased) if (level.teambased)
@ -706,18 +782,29 @@ function get_enemies( on_radar )
enemies = self GetEnemies(); enemies = self GetEnemies();
/#
for (i = 0; i < enemies.size; i++)
{
if (isplayer(enemies[i]) && enemies[i] isInMoveMode("ufo", "noclip"))
{
arrayRemoveIndex(enemies, i);
i--;
}
}
#/
if (on_radar && !self has_radar()) if (on_radar && !self has_radar())
{ {
for (i = 0; i < enemies.size; i++) for (i = 0; i < enemies.size; i++)
{ {
if (!isdefined(enemies[i].lastFireTime)) if (!isdefined(enemies[i].lastFireTime))
{ {
ArrayRemoveIndex( enemies, i ); arrayRemoveIndex(enemies, i);
i--; i--;
} }
else if (GetTime() - enemies[i].lastFireTime > 2000) else if (GetTime() - enemies[i].lastFireTime > 2000)
{ {
ArrayRemoveIndex( enemies, i ); arrayRemoveIndex(enemies, i);
i--; i--;
} }
} }
@ -728,7 +815,7 @@ function get_enemies( on_radar )
function set_rank() function set_rank()
{ {
players = GetPlayers(); players = getPlayers();
ranks = []; ranks = [];
bot_ranks = []; bot_ranks = [];
@ -760,12 +847,12 @@ function set_rank()
while (bot_ranks.size + human_ranks.size < 5) while (bot_ranks.size + human_ranks.size < 5)
{ {
// add some random ranks for better random number distribution // add some random ranks for better random number distribution
r = human_avg + RandomIntRange( -5, 5 ); r = human_avg + randomIntRange(-5, 5);
rank = math::clamp(r, 0, level.maxRank); rank = math::clamp(r, 0, level.maxRank);
human_ranks[human_ranks.size] = rank; human_ranks[human_ranks.size] = rank;
} }
ranks = ArrayCombine( human_ranks, bot_ranks, true, false ); ranks = arrayCombine(human_ranks, bot_ranks, true, false);
avg = math::array_average(ranks); avg = math::array_average(ranks);
s = math::array_std_deviation(ranks, avg); s = math::array_std_deviation(ranks, avg);
@ -786,69 +873,49 @@ function set_rank()
function init_bot_gametype() function init_bot_gametype()
{ {
switch(level.gametype) switch (level.gameType)
{ {
case "ball": case "ball":
{
bot_ball::init(); bot_ball::init();
return true; return true;
}
case "conf": case "conf":
{
bot_conf::init(); bot_conf::init();
return true; return true;
}
case "ctf": case "ctf":
{
bot_ctf::init(); bot_ctf::init();
return true; return true;
}
case "dem": case "dem":
{
bot_dem::init(); bot_dem::init();
return true; return true;
}
case "dm": case "dm":
{
return true; return true;
}
case "dom": case "dom":
{
bot_dom::init(); bot_dom::init();
return true; return true;
}
case "escort": case "escort":
{
bot_escort::init(); bot_escort::init();
return true; return true;
} // case "infect":
/* case "infect": // return true;
{ case "gun":
return true; return true;
}
*/ case "gun":
{
return true;
}
case "koth": case "koth":
{
bot_koth::init(); bot_koth::init();
return true; return true;
}
case "sd": case "sd":
{
bot_sd::init(); bot_sd::init();
return true; return true;
}
case "clean": case "clean":
{
bot_clean::init(); bot_clean::init();
return true; return true;
}
case "tdm": case "tdm":
{
return true; return true;
} case "sas":
return true;
case "prop":
return true;
case "sniperonly":
return true;
} }
return false; return false;
@ -856,7 +923,7 @@ function init_bot_gametype()
function get_bot_settings() function get_bot_settings()
{ {
switch ( GetDvarInt( "bot_difficulty", 1 ) ) switch (getDvarInt("bot_difficulty", 1))
{ {
case 0: case 0:
bundleName = "bot_mp_easy"; bundleName = "bot_mp_easy";
@ -907,3 +974,144 @@ function dive_to_prone( exit_stance )
} }
/#
// Devgui
//========================================
function bot_devgui_cmd(cmd)
{
cmdTokens = strtok(cmd, " ");
if (cmdTokens.size == 0)
{
return false;
}
host = util::getHostPlayerForBots();
team = get_host_team();
switch (cmdTokens[0])
{
case "spawn_enemy":
team = util::getotherteam(team);
case "spawn_friendly":
count = 1;
if (cmdTokens.size > 1)
{
count = int(cmdTokens[1]);
}
for (i = 0; i < count; i++)
{
add_bot(team);
}
return true;
case "remove_enemy":
team = util::getotherteam(team);
case "remove_friendly":
remove_bots(undefined, team);
return true;
case "fixed_spawn_enemy":
team = util::getotherteam(team);
case "fixed_spawn_friendly":
bot = add_bot_at_eye_trace(team);
if (isdefined(bot))
{
bot thread fixed_spawn_override();
}
return true;
case "player_weapon":
players = getPlayers();
foreach(player in players)
{
if (!player util::is_bot())
{
continue;
}
weapon = host getCurrentWeapon();
player weapons::detach_all_weapons();
player takeAllWeapons();
player giveWeapon(weapon);
player switchToWeapon(weapon);
player setSpawnWeapon(weapon);
player teams::set_player_model(player.team, weapon);
}
return true;
}
return false;
}
function system_devgui_gadget_think()
{
setDvar("devgui_bot_gadget", "");
for (;; )
{
wait(1);
gadget = getDvarString("devgui_bot_gadget");
if (gadget.size == 0)
{
bot_turn_on_gadget(getWeapon(gadget));
setDvar("devgui_bot_gadget", "");
}
}
}
function bot_turn_on_gadget(gadget)
{
players = getPlayers();
foreach(player in players)
{
if (!player util::is_bot())
{
continue;
}
host = util::getHostPlayer();
weapon = host getCurrentWeapon();
if (!isdefined(weapon) || weapon == level.weaponNone || weapon == level.weaponNull)
{
weapon = getWeapon("smg_standard");
}
player weapons::detach_all_weapons();
player takeAllWeapons();
player giveWeapon(weapon);
player switchToWeapon(weapon);
player setSpawnWeapon(weapon);
player teams::set_player_model(player.team, weapon);
player giveWeapon(gadget);
slot = player gadgetGetSlot(gadget);
player gadgetPowerSet(slot, 100.0);
player botPressButtonForGadget(gadget);
}
}
function fixed_spawn_override()
{
self endon("disconnect");
spawnOrigin = self.origin;
spawnAngles = self.angles;
while (1)
{
self waittill("spawned_player");
self setOrigin(spawnOrigin);
self setPlayerAngles(spawnAngles);
}
}
#/

View File

@ -16,6 +16,20 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup("MPStatsSettings", fun
if dvarName == "cg_unlockall_loot" then if dvarName == "cg_unlockall_loot" then
Engine.SetDvar("ui_enableAllHeroes", f1_arg1.value) Engine.SetDvar("ui_enableAllHeroes", f1_arg1.value)
end end
if dvarName == "all_ee_completed" then
Engine.ExecNow(f1_arg0, "statsetbyname darkops_zod_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_zod_super_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_factory_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_factory_super_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_castle_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_castle_super_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_island_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_island_super_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_stalingrad_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_stalingrad_super_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname darkops_genesis_ee " .. f1_arg1.value)
Engine.ExecNow(f1_arg0, "statsetbyname DARKOPS_GENESIS_SUPER_EE " .. f1_arg1.value)
end
end end
table.insert(optionsTable, table.insert(optionsTable,
@ -103,6 +117,7 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup("MPStatsSettings", fun
value = 1 value = 1
}, },
}, nil, updateDvar)) }, nil, updateDvar))
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
table.insert(optionsTable, table.insert(optionsTable,
CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock all Specialists Outfits", CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock all Specialists Outfits",
"All specialists outfits are unlocked.", "MPStatsSettings_unlockall_specialists_outfits", "All specialists outfits are unlocked.", "MPStatsSettings_unlockall_specialists_outfits",
@ -117,45 +132,76 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup("MPStatsSettings", fun
value = 1 value = 1
}, },
}, nil, updateDvar)) }, nil, updateDvar))
end
if Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
table.insert(optionsTable,
CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock Easter Eggs",
"Complete all Easter Egg Achievements.", "MPStatsSettings_complete_ee",
"all_ee_completed", {
{
option = "MENU_DISABLED",
value = 0,
default = true
},
{
option = "MENU_ENABLED",
value = 1
},
}, nil, updateDvar))
end
local rankLevels = {} local rankLevels = {}
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
rankLevels = { 1, 10, 20, 30, 40, 50, 55 }
elseif Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
rankLevels = { 1, 10, 20, 30, 35 }
end
local rankObjs = {} local rankObjs = {}
local hasDefault = false local hasDefault = true
local currentPrestige = CoD.PrestigeUtility.GetCurrentPLevel(controller, Engine.CurrentSessionMode())
local currentRank = CoD.BlackMarketUtility.GetCurrentRank(controller) + 1 local currentRank = CoD.BlackMarketUtility.GetCurrentRank(controller) + 1
local isMasterPrestige = currentPrestige == 11
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
if not isMasterPrestige then
rankLevels = { 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 }
else
rankLevels = { 56, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 }
end
elseif Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
if not isMasterPrestige then
rankLevels = { 1, 5, 10, 15, 20, 25, 30, 35 }
else
rankLevels = { 36, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 }
end
end
local maxlevel = math.max(table.unpack(rankLevels))
local minlevel = math.min(table.unpack(rankLevels))
for index, value in ipairs(rankLevels) do for index, value in ipairs(rankLevels) do
table.insert(rankObjs, { table.insert(rankObjs, {
name = value, name = value <= minlevel and "Min" or value >= maxlevel and "Max" or value,
value = value - 1, value = value - 1,
default = value == currentRank, default = value == currentRank,
title = "Rank Level", title = "Rank Level",
desc = "" desc = value ~= currentRank and "" or "Current Rank"
}) })
if not hasDefault then
hasDefault = value == currentRank
end
end end
if not hasDefault then if hasDefault and currentRank ~= minlevel and currentRank < maxlevel and not isMasterPrestige then
table.insert(rankObjs, { table.insert(rankObjs, {
name = currentRank, name = "Current: " ..
tostring(currentRank <= minlevel and "Min" or currentRank >= maxlevel and "Max" or currentRank),
value = currentRank - 1, value = currentRank - 1,
default = true, default = true,
title = "Rank Level", title = "Rank Level",
desc = "" desc = "Do not adjust rank"
}) })
end end
local prestigeTable = {} local prestigeTable = {}
for i = 0, 10 do for i = 0, 11 do
table.insert(prestigeTable, { table.insert(prestigeTable, {
name = i == 0 and "None" or i, name = i == 0 and "None" or i == 11 and "Master" or i,
value = i, value = i,
default = i == CoD.PrestigeUtility.GetCurrentPLevel(controller), default = i == currentPrestige,
title = "Prestige", title = "Prestige",
desc = "" desc = ""
}) })
@ -196,25 +242,19 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup("MPStatsSettings", fun
table.insert(optionsTable, { table.insert(optionsTable, {
models = { models = {
name = "Rank Level", name = "Prestige",
desc = "", desc = "",
image = nil, image = nil,
optionsDatasource = createSettingsDatasource(controller, "MPStatsSettings_rank_level", rankObjs, optionsDatasource = createSettingsDatasource(controller, "MPStatsSettings_rank_prestige", prestigeTable,
CoD.BlackMarketUtility.GetCurrentRank(controller), false, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4) CoD.PrestigeUtility.GetCurrentPLevel(controller, Engine.CurrentSessionMode()), false,
function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4)
UpdateInfoModels(f1_arg1) UpdateInfoModels(f1_arg1)
local rankTable = nil local newPrestige = f1_arg1.value
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then if newPrestige == 11 then
rankTable = "gamedata/tables/mp/mp_ranktable.csv" Engine.Exec(f1_arg0, "PrestigeStatsMaster " .. tostring(Engine.CurrentSessionMode()))
elseif Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
rankTable = "gamedata/tables/zm/zm_ranktable.csv"
end end
local skipLines = Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER and 3 or 2 Engine.ExecNow(f1_arg0, "statsetbyname plevel " .. newPrestige)
local maxXp = tonumber(Engine.TableLookupGetColumnValueForRow(rankTable, f1_arg1.value + skipLines, 7)) Engine.ExecNow(f1_arg0, "statsetbyname hasprestiged " .. (newPrestige > 0 and 1 or 0))
if maxXp == nil then
maxXp = 9999999999
end
Engine.ExecNow(f1_arg0, "statsetbyname rankxp " .. maxXp - 1)
Engine.ExecNow(f1_arg0, "statsetbyname rank " .. f1_arg1.value)
Engine.Exec(f1_arg0, "uploadstats " .. tostring(Engine.CurrentSessionMode())) Engine.Exec(f1_arg0, "uploadstats " .. tostring(Engine.CurrentSessionMode()))
end) end)
}, },
@ -226,16 +266,70 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup("MPStatsSettings", fun
table.insert(optionsTable, { table.insert(optionsTable, {
models = { models = {
name = "Prestige", name = "Rank Level",
desc = "", desc = "",
image = nil, image = nil,
optionsDatasource = createSettingsDatasource(controller, "MPStatsSettings_rank_prestige", prestigeTable, optionsDatasource = createSettingsDatasource(controller, "MPStatsSettings_rank_level", rankObjs,
CoD.PrestigeUtility.GetCurrentPLevel(controller), false, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4) CoD.BlackMarketUtility.GetCurrentRank(controller), false,
function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4)
UpdateInfoModels(f1_arg1) UpdateInfoModels(f1_arg1)
local newPrestige = f1_arg1.value local rankTable = nil
Engine.ExecNow(f1_arg0, "statsetbyname plevel " .. newPrestige) local rank = f1_arg1.value + 1
Engine.ExecNow(f1_arg0, "statsetbyname hasprestiged " .. (newPrestige > 0 and 1 or 0)) if currentPrestige <= 10 then
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
rankTable = "gamedata/tables/mp/mp_ranktable.csv"
elseif Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
rankTable = "gamedata/tables/zm/zm_ranktable.csv"
end
local skipLines = Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER and 3 or 2
local maxXp = tonumber(Engine.TableLookupGetColumnValueForRow(rankTable, rank - 2 + skipLines, 7))
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
if maxXp ~= nil and rank == maxlevel then
maxXp = maxXp + 55600
end
end
if Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
if maxXp ~= nil and rank == maxlevel then
maxXp = maxXp + 54244
end
end
if maxXp == nil then
maxXp = 0
end
Engine.ExecNow(f1_arg0, "statsetbyname rank " .. rank - 1)
Engine.ExecNow(f1_arg0, "statsetbyname rankxp " .. maxXp)
Engine.ExecNow(f1_arg0, "statsetbyname paragon_rankxp " .. 0)
else
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
rankTable = "gamedata/tables/mp/mp_paragonranktable.csv"
elseif Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
rankTable = "gamedata/tables/zm/zm_paragonranktable.csv"
end
local skipLines = 2
local maxXp = 0
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
maxXp = tonumber(Engine.TableLookupGetColumnValueForRow(rankTable, rank - 57 + skipLines, 7))
if maxXp ~= nil and rank == maxlevel then
maxXp = maxXp + 55600
end
rank = rank - 55
end
if Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
maxXp = tonumber(Engine.TableLookupGetColumnValueForRow(rankTable, rank - 37 + skipLines, 7))
if maxXp ~= nil and rank == maxlevel then
maxXp = maxXp + 54244
end
rank = rank - 35
end
if maxXp == nil then
maxXp = 0
end
Engine.ExecNow(f1_arg0, "statsetbyname paragon_rank " .. rank - 1)
Engine.ExecNow(f1_arg0, "statsetbyname paragon_rankxp " .. maxXp)
end
Engine.Exec(f1_arg0, "uploadstats " .. tostring(Engine.CurrentSessionMode())) Engine.Exec(f1_arg0, "uploadstats " .. tostring(Engine.CurrentSessionMode()))
currentRank = rank
end) end)
}, },
properties = { properties = {
@ -268,8 +362,8 @@ LUI.createMenu.BoiiiStatsMenu = function(controller)
GameSettingsBackground:setLeftRight(true, true, 0, 0) GameSettingsBackground:setLeftRight(true, true, 0, 0)
GameSettingsBackground:setTopBottom(true, true, 0, 0) GameSettingsBackground:setTopBottom(true, true, 0, 0)
GameSettingsBackground.MenuFrame.titleLabel:setText(Engine.Localize("STATS SETTINGS")) GameSettingsBackground.MenuFrame.titleLabel:setText(Engine.Localize("STATS SETTINGS"))
GameSettingsBackground.MenuFrame.cac3dTitleIntermediary0.FE3dTitleContainer0.MenuTitle.TextBox1.Label0:setText(Engine GameSettingsBackground.MenuFrame.cac3dTitleIntermediary0.FE3dTitleContainer0.MenuTitle.TextBox1.Label0:setText(
.Localize("STATS SETTINGS")) Engine.Localize("STATS SETTINGS"))
GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeInfo:setAlpha(0) GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeInfo:setAlpha(0)
GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeName:setAlpha(0) GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeName:setAlpha(0)
self:addElement(GameSettingsBackground) self:addElement(GameSettingsBackground)

2
deps/curl vendored

@ -1 +1 @@
Subproject commit 8e21b1a05f3c0ee098dbcb6c3d84cb61f102a122 Subproject commit ac5ad5214261a2237bdbe344708f9d32c9393fd6

2
deps/rapidjson vendored

@ -1 +1 @@
Subproject commit 949c771b03de448bdedea80c44a4a5f65284bfeb Subproject commit 2a1f586ba692ecbbf6d63c8ffbd4d837b1d4a9a4

View File

@ -98,7 +98,7 @@ namespace auth
{ {
static const auto is_first = [] static const auto is_first = []
{ {
static utils::nt::handle<> mutex = CreateMutexA(nullptr, FALSE, "boiii_mutex"); static utils::nt::handle mutex = CreateMutexA(nullptr, FALSE, "boiii_mutex");
return mutex && GetLastError() != ERROR_ALREADY_EXISTS; return mutex && GetLastError() != ERROR_ALREADY_EXISTS;
}(); }();
@ -134,8 +134,8 @@ namespace auth
const auto& fragment_packet = packet_buffer.get_buffer(); const auto& fragment_packet = packet_buffer.get_buffer();
game::NET_OutOfBandData( game::NET_OutOfBandData(sock, adr,
sock, adr, fragment_packet.data(), fragment_packet.data(),
static_cast<int>(fragment_packet.size())); static_cast<int>(fragment_packet.size()));
}); });
} }

View File

@ -2,6 +2,7 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "command.hpp" #include "command.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/io.hpp> #include <utils/io.hpp>
@ -96,7 +97,7 @@ namespace bots
int format_bot_string(char* buffer, [[maybe_unused]] const char* format, const char* name, const char* xuid, int format_bot_string(char* buffer, [[maybe_unused]] const char* format, const char* name, const char* xuid,
const char* xnaddr, int protocol, int net_field_chk, const char* session_mode, int qport) const char* xnaddr, int protocol, int net_field_chk, const char* session_mode, int qport)
{ {
const auto find_name = [](const std::string& needle) -> const char* const auto find_clan_name = [](const std::string& needle) -> const char*
{ {
for (const auto& entry : get_bot_names()) for (const auto& entry : get_bot_names())
{ {
@ -109,7 +110,8 @@ namespace bots
return "3arc"; return "3arc";
}; };
return sprintf_s(buffer, 1024, bot_format_string, name, find_name(name), xuid, xnaddr, protocol, net_field_chk, session_mode, qport); return sprintf_s(buffer, 1024, bot_format_string, name, find_clan_name(name),
xuid, xnaddr, protocol, net_field_chk, session_mode, qport);
} }
} }
@ -147,6 +149,8 @@ namespace bots
} }
} }
scheduler::once([count]
{
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
{ {
if (!game::SV_AddTestClient()) if (!game::SV_AddTestClient())
@ -154,6 +158,7 @@ namespace bots
break; break;
} }
} }
}, scheduler::server);
}); });
} }
}; };

View File

@ -5,8 +5,6 @@
#include "auth.hpp" #include "auth.hpp"
#include "steam/steam.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>

View File

@ -2,7 +2,6 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
@ -12,7 +11,7 @@ namespace dedicated_patches
{ {
utils::hook::detour spawn_server_hook; utils::hook::detour spawn_server_hook;
void scr_are_textures_loaded_stub([[maybe_unused]] game::scriptInstance_t inst) void scr_are_textures_loaded_stub()
{ {
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1); game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1);
} }

View File

@ -0,0 +1,45 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
namespace fov
{
namespace
{
void cg_calc_fov_stub(const int local_client_num, float* fov_x, float* dx_dz_at_default_aspect_ratio,
float* dx_dz, float* dy_dz)
{
game::CG_CalcFOVfromLens.call_safe(local_client_num, fov_x, dx_dz_at_default_aspect_ratio, dx_dz, dy_dz);
const game::dvar_t* cg_fovScale = *reinterpret_cast<game::dvar_t**>(0x144A31A88_g);
if (cg_fovScale && !game::Com_IsRunningUILevel())
{
const auto scale = cg_fovScale->current.value.value;
*fov_x *= scale;
*dx_dz *= scale;
*dy_dz *= scale;
}
}
}
struct component final : client_component
{
void post_unpack() override
{
// Hook CG_CalcFOVfromLens within CG_CalcFov
utils::hook::call(0x1404DADA7_g, cg_calc_fov_stub);
// Patch cg_fovScale flags
utils::hook::set<uint32_t>(0x14090E735_g, game::DVAR_ARCHIVE);
// Don't reset cg_fovScale
utils::hook::set<uint8_t>(0x140926D2A_g, 0xC3);
}
};
}
REGISTER_COMPONENT(fov::component)

View File

@ -68,14 +68,6 @@ namespace getinfo
return count; return count;
} }
namespace
{
int Com_SessionMode_GetGameMode()
{
return *reinterpret_cast<int*>(game::select(0x1568ED7F4, 0x14948DB04)) << 14 >> 28;
}
}
int get_assigned_team() int get_assigned_team()
{ {
return (rand() % 2) + 1; return (rand() % 2) + 1;
@ -112,7 +104,7 @@ namespace getinfo
info.set("protocol", std::to_string(PROTOCOL)); info.set("protocol", std::to_string(PROTOCOL));
info.set("sub_protocol", std::to_string(SUB_PROTOCOL)); info.set("sub_protocol", std::to_string(SUB_PROTOCOL));
info.set("playmode", std::to_string(game::Com_SessionMode_GetMode())); info.set("playmode", std::to_string(game::Com_SessionMode_GetMode()));
info.set("gamemode", std::to_string(Com_SessionMode_GetGameMode())); info.set("gamemode", std::to_string(game::Com_SessionMode_GetGameMode()));
info.set("sv_running", std::to_string(game::is_server_running())); info.set("sv_running", std::to_string(game::is_server_running()));
info.set("dedicated", game::is_server() ? "1" : "0"); info.set("dedicated", game::is_server() ? "1" : "0");
info.set("hc", std::to_string(game::Com_GametypeSettings_GetUInt("hardcoremode", false))); info.set("hc", std::to_string(game::Com_GametypeSettings_GetUInt("hardcoremode", false)));

View File

@ -54,7 +54,7 @@ namespace party
{ {
const auto local_client = *reinterpret_cast<DWORD*>(0x14342155C_g); const auto local_client = *reinterpret_cast<DWORD*>(0x14342155C_g);
const auto current_mode = game::Com_SessionMode_GetMode(); const auto current_mode = game::Com_SessionMode_GetMode();
game::Com_SwitchMode(local_client, current_mode, mode, 6); game::Com_SwitchMode(local_client, static_cast<game::eModes>(current_mode), mode, 6);
}, scheduler::main); }, scheduler::main);
} }

View File

@ -2,8 +2,7 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include <game/game.hpp> #include <game/game.hpp>
#include <game/utils.hpp>
#include "network.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
@ -11,21 +10,10 @@ namespace patches
{ {
namespace namespace
{ {
utils::hook::detour sv_execute_client_messages_hook; const game::dvar_t* lobby_min_players;
void sv_execute_client_messages_stub(game::client_s* client, game::msg_t* msg) void script_errors_stub([[maybe_unused]] const char* file, [[maybe_unused]] int line,
{ [[maybe_unused]] unsigned int code, const char* fmt, ...)
if ((client->reliableSequence - client->reliableAcknowledge) < 0)
{
client->reliableAcknowledge = client->reliableSequence;
network::send(client->address, "error", "EXE_LOSTRELIABLECOMMANDS");
return;
}
sv_execute_client_messages_hook.invoke<void>(client, msg);
}
void script_errors_stub(const char* file, int line, unsigned int code, const char* fmt, ...)
{ {
char buffer[0x1000]; char buffer[0x1000];
@ -38,28 +26,58 @@ namespace patches
game::Com_Error(game::ERROR_SCRIPT_DROP, "%s", buffer); game::Com_Error(game::ERROR_SCRIPT_DROP, "%s", buffer);
} }
void scr_get_num_expected_players()
{
auto expected_players = game::LobbyHost_GetClientCount(game::LOBBY_TYPE_GAME,
game::LOBBY_CLIENT_TYPE_ALL);
const auto mode = game::Com_SessionMode_GetMode();
if ((mode == game::MODE_ZOMBIES || mode == game::MODE_CAMPAIGN))
{
if (const auto min_players = lobby_min_players->current.value.integer)
{
expected_players = min_players;
}
}
const auto num_expected_players = std::max(1, expected_players);
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, num_expected_players);
}
void sv_execute_client_messages_stub(game::client_s* client, game::msg_t* msg)
{
if ((client->reliableSequence - client->reliableAcknowledge) < 0)
{
client->reliableAcknowledge = client->reliableSequence;
game::SV_DropClient(client, "EXE_LOSTRELIABLECOMMANDS", true, true);
return;
}
game::SV_ExecuteClientMessage(client, msg);
}
} }
struct component final : generic_component struct component final : generic_component
{ {
void post_unpack() override void post_unpack() override
{ {
// print hexadecimal xuids in status command
utils::hook::copy_string(game::select(0x143050560, 0x140E85B00), "%12llx ");
// print hexadecimal xuids in chat game log command // print hexadecimal xuids in chat game log command
utils::hook::set<char>(game::select(0x142FD9362, 0x140E16FA2), 'x'); utils::hook::set<char>(game::select(0x142FD9362, 0x140E16FA2), 'x');
// don't make script errors fatal error // don't make script errors fatal error
utils::hook::call(game::select(0x1412CAC4D, 0x140158EB2), script_errors_stub); utils::hook::call(game::select(0x1412CAC4D, 0x140158EB2), script_errors_stub);
// Change 4 character name limit to 3 characters // change 4 character name limit to 3 characters
utils::hook::set<uint8_t>(game::select(0x14224DA53, 0x140531143), 3); utils::hook::set<uint8_t>(game::select(0x14224DA53, 0x140531143), 3);
utils::hook::set<uint8_t>(game::select(0x14224DBB4, 0x1405312A8), 3); utils::hook::set<uint8_t>(game::select(0x14224DBB4, 0x1405312A8), 3);
utils::hook::set<uint8_t>(game::select(0x14224DF8C, 0x1405316DC), 3); utils::hook::set<uint8_t>(game::select(0x14224DF8C, 0x1405316DC), 3);
// make sure client's reliableAck are not negative // make sure reliableAck is not negative or too big
sv_execute_client_messages_hook.create(game::select(0x14224A460, 0x14052F840), sv_execute_client_messages_stub); utils::hook::call(game::select(0x14225489C, 0x140537C4C), sv_execute_client_messages_stub);
lobby_min_players = game::register_dvar_int("lobby_min_players", 0, 0, 8, game::DVAR_NONE, "");
utils::hook::jump(game::select(0x141A7BCF0, 0x1402CB900), scr_get_num_expected_players, true);
} }
}; };
} }

View File

@ -84,7 +84,7 @@ namespace scheduler
}; };
volatile bool kill = false; volatile bool kill = false;
std::thread thread; std::thread async_thread;
task_pipeline pipelines[pipeline::count]; task_pipeline pipelines[pipeline::count];
utils::hook::detour r_end_frame_hook; utils::hook::detour r_end_frame_hook;
@ -153,7 +153,7 @@ namespace scheduler
{ {
void post_load() override void post_load() override
{ {
thread = utils::thread::create_named_thread("Async Scheduler", []() async_thread = utils::thread::create_named_thread("Async Scheduler", []()
{ {
while (!kill) while (!kill)
{ {
@ -180,9 +180,9 @@ namespace scheduler
void pre_destroy() override void pre_destroy() override
{ {
kill = true; kill = true;
if (thread.joinable()) if (async_thread.joinable())
{ {
thread.join(); async_thread.join();
} }
} }
}; };

View File

@ -30,12 +30,12 @@ namespace scheduler
void execute(const pipeline type); void execute(const pipeline type);
void schedule(const std::function<bool()>& callback, pipeline type = pipeline::async, void schedule(const std::function<bool()>& callback, pipeline type,
std::chrono::milliseconds delay = 0ms); std::chrono::milliseconds delay = 0ms);
void loop(const std::function<void()>& callback, pipeline type = pipeline::async, void loop(const std::function<void()>& callback, pipeline type,
std::chrono::milliseconds delay = 0ms); std::chrono::milliseconds delay = 0ms);
void once(const std::function<void()>& callback, pipeline type = pipeline::async, void once(const std::function<void()>& callback, pipeline type,
std::chrono::milliseconds delay = 0ms); std::chrono::milliseconds delay = 0ms);
void on_game_initialized(const std::function<void()>& callback, pipeline type = pipeline::async, void on_game_initialized(const std::function<void()>& callback, pipeline type,
std::chrono::milliseconds delay = 0ms); std::chrono::milliseconds delay = 0ms);
} }

View File

@ -14,7 +14,7 @@ namespace script
{ {
constexpr size_t GSC_MAGIC = 0x1C000A0D43534780; constexpr size_t GSC_MAGIC = 0x1C000A0D43534780;
utils::hook::detour db_findxassetheader_hook; utils::hook::detour db_find_x_asset_header_hook;
utils::hook::detour gscr_get_bgb_remaining_hook; utils::hook::detour gscr_get_bgb_remaining_hook;
std::unordered_map<std::string, game::RawFile*> loaded_scripts; std::unordered_map<std::string, game::RawFile*> loaded_scripts;
@ -33,7 +33,6 @@ namespace script
void load_script(std::string& name, const std::string& data) void load_script(std::string& name, const std::string& data)
{ {
auto& allocator = *utils::memory::get_allocator(); auto& allocator = *utils::memory::get_allocator();
const auto* file_string = allocator.duplicate_string(data);
const auto appdata_path = (game::get_appdata_path() / "data/").generic_string(); const auto appdata_path = (game::get_appdata_path() / "data/").generic_string();
const auto host_path = (utils::nt::library{}.get_folder() / "boiii/").generic_string(); const auto host_path = (utils::nt::library{}.get_folder() / "boiii/").generic_string();
@ -62,12 +61,12 @@ namespace script
return; return;
} }
auto* rawfile = allocator.allocate<game::RawFile>(); auto* raw_file = allocator.allocate<game::RawFile>();
rawfile->name = name.data(); // use script name with .gsc suffix for FindXAssetHeader hook raw_file->name = allocator.duplicate_string(name); // use script name with .gsc suffix for FindXAssetHeader hook
rawfile->buffer = file_string; raw_file->buffer = allocator.duplicate_string(data);
rawfile->len = static_cast<int>(data.length()); raw_file->len = static_cast<int>(data.length());
loaded_scripts[name] = rawfile; loaded_scripts[name] = raw_file;
game::Scr_LoadScript(game::SCRIPTINSTANCE_SERVER, base_name.data()); game::Scr_LoadScript(game::SCRIPTINSTANCE_SERVER, base_name.data());
} }
@ -108,11 +107,11 @@ namespace script
load_scripts_folder((host.get_folder() / "boiii/scripts").string()); load_scripts_folder((host.get_folder() / "boiii/scripts").string());
} }
game::RawFile* db_findxassetheader_stub(const game::XAssetType type, const char* name, game::RawFile* db_find_x_asset_header_stub(const game::XAssetType type, const char* name,
const bool error_if_missing, const bool error_if_missing,
const int wait_time) const int wait_time)
{ {
auto* asset_header = db_findxassetheader_hook.invoke<game::RawFile*>( auto* asset_header = db_find_x_asset_header_hook.invoke<game::RawFile*>(
type, name, error_if_missing, wait_time); type, name, error_if_missing, wait_time);
if (type != game::ASSET_TYPE_SCRIPTPARSETREE) if (type != game::ASSET_TYPE_SCRIPTPARSETREE)

View File

@ -0,0 +1,47 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <game/game.hpp>
#include <utils/hook.hpp>
namespace status
{
namespace
{
thread_local int g_client_num{0};
void print_client_num(const int channel, const int label, const char* fmt, const int client_num)
{
g_client_num = client_num;
game::Com_Printf(channel, label, fmt, client_num);
}
void print_client_xuid(const int channel, const int label, [[maybe_unused]] const char* fmt, const uint64_t xuid)
{
if (game::SV_IsTestClient(g_client_num))
{
game::Com_Printf(channel, label, "%16s ", "bot0");
return;
}
game::Com_Printf(channel, label, "%12llx ", xuid);
}
}
struct component final : generic_component
{
void post_unpack() override
{
// Patch the status command for test clients
utils::hook::call(game::select(0x142246E37, 0x14052C527), print_client_num);
utils::hook::call(game::select(0x142246EDE, 0x14052C5CE), print_client_xuid);
utils::hook::copy_string(game::select(0x143050480, 0x140E85A20),
"num score ping xuid name address qport \n");
utils::hook::copy_string(game::select(0x1430504E0, 0x140E85A80),
"--- ----- ---- ---------------- ---------------- ------------------------ ------ \n");
}
};
}
REGISTER_COMPONENT(status::component)

View File

@ -890,7 +890,7 @@ namespace game
enum LobbyType enum LobbyType
{ {
LOBBY_TYPE_INVALID = 0xFFFFFFFF, LOBBY_TYPE_INVALID = -1,
LOBBY_TYPE_PRIVATE = 0x0, LOBBY_TYPE_PRIVATE = 0x0,
LOBBY_TYPE_GAME = 0x1, LOBBY_TYPE_GAME = 0x1,
LOBBY_TYPE_TRANSITION = 0x2, LOBBY_TYPE_TRANSITION = 0x2,
@ -900,6 +900,14 @@ namespace game
LOBBY_TYPE_AUTO = 0x3, LOBBY_TYPE_AUTO = 0x3,
}; };
enum LobbyClientType
{
LOBBY_CLIENT_TYPE_INVALID = -1,
LOBBY_CLIENT_TYPE_ALL = 0x0,
LOBBY_CLIENT_TYPE_LOCAL = 0x1,
LOBBY_CLIENT_TYPE_REMOTE = 0x2,
};
enum LobbyNetworkMode enum LobbyNetworkMode
{ {
LOBBY_NETWORKMODE_INVALID = 0xFFFFFFFF, LOBBY_NETWORKMODE_INVALID = 0xFFFFFFFF,
@ -1579,9 +1587,19 @@ namespace game
SV_CMD_RELIABLE_0 = 0x1, SV_CMD_RELIABLE_0 = 0x1,
}; };
enum
{
CS_FREE = 0x0,
CS_ZOMBIE = 0x1,
CS_RECONNECTING = 0x2,
CS_CONNECTED = 0x3,
CS_CLIENTLOADING = 0x4,
CS_ACTIVE = 0x5,
};
struct client_s struct client_s
{ {
int client_state; int state;
char __pad0[0x28]; char __pad0[0x28];
netadr_t address; netadr_t address;
char __pad1[20468]; char __pad1[20468];
@ -1600,7 +1618,6 @@ namespace game
char __pad6[171432]; char __pad6[171432];
}; };
#ifdef __cplusplus #ifdef __cplusplus
static_assert(sizeof(client_s) == 0xE5110); static_assert(sizeof(client_s) == 0xE5110);
@ -1637,14 +1654,23 @@ namespace game
struct EntityState struct EntityState
{ {
int number; int number;
}; }; // Incomplete
struct gentity_s struct gentity_s
{ {
EntityState s; EntityState s;
unsigned char __pad0[0x24C]; unsigned char __pad0[0x24C];
gclient_s* client; gclient_s* client;
unsigned char __pad1[0x2A0]; unsigned char __pad1[0x17C];
struct
{
unsigned int notifyString;
unsigned int index;
unsigned char stoppable;
int basetime;
int duration;
} snd_wait;
unsigned char __pad2[0x110];
}; };
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -4,11 +4,6 @@
namespace game namespace game
{ {
eModes Com_SessionMode_GetMode()
{
return eModes(*reinterpret_cast<uint32_t*>(game::select(0x1568ED7F4, 0x14948DB04)) << 28 >> 28);
}
bool I_islower(int c) bool I_islower(int c)
{ {
return c >= 'a' && c <= 'z'; return c >= 'a' && c <= 'z';

View File

@ -8,6 +8,10 @@ namespace game
{ {
#define Com_Error(code, fmt, ...) Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__) #define Com_Error(code, fmt, ...) Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
// CG
WEAK symbol<void(int localClientNum, float* fov_x, float* dxDzAtDefaultAspectRatio, float* dxDz, float* dyDz)>
CG_CalcFOVfromLens{0x1404D6230};
// CL // CL
WEAK symbol<void(int controllerIndex, XSESSION_INFO* hostInfo, const netadr_t* addr, int numPublicSlots, WEAK symbol<void(int controllerIndex, XSESSION_INFO* hostInfo, const netadr_t* addr, int numPublicSlots,
int numPrivateSlots, const char* mapname, const char* gametype, int numPrivateSlots, const char* mapname, const char* gametype,
@ -25,6 +29,8 @@ namespace game
WEAK symbol<void(int channel, unsigned int label, const char* fmt, ...)> Com_Printf{0x142148F60, 0x140505630}; WEAK symbol<void(int channel, unsigned int label, const char* fmt, ...)> Com_Printf{0x142148F60, 0x140505630};
WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{0x1420F8170, 0x140501470}; WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{0x1420F8170, 0x140501470};
WEAK symbol<bool(eModes mode)> Com_SessionMode_IsMode{0x1420F7370}; WEAK symbol<bool(eModes mode)> Com_SessionMode_IsMode{0x1420F7370};
WEAK symbol<int()> Com_SessionMode_GetMode{0x1420F6D30 , 0x1405002D0};
WEAK symbol<int()> Com_SessionMode_GetGameMode{0x1420F68B0, 0x1404FFE50};
WEAK symbol<void(eNetworkModes networkMode)> Com_SessionMode_SetNetworkMode{0x1420F75B0, 0x140500B80}; WEAK symbol<void(eNetworkModes networkMode)> Com_SessionMode_SetNetworkMode{0x1420F75B0, 0x140500B80};
WEAK symbol<eGameModes(eGameModes gameMode)> Com_SessionMode_SetGameMode{0x1420F7570, 0x140500B40}; WEAK symbol<eGameModes(eGameModes gameMode)> Com_SessionMode_SetGameMode{0x1420F7570, 0x140500B40};
WEAK symbol<eModes(eModes mode)> Com_SessionMode_SetMode{0x1420F7570}; WEAK symbol<eModes(eModes mode)> Com_SessionMode_SetMode{0x1420F7570};
@ -132,7 +138,7 @@ namespace game
}; };
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, int value, int min, int max, unsigned int flags, WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, int value, int min, int max, unsigned int flags,
const char* description)> Dvar_RegisterInt{ const char* description)> Dvar_RegisterInt{
0x0, 0x14057B7B0 0x1422D0AE0, 0x14057B7B0
}; };
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, float value, float min, float max, unsigned int flags, WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, float value, float min, float max, unsigned int flags,
const char* description)> Dvar_RegisterFloat{ const char* description)> Dvar_RegisterFloat{
@ -158,7 +164,9 @@ namespace game
}; };
// UI // UI
WEAK symbol<void(int localClientNumber, int errorcode, const char* errorMessage)> UI_OpenErrorPopupWithMessage{0x14228DEE0}; WEAK symbol<void(int localClientNumber, int errorcode, const char* errorMessage)> UI_OpenErrorPopupWithMessage{
0x14228DEE0
};
WEAK symbol<void(bool frontend)> UI_CoD_Init{0x141F29010, 0x1404A0A50}; WEAK symbol<void(bool frontend)> UI_CoD_Init{0x141F29010, 0x1404A0A50};
WEAK symbol<void()> UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50}; WEAK symbol<void()> UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50};
WEAK symbol<void()> UI_CoD_Shutdown{0x141F32E10, 0x0}; WEAK symbol<void()> UI_CoD_Shutdown{0x141F32E10, 0x0};
@ -206,10 +214,20 @@ namespace game
}; };
WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0x1420EF130, 0x1404FA6C0}; WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0x1420EF130, 0x1404FA6C0};
WEAK symbol<void()> SV_Cmd_EndTokenizedString{0x1420EF0E0, 0x1404FA670}; WEAK symbol<void()> SV_Cmd_EndTokenizedString{0x1420EF0E0, 0x1404FA670};
WEAK symbol<void(void* client, msg_t* msg)> SV_ExecuteClientMessage{0x14224A460, 0x14052F840};
WEAK symbol<void(void* drop, const char* reason, bool tellThem, bool removeFromLobby)> SV_DropClient{
0x14224A050, 0x14052F430
};
// FS // FS
WEAK symbol<char*(int bytes)> FS_AllocMem{0x1422AC9F0, 0x14056C340}; WEAK symbol<char*(int bytes)> FS_AllocMem{0x1422AC9F0, 0x14056C340};
// Lobby
WEAK symbol<int(LobbyType lobbyType, LobbyClientType clientType)> LobbyHost_GetClientCount{
0x141ED8AC0, 0x14048A360
};
// Utils // Utils
WEAK symbol<const char*(char* str)> I_CleanStr{0x1422E9050, 0x140580E80}; WEAK symbol<const char*(char* str)> I_CleanStr{0x1422E9050, 0x140580E80};
WEAK symbol<void(char* dest, size_t destsize, const char* src)> I_strcpy{ WEAK symbol<void(char* dest, size_t destsize, const char* src)> I_strcpy{
@ -265,8 +283,6 @@ namespace game
constexpr auto CMD_MAX_NESTING = 8; constexpr auto CMD_MAX_NESTING = 8;
// Re-implementations // Re-implementations
eModes Com_SessionMode_GetMode();
bool I_islower(int c); bool I_islower(int c);
bool I_isupper(int c); bool I_isupper(int c);

View File

@ -136,11 +136,10 @@ namespace game
} }
auto* dvar_to_change = dvar; auto* dvar_to_change = dvar;
if (dvar_to_change->type == DVAR_TYPE_SESSIONMODE_BASE_DVAR) if (dvar_to_change->type == DVAR_TYPE_SESSIONMODE_BASE_DVAR)
{ {
const auto mode = Com_SessionMode_GetMode(); const auto mode = Com_SessionMode_GetMode();
dvar_to_change = Dvar_GetSessionModeSpecificDvar(dvar_to_change, mode); dvar_to_change = Dvar_GetSessionModeSpecificDvar(dvar_to_change, static_cast<eModes>(mode));
} }
dvar_to_change->flags |= flags; dvar_to_change->flags |= flags;
@ -156,11 +155,10 @@ namespace game
} }
auto* dvar_to_change = dvar; auto* dvar_to_change = dvar;
if (dvar_to_change->type == DVAR_TYPE_SESSIONMODE_BASE_DVAR) if (dvar_to_change->type == DVAR_TYPE_SESSIONMODE_BASE_DVAR)
{ {
const auto mode = Com_SessionMode_GetMode(); const auto mode = Com_SessionMode_GetMode();
dvar_to_change = Dvar_GetSessionModeSpecificDvar(dvar_to_change, mode); dvar_to_change = Dvar_GetSessionModeSpecificDvar(dvar_to_change, static_cast<eModes>(mode));
} }
dvar_to_change->flags = flags; dvar_to_change->flags = flags;
@ -205,7 +203,7 @@ namespace game
} }
auto& client = client_states[index]; auto& client = client_states[index];
if (client.client_state <= 0) if (client.state == CS_FREE)
{ {
return false; return false;
} }
@ -238,7 +236,7 @@ namespace game
{ {
foreach_client([&](client_s& client, const size_t index) foreach_client([&](client_s& client, const size_t index)
{ {
if (client.client_state > 0) if (client.state != CS_FREE)
{ {
callback(client, index); callback(client, index);
} }

View File

@ -415,7 +415,12 @@ namespace utils::hook
bool is_relatively_far(const void* pointer, const void* data, const int offset) bool is_relatively_far(const void* pointer, const void* data, const int offset)
{ {
const int64_t diff = reinterpret_cast<size_t>(data) - (reinterpret_cast<size_t>(pointer) + offset); return is_relatively_far(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data), offset);
}
bool is_relatively_far(const size_t pointer, const size_t data, const int offset)
{
const auto diff = static_cast<int64_t>(data - (pointer + offset));
const auto small_diff = static_cast<int32_t>(diff); const auto small_diff = static_cast<int32_t>(diff);
return diff != static_cast<int64_t>(small_diff); return diff != static_cast<int64_t>(small_diff);
} }
@ -534,19 +539,25 @@ namespace utils::hook
return result; return result;
} }
void inject(void* pointer, const void* data) void inject(size_t pointer, size_t data)
{ {
if (is_relatively_far(pointer, data, 4)) if (is_relatively_far(pointer, data, 4))
{ {
throw std::runtime_error("Too far away to create 32bit relative branch"); throw std::runtime_error("Too far away to create 32bit relative branch");
} }
set<int32_t>(pointer, int32_t(size_t(data) - (size_t(pointer) + 4))); set<int32_t>(
pointer, static_cast<int32_t>(data - (pointer + 4)));
}
void inject(void* pointer, const void* data)
{
return inject(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data));
} }
void inject(const size_t pointer, const void* data) void inject(const size_t pointer, const void* data)
{ {
return inject(reinterpret_cast<void*>(pointer), data); return inject(pointer, reinterpret_cast<size_t>(data));
} }
std::vector<uint8_t> move_hook(void* pointer) std::vector<uint8_t> move_hook(void* pointer)

View File

@ -162,6 +162,7 @@ namespace utils::hook
void copy_string(size_t place, const char* str); void copy_string(size_t place, const char* str);
bool is_relatively_far(const void* pointer, const void* data, int offset = 5); bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
bool is_relatively_far(size_t pointer, size_t data, int offset = 5);
void call(void* pointer, void* data); void call(void* pointer, void* data);
void call(size_t pointer, void* data); void call(size_t pointer, void* data);
@ -175,6 +176,7 @@ namespace utils::hook
void inject(void* pointer, const void* data); void inject(void* pointer, const void* data);
void inject(size_t pointer, const void* data); void inject(size_t pointer, const void* data);
void inject(size_t pointer, size_t data);
std::vector<uint8_t> move_hook(void* pointer); std::vector<uint8_t> move_hook(void* pointer);
std::vector<uint8_t> move_hook(size_t pointer); std::vector<uint8_t> move_hook(size_t pointer);