if baseUtilsG then return baseUtilsG end local baseUtils = {} -- imports local mathUtils = require("MathUtils") local constants = require("Constants") local chunkPropertyUtils = require("ChunkPropertyUtils") local mapUtils = require("MapUtils") -- constants local FACTION_CHANGING_MAPPING = constants.FACTION_CHANGING_MAPPING local FACTION_EVOLVE_MAPPING = constants.FACTION_EVOLVE_MAPPING local MAGIC_MAXIMUM_NUMBER = constants.MAGIC_MAXIMUM_NUMBER local BASE_AI_STATE_ACTIVE = constants.BASE_AI_STATE_ACTIVE local BASE_AI_STATE_MUTATE = constants.BASE_AI_STATE_MUTATE local BASE_EVOLVE_THRESHOLD = constants.BASE_EVOLVE_THRESHOLD local GLOBAL_EVOLVE_COOLDOWN = constants.GLOBAL_EVOLVE_COOLDOWN local FACTION_SET = constants.FACTION_SET local BASE_DEADZONE_TTL = constants.BASE_DEADZONE_TTL local BASE_AI_MIN_STATE_DURATION = constants.BASE_AI_MIN_STATE_DURATION local BASE_AI_MAX_STATE_DURATION = constants.BASE_AI_MAX_STATE_DURATION local HIVE_BUILDINGS_COST = constants.HIVE_BUILDINGS_COST 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 local BASE_COLLECTION_THRESHOLD = constants.BASE_COLLECTION_THRESHOLD local CHUNK_SIZE = constants.CHUNK_SIZE -- imported functions local randomTickEvent = mathUtils.randomTickEvent local euclideanDistancePoints = mathUtils.euclideanDistancePoints local getChunkByPosition = mapUtils.getChunkByPosition local gaussianRandomRange = mathUtils.gaussianRandomRange local linearInterpolation = mathUtils.linearInterpolation local mFloor = math.floor local mMin = math.min local mMax = math.max local distort = mathUtils.distort local getChunkBase = chunkPropertyUtils.getChunkBase local setChunkBase = chunkPropertyUtils.setChunkBase local getResourceGenerator = chunkPropertyUtils.getResourceGenerator local getNestCount = chunkPropertyUtils.getNestCount local getHiveCount = chunkPropertyUtils.getHiveCount local getTurretCount = chunkPropertyUtils.getTurretCount local getEnemyStructureCount = chunkPropertyUtils.getEnemyStructureCount local getNestActiveness = chunkPropertyUtils.getNestActiveness local next = next local mRandom = math.random -- module code local function evoToTier(universe, evolutionFactor) local v for i=10,1,-1 do if universe.evoToTierMapping[i] <= evolutionFactor then v = i if mRandom() <= 0.65 then break end end end return v end -- + !КДА 2021.11 local function evoToTierNorandom(universe, evolutionFactor) local v for i=10,1,-1 do if universe.evoToTierMapping[i] <= evolutionFactor then v = i break end end return v end -- - !КДА 2021.11 function baseUtils.findNearbyBase(map, chunk, maxDistance, baseChangingChance) local x = chunk.x local y = chunk.y local closest = MAGIC_MAXIMUM_NUMBER local foundBase = getChunkBase(map, chunk) if not foundBase then if baseChangingChance and (mRandom() < baseChangingChance) then return nil else local bases = map.universe.bases local basesScanned = 0 for i, base in pairs(bases) do if base.mapIndex == map.surface.index then basesScanned = basesScanned + 1 local distance = euclideanDistancePoints(base.x, base.y, x, y) if (distance <= base.distanceThreshold) and (distance < closest) then closest = distance foundBase = bases[i] --base end end end if maxDistance and (closest > maxDistance) then foundBase = nil end end end return foundBase end -- + !КДА 2021.11 -- return faction name. local function getFactionFromAligment(baseAlignment) if not baseAlignment then return "neutral" end local factions = {} local factionTotal = 0 local ratesTotal = 0 for faction, rate in pairs(baseAlignment) do factionTotal = factionTotal + 1 factions[factionTotal] = faction ratesTotal = ratesTotal + mMax(rate,0) end if factionTotal == 0 then return "neutral" end if factionTotal == 1 then return factions[1] end local roll = mRandom()*ratesTotal for faction, rate in pairs(baseAlignment) do roll = roll - mMax(rate,0) if (roll <= 0) then return faction end end return factions[1] end -- - !КДА 2021.11 -- return faction name. "neutral", "troll, etc.. local function findBaseMutation(map, targetEvolution, targetTier) local universe = map.universe local tier if targetTier then tier = targetTier else tier = evoToTierNorandom(universe, targetEvolution or map.evolutionLevel) end local alignments = universe.evolutionTableAlignment[tier] local roll = mRandom() for i=1,#alignments do local alignment = alignments[i] roll = roll - alignment[1] if (roll <= 0) then return alignment[2] end end return (alignments[#alignments] or "neutral") end -- example: exceptFactions = {"neutral", "acid"} -- alignments[x] = {0.2, "neutral"}... -- filteredAligments{neutral = 0.2, acid = 0.1, ...} -- mutationRates = {n1, n2,...} ==> return {faction1 = n1, faction2 = n2,...} -- return {troll = 1, electic = 1,...} local function findBaseMutations(map, targetEvolution, targetTier, mutationCount, mutationRates,exceptFactions) local universe = map.universe local tier if targetTier then tier = targetTier else tier = evoToTierNorandom(universe, targetEvolution or map.evolutionLevel) end local alignments = universe.evolutionTableAlignment[tier] local filteredAligments = {} local aligmentsCount = 0 local ratesSum = 0 for i=1,#alignments do local alignment = alignments[i] local FactionEnabled = true if exceptFactions then for u=1,#exceptFactions do if alignment[2] == exceptFactions[u] then FactionEnabled = false break end end end if FactionEnabled then aligmentsCount = aligmentsCount + 1 ratesSum = ratesSum + alignment[1] filteredAligments[alignment[2]] = alignment[1] end end local maxMutations = 1 if mutationCount then maxMutations = mutationCount end local result = {} for u = 1, maxMutations do local roll = mRandom()*ratesSum for faction, rate in pairs(filteredAligments) do local alignment = alignments[i] roll = roll - filteredAligments[faction] if (roll <= 0) then if mutationRates and mutationRates[u] then result[faction] = mutationRates[u] else result[faction] = 1 end aligmentsCount = aligmentsCount - 1 ratesSum = ratesSum - filteredAligments[faction] filteredAligments[faction] = nil break end end if aligmentsCount == 0 then break end end return result end local function initialEntityUpgrade(faction, tier, maxTier, map, useHiveType) local evolutionTable = map.universe.buildingEvolveLookup local entity local useTier local tierRoll = mRandom() 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 upgradesTier = evolutionTable[faction] if not upgradesTier then upgradesTier = evolutionTable["neutral"] end local upgrades = upgradesTier[useTier] if upgrades then if useHiveType then for ui=1,#upgrades do local upgrade = upgrades[ui] if upgrade[3] == useHiveType then entity = upgrade[2][mRandom(#upgrade[2])] break end end end if not entity then local roll = mRandom() for ui=1,#upgrades do local upgrade = upgrades[ui] roll = roll - upgrade[1] if (roll <= 0) then entity = upgrade[2][mRandom(#upgrade[2])] break end end end end return entity end local function entityEvolve(originalEntity, map) local universe = map.universe local hiveType = universe.buildingHiveTypeLookup[originalEntity.name] if not hiveType then return originalEntity.name end local mapTypes = FACTION_EVOLVE_MAPPING[hiveType] if not mapTypes then return originalEntity.name end local faction = universe.enemyAlignmentLookup[originalEntity.name] if not faction then return originalEntity.name end local tier = universe.buildingTierLookup[originalEntity.name] if not tier then return originalEntity.name end local evolutionTable = map.universe.buildingEvolveLookup local upgradesTier = evolutionTable[faction] if not upgradesTier then upgradesTier = evolutionTable["neutral"] end local upgrades = upgradesTier[tier] if not upgrades then return originalEntity.name end -- example: evolutionTable["troll"][3][1]={0.1, buildingSet, "biter-spawner"} (tier 3) local factionUpgrades = {} local ratesSum = 0 for i=1, #upgrades do local upgrade = upgrades[i] if mapTypes[upgrade[3]] then ratesSum = ratesSum + upgrade[1] factionUpgrades[#factionUpgrades+1] = {upgrade[1], upgrade[2], upgrade[3]} end end local initRoll = mRandom()*ratesSum local roll = initRoll + 0 for i = 1,#factionUpgrades do local upgrade = factionUpgrades[i] roll = roll - upgrade[1] if roll<=0 then local buildingSet = upgrade[2] -- if (upgrade[3]~= hiveType) and (oldFaction==faction) then -- game.print("entityUpgrade---------------start") -- for u = 1,#factionUpgrades do -- game.print(""..u..":"..factionUpgrades[u][1].."," .. factionUpgrades[u][3]..( (u == 1 and "<--here") or "")) -- end -- game.print("entityUpgrade---------------end") -- end return buildingSet[mRandom(#buildingSet)] end end return originalEntity.name end local function entityUpgrade(faction, tier, maxTier, originalEntity, map) local universe = map.universe local hiveType = universe.buildingHiveTypeLookup[originalEntity.name] if not hiveType then return originalEntity.name end local oldFaction = universe.enemyAlignmentLookup[originalEntity.name] local mapTypes = {} if oldFaction==faction then mapTypes[hiveType] = 0 else mapTypes = FACTION_CHANGING_MAPPING[hiveType] if not mapTypes then return originalEntity.name end end local useTier if tier == maxTier then useTier = maxTier else local tierRoll = mRandom() 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 end local evolutionTable = map.universe.buildingEvolveLookup if not evolutionTable[faction] then return originalEntity.name end local upgrades = evolutionTable[faction][useTier] if not upgrades then return originalEntity.name end -- example: evolutionTable["troll"][3][1]={0.1, buildingSet, "biter-spawner"} (tier 3) local factionUpgrades = {} local ratesSum = 0 for i=1, #upgrades do local upgrade = upgrades[i] if mapTypes[upgrade[3]] then ratesSum = ratesSum + upgrade[1] factionUpgrades[#factionUpgrades+1] = {upgrade[1], upgrade[2], upgrade[3]} end end local initRoll = mRandom()*ratesSum local roll = initRoll + 0 for i = 1,#factionUpgrades do local upgrade = factionUpgrades[i] roll = roll - upgrade[1] if roll<=0 then local buildingSet = upgrade[2] -- if (upgrade[3]~= hiveType) and (oldFaction==faction) then -- game.print("entityUpgrade---------------start") -- for u = 1,#factionUpgrades do -- game.print(""..u..":"..factionUpgrades[u][1].."," .. factionUpgrades[u][3]..( (u == 1 and "<--here") or "")) -- end -- game.print("entityUpgrade---------------end") -- end return buildingSet[mRandom(#buildingSet)] end end return originalEntity.name end local function findEntityUpgrade(faction, currentEvo, evoIndex, originalEntity, map, evolve) local universe = map.universe local adjCurrentEvo = mMax( ((faction ~= universe.enemyAlignmentLookup[originalEntity.name]) and 0) or currentEvo, 0 ) local tier = evoToTier(universe, adjCurrentEvo) local maxTier = evoToTierNorandom(universe, evoIndex) if (tier > maxTier) then tier = maxTier -- return nil end if evolve then local chunk = getChunkByPosition(map, originalEntity.position) local makeHive = false if not chunk == -1 then if getResourceGenerator(map, chunk) > 0 then if mRandom() < 0.2 then makeHive = true end else if mRandom() < 0.02 then makeHive = true end end end return initialEntityUpgrade(faction, tier, maxTier, map, (makeHive and "hive")) else return entityUpgrade(faction, tier, maxTier, originalEntity, map) end end local function findBaseInitialAlignment(map, evoIndex) local dev = evoIndex * 0.3 local evoTop = evoIndex local result if mRandom() < 0.05 then result = findBaseMutations(map, evoTop, nil, 2, {0.5, 0.5}) --{troll = 0.5, acid = 0.5} -- + !КДА 2021.11 else result = findBaseMutations(map, evoTop, nil, 2, {0.8, 0.2}) end return result end function baseUtils.recycleBases(universe) if not universe.bases then return end local bases = universe.bases local id = universe.recycleBaseIterator if not id then id, base = next(bases, nil) end if not id then universe.recycleBaseIterator = nil else local base base = bases[id] if base then local map = universe.maps[base.mapIndex] if not map then universe.growingBases[id] = nil if universe.growingBasesIterator == id then universe.growingBasesIterator = nil end bases[id] = nil return end for chunk, _ in pairs(base.chunks) do chunkBase = getChunkBase(map, chunk) if (not chunkBase) then setChunkBase(map, chunk, base) --game.print("restore lost chunk [gps=" .. chunk.x .. "," .. chunk.y .."] to base [gps=" .. base.x .. "," .. base.y .."]") -- debug elseif chunkBase.id ~= base.id then base.chunks[chunk] = nil chunkBase.chunks[chunk] = true --game.print("reassign chunk [gps=" .. chunk.x .. "," .. chunk.y .."]".." from [gps=" .. base.x .. "," .. base.y .."] to [gps=" .. chunkBase.x .. "," .. chunkBase.y .."]") -- debug end end local chunk = next(base.chunks, nil) if not chunk then --game.print("recycled base [gps=" .. base.x .. "," .. base.y .."]".. serpent.dump(base.chunks)) -- debug universe.growingBases[id] = nil if universe.growingBasesIterator == id then universe.growingBasesIterator = nil end map.basesToGrow[id] = nil bases[id] = nil end end universe.recycleBaseIterator = next(bases, id) end end local function deleteAssociatedUnits(entity) if entity.valid and (entity.type == "unit-spawner") then if entity.units then for i, unit in pairs(entity.units) do if unit and unit.valid and (unit.type == "unit") and (not unit.unit_group) then unit.destroy() --unit.die(unit.force) end end end end end -- baseAlignment{fire = 0.8, acid = 0.2,...} function baseUtils.upgradeEntity(entity, baseAlignment, map, disPos, evolve, addEntity) local surface = map.surface local position = entity.position local currentEvo = (addEntity and 0) or entity.prototype.build_base_evolution_requirement or 0 local universe = map.universe local faction if type(baseAlignment)=="string" then faction = baseAlignment else faction = getFactionFromAligment(baseAlignment) end if not faction then if not addEntity then entity.destroy() end return nil end local distance = mMin(1, euclideanDistancePoints(position.x, position.y, 0, 0) * BASE_DISTANCE_TO_EVO_INDEX) local evoIndex = mMax(distance, map.evolutionLevel) local spawnerName = findEntityUpgrade(faction, currentEvo, evoIndex, entity, map, evolve) if spawnerName and (addEntity or (spawnerName~=entity.name)) then --game.print(entity.name.."->"..spawnerName.." [gps=" .. entity.position.x .. "," .. entity.position.y .."]") -- debug if not addEntity then deleteAssociatedUnits(entity) entity.destroy() end local name = universe.buildingSpaceLookup[spawnerName] or spawnerName local query = universe.upgradeEntityQuery query.name = name query.position = disPos or position if addEntity or (not surface.can_place_entity(query)) then local newPosition = surface.find_non_colliding_position( (addEntity and "chunk-scanner-squad-rampant") or name, disPos or position, CHUNK_SIZE, 1, true ) -- if addEntity then -- game.print(entity.name.."baseUtils.upgradeEntity: add source = "..entity.name .." [gps=" .. entity.position.x .. "," .. entity.position.y .."]") -- debug -- end if addEntity and not newPosition then return nil end query.position = newPosition or disPos or position end query.name = spawnerName if remote.interfaces["kr-creep"] then remote.call("kr-creep", "spawn_creep_at_position", surface, query.position) end local newEntity = surface.create_entity(query) return newEntity end return nil end function baseUtils.evolveEntity(entity, map) if not entity.valid then return nil end local oldName = entity.name local spawnerName = entityEvolve(entity, map) if spawnerName and (spawnerName~=oldName)then -- game.print("evolve "..entity.name.."-->"..spawnerName) -- debug local surface = map.surface local position = entity.position local universe = map.universe deleteAssociatedUnits(entity) entity.destroy() local name = universe.buildingSpaceLookup[spawnerName] or spawnerName local query = universe.upgradeEntityQuery query.name = name query.position = position if not surface.can_place_entity(query) then local newPosition = surface.find_non_colliding_position( name, position, CHUNK_SIZE, 1, true ) query.position = newPosition or position end query.name = spawnerName if remote.interfaces["kr-creep"] then remote.call("kr-creep", "spawn_creep_at_position", surface, query.position) end return surface.create_entity(query) else --game.print("cant evolve "..entity.name) end return nil end function baseUtils.changeEntityTier(entity, newTier, map) local surface = map.surface local position = entity.position local universe = map.universe local oldFaction = universe.enemyAlignmentLookup[entity.name] if not oldFaction then return nil end local oldName = entity.name --local tier = universe.buildingTierLookup[entity.name] local spawnerName = entityUpgrade(oldFaction, newTier, newTier, entity, map) if spawnerName and (spawnerName~=oldName)then local name = spawnerName local query = {} query.name = name query.position = position --position query.move_stuck_players = true local NewEntity = surface.create_entity(query) if NewEntity then deleteAssociatedUnits(entity) entity.destroy() return NewEntity end end return nil -- if not (spawnerName==oldName)then -- entity.destroy() -- local name = universe.buildingSpaceLookup[spawnerName] or spawnerName -- local query = universe.upgradeEntityQuery -- query.name = name -- query.position = position -- if not surface.can_place_entity(query) then -- local newPosition = surface.find_non_colliding_position( -- name, -- position, -- CHUNK_SIZE, -- 1, -- true -- ) -- query.position = newPosition or position -- end -- query.name = spawnerName -- if remote.interfaces["kr-creep"] then -- remote.call("kr-creep", "spawn_creep_at_position", surface, query.position) -- end -- local newEntity = surface.create_entity(query) -- if newEntity.valid then -- return newEntity -- else -- return nil -- end -- end -- return nil end function baseUtils.changeEntityAligment(entity, newAlignments, map) if not entity.valid then return nil end local surface = map.surface local position = entity.position local universe = map.universe local newFaction if type(newAlignments)=="string" then newFaction = newAlignments else newFaction = getFactionFromAligment(newAlignments) end if not newFaction then return nil end local oldFaction = universe.enemyAlignmentLookup[entity.name] if oldFaction == newFaction then return nil end local oldName = entity.name local tier = universe.buildingTierLookup[entity.name] local spawnerName = entityUpgrade(newFaction, tier, tier, entity, map) if spawnerName and (spawnerName~=oldName) then local name = spawnerName local query = {} query.name = name query.position = position --position query.move_stuck_players = true local NewEntity = surface.create_entity(query) if NewEntity then deleteAssociatedUnits(entity) entity.destroy() return NewEntity end end return nil end -- - !КДА 2021.11 local function replaceDeletedFactions(baseAlignment, map) local evolutionTable = map.universe.buildingEvolveLookup local factionsArray = {} local alignmentCount = 0 for faction, rate in pairs(baseAlignment) do alignmentCount = alignmentCount + 1 if not evolutionTable[faction] then factionsArray[#factionsArray+1] = faction end end for i = 1,#factionsArray do alignmentCount = alignmentCount - 1 baseAlignment[factionsArray[i]] = nil -- game.print("Removeing deleted faction:"..factionsArray[i]) end if alignmentCount == 0 then baseAlignment["neutral"] = 1 end end local function baseAlignmentAsString(baseAlignment) local baseAlignmentAsString = "" for faction, rate in pairs(baseAlignment) do baseAlignmentAsString = baseAlignmentAsString..faction.."="..rate.."%, " end return baseAlignmentAsString end -- base.baseAlignment{troll = 0.7, acid = .3} -- upgradeType = 0 - change rates: instant changes (changing base.alignment) -- 1 - change rates, allow change lowest rated faction: instant changes -- 2 - change lowest rated faction: lasting changes (changing base.newAlignmentAndSteps) -- 3 - new aligments (new tier, for example): lasting changes -- 4 - new rates for same factions: lasting changes local function upgradeBase(map, base, upgradeType) local RateStep = 0.05 local result = false local oldAlignment if base.newAlignmentAndSteps then oldAlignment = base.newAlignmentAndSteps[2] else oldAlignment = base.alignment end local baseAlignment = base.alignment local baseTier = base.tier -- debug -- local alignmentStr = "" -- for faction, rate in pairs(oldAlignment) do -- alignmentStr = alignmentStr .. faction .. ": "..rate.."," -- end -- if (upgradeType == 0) or (upgradeType == 1) then -- not used since v1.3. So, mistakes possible local factions = {} local factionTotal = 0 for faction, rate in pairs(oldAlignment) do factionTotal = factionTotal + 1 factions[factionTotal] = faction end if factionTotal>1 then local roll = mRandom(factionTotal) local roll2 = mRandom(factionTotal-1) if roll2 >= roll then roll2 = roll2+1 end if (oldAlignment[factions[roll]] - RateStep) < 0.005 then -- if rate too low, then do nothing or change faction if upgradeType == 1 then newFaction = findBaseMutation(map, nil, baseTier) if not oldAlignment[newFaction] then oldAlignment[newFaction] = oldAlignment[factions[roll]] oldAlignment[factions[roll]] = nil factions[roll] = newFaction -- optional, but let it be result = true end end else oldAlignment[factions[roll]] = oldAlignment[factions[roll]] - RateStep oldAlignment[factions[roll2]] = oldAlignment[factions[roll2]] + RateStep result = true end end elseif upgradeType == 2 then local newFaction = findBaseMutation(map, nil, baseTier) if not oldAlignment[newFaction] then local oldFaction local newAlignmentAndSteps = {10, {}} local newAlignment = newAlignmentAndSteps[2] for faction, rate in pairs(oldAlignment) do newAlignment[faction] = rate if not oldFaction or rate" -- for faction, rate in pairs(newAlignmentDebug) do -- alignmentStr = alignmentStr .. faction .. ": "..rate.."," -- end -- game.print("upgradeBase #"..base.id..", type = " ..upgradeType.." "..alignmentStr.." [gps=" .. base.x .. "," .. base.y .."]") -- alignmentStr = "" -- for chunk, _ in pairs(base.chunks) do -- alignmentStr = alignmentStr .. "[gps=" .. chunk.x .. "," .. chunk.y .."]" -- end -- game.print(alignmentStr) -- end return result end local function updateBaseFactionsTotal(base) local factionsTotal = {} local totalCount = 0 for chunk, counts in pairs(base.chunkFactions) do for faction,count in pairs(counts) do if count>0 then factionsTotal[faction] = (factionsTotal[faction] or 0) + count totalCount = totalCount + count end end end base.factionsTotal = {totalCount, factionsTotal} end local function calculateDynamicRates(dynamicRates, factionsTotal) if not factionsTotal then return end local totalDynamicRate = 0 local changesCnt = 0 -- recommended changes local obsoleteCnt = 0 local dynamicRatesTable = dynamicRates.dynamicRatesTable dynamicRatesTable.dynamicRate = {} local fieldRate = dynamicRatesTable.rate local fieldDynamicRate = dynamicRatesTable.dynamicRate local fieldReqCount = dynamicRatesTable.reqCount for faction,rates in pairs(fieldRate) do fieldDynamicRate[faction] = fieldReqCount[faction] - (factionsTotal[faction] or 0) local dynamicRate = fieldDynamicRate[faction] if dynamicRate>0 then totalDynamicRate = totalDynamicRate + dynamicRate end changesCnt = changesCnt + mMax(mFloor(dynamicRate), 0) if (fieldRate[faction] or 0) <=0 and (factionsTotal[faction] or 0)>0 then obsoleteCnt = obsoleteCnt + 1 end end if totalDynamicRate == 0 then totalDynamicRate = 1 end dynamicRates.totalDynamicRate = totalDynamicRate dynamicRates.changesCnt = changesCnt dynamicRates.obsoleteCnt = obsoleteCnt end -- return {totalRate=N, totalDynamicRate=N, changesCnt=N, obsoleteCnt=N, dynamicRatesTable={rate={},reqCount = {},dynamicRate = {}}) -- example : base.factionsTotal{{"acid",7},{"fire", 3}}, base.alignment{{"acid",0.8},{"troll",0.2}} -- => totalCount=10, totalRate = 1 -- => dynamicRatesTable["reqCount"] = {acid = 10*(0.8/1)=8, troll = 10*(0.2/1)=2, fire = 10*0=0 } -- => dynamicRatesTable["dynamicRate"] = {acid = 8-7=1, troll = 2 - 0 = 2, fire = 0-3 =>0 } , totalDynamicRate = 1+2=3 -- => 33% acid, 67% troll, 0% fire function baseUtils.getDynamicRates(base) local msgSting = "baseUtils.getDynamicRates: ("..tostring(base.x)..","..tostring(base.y)..")" -- + !КДА 2021.11 local aligments aligments = base.alignment local totalRate = 0 local dynamicRates = {totalRate = 0, totalDynamicRate = 0, changesCnt = 0, obsoleteCnt = 0, dynamicRatesTable={}} local dynamicRatesTable = {rate={}} local fieldRate = dynamicRatesTable.rate for faction,rate in pairs(aligments) do totalRate = totalRate + rate fieldRate[faction] = rate end if totalRate == 0 then totalRate = 1 end local totalCount if not(base.factionsTotal) or base.factionsTotal[1]==0 then totalCount = 0 else totalCount = base.factionsTotal[1] end local factionsTotal = base.factionsTotal[2] for faction,rate in pairs(factionsTotal) do if not fieldRate[faction] then fieldRate[faction] = 0 end end dynamicRatesTable.reqCount = {} local fieldReqCount = dynamicRatesTable.reqCount for faction,rates in pairs(fieldRate) do fieldReqCount[faction] = totalCount*(fieldRate[faction]/totalRate) end dynamicRates.dynamicRatesTable = dynamicRatesTable calculateDynamicRates(dynamicRates, factionsTotal) dynamicRates.totalRate = totalRate return dynamicRates end function baseUtils.changeEntityAndUpdateDynamicRates(entity, dynamicRates, map, base) local universe = map.universe local oldCnt = 0 local newFaction if not universe.buildingHiveTypeLookup[entity.name] then return nil end local oldFaction = universe.enemyAlignmentLookup[entity.name] if not oldFaction then return nil end local dynamicRatesTable = dynamicRates.dynamicRatesTable if (dynamicRates.dynamicRatesTable.dynamicRate[oldFaction] or 0)>0 then return nil end -- preventing the "flashing" of the hive. if (universe.buildingHiveTypeLookup[entity.name] == "hive") and (dynamicRates.dynamicRatesTable.dynamicRate[oldFaction] or 0)>-0.3 then return nil end if entity.type =="unit-spawner" then oldCnt = 1 newFaction = getFactionFromAligment(dynamicRates.dynamicRatesTable.dynamicRate) else newFaction = getFactionFromAligment(dynamicRates.dynamicRatesTable.rate) end if oldFaction == newFaction then return nil end local newEntity = baseUtils.changeEntityAligment(entity, newFaction, map) if newEntity then if base and base.factionsTotal then local factionsTotal = base.factionsTotal[2] local newCnt = 0 if newEntity.type =="unit-spawner" then newCnt = 1 end if oldCnt > 0 then factionsTotal[oldFaction] = (factionsTotal[oldFaction] or 0) - oldCnt end if newCnt > 0 then factionsTotal[newFaction] = (factionsTotal[newFaction] or 0) - newCnt end base.factionsTotal[1] = base.factionsTotal[1] + newCnt - oldCnt if oldCnt>0 or newCnt>0 then calculateDynamicRates(dynamicRates, factionsTotal) end end return newEntity end return nil end local function updateBaseStats(base) local aligments aligments = base.alignment if not aligments then return false end -- base.factionsTotal = {totalCount, factionsTotal} updateBaseFactionsTotal(base) dynamicRates = baseUtils.getDynamicRates(base) -- => {totalRate=N, totalDynamicRate=N, changesCnt=N, obsoleteCnt=N, dynamicRatesTable=Table) base.changingEntities = false -- if dynamicRates.obsoleteCnt > 0 then -- base.changingEntities = true -- else if base.factionsTotal then local totalCount = base.factionsTotal[1] local thresholdCount = 0 if totalCount <= 3 then thresholdCount = 1 elseif totalCount<10 then thresholdCount = 2 else thresholdCount = mMin(totalCount*0.2, 8) end if dynamicRates.changesCnt >= thresholdCount then base.changingEntities = true end end end -- alignmentAndStep{N, alignment} -- be careful: changing alignmentAndStep[1] local function convertAlignment(alignment, alignmentAndStep, writeToalignment) if not alignmentAndStep then return alignment end local steps = alignmentAndStep[1] if steps<=1 then if not writeToalignment then alignmentAndStep[1] = 0 return alignmentAndStep[2] else alignmentAndStep[1] = 1 end end local newAlignment = {} for faction,rate in pairs(alignmentAndStep[2]) do newAlignment[faction] = rate end for faction,rate in pairs(alignment) do newAlignment[faction] = (newAlignment[faction] or 0) - rate end local TableToChange if writeToalignment then TableToChange = alignment else TableToChange = newAlignment end for faction,rate in pairs(newAlignment) do TableToChange[faction] = (alignment[faction] or 0) + rate/steps if TableToChange[faction]<=0 then TableToChange[faction]=nil end end alignmentAndStep[1] = alignmentAndStep[1] - 1 return TableToChange end function baseUtils.chunkCanGrow(map, chunk) if chunk.growFails and (chunk.growFails > 5) then return false end local nestCount = getNestCount(map, chunk) + getHiveCount(map, chunk) local turretCount = getTurretCount(map, chunk) if (nestCount < 3) or ((nestCount + turretCount) < 10) then return true end --game.print("baseUtils.chunkCanGrow: [gps=" .. chunk.x .. "," .. chunk.y .."] = false" ) -- debug return false end function baseUtils.processBase(chunk, map, tick, base) if not base.alignment then return end if not tick then return end if not chunk then return end if not base.thisIsRampantEnemy then base.tick = tick+36000 return end local surface = map.surface local universe = map.universe local point = universe.position point.x = chunk.x + (CHUNK_SIZE * mRandom()) point.y = chunk.y + (CHUNK_SIZE * mRandom()) local newTier = evoToTierNorandom(map.universe, map.evolutionLevel) - base.tierHandicap if base.tier < newTier then base.tier = newTier base.nextMutationTick = tick + 36000 upgradeBase(map, base, 3) base.state = BASE_AI_STATE_ACTIVE elseif (base.state == BASE_AI_STATE_MUTATE) and ((base.nextMutationTick or 0) < tick) and (not base.changingEntities) and (not base.newAlignmentAndSteps) then local mutateRoll = mRandom() --base.nextMutationTick = tick + 36000 if (mutateRoll < 0.05) then upgradeBase(map, base, 3) elseif (mutateRoll< 0.3) then upgradeBase(map, base, 2) else upgradeBase(map, base, 4) end base.state = BASE_AI_STATE_ACTIVE end replaceDeletedFactions(base.alignment, map) if base.newAlignmentAndSteps then replaceDeletedFactions(base.newAlignmentAndSteps[2], map) end updateBaseStats(base) ------- growing local baseCanGrow = false local chunksCount = 0 local activeChunks = 0 for chunk, _ in pairs(base.chunks) do if (getNestActiveness(map, chunk) > 0) then activeChunks = activeChunks + 1 -- baseCanGrow = baseCanGrow or baseUtils.chunkCanGrow(map, chunk) -- only active chunk end chunksCount = chunksCount + 1 end if (activeChunks > 0) and (chunksCount <= 5) then for chunk, _ in pairs(base.chunks) do baseCanGrow = baseCanGrow or baseUtils.chunkCanGrow(map, chunk) -- only active chunk end end if baseCanGrow then if not map.basesToGrow[base.id] then map.basesToGrow[base.id] = base -- game.print("add base to growing list: #"..base.id) -- debug end else if map.basesToGrow[base.id] then universe.growingBases[base.id] = nil map.basesToGrow[base.id] = nil if universe.growingBasesIterator == base.id then universe.growingBasesIterator = nil end -- game.print("base stop growing: #"..base.id.." activeChunks = "..activeChunks..", baseCanGrow =".. tostring(baseCanGrow)..", chunksCount = "..chunksCount) -- debug end end ------- if base.newAlignmentAndSteps then convertAlignment(base.alignment, base.newAlignmentAndSteps, true) if base.newAlignmentAndSteps[1] == 0 then base.newAlignmentAndSteps = nil end end if (universe.evolveTick <=tick) and (not base.changingEntities) and (not base.newAlignmentAndSteps) and ((base.evolveTick or 0) <= tick) then local entities = surface.find_entities_filtered(universe.filteredEntitiesPointQueryLimited) if #entities ~= 0 then local entity = entities[1] local cost = universe.costLookup[entity.name] if cost then local newEntity = baseUtils.evolveEntity(entity, map) if newEntity then base.evolveTick = tick + cost universe.evolveTick = tick + GLOBAL_EVOLVE_COOLDOWN end end end end base.tick = tick end function baseUtils.createBase(map, chunk, tick, thisIsRampantEnemy) if not tick then return nil end if not chunk then return nil end local universe = map.universe local x = chunk.x local y = chunk.y local distance = euclideanDistancePoints(x, y, 0, 0) local meanLevel = mFloor(distance * 0.005) local distanceIndex = mMin(1, distance * BASE_DISTANCE_TO_EVO_INDEX) local evoIndex = mMax(distanceIndex, map.evolutionLevel) local tier = evoToTierNorandom(map.universe, evoIndex) local tierHandicap if tier == 1 then if mRandom()<0.7 then tierHandicap = 0 else tierHandicap = 1 end else tierHandicap = mRandom(mMin(3, tier))-1 end local baseTick = (tick or 0) local alignment alignment = findBaseInitialAlignment(map, evoIndex) or {neutral = 1} local baseLevel = gaussianRandomRange(meanLevel, meanLevel * 0.3, meanLevel * 0.50, meanLevel * 1.50) local baseDistanceThreshold = gaussianRandomRange(BASE_DISTANCE_THRESHOLD, BASE_DISTANCE_THRESHOLD * 0.2, BASE_DISTANCE_THRESHOLD * 0.75, BASE_DISTANCE_THRESHOLD * 1.50) local distanceThreshold = (baseLevel * BASE_DISTANCE_LEVEL_BONUS) + baseDistanceThreshold local base = { x = x, y = y, distanceThreshold = distanceThreshold, -- not used, but maybe later... tick = baseTick, alignment = alignment, state = BASE_AI_STATE_ACTIVE, damagedBy = {}, stateTick = 0, createdTick = (tick or 0), evolveTick = (tick or 0) + BASE_EVOLVE_THRESHOLD, nextMutationTick = tick + 36000, points = 0, thisIsRampantEnemy = thisIsRampantEnemy, mapIndex = map.surface.index, chunks = {}, basesToGrow = {}, id = universe.baseId , chunkFactions = {}, factionsTotal = nil, changingEntities = false, tier = tier, tierHandicap = tierHandicap, newAlignmentAndSteps = nil } universe.baseId = universe.baseId + 1 setChunkBase(map, chunk, base) universe.bases[base.id] = base return base end -- + !КДА 2021.11 function baseUtils.findNewBaseAligment(map, chunk) if not chunk then return {neutral = 1} end local x = chunk.x local y = chunk.y local distance = euclideanDistancePoints(x, y, 0, 0) local distanceIndex = mMin(1, distance * BASE_DISTANCE_TO_EVO_INDEX) local evoIndex = mMax(distanceIndex, map.evolutionLevel) local alignment = findBaseInitialAlignment(map, evoIndex) or {neutral = 1} return alignment end -- - !КДА 2021.11 function baseUtils.rebuildNativeTables(universe) local alignmentSet = {} universe.evolutionTableAlignment = alignmentSet local buildingSpaceLookup = {} universe.buildingSpaceLookup = buildingSpaceLookup local enemyAlignmentLookup = {} universe.enemyAlignmentLookup = enemyAlignmentLookup local evoToTierMapping = {} universe.evoToTierMapping = evoToTierMapping local upgradeLookup = {} universe.upgradeLookup = upgradeLookup local buildingEvolveLookup = {} universe.buildingEvolveLookup = buildingEvolveLookup local costLookup = {} universe.costLookup = costLookup local buildingHiveTypeLookup = {} universe.buildingHiveTypeLookup = buildingHiveTypeLookup local buildingTierLookup = {} -- + !КДА 2021.11 universe.buildingTierLookup = buildingTierLookup for i=1,10 do evoToTierMapping[#evoToTierMapping+1] = (((i - 1) * 0.1) ^ 0.8) - 0.01 end for i=1,#FACTION_SET do local faction = FACTION_SET[i] local factionUpgradeLookup = {} upgradeLookup[faction.type] = factionUpgradeLookup local factionBuildingPicker = {} buildingEvolveLookup[faction.type] = factionBuildingPicker for t=1,10 do local alignments = alignmentSet[t] if not alignments then alignments = {} alignmentSet[t] = alignments end --[[ alignments table is a table that is used for selecting what factions are available to pick given an evolution level. evolutionTable is a table that given a faction allows the selection of a building type based on the propabilities given. Once the the building type is selected given a faction, then the evolution decides what level of building to select --]] local factionAcceptRate = faction.acceptRate local low = factionAcceptRate[1] local high = factionAcceptRate[2] if (low <= t) and (t <= high) then alignments[#alignments+1] = {linearInterpolation((t - low) / (high - low), factionAcceptRate[3], factionAcceptRate[4]), faction.type} end local tieredUpgradeBuildingSet = factionUpgradeLookup[t] if not tieredUpgradeBuildingSet then tieredUpgradeBuildingSet = {} factionUpgradeLookup[t] = tieredUpgradeBuildingSet end local tieredBuildingPickerSet = factionBuildingPicker[t] if not tieredBuildingPickerSet then tieredBuildingPickerSet = {} factionBuildingPicker[t] = tieredBuildingPickerSet end for b=1,#faction.buildings do local building = faction.buildings[b] local buildingSet = tieredUpgradeBuildingSet[building.type] if not buildingSet then buildingSet = {} tieredUpgradeBuildingSet[building.type] = buildingSet end local variationSet = {} for v=1,universe.ENEMY_VARIATIONS do local entry = faction.type .. "-" .. building.name .. "-v" .. v .. "-t" .. t .. "-rampant" enemyAlignmentLookup[entry] = faction.type costLookup[entry] = HIVE_BUILDINGS_COST[building.type] buildingHiveTypeLookup[entry] = building.type buildingTierLookup[entry] = t if v==1 then variationSet[#variationSet+1] = entry end end local buildingAcceptRate = building.acceptRate local buildingLow = buildingAcceptRate[1] local buildingHigh = buildingAcceptRate[2] if (buildingLow <= t) and (t <= buildingHigh) then for vi=1,#variationSet do local variation = variationSet[vi] buildingSet[#buildingSet+1] = variation end tieredBuildingPickerSet[#tieredBuildingPickerSet+1] = { linearInterpolation((t - buildingLow) / (buildingHigh - buildingLow), buildingAcceptRate[3], buildingAcceptRate[4]), variationSet, building.type } end end end end for t=1,10 do local alignments = alignmentSet[t] local totalAlignment = 0 for i=1,#alignments do totalAlignment = totalAlignment + alignments[i][1] end for i=1,#alignments do alignments[i][1] = alignments[i][1] / totalAlignment end for fi=1,#FACTION_SET do local faction = FACTION_SET[fi] local factionBuildingSet = buildingEvolveLookup[faction.type][t] local totalBuildingSet = 0 for i=1,#factionBuildingSet do totalBuildingSet = totalBuildingSet + factionBuildingSet[i][1] end for i=1,#factionBuildingSet do factionBuildingSet[i][1] = factionBuildingSet[i][1] / totalBuildingSet end end end local cnt = 0 --------------------------- if universe.maps then for _,base in pairs(universe.bases) do local map = universe.maps[base.mapIndex] if map then local evoIndex = evoToTier(universe, map.evolutionLevel) cnt = cnt + 1 for x=1,#base.alignment do local alignment = base.alignment[x] if not universe.buildingEvolveLookup[alignment] then base.alignment = findBaseInitialAlignment(map, evoIndex) or {neutral = 1} break end end end end end end function baseUtils.setRandomBaseToMutate(universe) if not universe.NEW_ENEMIES then return end if game.tick < 2000 then return end local basesId = {} for _,base in pairs(universe.bases) do if base and base.thisIsRampantEnemy and base.alignment and (base.state ~= BASE_AI_STATE_MUTATE) and (base.tier > 1) then basesId[#basesId + 1] = base.id end end if #basesId == 0 then return end local base = universe.bases[basesId[mRandom(1, #basesId)]] base.state = BASE_AI_STATE_MUTATE end -- + !КДА 2021.10 debug function baseUtils.ShowNewBaseAligments(universe, targetEvolution) if universe == nil then return "" end local tier = evoToTierNorandom(universe, targetEvolution) local alignments = universe.evolutionTableAlignment[tier] game.print("Evo = "..tostring(targetEvolution*100).."%, tier:"..tostring(tier)) for i=1,#alignments do local alignment = alignments[i] local aligmentStats = tostring(i)..":" for x=1,#alignment do aligmentStats = aligmentStats .. ", " .. alignment[x] end game.print(aligmentStats) end return "" end function baseUtils.ShowEvoToTierMapping(universe) for i=10,1,-1 do game.print(tostring(i)..":"..tostring(universe.evoToTierMapping[i])) end return "" end -- - !КДА baseUtilsG = baseUtils return baseUtils