update
This commit is contained in:
@@ -18,6 +18,45 @@ end
|
|||||||
|
|
||||||
local config = safeRequire("OFBlockConfig")
|
local config = safeRequire("OFBlockConfig")
|
||||||
local spawnProfile = safeRequire("OFSpawnProfile")
|
local spawnProfile = safeRequire("OFSpawnProfile")
|
||||||
|
local sourceCatalog = safeRequire("OFSourceCatalog")
|
||||||
|
|
||||||
|
local function buildLookupSet(values)
|
||||||
|
local lookup = {}
|
||||||
|
if type(values) ~= "table" then
|
||||||
|
return lookup
|
||||||
|
end
|
||||||
|
for _, value in ipairs(values) do
|
||||||
|
local s = tostring(value):lower()
|
||||||
|
lookup[s] = true
|
||||||
|
end
|
||||||
|
return lookup
|
||||||
|
end
|
||||||
|
|
||||||
|
local attachmentTypeLookup = buildLookupSet(sourceCatalog.attachments)
|
||||||
|
local magazineTypeLookup = buildLookupSet(sourceCatalog.magazines)
|
||||||
|
|
||||||
|
local function isMagazineType(itemType)
|
||||||
|
local s = tostring(itemType or ""):lower()
|
||||||
|
if s == "" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if magazineTypeLookup[s] then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if s:sub(1, 10) == "base.clip_" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if s:find("magazine", 1, true) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if s:find("drum", 1, true) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if s:find("clip", 1, true) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local function trim(value)
|
local function trim(value)
|
||||||
if type(value) ~= "string" then
|
if type(value) ~= "string" then
|
||||||
@@ -40,6 +79,25 @@ local function normalizeItemType(value)
|
|||||||
return "Base." .. s
|
return "Base." .. s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getMagazinePartAlias(itemType)
|
||||||
|
local normalized = normalizeItemType(itemType)
|
||||||
|
if not normalized then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local lowered = normalized:lower()
|
||||||
|
if not isMagazineType(lowered) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if lowered:sub(1, 10) == "base.clip_" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local short = normalized:match("^Base%.(.+)$")
|
||||||
|
if not short or short == "" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return "Base.Clip_" .. short
|
||||||
|
end
|
||||||
|
|
||||||
local function normalizePrefix(value)
|
local function normalizePrefix(value)
|
||||||
local s = trim(value)
|
local s = trim(value)
|
||||||
if not s or s == "" then
|
if not s or s == "" then
|
||||||
@@ -271,9 +329,16 @@ local ruleMatchers = compileRules(config.rules, aliasMap)
|
|||||||
|
|
||||||
local function compileSpawnProfile(rawProfile)
|
local function compileSpawnProfile(rawProfile)
|
||||||
local managedItemSet = {}
|
local managedItemSet = {}
|
||||||
|
local disabledManagedItemSet = {}
|
||||||
local placementsByList = {}
|
local placementsByList = {}
|
||||||
local managedCount = 0
|
local managedCount = 0
|
||||||
|
|
||||||
|
local function clearPlacementsForItem(itemType)
|
||||||
|
for _, entries in pairs(placementsByList) do
|
||||||
|
entries[itemType] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function addPlacement(listName, itemType, rawWeight)
|
local function addPlacement(listName, itemType, rawWeight)
|
||||||
local cleanList = trim(listName)
|
local cleanList = trim(listName)
|
||||||
local weight = tonumber(rawWeight)
|
local weight = tonumber(rawWeight)
|
||||||
@@ -303,9 +368,18 @@ local function compileSpawnProfile(rawProfile)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if entry.enabled == false then
|
if entry.enabled == false then
|
||||||
|
disabledManagedItemSet[lowered] = true
|
||||||
|
local partAlias = getMagazinePartAlias(normalized)
|
||||||
|
if partAlias then
|
||||||
|
managedItemSet[partAlias:lower()] = true
|
||||||
|
disabledManagedItemSet[partAlias:lower()] = true
|
||||||
|
end
|
||||||
|
clearPlacementsForItem(normalized)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
disabledManagedItemSet[lowered] = nil
|
||||||
|
|
||||||
if type(entry.placements) == "table" then
|
if type(entry.placements) == "table" then
|
||||||
if entry.placements[1] then
|
if entry.placements[1] then
|
||||||
for _, row in ipairs(entry.placements) do
|
for _, row in ipairs(entry.placements) do
|
||||||
@@ -337,10 +411,10 @@ local function compileSpawnProfile(rawProfile)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return managedItemSet, placementsByList, managedCount
|
return managedItemSet, disabledManagedItemSet, placementsByList, managedCount
|
||||||
end
|
end
|
||||||
|
|
||||||
local managedSpawnItems, profilePlacementsByList, managedSpawnItemCount = compileSpawnProfile(spawnProfile)
|
local managedSpawnItems, disabledManagedSpawnItems, profilePlacementsByList, managedSpawnItemCount = compileSpawnProfile(spawnProfile)
|
||||||
|
|
||||||
local function isRuleActive(rule, nowEpoch)
|
local function isRuleActive(rule, nowEpoch)
|
||||||
if not rule.enabled then
|
if not rule.enabled then
|
||||||
@@ -389,6 +463,51 @@ local function shouldBlock(listName, itemType, nowEpoch)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function shouldBlockByRulesOnly(listName, itemType, nowEpoch)
|
||||||
|
local listLower = (trim(listName) or "unknown"):lower()
|
||||||
|
local itemLower = normalizeItemType(itemType)
|
||||||
|
if not itemLower then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
itemLower = itemLower:lower()
|
||||||
|
|
||||||
|
if itemMatches(itemLower, globalMatcher) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, entry in ipairs(byListMatchers) do
|
||||||
|
if entry.pattern ~= "" and listMatchesPattern(listLower, entry.pattern) and itemMatches(itemLower, entry.matcher) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, rule in ipairs(ruleMatchers) do
|
||||||
|
if isRuleActive(rule, nowEpoch) and listMatches(listLower, rule.listPatterns) and itemMatches(itemLower, rule.matcher) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isWeaponPartBlockedForUpgrades(itemType, nowEpoch)
|
||||||
|
local normalized = normalizeItemType(itemType)
|
||||||
|
if not normalized then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local lowered = normalized:lower()
|
||||||
|
if not attachmentTypeLookup[lowered] and not isMagazineType(lowered) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if disabledManagedSpawnItems[lowered] then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return shouldBlockByRulesOnly("GGSWeaponUpgrades", normalized, nowEpoch)
|
||||||
|
end
|
||||||
|
|
||||||
local function removeBlockedEntries(items, listName, nowEpoch)
|
local function removeBlockedEntries(items, listName, nowEpoch)
|
||||||
if type(items) ~= "table" then
|
if type(items) ~= "table" then
|
||||||
return 0
|
return 0
|
||||||
@@ -503,6 +622,52 @@ local function applyProfilePlacements()
|
|||||||
return added
|
return added
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function removeBlockedAttachmentEntries(entries, nowEpoch)
|
||||||
|
if type(entries) ~= "table" then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local removed = 0
|
||||||
|
for i = #entries, 1, -1 do
|
||||||
|
local value = entries[i]
|
||||||
|
if type(value) == "string" and isWeaponPartBlockedForUpgrades(value, nowEpoch) then
|
||||||
|
table.remove(entries, i)
|
||||||
|
removed = removed + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return removed
|
||||||
|
end
|
||||||
|
|
||||||
|
local function patchWeaponUpgradeAttachmentSources(nowEpoch)
|
||||||
|
local removed = 0
|
||||||
|
|
||||||
|
if type(GGSWeaponUpgrades) == "table" and type(GGSWeaponUpgrades.Lists) == "table" then
|
||||||
|
for _, listDef in pairs(GGSWeaponUpgrades.Lists) do
|
||||||
|
if type(listDef) == "table" then
|
||||||
|
if type(listDef.items) == "table" then
|
||||||
|
removed = removed + removeBlockedAttachmentEntries(listDef.items, nowEpoch)
|
||||||
|
elseif listDef[1] ~= nil then
|
||||||
|
removed = removed + removeBlockedAttachmentEntries(listDef, nowEpoch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(WeaponUpgrades) == "table" then
|
||||||
|
for _, listDef in pairs(WeaponUpgrades) do
|
||||||
|
if type(listDef) == "table" then
|
||||||
|
if type(listDef.items) == "table" then
|
||||||
|
removed = removed + removeBlockedAttachmentEntries(listDef.items, nowEpoch)
|
||||||
|
elseif listDef[1] ~= nil then
|
||||||
|
removed = removed + removeBlockedAttachmentEntries(listDef, nowEpoch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return removed
|
||||||
|
end
|
||||||
|
|
||||||
local function patchAllDistributions()
|
local function patchAllDistributions()
|
||||||
local nowEpoch = nil
|
local nowEpoch = nil
|
||||||
if os and os.time then
|
if os and os.time then
|
||||||
@@ -513,13 +678,14 @@ local function patchAllDistributions()
|
|||||||
removed = removed + patchProceduralDistributions(nowEpoch)
|
removed = removed + patchProceduralDistributions(nowEpoch)
|
||||||
removed = removed + patchNestedDistributionTree("SuburbsDistributions", SuburbsDistributions, nowEpoch)
|
removed = removed + patchNestedDistributionTree("SuburbsDistributions", SuburbsDistributions, nowEpoch)
|
||||||
removed = removed + patchNestedDistributionTree("VehicleDistributions", VehicleDistributions, nowEpoch)
|
removed = removed + patchNestedDistributionTree("VehicleDistributions", VehicleDistributions, nowEpoch)
|
||||||
|
local upgradeAttachmentRemoved = patchWeaponUpgradeAttachmentSources(nowEpoch)
|
||||||
local added = applyProfilePlacements()
|
local added = applyProfilePlacements()
|
||||||
|
|
||||||
if ItemPickerJava and ItemPickerJava.Parse then
|
if ItemPickerJava and ItemPickerJava.Parse then
|
||||||
ItemPickerJava.Parse()
|
ItemPickerJava.Parse()
|
||||||
end
|
end
|
||||||
|
|
||||||
print(string.format("[OFDistributionBlocker] Removed %d entries and added %d profile entries (managed items: %d).", removed, added, managedSpawnItemCount))
|
print(string.format("[OFDistributionBlocker] Removed %d distro entries, removed %d upgrade attachments, and added %d profile entries (managed items: %d).", removed, upgradeAttachmentRemoved, added, managedSpawnItemCount))
|
||||||
end
|
end
|
||||||
|
|
||||||
Events.OnInitWorld.Add(patchAllDistributions)
|
Events.OnInitWorld.Add(patchAllDistributions)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
name=Opinionated Firearms
|
name=Opinionated Firearms
|
||||||
id=opinionated_firearms
|
id=hrsys_opinionated_firearms
|
||||||
author=Riggs0
|
author=Riggs0
|
||||||
modversion=1.0.0
|
modversion=1.0.0
|
||||||
versionMin=42.12.13
|
versionMin=42.12.13
|
||||||
require=GaelGunStore_ALPHA
|
require=\GaelGunStore_ALPHA
|
||||||
|
|
||||||
description=Opinionated Firearms spawn distribution controller for GaelGunStore (B42).
|
description=Opinionated Firearms spawn distribution controller for GaelGunStore (B42).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Opinionated Firearms
|
# Opinionated Firearms
|
||||||
|
|
||||||
Project Zomboid B42 patch mod + tooling for managing `GaelGunStore` firearm/attachment spawn distribution.
|
Project Zomboid B42 patch mod + tooling for managing `GaelGunStore` firearm/attachment/magazine spawn distribution.
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ node tools/ggs-dist-cli.js extract --ggs-root source/GaelGunStore/42 --out data/
|
|||||||
Output contains:
|
Output contains:
|
||||||
|
|
||||||
- all firearms/attachments from GGS scripts
|
- all firearms/attachments from GGS scripts
|
||||||
|
- magazines from GGS loot distribution data
|
||||||
- where they spawn (`list`)
|
- where they spawn (`list`)
|
||||||
- base spawn weight (`weight`)
|
- base spawn weight (`weight`)
|
||||||
- sandbox key (`sv`) used by GGS spawn multipliers
|
- sandbox key (`sv`) used by GGS spawn multipliers
|
||||||
@@ -65,6 +66,8 @@ Main patcher: `42/media/lua/server/distribution/OFDistributionBlocker.lua`
|
|||||||
|
|
||||||
- loads block rules (`OFBlockConfig`) and spawn profile (`OFSpawnProfile`)
|
- loads block rules (`OFBlockConfig`) and spawn profile (`OFSpawnProfile`)
|
||||||
- removes blocked/managed entries from distributions
|
- removes blocked/managed entries from distributions
|
||||||
|
- removes disabled attachment entries from weapon auto-upgrade attachment pools
|
||||||
|
- removes disabled magazine entries from weapon auto-upgrade attachment pools
|
||||||
- re-adds managed item placements with chosen weights from spawn profile
|
- re-adds managed item placements with chosen weights from spawn profile
|
||||||
- reparses ItemPicker after patching
|
- reparses ItemPicker after patching
|
||||||
|
|
||||||
|
|||||||
BIN
art/logo.png
Normal file
BIN
art/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
BIN
art/logo.psd
Normal file
BIN
art/logo.psd
Normal file
Binary file not shown.
@@ -76,6 +76,44 @@ local function mergeByList(baseByList, extraByList)
|
|||||||
return merged
|
return merged
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function isMagazineType(itemType)
|
||||||
|
local s = tostring(itemType or ""):lower()
|
||||||
|
if s == "" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if s:sub(1, 10) == "base.clip_" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if s:find("magazine", 1, true) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if s:find("drum", 1, true) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if s:find("clip", 1, true) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function collectMagazines(values)
|
||||||
|
local out = {}
|
||||||
|
local seen = {}
|
||||||
|
if type(values) ~= "table" then
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
for _, value in ipairs(values) do
|
||||||
|
if isMagazineType(value) then
|
||||||
|
local key = tostring(value):lower()
|
||||||
|
if not seen[key] then
|
||||||
|
seen[key] = true
|
||||||
|
out[#out + 1] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
local defaults = safeRequire("OFBlockRules_Default")
|
local defaults = safeRequire("OFBlockRules_Default")
|
||||||
local user = safeRequire("OFBlockRules_User")
|
local user = safeRequire("OFBlockRules_User")
|
||||||
local sourceCatalog = safeRequire("OFSourceCatalog")
|
local sourceCatalog = safeRequire("OFSourceCatalog")
|
||||||
@@ -83,6 +121,7 @@ local sourceCatalog = safeRequire("OFSourceCatalog")
|
|||||||
local aliasCatalog = {
|
local aliasCatalog = {
|
||||||
firearms = sourceCatalog.firearms or {},
|
firearms = sourceCatalog.firearms or {},
|
||||||
attachments = sourceCatalog.attachments or {},
|
attachments = sourceCatalog.attachments or {},
|
||||||
|
magazines = sourceCatalog.magazines or collectMagazines(sourceCatalog.attachments),
|
||||||
ggs_all = sourceCatalog.ggs_all or {},
|
ggs_all = sourceCatalog.ggs_all or {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,34 @@
|
|||||||
-- Auto-generated by tools/ggs-dist-cli.js apply
|
-- Auto-generated by tools/ggs-dist-cli.js apply
|
||||||
-- Keep this empty template in git; generated content can overwrite it.
|
-- Generated at: 2026-02-12T19:59:03.395Z
|
||||||
return {
|
return {
|
||||||
items = {},
|
items = {
|
||||||
|
["Base.12GClip"] = {
|
||||||
|
enabled = false,
|
||||||
|
placements = {
|
||||||
|
["ArmyStorageAmmunition"] = 1,
|
||||||
|
["ArmyStorageGuns"] = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["Base.1P78"] = {
|
||||||
|
enabled = false,
|
||||||
|
placements = {
|
||||||
|
["ArmyStorageAmmunition"] = 1,
|
||||||
|
["ArmyStorageGuns"] = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["Base.9mmClip"] = {
|
||||||
|
enabled = false,
|
||||||
|
placements = {
|
||||||
|
["GunStoreMagsAmmo"] = 1,
|
||||||
|
["PoliceStorageGuns"] = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["Base.A2000"] = {
|
||||||
|
enabled = true,
|
||||||
|
placements = {
|
||||||
|
["ArmyStorageAmmunition"] = 1,
|
||||||
|
["ArmyStorageGuns"] = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
-- Auto-generated by tools/ggs-dist-cli.js apply
|
-- Auto-generated by tools/ggs-dist-cli.js apply
|
||||||
-- Generated at: 2026-02-11T22:49:03.184Z
|
-- Generated at: 2026-02-12T19:50:17.985Z
|
||||||
return {
|
return {
|
||||||
items = {
|
items = {
|
||||||
["Base.1P78"] = {
|
["Base.12GClip"] = {
|
||||||
enabled = true,
|
|
||||||
placements = {
|
|
||||||
["ArmyStorageAmmunition"] = 1,
|
|
||||||
["ArmyStorageGuns"] = 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
["Base.1PN93_4"] = {
|
|
||||||
enabled = false,
|
enabled = false,
|
||||||
placements = {
|
placements = {
|
||||||
["ArmyStorageAmmunition"] = 1,
|
["ArmyStorageAmmunition"] = 1,
|
||||||
["ArmyStorageGuns"] = 1,
|
["ArmyStorageGuns"] = 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
["Base.9x39_Silencer"] = {
|
["Base.1P78"] = {
|
||||||
|
enabled = false,
|
||||||
|
placements = {
|
||||||
|
["ArmyStorageAmmunition"] = 1,
|
||||||
|
["ArmyStorageGuns"] = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["Base.9mmClip"] = {
|
||||||
|
enabled = false,
|
||||||
|
placements = {
|
||||||
|
["GunStoreMagsAmmo"] = 1,
|
||||||
|
["PoliceStorageGuns"] = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["Base.A2000"] = {
|
||||||
enabled = true,
|
enabled = true,
|
||||||
placements = {
|
placements = {
|
||||||
["ArmyStorageAmmunition"] = 1,
|
["ArmyStorageAmmunition"] = 1,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
|
"generatedAt": "2026-02-12T19:50:12.875Z",
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"item": "Base.1P78",
|
"item": "Base.A2000",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"placements": [
|
"placements": [
|
||||||
{
|
{
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"item": "Base.1PN93_4",
|
"item": "Base.1P78",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"placements": [
|
"placements": [
|
||||||
{
|
{
|
||||||
@@ -30,8 +31,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"item": "Base.9x39_Silencer",
|
"item": "Base.12GClip",
|
||||||
"enabled": true,
|
"enabled": false,
|
||||||
"placements": [
|
"placements": [
|
||||||
{
|
{
|
||||||
"list": "ArmyStorageAmmunition",
|
"list": "ArmyStorageAmmunition",
|
||||||
@@ -42,6 +43,20 @@
|
|||||||
"weight": 1
|
"weight": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "Base.9mmClip",
|
||||||
|
"enabled": false,
|
||||||
|
"placements": [
|
||||||
|
{
|
||||||
|
"list": "GunStoreMagsAmmo",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"list": "PoliceStorageGuns",
|
||||||
|
"weight": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
4
mod.info
4
mod.info
@@ -1,8 +1,8 @@
|
|||||||
name=Opinionated Firearms
|
name=Opinionated Firearms
|
||||||
id=opinionated_firearms
|
id=hrsys_opinionated_firearms
|
||||||
author=Riggs0
|
author=Riggs0
|
||||||
modversion=1.0.0
|
modversion=1.0.0
|
||||||
versionMin=42.12.13
|
versionMin=42.12.13
|
||||||
require=GaelGunStore_ALPHA
|
require=\GaelGunStore_ALPHA
|
||||||
|
|
||||||
description=Opinionated Firearms spawn distribution controller for GaelGunStore (B42).
|
description=Opinionated Firearms spawn distribution controller for GaelGunStore (B42).
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Usage:
|
|||||||
node tools/ggs-dist-cli.js apply --profile <file> [--out <file>]
|
node tools/ggs-dist-cli.js apply --profile <file> [--out <file>]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
extract Build a full firearm/attachment spawn catalog from GaelGunStore.
|
extract Build a full firearm/attachment/magazine spawn catalog from GaelGunStore.
|
||||||
apply Convert a webapp profile JSON into Lua used by the B42 mod patcher.
|
apply Convert a webapp profile JSON into Lua used by the B42 mod patcher.
|
||||||
`.trim());
|
`.trim());
|
||||||
}
|
}
|
||||||
@@ -117,6 +117,26 @@ function normalizeItemType(item) {
|
|||||||
return trimmed.includes(".") ? trimmed : `Base.${trimmed}`;
|
return trimmed.includes(".") ? trimmed : `Base.${trimmed}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMagazineType(itemType) {
|
||||||
|
const s = String(itemType || "").toLowerCase();
|
||||||
|
if (!s) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (s.startsWith("base.clip_")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (s.includes("magazine")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (s.includes("drum")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (s.includes("clip")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function buildCatalog(ggsRoot) {
|
function buildCatalog(ggsRoot) {
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const firearmScriptsDir = path.join(ggsRoot, "media", "scripts", "Firearms");
|
const firearmScriptsDir = path.join(ggsRoot, "media", "scripts", "Firearms");
|
||||||
@@ -137,12 +157,24 @@ function buildCatalog(ggsRoot) {
|
|||||||
const attachments = collectItemTypesFromScripts(attachmentScriptsDir);
|
const attachments = collectItemTypesFromScripts(attachmentScriptsDir);
|
||||||
const firearmSet = new Set(firearms);
|
const firearmSet = new Set(firearms);
|
||||||
const attachmentSet = new Set(attachments);
|
const attachmentSet = new Set(attachments);
|
||||||
|
const magazineSet = new Set();
|
||||||
|
|
||||||
const lootEntries = parseLootEntries(lootLuaPath);
|
const lootEntries = parseLootEntries(lootLuaPath);
|
||||||
const perItemLoot = new Map();
|
const perItemLoot = new Map();
|
||||||
const allLists = new Set();
|
const allLists = new Set();
|
||||||
const allItems = new Set([...firearms, ...attachments]);
|
const allItems = new Set([...firearms, ...attachments]);
|
||||||
|
|
||||||
|
for (const entry of lootEntries) {
|
||||||
|
const normalized = normalizeItemType(entry.item);
|
||||||
|
if (!normalized) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isMagazineType(normalized)) {
|
||||||
|
magazineSet.add(normalized);
|
||||||
|
allItems.add(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const entry of lootEntries) {
|
for (const entry of lootEntries) {
|
||||||
const normalized = normalizeItemType(entry.item);
|
const normalized = normalizeItemType(entry.item);
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
@@ -171,6 +203,8 @@ function buildCatalog(ggsRoot) {
|
|||||||
let category = "unknown";
|
let category = "unknown";
|
||||||
if (firearmSet.has(itemType)) {
|
if (firearmSet.has(itemType)) {
|
||||||
category = "firearm";
|
category = "firearm";
|
||||||
|
} else if (magazineSet.has(itemType)) {
|
||||||
|
category = "magazine";
|
||||||
} else if (attachmentSet.has(itemType)) {
|
} else if (attachmentSet.has(itemType)) {
|
||||||
category = "attachment";
|
category = "attachment";
|
||||||
}
|
}
|
||||||
@@ -202,6 +236,7 @@ function buildCatalog(ggsRoot) {
|
|||||||
counts: {
|
counts: {
|
||||||
firearms: firearms.length,
|
firearms: firearms.length,
|
||||||
attachments: attachments.length,
|
attachments: attachments.length,
|
||||||
|
magazines: magazineSet.size,
|
||||||
totalItems: items.length,
|
totalItems: items.length,
|
||||||
placementRows: lootEntries.length,
|
placementRows: lootEntries.length,
|
||||||
distributionLists: allLists.size,
|
distributionLists: allLists.size,
|
||||||
@@ -296,7 +331,7 @@ function commandExtract(args) {
|
|||||||
|
|
||||||
console.log(`Extracted catalog: ${outPath}`);
|
console.log(`Extracted catalog: ${outPath}`);
|
||||||
console.log(
|
console.log(
|
||||||
`Items=${catalog.counts.totalItems}, Firearms=${catalog.counts.firearms}, Attachments=${catalog.counts.attachments}, Lists=${catalog.counts.distributionLists}`
|
`Items=${catalog.counts.totalItems}, Firearms=${catalog.counts.firearms}, Attachments=${catalog.counts.attachments}, Magazines=${catalog.counts.magazines}, Lists=${catalog.counts.distributionLists}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div>
|
<div>
|
||||||
<h1>Opinionated Firearms Spawn List Builder</h1>
|
<h1>Opinionated Firearms Spawn List Builder</h1>
|
||||||
<p>Edit firearm/attachment spawn enablement, placement lists, and spawn rates.</p>
|
<p>Edit firearm/attachment/magazine spawn enablement, placement lists, and spawn rates.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<label class="file-button">
|
<label class="file-button">
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
<option value="all">All categories</option>
|
<option value="all">All categories</option>
|
||||||
<option value="firearm">Firearms</option>
|
<option value="firearm">Firearms</option>
|
||||||
<option value="attachment">Attachments</option>
|
<option value="attachment">Attachments</option>
|
||||||
|
<option value="magazine">Magazines</option>
|
||||||
<option value="unknown">Unknown</option>
|
<option value="unknown">Unknown</option>
|
||||||
</select>
|
</select>
|
||||||
<select id="spawnFilter">
|
<select id="spawnFilter">
|
||||||
|
|||||||
Reference in New Issue
Block a user