Skip to content

Commit

Permalink
[COC] Support usage of multiple spawn_loadout sections in character…
Browse files Browse the repository at this point in the history
… descriptions. (#1590)
  • Loading branch information
Neloreck authored Feb 9, 2025
1 parent 4003352 commit 95b9bbc
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 40 deletions.
128 changes: 88 additions & 40 deletions src/xrGame/alife_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,72 +22,81 @@ void CSE_ALifeObject::spawn_supplies(LPCSTR ini_string)
if (!xr_strlen(ini_string))
return;

luabind::functor<bool> funct;
if (GEnv.ScriptEngine->functor("ai_stalker.CSE_ALifeObject_spawn_supplies", funct))
{
if (funct(this, ID, ini_string))
return;
}

#pragma warning(push)
#pragma warning(disable : 4238)
IReader reader((void*)ini_string, xr_strlen(ini_string));
CInifile ini(&reader, FS.get_path("$game_config$")->m_Path);
#pragma warning(pop)

// Alundaio: This will spawn a single random section listed in [spawn_loadout]
// No need to spawn ammo, this will automatically spawn 1 box for weapon and if ammo_type is specified it will spawn that type
// count is used only for ammo boxes (ie wpn_pm = 3) will spawn 3 boxes, not 3 wpn_pm
// Usage: to create random weapon loadouts
static constexpr cpcstr LOADOUT_SECTION = "spawn_loadout";
if (ini.section_exist(LOADOUT_SECTION))
u8 loadoutIndex = 0;
string32 loadoutSection = "spawn_loadout";

// Alundaio: This will spawn a single random section listed in [spawn_loadout].
// No need to spawn ammo, this will automatically spawn 1 box for weapon and if ammo_type is specified it will spawn that type.
// Count is used only for ammo boxes (ie wpn_pm = 3) will spawn 3 boxes, not 3 wpn_pm.
// Supports few loadout options, iterates over `spawn_loadout`, `spawn_loadout2` ... `spawn_loadoutN`.
while (ini.section_exist(loadoutSection))
{
pcstr itmSection, V;
xr_vector<u32> OnlyOne;
xr_vector<u32> spawnLoadouts;

pcstr lname = ai().game_graph().header().level(ai().game_graph().vertex(m_tGraphID)->level_id()).name().c_str();

for (u32 k = 0; ini.r_line(LOADOUT_SECTION, k, &itmSection, &V); k++)
for (u32 k = 0; ini.r_line(loadoutSection, k, &itmSection, &V); k++)
{
// If level=<lname> then only spawn items if object on that level
if (strstr(V, "level=") != nullptr)
{
if (strstr(V, lname) != nullptr)
OnlyOne.push_back(k);
spawnLoadouts.push_back(k);
}
else
{
OnlyOne.push_back(k);
spawnLoadouts.push_back(k);
}
}

if (!OnlyOne.empty())
if (!spawnLoadouts.empty())
{
s32 sel = Random.randI(0, OnlyOne.size());
if (ini.r_line(LOADOUT_SECTION, OnlyOne.at(sel), &itmSection, &V))
s32 sel = Random.randI(0, spawnLoadouts.size());
if (ini.r_line(loadoutSection, spawnLoadouts.at(sel), &itmSection, &V))
{
VERIFY(xr_strlen(itmSection));
if (pSettings->section_exist(itmSection))
{
u32 spawn_count = 1;
u32 spawnCount = 1;
bool bScope = false;
bool bSilencer = false;
bool bLauncher = false;
float f_cond = 1.0f;
int i_ammo_type = 0, n = 0;
float fCond = 1.0f;
int iAmmoType = 0, n = 0;

if (V && xr_strlen(V))
{
n = _GetItemCount(V);
if (n > 0)
{
string64 tmp;
spawn_count = atoi(_GetItem(V, 0, tmp)); //count
spawnCount = atoi(_GetItem(V, 0, tmp)); //count
}

if (!spawn_count) spawn_count = 1;
if (nullptr != strstr(V, "cond="))
f_cond = static_cast<float>(atof(strstr(V, "cond=") + 5));
bScope = nullptr != strstr(V, "scope");
bSilencer = nullptr != strstr(V, "silencer");
bLauncher = nullptr != strstr(V, "launcher");
if (nullptr != strstr(V, "ammo_type="))
i_ammo_type = atoi(strstr(V, "ammo_type=") + 10);
}
bScope = is_spawn_supplies_flag_set(V, "scope");
bSilencer = is_spawn_supplies_flag_set(V, "silencer");
bLauncher = is_spawn_supplies_flag_set(V, "launcher");

if (!spawnCount)
spawnCount = 1;
if (strstr(V, "cond=") != nullptr)
fCond = static_cast<float>(atof(strstr(V, "cond=") + 5));
if (strstr(V, "ammo_type=") != nullptr)
iAmmoType = atoi(strstr(V, "ammo_type=") + 10);
}

CSE_Abstract* E = alife().spawn_item(itmSection, o_Position, m_tNodeID, m_tGraphID, ID);
CSE_ALifeItemWeapon* W = smart_cast<CSE_ALifeItemWeapon*>(E);
Expand All @@ -109,19 +118,29 @@ void CSE_ALifeObject::spawn_supplies(LPCSTR ini_string)
{
string128 tmp;
ammoSec = _GetItem(ammo_class, i, tmp);
if (i == i_ammo_type)
if (i == iAmmoType)
break;
}
if (xr_strlen(ammoSec) && pSettings->section_exist(ammoSec))
for (u32 i = 1; i <= spawn_count; ++i)
for (u32 i = 1; i <= spawnCount; ++i)
alife().spawn_item(ammoSec, o_Position, m_tNodeID, m_tGraphID, ID);
}
}
// If not weapon item, handle count as literal count, not ammo (useful for grenades and consumables).
else
{
for (u32 i = 1; i <= spawnCount - 1; ++i)
alife().spawn_item(itmSection, o_Position, m_tNodeID, m_tGraphID, ID);
}

if (const auto IItem = smart_cast<CSE_ALifeInventoryItem*>(E))
IItem->m_fCondition = f_cond;
IItem->m_fCondition = fCond;
}
}
}

