Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 105 additions & 12 deletions csgo_gc/item_schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ ItemInfo::ItemInfo(uint32_t defIndex)
, m_rarity{ ItemSchema::RarityCommon }
, m_quality{ ItemSchema::QualityUnique }
, m_supplyCrateSeries{ 0 }
, m_tournamentEventId{ 0 }
{
// RecursiveParseItem parses the rest
}
Expand Down Expand Up @@ -555,7 +556,13 @@ bool ItemSchema::SelectItemFromCrate(const CSOEconItem &crate, CSOEconItem &item
lootListItems.reserve(32); // overkill
bool containsUnusuals = GetLootListItems(lootList, lootListItems);

// stattrak def
if (!lootListItems.size())
{
assert(false);
return false;
}

// handle stattrak
GenerateStatTrak generateStatTrak = GenerateStatTrak::No;
if (lootList.willProduceStatTrak)
{
Expand All @@ -566,19 +573,94 @@ bool ItemSchema::SelectItemFromCrate(const CSOEconItem &crate, CSOEconItem &item
generateStatTrak = GenerateStatTrak::Maybe;
}

if (lootListItems.size())
// group items by rarity and map rarities to base weights
std::unordered_map<uint32_t, std::vector<const LootListItem*>> itemsByRarity;
std::unordered_map<uint32_t, uint32_t> rarityWeights;
uint32_t totalWeight = 0;

// rarity weight map
const std::unordered_map<uint32_t, uint32_t> baseWeights = {
{RarityDefault, 15625}, // Consumer (Gray)
{RarityCommon, 3125}, // Industrial (Light Blue)
{RarityUncommon, 625}, // Mil-Spec (Blue)
{RarityRare, 125}, // Restricted (Purple)
{RarityMythical, 25}, // Classified (Pink)
{RarityLegendary, 5} // Covert (Red)
};

// group items by rarity and calculate total weight
for (const auto* lootItem : lootListItems)
{
size_t index = g_random.RandomIndex(lootListItems.size());
const LootListItem &lootListItem = *lootListItems[index];
return EconItemFromLootListItem(lootListItem, item, generateStatTrak);
if (lootItem->quality != QualityUnusual)
{
// if we find an item of this rarity, add its base weight to total
if (itemsByRarity[lootItem->rarity].empty() && baseWeights.count(lootItem->rarity))
{
rarityWeights[lootItem->rarity] = baseWeights.at(lootItem->rarity);
totalWeight += baseWeights.at(lootItem->rarity);
}
itemsByRarity[lootItem->rarity].push_back(lootItem);
}
}
else

// check for industrial grade items (for souvenir packages)
bool hasIndustrialItems = false;
if (itemsByRarity.count(RarityCommon) > 0 && !itemsByRarity[RarityCommon].empty())
{
assert(false);
return false;
hasIndustrialItems = true;
Platform::Print("[GC] Found consumer grade items (%zu)\n", itemsByRarity[RarityCommon].size());
}

return true;
// check for golds
if (!lootList.isUnusual && containsUnusuals)
{
std::vector<const LootListItem*> unusualItems;
if (g_random.Uint32(0, totalWeight + 2) < 2) // 2 weight
{
for (const auto* lootItem : lootListItems)
{
if (lootItem->quality == QualityUnusual)
{
unusualItems.push_back(lootItem);
}
}

if (!unusualItems.empty())
{
size_t index = g_random.RandomIndex(unusualItems.size());
return EconItemFromLootListItem(*unusualItems[index], item, generateStatTrak);
}
}
}

uint32_t roll = g_random.Uint32(0, totalWeight);
uint32_t currentWeight = 0;

for (const auto& [rarity, items] : itemsByRarity)
{
if (rarityWeights.count(rarity))
{
currentWeight += rarityWeights[rarity];
if (roll < currentWeight)
{
// random item from this rarity
size_t index = g_random.RandomIndex(items.size());
bool result = EconItemFromLootListItem(*items[index], item, generateStatTrak);

// set quality to 9 if souvenir package
if (result && itemSearch->second.m_tournamentEventId != 0 && hasIndustrialItems)
{
Platform::Print("[GC] Setting quality to Tournament\n");
item.set_quality(QualityTournament);
}

return result;
}
}
}

assert(false);
return false;
}

void ItemSchema::ParseItems(const KeyValue *itemsKey, const KeyValue *prefabsKey)
Expand Down Expand Up @@ -670,6 +752,12 @@ void ItemSchema::ParseItemRecursive(ItemInfo &info, const KeyValue &itemKey, con
{
info.m_supplyCrateSeries = supplyCrateSeries->GetNumber<uint32_t>("value");
}

const KeyValue* tournamentEventId = attributes->GetSubkey("tournament event id");
if (tournamentEventId)
{
info.m_tournamentEventId = tournamentEventId->GetNumber<uint32_t>("value");
}
}
}

