1
0
mirror of https://github.com/veden/Rampant.git synced 2025-03-17 20:58:35 +02:00
Rampant/libs/BaseUtils.lua

613 lines
20 KiB
Lua
Raw Normal View History

2022-01-14 14:08:58 -08:00
-- Copyright (C) 2022 veden
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
if BaseUtilsG then
return BaseUtilsG
2019-02-15 20:17:30 -08:00
end
local BaseUtils = {}
--
local Universe
2017-05-07 23:56:11 -07:00
-- imports
local stringUtils = require("StringUtils")
local mathUtils = require("MathUtils")
2017-05-07 23:56:11 -07:00
local constants = require("Constants")
local chunkPropertyUtils = require("ChunkPropertyUtils")
2019-11-29 16:49:22 -08:00
local mapUtils = require("MapUtils")
local queryUtils = require("QueryUtils")
2017-06-07 17:57:24 -07:00
2017-05-07 23:56:11 -07:00
-- constants
local TIERS = constants.TIERS
local EVO_TO_TIER_MAPPING = constants.EVO_TO_TIER_MAPPING
local PROXY_ENTITY_LOOKUP = constants.PROXY_ENTITY_LOOKUP
local BUILDING_HIVE_TYPE_LOOKUP = constants.BUILDING_HIVE_TYPE_LOOKUP
local COST_LOOKUP = constants.COST_LOOKUP
local UPGRADE_LOOKUP = constants.UPGRADE_LOOKUP
local ENEMY_ALIGNMENT_LOOKUP = constants.ENEMY_ALIGNMENT_LOOKUP
local EVOLUTION_TABLE_ALIGNMENT = constants.EVOLUTION_TABLE_ALIGNMENT
local BUILDING_EVOLVE_LOOKUP = constants.BUILDING_EVOLVE_LOOKUP
local MINIMUM_BUILDING_COST = constants.MINIMUM_BUILDING_COST
2019-12-17 17:09:08 -08:00
local FACTION_MUTATION_MAPPING = constants.FACTION_MUTATION_MAPPING
2018-08-01 20:18:52 -07:00
local MAGIC_MAXIMUM_NUMBER = constants.MAGIC_MAXIMUM_NUMBER
local FACTIONS_BY_DAMAGE_TYPE = constants.FACTIONS_BY_DAMAGE_TYPE
local BASE_GENERATION_STATE_ACTIVE = constants.BASE_GENERATION_STATE_ACTIVE
2019-02-11 22:30:13 -08:00
local BASE_DISTANCE_THRESHOLD = constants.BASE_DISTANCE_THRESHOLD
local BASE_DISTANCE_LEVEL_BONUS = constants.BASE_DISTANCE_LEVEL_BONUS
local BASE_DISTANCE_TO_EVO_INDEX = constants.BASE_DISTANCE_TO_EVO_INDEX
2017-05-07 23:56:11 -07:00
local CHUNK_SIZE = constants.CHUNK_SIZE
local BASE_AI_STATE_PEACEFUL = constants.BASE_AI_STATE_PEACEFUL
2017-05-07 23:56:11 -07:00
-- imported functions
local isMember = stringUtils.isMember
local setPositionXYInQuery = queryUtils.setPositionXYInQuery
local euclideanDistancePoints = mathUtils.euclideanDistancePoints
2017-05-07 23:56:11 -07:00
2019-11-29 16:49:22 -08:00
local getChunkByPosition = mapUtils.getChunkByPosition
2021-12-05 15:33:24 -08:00
local gaussianRandomRangeRG = mathUtils.gaussianRandomRangeRG
2017-05-31 18:46:53 -07:00
local mFloor = math.floor
local tableRemove = table.remove
local mMin = math.min
local mMax = math.max
2020-05-19 19:37:16 -07:00
local next = next
2017-05-19 00:47:24 -07:00
-- module code
2017-05-07 23:56:11 -07:00
local function evoToTier(evolutionFactor, maxSkips)
2019-11-29 16:49:22 -08:00
local v
2021-11-26 14:00:08 -08:00
local skipsRemaining = maxSkips
for i=TIERS,1,-1 do
if EVO_TO_TIER_MAPPING[i] <= evolutionFactor then
2019-11-29 16:49:22 -08:00
v = i
if (skipsRemaining == 0) or (Universe.random() <= 0.75) then
2019-11-29 16:49:22 -08:00
break
2019-03-11 23:03:26 -07:00
end
2021-11-26 14:00:08 -08:00
skipsRemaining = skipsRemaining - 1
2019-10-19 12:13:48 -07:00
end
2019-03-11 23:03:26 -07:00
end
2019-11-29 16:49:22 -08:00
return v
2018-08-01 20:18:52 -07:00
end
local function findBaseMutation(targetEvolution, excludeFactions)
local tier = evoToTier(targetEvolution or Universe.evolutionLevel, 2)
local availableAlignments
local alignments = EVOLUTION_TABLE_ALIGNMENT[tier]
2019-02-02 22:01:28 -08:00
if excludeFactions then
availableAlignments = {}
for _,alignment in pairs(alignments) do
if not isMember(alignment[2], excludeFactions) then
availableAlignments[#availableAlignments+1] = alignment
end
end
else
availableAlignments = alignments
end
local roll = Universe.random()
for i=1,#availableAlignments do
local alignment = availableAlignments[i]
2019-11-29 16:49:22 -08:00
roll = roll - alignment[1]
2019-11-03 22:19:22 -08:00
2019-11-29 16:49:22 -08:00
if (roll <= 0) then
return alignment[2]
end
end
return availableAlignments[#availableAlignments]
2019-11-29 16:49:22 -08:00
end
2019-02-02 22:01:28 -08:00
local function initialEntityUpgrade(baseAlignment, tier, maxTier, map, useHiveType, entityType)
2019-11-29 16:49:22 -08:00
local entity
2019-12-15 17:16:56 -08:00
local useTier
local tierRoll = Universe.random()
2019-12-15 17:16:56 -08:00
if (tierRoll < 0.4) then
useTier = maxTier
elseif (tierRoll < 0.7) then
useTier = mMax(maxTier - 1, tier)
elseif (tierRoll < 0.9) then
useTier = mMax(maxTier - 2, tier)
else
useTier = mMax(maxTier - 3, tier)
end
local alignmentTable = BUILDING_EVOLVE_LOOKUP[baseAlignment]
if not alignmentTable then
alignmentTable = BUILDING_EVOLVE_LOOKUP["neutral"]
end
local upgrades = alignmentTable[useTier]
2019-12-15 17:16:56 -08:00
if alignmentTable and upgrades then
2019-12-15 17:16:56 -08:00
if useHiveType then
for ui=1,#upgrades do
local upgrade = upgrades[ui]
if upgrade[3] == useHiveType then
entity = upgrade[2][Universe.random(#upgrade[2])]
2019-12-15 17:16:56 -08:00
break
2019-03-11 23:03:26 -07:00
end
2019-12-15 17:16:56 -08:00
end
2020-05-23 20:47:14 -07:00
end
if not entity then
2019-12-15 17:16:56 -08:00
for ui=1,#upgrades do
local upgrade = upgrades[ui]
if upgrade[3] == entityType then
entity = upgrade[2][Universe.random(#upgrade[2])]
end
end
if not entity then
local mapTypes = FACTION_MUTATION_MAPPING[entityType]
for i=1, #mapTypes do
local mappedType = mapTypes[i]
for ui=1,#upgrades do
local upgrade = upgrades[ui]
if upgrade[3] == mappedType then
return upgrade[2][Universe.random(#upgrade[2])]
end
end
2019-03-11 23:03:26 -07:00
end
end
2019-11-03 22:19:22 -08:00
end
2019-11-29 16:49:22 -08:00
end
2019-12-15 17:16:56 -08:00
2019-11-29 16:49:22 -08:00
return entity
end
2021-02-19 23:31:36 -08:00
local function entityUpgrade(baseAlignment, tier, maxTier, originalEntity, map)
2019-11-29 16:49:22 -08:00
local entity
local hiveType = BUILDING_HIVE_TYPE_LOOKUP[originalEntity.name]
2019-11-29 16:49:22 -08:00
2019-12-17 17:09:08 -08:00
for t=maxTier,tier,-1 do
local factionLookup = UPGRADE_LOOKUP[baseAlignment][t]
2019-12-17 17:09:08 -08:00
local upgrades = factionLookup[hiveType]
if not upgrades then
local mapTypes = FACTION_MUTATION_MAPPING[hiveType]
for i=1, #mapTypes do
local upgrade = factionLookup[mapTypes[i]]
if upgrade and (#upgrade > 0) then
entity = upgrade[Universe.random(#upgrade)]
if Universe.random() < 0.55 then
2019-12-17 17:09:08 -08:00
return entity
end
end
end
elseif (#upgrades > 0) then
entity = upgrades[Universe.random(#upgrades)]
if Universe.random() < 0.55 then
2020-05-23 20:47:14 -07:00
return entity
2019-03-11 23:03:26 -07:00
end
2019-11-03 22:19:22 -08:00
end
end
return entity
end
function BaseUtils.findEntityUpgrade(baseAlignment, currentEvo, evoIndex, originalEntity, map, evolve)
2019-11-29 16:49:22 -08:00
local adjCurrentEvo = mMax(
((baseAlignment ~= ENEMY_ALIGNMENT_LOOKUP[originalEntity.name]) and 0) or currentEvo,
2019-11-29 16:49:22 -08:00
0
)
local tier = evoToTier(adjCurrentEvo, 5)
local maxTier = evoToTier(evoIndex, 4)
2019-11-29 16:49:22 -08:00
if (tier > maxTier) then
maxTier = tier
2019-11-29 16:49:22 -08:00
end
2019-12-15 17:16:56 -08:00
2019-11-29 16:49:22 -08:00
if evolve then
2021-02-19 23:31:36 -08:00
local chunk = getChunkByPosition(map, originalEntity.position)
local entityName = originalEntity.name
local entityType = BUILDING_HIVE_TYPE_LOOKUP[entityName]
if not entityType then
if Universe.random() < 0.5 then
entityType = "biter-spawner"
else
entityType = "spitter-spawner"
end
end
local roll = Universe.random()
local makeHive = (chunk ~= -1) and
(
(entityType == "biter-spawner") or (entityType == "spitter-spawner")
)
and
(
(
(roll <= 0.01) and
not PROXY_ENTITY_LOOKUP[entityName]
)
or
(
(roll <= 0.210) and
chunk.resourceGenerator
)
)
return initialEntityUpgrade(baseAlignment, tier, maxTier, map, (makeHive and "hive"), entityType)
2019-11-29 16:49:22 -08:00
else
2021-02-19 23:31:36 -08:00
return entityUpgrade(baseAlignment, tier, maxTier, originalEntity, map)
2019-12-15 17:16:56 -08:00
end
2019-11-29 16:49:22 -08:00
end
-- local
function BaseUtils.findBaseInitialAlignment(evoIndex, excludeFactions)
local dev = evoIndex * 0.15
local evoTop = gaussianRandomRangeRG(evoIndex - (evoIndex * 0.075), dev, 0, evoIndex, Universe.random)
2019-11-29 16:49:22 -08:00
local result
if Universe.random() < 0.05 then
result = {findBaseMutation(evoTop, excludeFactions), findBaseMutation(evoTop, excludeFactions)}
2019-11-29 16:49:22 -08:00
else
result = {findBaseMutation(evoTop, excludeFactions)}
end
2019-02-02 22:01:28 -08:00
2019-11-29 16:49:22 -08:00
return result
end
function BaseUtils.recycleBases()
local bases = Universe.bases
local id = Universe.recycleBaseIterator
2021-11-24 20:49:22 -08:00
local base
if not id then
id, base = next(bases, nil)
else
base = bases[id]
end
2021-02-19 21:41:30 -08:00
if not id then
Universe.recycleBaseIterator = nil
2021-02-19 21:41:30 -08:00
else
Universe.recycleBaseIterator = next(bases, id)
local map = base.map
if (base.chunkCount == 0) or not map.surface.valid then
2021-02-19 21:41:30 -08:00
bases[id] = nil
if Universe.processBaseAIIterator == id then
Universe.processBaseAIIterator = nil
end
if map.surface.valid then
Universe.bases[id] = nil
end
2019-02-02 22:01:28 -08:00
end
2018-01-25 21:48:12 -08:00
end
end
function BaseUtils.queueUpgrade(entity, base, disPos, evolve, register, timeDelay)
Universe.pendingUpgrades[entity.unit_number] = {
["position"] = disPos,
["register"] = register,
["evolve"] = evolve,
["base"] = base,
["entity"] = entity,
["delayTLL"] = timeDelay
}
2019-02-11 22:30:13 -08:00
end
local function pickMutationFromDamageType(damageType, roll, base)
local baseAlignment = base.alignment
local damageFactions = FACTIONS_BY_DAMAGE_TYPE[damageType]
2021-07-25 17:25:14 -07:00
local mutation
local mutated = false
if damageFactions and (#damageFactions > 0) then
mutation = damageFactions[Universe.random(#damageFactions)]
if not isMember(mutation, base.alignmentHistory) then
if baseAlignment[2] then
if (baseAlignment[1] ~= mutation) and (baseAlignment[2] ~= mutation) then
mutated = true
if (roll < 0.05) then
base.alignmentHistory[#base.alignmentHistory+1] = baseAlignment[1]
base.alignmentHistory[#base.alignmentHistory+1] = baseAlignment[2]
baseAlignment[1] = mutation
baseAlignment[2] = nil
elseif (roll < 0.75) then
base.alignmentHistory[#base.alignmentHistory+1] = baseAlignment[1]
baseAlignment[1] = mutation
else
baseAlignment[2] = mutation
end
end
elseif (baseAlignment[1] ~= mutation) then
mutated = true
if (roll < 0.85) then
base.alignmentHistory[#base.alignmentHistory+1] = baseAlignment[1]
baseAlignment[1] = mutation
else
baseAlignment[2] = mutation
end
end
end
else
mutation = findBaseMutation()
if not isMember(mutation, base.alignmentHistory) then
if baseAlignment[2] then
if (baseAlignment[1] ~= mutation) and (baseAlignment[2] ~= mutation) then
mutated = true
if (roll < 0.05) then
base.alignmentHistory[#base.alignmentHistory+1] = baseAlignment[1]
base.alignmentHistory[#base.alignmentHistory+1] = baseAlignment[2]
baseAlignment[1] = mutation
baseAlignment[2] = nil
elseif (roll < 0.75) then
base.alignmentHistory[#base.alignmentHistory+1] = baseAlignment[1]
baseAlignment[1] = mutation
else
baseAlignment[2] = mutation
end
end
elseif (baseAlignment[1] ~= mutation) then
mutated = true
if (roll < 0.85) then
base.alignmentHistory[#base.alignmentHistory+1] = baseAlignment[1]
base.alignment[1] = mutation
else
base.alignment[2] = mutation
end
end
end
end
if mutated and Universe.printBaseAdaptation then
2021-11-25 09:04:52 -08:00
if baseAlignment[2] then
2021-07-25 17:25:14 -07:00
game.print({"description.rampant--adaptation2DebugMessage",
base.id,
base.map.surface.name,
2021-07-25 17:25:14 -07:00
damageType,
{"description.rampant--"..baseAlignment[1].."EnemyName"},
{"description.rampant--"..baseAlignment[2].."EnemyName"},
base.x,
2021-11-25 09:04:52 -08:00
base.y,
base.mutations,
Universe.MAX_BASE_MUTATIONS})
2021-07-25 17:25:14 -07:00
else
game.print({"description.rampant--adaptation1DebugMessage",
base.id,
base.map.surface.name,
2021-07-25 17:25:14 -07:00
damageType,
{"description.rampant--"..baseAlignment[1].."EnemyName"},
base.x,
2021-11-25 09:04:52 -08:00
base.y,
base.mutations,
Universe.MAX_BASE_MUTATIONS})
2021-07-25 17:25:14 -07:00
end
end
local alignmentCount = table_size(base.alignmentHistory)
while (alignmentCount > Universe.MAX_BASE_ALIGNMENT_HISTORY) do
tableRemove(base.alignmentHistory, 1)
alignmentCount = alignmentCount - 1
end
return mutated
end
function BaseUtils.upgradeBaseBasedOnDamage(base)
local total = 0
for _,amount in pairs(base.damagedBy) do
total = total + amount
end
2021-07-24 18:20:13 -07:00
local mutationAmount = total * 0.176471
base.damagedBy["RandomMutation"] = mutationAmount
total = total + mutationAmount
local pickedDamage
local roll = Universe.random()
2021-07-24 18:20:13 -07:00
for damageTypeName,amount in pairs(base.damagedBy) do
base.damagedBy[damageTypeName] = amount / total
end
for damageType,amount in pairs(base.damagedBy) do
roll = roll - amount
if (roll <= 0) then
pickedDamage = damageType
break
end
end
return pickMutationFromDamageType(pickedDamage, roll, base)
end
function BaseUtils.processBaseMutation(chunk, map, base)
if not base.alignment[1] or
(base.stateGeneration ~= BASE_GENERATION_STATE_ACTIVE) or
(Universe.random() >= 0.30)
then
return
end
if (base.points >= MINIMUM_BUILDING_COST) then
local surface = map.surface
setPositionXYInQuery(Universe.pbFilteredEntitiesPointQueryLimited,
chunk.x + (CHUNK_SIZE * Universe.random()),
chunk.y + (CHUNK_SIZE * Universe.random()))
2019-11-03 22:19:22 -08:00
local entities = surface.find_entities_filtered(Universe.pbFilteredEntitiesPointQueryLimited)
if #entities ~= 0 then
local entity = entities[1]
local cost = (COST_LOOKUP[entity.name] or MAGIC_MAXIMUM_NUMBER)
if (base.points >= cost) then
local position = entity.position
BaseUtils.modifyBaseSpecialPoints(base, -cost, "Scheduling Entity upgrade", position.x, position.y)
BaseUtils.queueUpgrade(entity, base, nil, false, true)
2019-11-29 16:49:22 -08:00
end
2019-02-02 22:01:28 -08:00
end
2021-04-30 21:25:55 -07:00
end
end
function BaseUtils.createBase(map, chunk, tick)
local x = chunk.x
local y = chunk.y
local distance = euclideanDistancePoints(x, y, 0, 0)
2019-11-29 16:49:22 -08:00
local meanLevel = mFloor(distance * 0.005)
2019-02-02 22:01:28 -08:00
local distanceIndex = mMin(1, distance * BASE_DISTANCE_TO_EVO_INDEX)
local evoIndex = mMax(distanceIndex, Universe.evolutionLevel)
2019-11-03 22:19:22 -08:00
local alignment =
(Universe.NEW_ENEMIES and BaseUtils.findBaseInitialAlignment(evoIndex))
or {"neutral"}
2019-02-02 22:01:28 -08:00
local baseLevel = gaussianRandomRangeRG(meanLevel,
meanLevel * 0.3,
meanLevel * 0.50,
meanLevel * 1.50,
Universe.random)
2021-12-05 15:33:24 -08:00
local baseDistanceThreshold = gaussianRandomRangeRG(BASE_DISTANCE_THRESHOLD,
BASE_DISTANCE_THRESHOLD * 0.2,
BASE_DISTANCE_THRESHOLD * 0.75,
BASE_DISTANCE_THRESHOLD * 1.50,
Universe.random)
local distanceThreshold = (baseLevel * BASE_DISTANCE_LEVEL_BONUS) + baseDistanceThreshold
2017-05-11 21:50:06 -07:00
local base = {
2019-02-02 22:01:28 -08:00
x = x,
y = y,
distanceThreshold = distanceThreshold * Universe.baseDistanceModifier,
tick = tick,
2019-11-29 16:49:22 -08:00
alignment = alignment,
alignmentHistory = {},
2021-04-29 22:24:14 -07:00
damagedBy = {},
2021-07-24 18:20:13 -07:00
deathEvents = 0,
2021-11-25 09:04:52 -08:00
mutations = 0,
stateGeneration = BASE_GENERATION_STATE_ACTIVE,
stateGenerationTick = 0,
chunkCount = 0,
createdTick = tick,
2020-05-19 19:37:16 -07:00
points = 0,
unitPoints = 0,
stateAI = BASE_AI_STATE_PEACEFUL,
stateAITick = 0,
maxAggressiveGroups = 0,
sentAggressiveGroups = 0,
resetExpensionGroupsTick = 0,
maxExpansionGroups = 0,
sentExpansionGroups = 0,
activeRaidNests = 0,
activeNests = 0,
destroyPlayerBuildings = 0,
lostEnemyUnits = 0,
lostEnemyBuilding = 0,
rocketLaunched = 0,
builtEnemyBuilding = 0,
ionCannonBlasts = 0,
artilleryBlasts = 0,
resourceChunks = {},
resourceChunkCount = 0,
temperament = 0.5,
temperamentScore = 0,
map = map,
id = Universe.baseId
2017-05-11 21:50:06 -07:00
}
Universe.baseId = Universe.baseId + 1
2021-02-19 23:31:36 -08:00
map.bases[base.id] = base
Universe.bases[base.id] = base
2019-02-02 22:01:28 -08:00
2017-05-11 21:50:06 -07:00
return base
2017-05-07 23:56:11 -07:00
end
function BaseUtils.modifyBaseUnitPoints(base, points, tag, x, y)
2021-04-04 21:46:43 -07:00
if points > 0 and base.stateAI == BASE_AI_STATE_PEACEFUL then
return
end
tag = tag or ""
x = x or nil
y = y or nil
base.unitPoints = base.unitPoints + points
local overflowMessage = ""
if base.unitPoints > Universe.maxOverflowPoints then
base.unitPoints = Universe.maxOverflowPoints
overflowMessage = " [Point cap reached]"
end
local printPointChange = ""
if points > 0 and Universe.aiPointsPrintGainsToChat then
printPointChange = "+" .. string.format("%.2f", points)
elseif points < 0 and Universe.aiPointsPrintSpendingToChat then
printPointChange = string.format("%.2f", points)
end
if printPointChange ~= "" then
local gps = ""
if x ~= nil then
gps = " [gps=" .. x .. "," .. y .. "]"
2021-04-04 21:46:43 -07:00
end
game.print("[" .. base.id .. "]:" .. base.map.surface.name .. " " .. printPointChange .. " [" .. tag .. "] Unit Total:" .. string.format("%.2f", base.unitPoints) .. overflowMessage .. gps)
2021-04-04 21:46:43 -07:00
end
end
function BaseUtils.modifyBaseSpecialPoints(base, points, tag, x, y)
if points > 0 and base.stateAI == BASE_AI_STATE_PEACEFUL then
return
end
tag = tag or ""
x = x or nil
y = y or nil
base.points = base.points + points
local overflowMessage = ""
if base.points > Universe.maxOverflowPoints then
base.points = Universe.maxOverflowPoints
overflowMessage = " [Point cap reached]"
end
local printPointChange = ""
if points > 0 and Universe.aiPointsPrintGainsToChat then
printPointChange = "+" .. string.format("%.2f", points)
elseif points < 0 and Universe.aiPointsPrintSpendingToChat then
printPointChange = string.format("%.2f", points)
end
if printPointChange ~= "" then
local gps = ""
if x ~= nil then
gps = " [gps=" .. x .. "," .. y .. "]"
end
game.print("[" .. base.id .. "]:" .. base.map.surface.name .. " " .. printPointChange .. " [" .. tag .. "] Special Total:" .. string.format("%.2f", base.points) .. overflowMessage .. gps)
end
end
function BaseUtils.init(universe)
Universe = universe
end
BaseUtilsG = BaseUtils
return BaseUtils