Module:BATTD costs: Difference between revisions

From Blooncyclopedia, the independent Bloons knowledge base
Jump to navigation Jump to search
mNo edit summary
mNo edit summary
Line 114: Line 114:
root = {}
root = {}
}
}
local purchasedUpgrades = {}
local purchasedUpgrades = {} -- this upgrade and any other upgrades that had to already be purchased to get it
local accessibleUpgrades = {} -- list of upgrades that we can get from this upgrade
local accessibleUpgrades = {} -- list of upgrades that are possible to reach from this upgrade
local nextUpgrades = {}
local nextUpgrades = {} -- list of upgrades that are currently purchasable from this upgrade
-- cargo queries for data
-- cargo queries for data
Line 163: Line 163:


-- build upgradeLocksById
-- build upgradeLocksById
for i, upgrade in ipairs(upgradeLocks) do
for _, upgrade in ipairs(upgradeLocks) do
if upgrade.lockId then
if upgrade.lockId then
if upgradeLocksById[upgrade.thisId] then
if upgradeLocksById[upgrade.thisId] then
Line 173: Line 173:
end
end
-- build purchasedUpgrades
-- build purchasedUpgrades recursively
local function GetPurchasedUpgrades(myUpgradeId)
local function GetPurchasedUpgrades(myUpgradeId)
local cost = upgradesById[myUpgradeId].cost
local cost = upgradesById[myUpgradeId].cost
Line 191: Line 191:
-- generate a list of upgrades that are reachable from this upgrade, but do not have this upgrade as a prerequisite
-- 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
-- also generate a list of upgrades that are immediately reachable from this upgrade
local function GetAccessibleUpgrades(myUpgradeId)
local function DetermineAccessibility(myUpgradeId)
for _, prereqOfId in ipairs(upgradePrereqsOfById[myUpgradeId]) do
if myUpgradeId == upgradeId then return end
if prereqOfId ~= upgradeId then
local doNext = true
-- if this upgrade is locked by an already purchased upgrade, then it is inaccessible
-- if this upgrade is locked by an already purchased upgrade, then it is inaccessible
if upgradeLocksById[myUpgradeId] then
if upgradeLocksById[prereqOfId] then
for _, upgradeLock in ipairs(upgradeLocksById[myUpgradeId]) do
for _, upgradeLock in ipairs(upgradeLocksById[prereqOfId]) do
if purchasedUpgrades[upgradeLock] then return end
if purchasedUpgrades[upgradeLock] then
end
doNext = false
end
break
end
-- find upgrades that are reachable from the current upgrades owned
end
local isNextUpgrade = true
end
for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
-- upgrades with multiple prereqs are only accessible if we can reach all its prereqs
-- 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 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
for _, prereqId in ipairs(upgradePrereqsById[prereqOfId]) do
if not (accessibleUpgrades[prereqId] or purchasedUpgrades[prereqId] or prereqId == "root") or prereqId == upgradeId then
if not (purchasedUpgrades[prereqId] or prereqId == "root") or purchasedUpgrades[myUpgradeId] then isNextUpgrade = nil end
doNext = false
end
break
nextUpgrades[myUpgradeId] = isNextUpgrade
end
if not purchasedUpgrades[myUpgradeId] then accessibleUpgrades[myUpgradeId] = true end
if (prereqId == "root" or purchasedUpgrades[prereqId]) and not purchasedUpgrades[prereqOfId] then nextUpgrades[prereqOfId] = true end
end
-- determine accessibility of next upgrades
if upgradePrereqsOfById[myUpgradeId] then  
if doNext then
for _, prereqOfId in ipairs(upgradePrereqsOfById[myUpgradeId]) do
if not purchasedUpgrades[prereqOfId] then accessibleUpgrades[prereqOfId] = true end
DetermineAccessibility(prereqOfId)
-- do not check leaves
if upgradePrereqsOfById[prereqOfId] then GetAccessibleUpgrades(prereqOfId) end
end
end
end
end
end
end
-- start with the root
for _, prereqOfId in ipairs(upgradePrereqsOfById.root) do
DetermineAccessibility(prereqOfId)
end
end
GetAccessibleUpgrades("root")
return GetTotalCosts(baseCost, purchasedUpgrades, accessibleUpgrades, nextUpgrades,
return GetTotalCosts(baseCost, purchasedUpgrades, accessibleUpgrades, nextUpgrades,
upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
Line 238: Line 238:


p["upgrade"] = function(frame)
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], frame:expandTemplate{title = "Y", args = {}}, frame:expandTemplate{title = "N", args = {}})
--return Upgrade(frame.args[1], frame.args[2], "Y", "N")
return Upgrade(frame.args[1], frame.args[2], "Y", "N")
end
end


