Module:BATTD costs

Revision as of 11:44, 23 June 2026 by Polavux (talk | contribs)

Documentation for this module may be created at Module:BATTD costs/doc

local p = {}

-- helper functions

local function GetTotalCosts(baseCost, initialPurchasedUpgrades, accessibleUpgrades, nextUpgrades,
	upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
	local sFormat = string.format
	local tConcat = table.concat
	local sGsub = string.gsub
	
	-- create the table headers
	local headerRow = {"{|class=\"wikitable sortable\" style=\"text-align:center\"\n!Total cost!!Sell value"}
	
	for upgradeId, _ in pairs(accessibleUpgrades) do
		headerRow[#headerRow+1] = sFormat("!![[%s|%s]]", upgradesById[upgradeId]._pageName, upgradesById[upgradeId].name)
	end
	
	local outputTable = {tConcat(headerRow, "")}
	
	-- english-only alternative to FormatNum that takes up less time
	local function FormatNum(num)
	    local formatted = tostring(num)
	    
	    -- skip if less than 1000
	    local k = (num > 999 and 1 or 0)
	    while k ~= 0 do
	        formatted, k = sGsub(formatted, "^(-?%d+)(%d%d%d)", "%1,%2")
	    end
	    
	    return formatted
	end
	
	local function IsUpgradeAvailable(myUpgradeId, purchasedUpgrades)
		for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
			if not (prereqId == "root" or purchasedUpgrades[prereqId]) then return false end
		end
		
		for _, upgradeLock in ipairs(upgradeLocksById[myUpgradeId]) do
			if purchasedUpgrades[upgradeLock] then return false end
		end
		
		return not purchasedUpgrades[myUpgradeId]
	end
	
	local function GetPurchasedAndAvailableUpgrades(purchasedUpgrades, availableUpgradeId)
		-- skip this upgrade if we don't have all the prereqs
		for _, prereqId in ipairs(upgradePrereqsById[availableUpgradeId]) do
			if not (prereqId == "root" or purchasedUpgrades[prereqId]) then return nil, nil end
		end
		
		-- skip this upgrade if it's locked by an already purchased upgrade
		for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
			if upgradeLocksById[purchasedUpgradeId][availableUpgradeId] then return nil, nil end
		end
		
		-- shallow copy purchasedUpgrades but with this upgrade included in it now
		local purchasedUpgradesNext = { [availableUpgradeId] = true }
		
		for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
			purchasedUpgradesNext[purchasedUpgradeId] = true
			
			-- lock this upgrade combination so we never retread this ground again
			if not initialPurchasedUpgrades[purchasedUpgradeId] then
				upgradeLocksById[purchasedUpgradeId][availableUpgradeId] = true
				upgradeLocksById[availableUpgradeId][purchasedUpgradeId] = true
			end
		end
		
		-- determine new availableUpgrades
		local availableUpgradesNext = {}
		
		for accessibleUpgradeId, _ in pairs(accessibleUpgrades) do
			if IsUpgradeAvailable(accessibleUpgradeId, purchasedUpgradesNext) then availableUpgradesNext[accessibleUpgradeId] = true end
		end
		
		return purchasedUpgradesNext, availableUpgradesNext
	end
	
	local function RecursiveGetTotalCosts(currentCost, purchasedUpgrades, availableUpgrades)
		row = {sFormat("\n|-\n|$%s||$%s", FormatNum(currentCost), FormatNum(currentCost*0.7))}
		
		mw.logObject(purchasedUpgrades)
		mw.logObject(availableUpgrades)
		
		for upgradeId, _ in pairs(accessibleUpgrades) do
			row[#row+1] = sFormat("||%s", purchasedUpgrades[upgradeId] and Y or N)
		end
		
		outputTable[#outputTable+1] = tConcat(row, "")
		
		for availableUpgradeId, _ in pairs(availableUpgrades) do
			local purchasedUpgradesNext, availableUpgradesNext = GetPurchasedAndAvailableUpgrades(purchasedUpgrades, availableUpgradeId)
			
			if purchasedUpgradesNext and availableUpgradesNext then
	
				--if #outputTable == 20 then return end
				RecursiveGetTotalCosts(currentCost + upgradesById[availableUpgradeId].cost, purchasedUpgradesNext, availableUpgradesNext)
			end
		end
	end
	
	RecursiveGetTotalCosts(baseCost, initialPurchasedUpgrades, nextUpgrades)
	
	outputTable[#outputTable+1] = "\n|}"
	
	return tConcat(outputTable, "")
end

local function SetUpTables(towerName)
	local sFormat = string.format
	local query = mw.ext.cargo.query
	
	local upgradesById = {}				-- key:value table  - upgrade:upgrade data
	local upgradeLocksById = {}			-- key:values table - upgrades:upgrades that this upgrade locks
	local upgradePrereqsById = {}		-- key:values table - upgrade:prerequisites to purchase this upgrade
	local upgradePrereqsOfById = {		-- key:values table - upgrades:upgrades this is a prerequisite of
		root = {}
	}
	
	-- cargo queries for data
	local upgrades, upgradePrereqs, upgradeLocks =
	query("battd_upgrades", "_pageName, id, name, tower, cost", {
		where=sFormat("tower='%s'", towerName),
		orderBy="cost"
	}),
	query("battd_upgrades=main, battd_upgrades__previous=prev", "main.id=thisId, prev._value=prevId", {
		where=sFormat("main.tower='%s' AND NOT unused", towerName),
		join="main._ID=prev._RowID"
	}),
	query("battd_upgrades=main, battd_upgrades__locked_by_upgrades=lock", "main.id=thisId, lock._value=lockId", {
		where=sFormat("main.tower='%s' AND NOT unused", towerName),
		join="main._ID=lock._RowID"
	})

	-- build upgradesById
	for i, upgrade in ipairs(upgrades) do
		upgradesById[upgrade.id] = upgrade
		if upgrade.name == upgradeName then upgradeId = upgrade.id end
		
		upgradeLocksById[upgrade.id] = {}
	end

	-- build upgradePrereqsById, upgradePrereqsOfById
	for i, upgrade in ipairs(upgradePrereqs) do
		-- upgrade:next table
		if upgrade.prevId then
			if upgradePrereqsOfById[upgrade.prevId] then
				upgradePrereqsOfById[upgrade.prevId][#upgradePrereqsOfById[upgrade.prevId]+1] = upgrade.thisId
			else
				upgradePrereqsOfById[upgrade.prevId] = {upgrade.thisId}
			end
		else
			-- if this upgrade has no prereqs, give it a fake upgrade called "root" as a prereq
			upgradePrereqsOfById["root"][#upgradePrereqsOfById["root"]+1] = upgrade.thisId
			upgradePrereqsById[upgrade.thisId] = {"root"}
		end
		
		-- upgrade:prev table
		if upgradePrereqsById[upgrade.thisId] then
			upgradePrereqsById[upgrade.thisId][#upgradePrereqsById[upgrade.thisId]+1] = upgrade.prevId
		else
			upgradePrereqsById[upgrade.thisId] = {upgrade.prevId}
		end
	end

	-- build upgradeLocksById
	for _, upgrade in ipairs(upgradeLocks) do
		if upgrade.lockId then
			upgradeLocksById[upgrade.thisId][upgrade.lockId] = true
		end
	end
	
	-- tower cost + total cost of this upgrade and all its prereqs
	local baseCost = tonumber(query("battd_characters", "cost", {
		where=sFormat("name='%s'", towerName)
	})[1].cost)

	return upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost
end

local function Upgrade(towerName, upgradeName, Y, N)
	local upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost = SetUpTables(towerName)
	
	local upgradeId = ""
	
	local purchasedUpgrades = {}		-- this upgrade and any other upgrades that had to already be purchased to get it
	local accessibleUpgrades = {}		-- list of upgrades that are possible to reach from this upgrade
	local nextUpgrades = {}				-- list of upgrades that are currently purchasable from this upgrade
	
	-- build purchasedUpgrades recursively
	local function GetPurchasedUpgrades(myUpgradeId)
		baseCost = baseCost + upgradesById[myUpgradeId].cost
		purchasedUpgrades[myUpgradeId] = true
		
		for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
			if prereqId ~= "root" then GetPurchasedUpgrades(myUpgradeId) end
		end
	end
	
	-- generate a list of upgrades that are reachable from this upgrade, but do not have this upgrade as a prerequisite
	-- also generate a list of upgrades that are immediately reachable from this upgrade
	local function DetermineAccessibility(myUpgradeId)
		if myUpgradeId == upgradeId then return end
		
		-- if this upgrade is locked by an already purchased upgrade, then it is inaccessible
		for _, upgradeLock in ipairs(upgradeLocksById[myUpgradeId]) do
			if purchasedUpgrades[upgradeLock] then return end
		end
		
		-- find upgrades that are reachable from the current upgrades owned
		local isNextUpgrade = true
		for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
			-- upgrades with multiple prereqs are only accessible if we can reach all its prereqs
			-- if it is accessible, it'll be reached again on another recursion
			if not (accessibleUpgrades[prereqId] or purchasedUpgrades[prereqId] or prereqId == "root") or prereqId == upgradeId then return end
			
			if not (purchasedUpgrades[prereqId] or prereqId == "root") or purchasedUpgrades[myUpgradeId] then isNextUpgrade = nil end
		end
		nextUpgrades[myUpgradeId] = isNextUpgrade
		
		if not purchasedUpgrades[myUpgradeId] then accessibleUpgrades[myUpgradeId] = true end
		
		-- determine accessibility of next upgrades
		if upgradePrereqsOfById[myUpgradeId] then 
			for _, prereqOfId in ipairs(upgradePrereqsOfById[myUpgradeId]) do
				DetermineAccessibility(prereqOfId)
			end
		end
	end

	-- start with the root
	for _, prereqOfId in ipairs(upgradePrereqsOfById.root) do
		DetermineAccessibility(prereqOfId)
	end
	
	return GetTotalCosts(baseCost, purchasedUpgrades, accessibleUpgrades, nextUpgrades,
		upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
end

-- GLOBAL FUNCTIONS

p["tower"] = function(frame)
	local upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost = SetUpTables(frame.args[1])
end

p["upgrade"] = function(frame)
	return Upgrade(frame.args[1], frame.args[2], frame:expandTemplate{title = "Y", args = {}}, frame:expandTemplate{title = "N", args = {}})
	--return Upgrade(frame.args[1], frame.args[2], "Y", "N")
end

return p