loadoutIndex += 1;
xr_sprintf(loadoutSection, "spawn_loadout%d", loadoutIndex);
}
//-Alundaio

Expand All @@ -135,7 +154,7 @@ void CSE_ALifeObject::spawn_supplies(LPCSTR ini_string)

if (pSettings->section_exist(N)) //Alundaio: verify item section exists!
{
float f_cond = 1.0f;
float fCond = 1.0f;
bool bScope = false;
bool bSilencer = false;
bool bLauncher = false;
Expand All @@ -150,16 +169,15 @@ void CSE_ALifeObject::spawn_supplies(LPCSTR ini_string)
if (!j)
j = 1;

bScope = nullptr != strstr(V, "scope");
bSilencer = nullptr != strstr(V, "silencer");
bLauncher = nullptr != strstr(V, "launcher");
bScope = is_spawn_supplies_flag_set(V, "scope");
bSilencer = is_spawn_supplies_flag_set(V, "silencer");
bLauncher = is_spawn_supplies_flag_set(V, "launcher");

// probability
if (nullptr != strstr(V, "prob="))
if (strstr(V, "prob=") != nullptr)
p = static_cast<float>(atof(strstr(V, "prob=") + 5));
if (fis_zero(p))
p = 1.0f;
if (nullptr != strstr(V, "cond="))
f_cond = static_cast<float>(atof(strstr(V, "cond=") + 5));
if (strstr(V, "cond=") != nullptr)
fCond = static_cast<float>(atof(strstr(V, "cond=") + 5));
}
for (u32 i = 0; i < j; ++i)
{
Expand All @@ -179,7 +197,7 @@ void CSE_ALifeObject::spawn_supplies(LPCSTR ini_string)
}
CSE_ALifeInventoryItem* IItem = smart_cast<CSE_ALifeInventoryItem*>(E);
if (IItem)
IItem->m_fCondition = f_cond;
IItem->m_fCondition = fCond;
}
}
}
Expand All @@ -188,3 +206,33 @@ void CSE_ALifeObject::spawn_supplies(LPCSTR ini_string)
}

bool CSE_ALifeObject::keep_saved_data_anyway() const /* noexcept */ { return false; }

/// Check if spawn supplies section flag was set.
/// Comparing to original `section = value, scope, silencer, launcher, cond=0.5`,
/// also support extended variants like `section = value, scope=true, silencer=0.5, launcher, cond=0.5`.
bool CSE_ALifeObject::is_spawn_supplies_flag_set(pcstr value, pcstr flag)
{
pcstr flagSubstring = strstr(value, flag);
int flagLength = strlen(flag);

if (flagSubstring != nullptr)
{
// Got full definition with '=' char, not simple shorthand like scope/silencer.
if (*(flagSubstring + flagLength) == '=')
{
if (strncmp(flagSubstring + flagLength, "=true", 5) != -1)
{
return true;
}

return randF(1.f) <= static_cast<float>(atof(flagSubstring + flagLength + 1));
}
// Short variant of flag without assigned value.
else
{
return true;
}
}

return false;
}
1 change: 1 addition & 0 deletions src/xrServerEntities/xrServer_Objects_ALife.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class CSE_ALifeObject : public CSE_Abstract, public CRandom
virtual u32 ef_weapon_type() const;
virtual u32 ef_detector_type() const;
#ifdef XRGAME_EXPORTS
virtual bool is_spawn_supplies_flag_set(pcstr value, pcstr flag);
virtual void spawn_supplies(LPCSTR);
virtual void spawn_supplies();
CALifeSimulator& alife() const;
Expand Down

0 comments on commit 95b9bbc

Please sign in to comment.