return p
return p

Revision as of 11:40, 21 June 2026

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

local p = {}

-- LOCAL 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
	
	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 GetPurchasedAndAvailableUpgrades(purchasedUpgrades, availableUpgradeId)
		-- skip this upgrade if we don't have all the prereqs
		if upgradePrereqsById[availableUpgradeId] then
			for _, prereqId in ipairs(upgradePrereqsById[availableUpgradeId]) do
				if prereqId ~= "root" and not purchasedUpgrades[prereqId] then return nil, nil end
			end
		end
		
		-- shallow copy purchasedUpgrades and availableUpgrades
		local purchasedUpgradesNext = {}
		local availableUpgradesNext = {}
		
		for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
			if upgradeLocksById[purchasedUpgradeId] then
				if upgradeLocksById[purchasedUpgradeId][availableUpgradeId] then
					return nil, nil
				else
					upgradeLocksById[purchasedUpgradeId][availableUpgradeId] = true
				end
			else
				upgradeLocksById[purchasedUpgradeId] = {[availableUpgradeId] = true}
			end
			
			-- add this upgrade to the list of purchased upgrades
			purchasedUpgradesNext[purchasedUpgradeId] = true
			
			if upgradePrereqsOfById[purchasedUpgradeId] then
				for _, nextUpgradeId in ipairs(upgradePrereqsOfById[purchasedUpgradeId]) do
					if accessibleUpgrades[nextUpgradeId] and not purchasedUpgrades[nextUpgradeId] then availableUpgradesNext[nextUpgradeId] = true end
				end
			end
		end
		
		purchasedUpgradesNext[availableUpgradeId] = true
		
		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, "")
		
		if purchasedUpgrades and availableUpgrades then
			for availableUpgradeId, _ in pairs(availableUpgrades) do
				local purchasedUpgradesNext, availableUpgradesNext = GetPurchasedAndAvailableUpgrades(purchasedUpgrades, availableUpgradeId)
				
				if purchasedUpgradesNext and availableUpgradesNext then
					purchasedUpgradesNext[availableUpgradeId] = true
		
					--if #outputTable == 20 then return end
					RecursiveGetTotalCosts(currentCost + upgradesById[availableUpgradeId].cost, purchasedUpgradesNext, availableUpgradesNext)
				end
			end
		end
	end
	
	RecursiveGetTotalCosts(baseCost, initialPurchasedUpgrades, nextUpgrades)
	
	outputTable[#outputTable+1] = "\n|}"
	
	return tConcat(outputTable, "")
end

local function Upgrade(towerName, upgradeName, Y, N)
	local sFormat = string.format
	local query = mw.ext.cargo.query
	
	local outputTable = {}
	local upgradeId = ""
	
	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 = {}
	}
	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
	
	-- 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
	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 is a root upgrade
			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
			if upgradeLocksById[upgrade.thisId] then
				upgradeLocksById[upgrade.thisId][upgrade.lockId] = true
			else
				upgradeLocksById[upgrade.thisId] = {[upgrade.lockId] = true}
			end
		end
	end
	
	-- build purchasedUpgrades recursively
	local function GetPurchasedUpgrades(myUpgradeId)
		local cost = upgradesById[myUpgradeId].cost
		purchasedUpgrades[myUpgradeId] = true
		
		for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
			if prereqId ~= "root" then cost = cost + GetPurchasedUpgrades(prereqId) end
		end
		return cost
	end
	
	-- tower cost + total cost of this upgrade and all its prereqs
	local baseCost = GetPurchasedUpgrades(upgradeId) + query("battd_characters", "cost", {
		where=sFormat("name='%s'", towerName)
	})[1].cost
	
	-- 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
		if upgradeLocksById[myUpgradeId] then
			for _, upgradeLock in ipairs(upgradeLocksById[myUpgradeId]) do
				if purchasedUpgrades[upgradeLock] then return end
			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)
	return ""
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