Expand Down Expand Up @@ -792,18 +880,23 @@ static LootListItemType LootListItemTypeFromName(std::string_view name, std::str
return LootListItemPaintable;
}

void ItemSchema::ParseLootLists(const KeyValue *lootListsKey, bool unusual)
void ItemSchema::ParseLootLists(const KeyValue *lootListsKey, bool parentIsUnusual)
{
m_lootLists.reserve(lootListsKey->SubkeyCount());

for (const KeyValue &lootListKey : *lootListsKey)
{
std::string_view listName = lootListKey.Name();

// check if this list should be treated as unusual
bool isUnusual = parentIsUnusual && (listName.find("unusual") != std::string_view::npos);

auto emplace = m_lootLists.emplace(std::piecewise_construct,
std::forward_as_tuple(lootListKey.Name()),
std::forward_as_tuple());

LootList &lootList = emplace.first->second;
lootList.isUnusual = unusual;
lootList.isUnusual = isUnusual; // only set unusual if parent is unusual AND name contains "unusual"

for (const KeyValue &entryKey : lootListKey)
{
Expand Down Expand Up @@ -841,7 +934,7 @@ void ItemSchema::ParseLootLists(const KeyValue *lootListsKey, bool unusual)
LootListItem item;
if (ParseLootListItem(item, entryName))
{
if (unusual)
if (isUnusual)
{
// override the quality here...
item.quality = QualityUnusual;
Expand Down
1 change: 1 addition & 0 deletions csgo_gc/item_schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ItemInfo
uint32_t m_rarity;
uint32_t m_quality;
uint32_t m_supplyCrateSeries; // cases only
uint32_t m_tournamentEventId; // souvenirs only
};

class PaintKitInfo
Expand Down
114 changes: 114 additions & 0 deletions examples/unusual_loot_lists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,117 @@
"crate_dhw13_promo_common"
{
"[hy_desert]weapon_g3sg1" "1"
"[so_sand]weapon_p250" "1"
"[sp_mesh_sand]weapon_scar20" "1"
"[sp_spray_sand]weapon_p90" "1"
"[sp_tape_short_sand]weapon_mp9" "1"
"[sp_zebracam]weapon_nova" "1"
"[so_pmc]weapon_elite" "1"
"[so_pmc]weapon_scar20" "1"
"[so_moss]weapon_ssg08" "1"
"[sp_mesh_army]weapon_tec9" "1"
"[sp_spray_army]weapon_mp7" "1"
"[hy_forest_boreal]weapon_p250" "1"
"[so_moss]weapon_xm1014" "1"
"[so_stormfront]weapon_aug" "1"
"[sp_spray_desert_sage]weapon_galilar" "1"
"[sp_tape_dots_waves]weapon_sg556" "1"
"[sp_tape_short_jungle]weapon_g3sg1" "1"
"[so_olive]weapon_tec9" "1"
"[so_pmc]weapon_aug" "1"
"[so_space_marine]weapon_famas" "1"
"[so_sand]weapon_nova" "1"
"[sp_tape_short_sand]weapon_bizon" "1"
"[hy_ddpat_urb]weapon_ump45" "1"
"[so_space_marine]weapon_elite" "1"
"[hy_arctic_contrast]weapon_g3sg1" "1"
"[hy_forest_night]weapon_fiveseven" "1"
"[sp_mesh_arctic_contrast]weapon_nova" "1"
"[sp_tape_short_urban]weapon_bizon" "1"
"[sp_tape]weapon_p250" "1"
"[so_pmc]weapon_fiveseven" "1"
"[so_space_marine]weapon_aug" "1"
"[sp_mesh_tan]weapon_g3sg1" "1"
"[sp_dapple]weapon_p90" "1"
"[sp_mesh_slashes]weapon_galilar" "1"
}
"crate_dhw13_promo_uncommon"
{
"[sp_snake]weapon_sawedoff" "1"
"[sp_mesh_tan]weapon_ak47" "1"
"[sp_tape_orange]weapon_fiveseven" "1"
"[sp_palm]weapon_mac10" "1"
"[hy_varicamo]weapon_tec9" "1"
"[sp_leaves]weapon_usp_silencer" "1"
"[sp_mesh_forest_fire]weapon_aug" "1"
"[sp_tape_orange]weapon_mp9" "1"
"[hy_varicamo]weapon_g3sg1" "1"
"[hy_varicamo]weapon_galilar" "1"
"[sp_mesh_python]weapon_m249" "1"
"[aq_blued]weapon_xm1014" "1"
"[sp_mesh_tan]weapon_awp" "1"
"[hy_mottled_sand]weapon_deagle" "1"
"[hy_reef]weapon_famas" "1"
"[hy_varicamo_night]weapon_bizon" "1"
"[so_red]weapon_nova" "1"
"[hy_gelpen]weapon_ump45" "1"
"[hy_granite]weapon_hkp2000" "1"
"[aq_forced]weapon_elite" "1"
"[hy_forest_boreal]weapon_m4a1_silencer" "1"
"[hy_varicamo_desert]weapon_xm1014" "1"
"[so_red]weapon_mac10" "1"
"[hy_ddpat_urb]weapon_m4a1" "1"
"[am_urban]weapon_mag7" "1"
"[am_urban]weapon_p250" "1"
"[am_carbon_fiber]weapon_scar20" "1"
"[sp_twigs]weapon_p90" "1"
"[so_olive]weapon_glock" "1"
"[sp_tape_orange]weapon_mp7" "1"
"[sp_palm_shadow]weapon_ssg08" "1"
"[hy_varicamo_desert]weapon_negev" "1"
"[sp_mesh_python]weapon_sg556" "1"
}
"crate_dhw13_promo_rare"
{
"[aq_brass]weapon_bizon" "1"
"[hy_varicamo]weapon_m4a1_silencer" "1"
"[aq_damascus_sg553]weapon_sg556" "1"
"[sp_mesh_hot_and_cold]weapon_famas" "1"
"[am_crystallized_silver]weapon_fiveseven" "1"
"[aa_fade_grassland]weapon_ssg08" "1"
"[an_navy]weapon_sg556" "1"
"[hy_varicamo_night]weapon_usp_silencer" "1"
"[sp_mesh_hot_and_cold]weapon_p90" "1"
"[so_red]weapon_glock" "1"
"[an_navy]weapon_mp7" "1"
"[hy_varicamo_red]weapon_sawedoff" "1"
"[hy_varicamo_urban]weapon_deagle" "1"
"[aa_fade_metallic]weapon_sawedoff" "1"
"[an_red]weapon_mp9" "1"
"[aa_flames]weapon_ump45" "1"
"[aa_fade_metallic]weapon_mac10" "1"
}
"crate_dhw13_promo_mythical"
{
"[aa_fade_metallic]weapon_hkp2000" "1"
"[so_orange_accents]weapon_m4a1_silencer" "1"
"[am_crystallized_blue]weapon_elite" "1"
"[hy_snakeskin]weapon_awp" "1"
"[am_crystallized]weapon_tec9" "1"
"[so_yellow]weapon_mag7" "1"
}
"crate_dhw13_promo_legendary"
{
"[aa_fade_metallic_revolver]weapon_revolver" "1"
}
"crate_dhw13_promo"
{
"crate_dhw13_promo_common" "1"
"crate_dhw13_promo_uncommon" "1"
"crate_dhw13_promo_rare" "1"
"crate_dhw13_promo_mythical" "1"
"crate_dhw13_promo_legendary" "1"
}
"unusual_revolving_list"
{
"[aa_fade]weapon_bayonet" "1"
Expand Down