1
0
mirror of https://github.com/veden/Rampant.git synced 2025-09-16 09:16:43 +02:00

trying to better represent map features

This commit is contained in:
Aaron Veden
2017-12-28 21:38:10 -08:00
parent d0ed774587
commit 319fb66845
16 changed files with 569 additions and 431 deletions

View File

@@ -88,8 +88,7 @@ local function onIonCannonFired(event)
if (natives.points > AI_MAX_OVERFLOW_POINTS) then if (natives.points > AI_MAX_OVERFLOW_POINTS) then
natives.points = AI_MAX_OVERFLOW_POINTS natives.points = AI_MAX_OVERFLOW_POINTS
end end
local chunkX, chunkY = positionToChunkXY(event.position) local chunk = getChunkByPosition(regionMap, event.position)
local chunk = getChunkByPosition(regionMap, chunkX, chunkY)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
rallyUnits(chunk, regionMap, surface, natives, event.tick) rallyUnits(chunk, regionMap, surface, natives, event.tick)
end end
@@ -158,10 +157,13 @@ local function rebuildRegionMap()
y=0} y=0}
--this is shared between two different queries --this is shared between two different queries
local sharedFilterArea = {{0, 0}, {0, 0}} regionMap.area = {{0, 0}, {0, 0}}
regionMap.filteredEntitiesQuery = { area=sharedFilterArea, force=false } regionMap.countResourcesQuery = { area=regionMap.area, type="resource" }
regionMap.cliffQuery = { type="cliff", area={{0, 0}, {0, 0}} } regionMap.filteredEntitiesEnemyQuery = { area=regionMap.area, force="enemy" }
regionMap.filteredTilesQuery = { name="", area=sharedFilterArea } regionMap.filteredEntitiesEnemyTypeQuery = { area=regionMap.area, force="enemy", type="unit-spawner" }
regionMap.filteredEntitiesPlayerQuery = { area=regionMap.area, force="player" }
regionMap.canPlaceQuery = { name="", position={0,0} }
regionMap.filteredTilesQuery = { name="", area=regionMap.area }
-- switched over to tick event -- switched over to tick event
regionMap.logicTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC) regionMap.logicTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
@@ -253,28 +255,28 @@ local function onTick(event)
processPendingChunks(natives, regionMap, surface, pendingChunks, tick) processPendingChunks(natives, regionMap, surface, pendingChunks, tick)
scanMap(regionMap, surface, natives) scanMap(regionMap, surface, natives)
if (tick == regionMap.logicTick) then -- if (tick == regionMap.logicTick) then
regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC -- regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC
local players = gameRef.players -- local players = gameRef.players
planning(natives, -- planning(natives,
gameRef.forces.enemy.evolution_factor, -- gameRef.forces.enemy.evolution_factor,
tick, -- tick,
surface) -- surface)
cleanSquads(natives) -- cleanSquads(natives)
regroupSquads(natives) -- regroupSquads(natives)
processPlayers(players, regionMap, surface, natives, tick) -- processPlayers(players, regionMap, surface, natives, tick)
if natives.useCustomAI then -- if natives.useCustomAI then
processBases(regionMap, surface, natives, tick) -- processBases(regionMap, surface, natives, tick)
end -- end
squadsBeginAttack(natives, players) -- squadsBeginAttack(natives, players)
squadsAttack(regionMap, surface, natives) -- squadsAttack(regionMap, surface, natives)
end -- end
processMap(regionMap, surface, natives, tick) processMap(regionMap, surface, natives, tick)
end end
@@ -299,19 +301,18 @@ local function onDeath(event)
local surface = entity.surface local surface = entity.surface
if (surface.index == 1) then if (surface.index == 1) then
local entityPosition = entity.position local entityPosition = entity.position
local chunkX, chunkY = positionToChunkXY(entityPosition) local chunk = getChunkByPosition(regionMap, entityPosition)
if (entity.force.name == "enemy") then if (entity.force.name == "enemy") then
if (entity.type == "unit") then if (entity.type == "unit") then
local deathChunk = getChunkByPosition(regionMap, chunkX, chunkY)
if (deathChunk ~= SENTINEL_IMPASSABLE_CHUNK) then if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
-- drop death pheromone where unit died -- drop death pheromone where unit died
deathScent(deathChunk) deathScent(chunk)
if event.force and (event.force.name == "player") and (deathChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold) then if event.force and (event.force.name == "player") and (chunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold) then
local tick = event.tick local tick = event.tick
retreatUnits(deathChunk, retreatUnits(chunk,
entityPosition, entityPosition,
convertUnitGroupToSquad(natives, entity.unit_group), convertUnitGroupToSquad(natives, entity.unit_group),
regionMap, regionMap,
@@ -320,7 +321,7 @@ local function onDeath(event)
tick) tick)
if (mRandom() < natives.rallyThreshold) and not surface.peaceful_mode then if (mRandom() < natives.rallyThreshold) and not surface.peaceful_mode then
rallyUnits(deathChunk, regionMap, surface, natives, tick) rallyUnits(chunk, regionMap, surface, natives, tick)
end end
end end
end end
@@ -332,9 +333,8 @@ local function onDeath(event)
local creditNatives = false local creditNatives = false
if (event.force ~= nil) and (event.force.name == "enemy") then if (event.force ~= nil) and (event.force.name == "enemy") then
creditNatives = true creditNatives = true
local victoryChunk = getChunkByPosition(regionMap, chunkX, chunkY) if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
if (victoryChunk ~= SENTINEL_IMPASSABLE_CHUNK) then victoryScent(chunk, entity.type)
victoryScent(victoryChunk, entity.type)
end end
end end
if creditNatives and natives.safeBuildings and (natives.safeEntities[entity.type] or natives.safeEntityName[entity.name]) then if creditNatives and natives.safeBuildings and (natives.safeEntities[entity.type] or natives.safeEntityName[entity.name]) then
@@ -403,15 +403,13 @@ remote.add_interface("rampantTests",
cheatMode = tests.cheatMode, cheatMode = tests.cheatMode,
gaussianRandomTest = tests.gaussianRandomTest, gaussianRandomTest = tests.gaussianRandomTest,
reveal = tests.reveal, reveal = tests.reveal,
showMovementGrid = tests.showMovementGrid,
baseStats = tests.baseStats, baseStats = tests.baseStats,
baseTiles = tests.baseTiles,
mergeBases = tests.mergeBases, mergeBases = tests.mergeBases,
clearBases = tests.clearBases, clearBases = tests.clearBases,
getOffsetChunk = tests.getOffsetChunk, getOffsetChunk = tests.getOffsetChunk,
registeredNest = tests.registeredNest, registeredNest = tests.registeredNest,
colorResourcePoints = tests.colorResourcePoints, stepAdvanceTendrils = tests.stepAdvanceTendrils,
stepAdvanceTendrils = tests.stepAdvanceTendrils exportAiState = tests.exportAiState
} }
) )

View File

@@ -46,7 +46,7 @@ local getRallyTick = chunkUtils.getRallyTick
local setRallyTick = chunkUtils.setRallyTick local setRallyTick = chunkUtils.setRallyTick
local getNeighborChunks = mapUtils.getNeighborChunks local getNeighborChunks = mapUtils.getNeighborChunks
local getChunkByPosition = mapUtils.getChunkByPosition local getChunkByXY = mapUtils.getChunkByXY
local scoreNeighborsForFormation = movementUtils.scoreNeighborsForFormation local scoreNeighborsForFormation = movementUtils.scoreNeighborsForFormation
local createSquad = unitGroupUtils.createSquad local createSquad = unitGroupUtils.createSquad
local attackWaveScaling = config.attackWaveScaling local attackWaveScaling = config.attackWaveScaling
@@ -94,7 +94,7 @@ function aiAttackWave.rallyUnits(chunk, regionMap, surface, natives, tick)
for x=cX - RALLY_CRY_DISTANCE, cX + RALLY_CRY_DISTANCE, 32 do for x=cX - RALLY_CRY_DISTANCE, cX + RALLY_CRY_DISTANCE, 32 do
for y=cY - RALLY_CRY_DISTANCE, cY + RALLY_CRY_DISTANCE, 32 do for y=cY - RALLY_CRY_DISTANCE, cY + RALLY_CRY_DISTANCE, 32 do
if (x ~= cX) and (y ~= cY) then if (x ~= cX) and (y ~= cY) then
local rallyChunk = getChunkByPosition(regionMap, x, y) local rallyChunk = getChunkByXY(regionMap, x, y)
if (rallyChunk ~= SENTINEL_IMPASSABLE_CHUNK) and (getNestCount(regionMap, rallyChunk) > 0) then if (rallyChunk ~= SENTINEL_IMPASSABLE_CHUNK) and (getNestCount(regionMap, rallyChunk) > 0) then
aiAttackWave.formSquads(regionMap, surface, natives, rallyChunk, AI_VENGENCE_SQUAD_COST) aiAttackWave.formSquads(regionMap, surface, natives, rallyChunk, AI_VENGENCE_SQUAD_COST)
if (natives.points < AI_VENGENCE_SQUAD_COST) and (#natives.squads < natives.maxSquads) then if (natives.points < AI_VENGENCE_SQUAD_COST) and (#natives.squads < natives.maxSquads) then

View File

@@ -14,22 +14,17 @@ local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK
-- imported functions -- imported functions
local createChunk = chunkUtils.createChunk local createChunk = chunkUtils.createChunk
local checkChunkPassability = chunkUtils.checkChunkPassability local analyzeChunk = chunkUtils.analyzeChunk
local scoreChunk = chunkUtils.scoreChunk
local registerChunkEnemies = chunkUtils.registerChunkEnemies
-- module code -- module code
function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendingStack) function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendingStack)
local processQueue = regionMap.processQueue local processQueue = regionMap.processQueue
local filteredEntitiesQuery = regionMap.filteredEntitiesQuery local area = regionMap.area
local topOffset = filteredEntitiesQuery.area[1] local topOffset = area[1]
local bottomOffset = filteredEntitiesQuery.area[2] local bottomOffset = area[2]
local filteredTilesQuery = regionMap.filteredTilesQuery
local cliffQuery = regionMap.cliffQuery
for i=#pendingStack, 1, -1 do for i=#pendingStack, 1, -1 do
local event = pendingStack[i] local event = pendingStack[i]
@@ -45,12 +40,9 @@ function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendin
bottomOffset[1] = x + CHUNK_SIZE bottomOffset[1] = x + CHUNK_SIZE
bottomOffset[2] = y + CHUNK_SIZE bottomOffset[2] = y + CHUNK_SIZE
chunk = checkChunkPassability(chunk, surface, filteredTilesQuery, cliffQuery) chunk = analyzeChunk(chunk, natives, surface, regionMap)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
registerChunkEnemies(regionMap, chunk, surface, filteredEntitiesQuery)
scoreChunk(regionMap, chunk, surface, natives, filteredEntitiesQuery)
local chunkX = chunk.x local chunkX = chunk.x
if regionMap[chunkX] == nil then if regionMap[chunkX] == nil then

View File

@@ -28,7 +28,6 @@ local CHUNK_ALL_DIRECTIONS = constants.CHUNK_ALL_DIRECTIONS
local CHUNK_IMPASSABLE = constants.CHUNK_IMPASSABLE local CHUNK_IMPASSABLE = constants.CHUNK_IMPASSABLE
local CHUNK_TICK = constants.CHUNK_TICK local CHUNK_TICK = constants.CHUNK_TICK
local CHUNK_SIZE = constants.CHUNK_SIZE
local PATH_RATING = constants.PATH_RATING local PATH_RATING = constants.PATH_RATING
@@ -36,42 +35,42 @@ local PASSABLE = constants.PASSABLE
-- imported functions -- imported functions
local getChunkByPosition = mapUtils.getChunkByPosition local getChunkByXY = mapUtils.getChunkByXY
local mFloor = math.floor local mFloor = math.floor
-- module code -- module code
local function fullScan(x, y, count_entities_filtered, cliffQuery) local function fullScan(chunk, can_place_entity, canPlaceQuery)
local x = chunk.x
local y = chunk.y
local passableNorthSouth = false local passableNorthSouth = false
local passableEastWest = false local passableEastWest = false
local area = cliffQuery.area canPlaceQuery.name = "chunk-scanner-ns-rampant"
local top = area[1]
local bottom = area[2]
top[2] = y
bottom[2] = y + CHUNK_SIZE
for xi=x, x + 31 do local position = canPlaceQuery.position
top[1] = xi position[2] = y
bottom[1] = xi + 1
if (count_entities_filtered(cliffQuery) == 0) then for xi=x, x + 32 do
position[1] = xi
if can_place_entity(canPlaceQuery) then
passableNorthSouth = true passableNorthSouth = true
break break
end end
end end
top[1] = x position[1] = x
bottom[1] = x + CHUNK_SIZE canPlaceQuery.name = "chunk-scanner-ew-rampant"
for yi=y, y + 31 do
top[2] = yi for yi=y, y + 32 do
bottom[2] = yi + 1 position[2] = yi
if (count_entities_filtered(cliffQuery) == 0) then if can_place_entity(canPlaceQuery) then
passableEastWest = true passableEastWest = true
break break
end end
end end
return passableNorthSouth, passableEastWest return passableNorthSouth, passableEastWest
end end
@@ -140,7 +139,7 @@ local function getEntityOverlapChunks(regionMap, entity)
local leftBottomChunk = SENTINEL_IMPASSABLE_CHUNK local leftBottomChunk = SENTINEL_IMPASSABLE_CHUNK
local rightBottomChunk = SENTINEL_IMPASSABLE_CHUNK local rightBottomChunk = SENTINEL_IMPASSABLE_CHUNK
if (boundingBox ~= nil) then if boundingBox then
local center = entity.position local center = entity.position
local topXOffset local topXOffset
local topYOffset local topYOffset
@@ -174,15 +173,15 @@ local function getEntityOverlapChunks(regionMap, entity)
local rightBottomChunkX = rightTopChunkX local rightBottomChunkX = rightTopChunkX
local rightBottomChunkY = leftBottomChunkY local rightBottomChunkY = leftBottomChunkY
leftTopChunk = getChunkByPosition(regionMap, leftTopChunkX, leftTopChunkY) leftTopChunk = getChunkByXY(regionMap, leftTopChunkX, leftTopChunkY)
if (leftTopChunkX ~= rightTopChunkX) then if (leftTopChunkX ~= rightTopChunkX) then
rightTopChunk = getChunkByPosition(regionMap, rightTopChunkX, rightTopChunkY) rightTopChunk = getChunkByXY(regionMap, rightTopChunkX, rightTopChunkY)
end end
if (leftTopChunkY ~= leftBottomChunkY) then if (leftTopChunkY ~= leftBottomChunkY) then
leftBottomChunk = getChunkByPosition(regionMap, leftBottomChunkX, leftBottomChunkY) leftBottomChunk = getChunkByXY(regionMap, leftBottomChunkX, leftBottomChunkY)
end end
if (leftTopChunkX ~= rightBottomChunkX) and (leftTopChunkY ~= rightBottomChunkY) then if (leftTopChunkX ~= rightBottomChunkX) and (leftTopChunkY ~= rightBottomChunkY) then
rightBottomChunk = getChunkByPosition(regionMap, rightBottomChunkX, rightBottomChunkY) rightBottomChunk = getChunkByXY(regionMap, rightBottomChunkX, rightBottomChunkY)
end end
end end
return leftTopChunk, rightTopChunk, leftBottomChunk, rightBottomChunk return leftTopChunk, rightTopChunk, leftBottomChunk, rightBottomChunk
@@ -190,9 +189,9 @@ end
-- external functions -- external functions
function chunkUtils.checkChunkPassability(chunk, surface, filteredTilesQuery, cliffQuery) function chunkUtils.analyzeChunk(chunk, natives, surface, regionMap)
local count_tiles_filtered = surface.count_tiles_filtered local count_tiles_filtered = surface.count_tiles_filtered
local count_entities_filtered = surface.count_entities_filtered local filteredTilesQuery = regionMap.filteredTilesQuery
local passScore = 0 local passScore = 0
for i=1,#WATER_TILE_NAMES do for i=1,#WATER_TILE_NAMES do
@@ -204,7 +203,10 @@ function chunkUtils.checkChunkPassability(chunk, surface, filteredTilesQuery, cl
local pass = CHUNK_IMPASSABLE local pass = CHUNK_IMPASSABLE
if (passScore >= 0.60) then if (passScore >= 0.60) then
local passableNorthSouth, passableEastWest = fullScan(chunk.x, chunk.y, count_entities_filtered, cliffQuery) local passableNorthSouth, passableEastWest = fullScan(chunk,
surface.can_place_entity,
regionMap.canPlaceQuery)
if passableEastWest and passableNorthSouth then if passableEastWest and passableNorthSouth then
pass = CHUNK_ALL_DIRECTIONS pass = CHUNK_ALL_DIRECTIONS
elseif passableEastWest then elseif passableEastWest then
@@ -212,7 +214,54 @@ function chunkUtils.checkChunkPassability(chunk, surface, filteredTilesQuery, cl
elseif passableNorthSouth then elseif passableNorthSouth then
pass = CHUNK_NORTH_SOUTH pass = CHUNK_NORTH_SOUTH
end end
local entities = surface.find_entities_filtered(regionMap.filteredEntitiesPlayerQuery)
local playerObjects = 0
local safeBuildings = natives.safeBuildings
local safeEntities = natives.safeEntities
local safeEntityName = natives.safeEntityName
if safeBuildings then
for i=1, #entities do
local entity = entities[i]
local entityType = entity.type
if safeEntities[entityType] or safeEntityName[entity.name] then
entity.destructible = false
end
local entityScore = BUILDING_PHEROMONES[entityType] or 0
playerObjects = playerObjects + entityScore
end
else
for i=1, #entities do
local entityScore = BUILDING_PHEROMONES[entities[i].type] or 0
playerObjects = playerObjects + entityScore
end
end
local query = regionMap.filteredEntitiesEnemyTypeQuery
query.type = "unit-spawner"
local nests = surface.count_entities_filtered(regionMap.filteredEntitiesEnemyTypeQuery)
chunkUtils.setNestCount(regionMap, chunk, nests)
query.type = "turret"
local worms = surface.count_entities_filtered(regionMap.filteredEntitiesEnemyTypeQuery)
chunkUtils.setWormCount(regionMap, chunk, worms)
local resources = surface.count_entities_filtered(regionMap.countResourcesQuery) * 0.001
local total = nests + worms + resources
if (playerObjects > 0) then
pass = CHUNK_PLAYER_BORDER
end
chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerObjects)
chunkUtils.setResourceGenerator(regionMap, chunk, resources)
end end
if (pass == CHUNK_IMPASSABLE) then if (pass == CHUNK_IMPASSABLE) then
return SENTINEL_IMPASSABLE_CHUNK return SENTINEL_IMPASSABLE_CHUNK
else else
@@ -247,16 +296,27 @@ end
-- -- end -- -- end
-- end -- end
function chunkUtils.registerChunkEnemies(regionMap, chunk, surface, filteredEntitiesQuery) function chunkUtils.getNestCount(regionMap, chunk)
filteredEntitiesQuery.force = "enemy" return regionMap.chunkToNests[chunk] or 0
local enemies = surface.find_entities_filtered(filteredEntitiesQuery) end
for i=1, #enemies do function chunkUtils.getWormCount(regionMap, chunk)
local enemy = enemies[i] return regionMap.chunkToWorms[chunk] or 0
local enemyType = enemy.type end
if (enemyType == "unit-spawner") or (enemyType == "turret") then
addEnemyStructureToChunk(regionMap, chunk, enemy, nil) function chunkUtils.setWormCount(regionMap, chunk, count)
end if (count == 0) then
regionMap.chunkToWorms[chunk] = nil
else
regionMap.chunkToWorms[chunk] = count
end
end
function chunkUtils.setNestCount(regionMap, chunk, count)
if (count == 0) then
regionMap.chunkToNests[chunk] = nil
else
regionMap.chunkToNests[chunk] = count
end end
end end
@@ -288,8 +348,12 @@ function chunkUtils.setRetreatTick(regionMap, chunk, tick)
regionMap.chunkToRetreats[chunk] = tick regionMap.chunkToRetreats[chunk] = tick
end end
function chunkUtils.setResourceGenerator(regionMap, chunk, playerGenerator) function chunkUtils.setResourceGenerator(regionMap, chunk, resourceGenerator)
regionMap.chunkToResource[chunk] = playerGenerator if (resourceGenerator == 0) then
regionMap.chunkToResource[chunk] = nil
else
regionMap.chunkToResource[chunk] = resourceGenerator
end
end end
function chunkUtils.getResourceGenerator(regionMap, chunk) function chunkUtils.getResourceGenerator(regionMap, chunk)
@@ -301,43 +365,17 @@ function chunkUtils.getPlayerBaseGenerator(regionMap, chunk)
end end
function chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerGenerator) function chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerGenerator)
regionMap.chunkToPlayerBase[chunk] = playerGenerator if (playerGenerator == 0) then
regionMap.chunkToPlayerBase[chunk] = nil
else
regionMap.chunkToPlayerBase[chunk] = playerGenerator
end
end end
function chunkUtils.addPlayerBaseGenerator(regionMap, chunk, playerGenerator) function chunkUtils.addPlayerBaseGenerator(regionMap, chunk, playerGenerator)
regionMap.chunkToPlayerBase[chunk] = regionMap.chunkToPlayerBase[chunk] + playerGenerator regionMap.chunkToPlayerBase[chunk] = regionMap.chunkToPlayerBase[chunk] + playerGenerator
end end
function chunkUtils.scoreChunk(regionMap, chunk, surface, natives, filteredEntitiesQuery)
filteredEntitiesQuery.force = nil
filteredEntitiesQuery.type = "resource"
chunkUtils.setResourceGenerator(regionMap, chunk, surface.count_entities_filtered(filteredEntitiesQuery) * 0.001)
filteredEntitiesQuery.type = nil
filteredEntitiesQuery.force = "player"
local entities = surface.find_entities_filtered(filteredEntitiesQuery)
local playerObjects = 0
local safeBuildings = natives.safeBuildings
for i=1, #entities do
local entity = entities[i]
local entityType = entity.type
if safeBuildings then
if natives.safeEntities[entityType] or natives.safeEntityName[entity.name] then
entity.destructible = false
end
end
local entityScore = BUILDING_PHEROMONES[entityType]
if entityScore then
playerObjects = playerObjects + entityScore
end
end
chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerObjects)
end
function chunkUtils.createChunk(topX, topY) function chunkUtils.createChunk(topX, topY)
local chunk = { local chunk = {
x = topX, x = topX,
@@ -354,16 +392,6 @@ function chunkUtils.createChunk(topX, topY)
return chunk return chunk
end end
function chunkUtils.colorChunk(x, y, tileType, surface)
local tiles = {}
for xi=x+5, x + 27 do
for yi=y+5, y + 27 do
tiles[#tiles+1] = {name=tileType, position={xi, yi}}
end
end
surface.set_tiles(tiles, false)
end
function chunkUtils.registerEnemyBaseStructure(regionMap, entity, base) function chunkUtils.registerEnemyBaseStructure(regionMap, entity, base)
local entityType = entity.type local entityType = entity.type
if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then

View File

@@ -18,7 +18,6 @@ constants.VERSION_28 = 28
constants.VERSION_33 = 33 constants.VERSION_33 = 33
constants.VERSION_36 = 36 constants.VERSION_36 = 36
-- misc -- misc
constants.WATER_TILE_NAMES = { "water", "deepwater", "water-green", "deepwater-green" } constants.WATER_TILE_NAMES = { "water", "deepwater", "water-green", "deepwater-green" }
@@ -30,13 +29,10 @@ constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL = 10000
constants.PROCESS_QUEUE_SIZE = 400 constants.PROCESS_QUEUE_SIZE = 400
constants.SCAN_QUEUE_SIZE = 5 constants.SCAN_QUEUE_SIZE = 5
constants.ITEM_COLLECTOR_QUEUE_SIZE = 6
constants.BASE_QUEUE_SIZE = 1 constants.BASE_QUEUE_SIZE = 1
constants.SQUAD_QUEUE_SIZE = 2 constants.SQUAD_QUEUE_SIZE = 2
constants.PROCESS_PLAYER_BOUND = 128 constants.PROCESS_PLAYER_BOUND = 128
constants.ITEM_COLLECTOR_MAX_QUEUE_SIZE = 20
constants.TICKS_A_SECOND = 60 constants.TICKS_A_SECOND = 60
constants.TICKS_A_MINUTE = constants.TICKS_A_SECOND * 60 constants.TICKS_A_MINUTE = constants.TICKS_A_SECOND * 60
@@ -47,22 +43,6 @@ constants.PLAYER_PHEROMONE_MULTIPLER = 100
constants.DEV_CUSTOM_AI = false constants.DEV_CUSTOM_AI = false
-- mask
constants.MASK_PASSABLE = 3
constants.MASK_PASSABLE_SIZE = 2
constants.MASK_NEST_COUNT = 511
constants.MASK_NEST_COUNT_SIZE = 9
constants.MASK_WORM_COUNT = 511
constants.MASK_WORM_COUNT_SIZE = 9
constants.MASK_PASSABLE_AND_NEST_COUNT = 2047
constants.MASK_PASSABLE_AND_NEST_COUNT_SIZE = 11
-- item collector
constants.ITEM_COLLECTOR_DISTANCE = 50
-- chunk properties -- chunk properties
constants.CHUNK_SIZE = 32 constants.CHUNK_SIZE = 32
@@ -75,6 +55,8 @@ constants.CHUNK_IMPASSABLE = 0
constants.CHUNK_NORTH_SOUTH = 1 constants.CHUNK_NORTH_SOUTH = 1
constants.CHUNK_EAST_WEST = 2 constants.CHUNK_EAST_WEST = 2
constants.CHUNK_ALL_DIRECTIONS = 3 constants.CHUNK_ALL_DIRECTIONS = 3
constants.CHUNK_PLAYER_BORDER = 4
constants.CHUNK_PLAYER_INTERIOR = 5
-- ai -- ai

View File

@@ -42,16 +42,16 @@ local playerScent = pheromoneUtils.playerScent
local formSquads = aiAttackWave.formSquads local formSquads = aiAttackWave.formSquads
local getChunkByPosition = mapUtils.getChunkByPosition local getChunkByPosition = mapUtils.getChunkByPosition
local positionToChunkXY = mapUtils.positionToChunkXY local getChunkByXY = mapUtils.getChunkByXY
local recycleBiters = unitGroupUtils.recycleBiters local recycleBiters = unitGroupUtils.recycleBiters
local validPlayer = playerUtils.validPlayer local validPlayer = playerUtils.validPlayer
local setResourceGenerator = chunkUtils.setResourceGenerator local analyzeChunk = chunkUtils.analyzeChunk
local setPlayerBaseGenerator = chunkUtils.setPlayerBaseGenerator
local getNestCount = chunkUtils.getNestCount local getNestCount = chunkUtils.getNestCount
local getWormCount = chunkUtils.getWormCount local getEnemyStructureCount = chunkUtils.getEnemyStructureCount
local canAttack = aiPredicates.canAttack local canAttack = aiPredicates.canAttack
@@ -141,8 +141,7 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick)
for i=1,#playerOrdering do for i=1,#playerOrdering do
local player = players[playerOrdering[i]] local player = players[playerOrdering[i]]
if validPlayer(player) then if validPlayer(player) then
local chunkX, chunkY = positionToChunkXY(player.character.position) local playerChunk = getChunkByPosition(regionMap, player.character.position)
local playerChunk = getChunkByPosition(regionMap, chunkX, chunkY)
if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
playerScent(playerChunk) playerScent(playerChunk)
@@ -152,17 +151,16 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick)
for i=1,#playerOrdering do for i=1,#playerOrdering do
local player = players[playerOrdering[i]] local player = players[playerOrdering[i]]
if validPlayer(player) then if validPlayer(player) then
local chunkX, chunkY = positionToChunkXY(player.character.position) local playerChunk = getChunkByPosition(regionMap, player.character.position)
local playerChunk = getChunkByPosition(regionMap, chunkX, chunkY)
if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
local vengence = (allowingAttacks and local vengence = (allowingAttacks and
(natives.points >= AI_VENGENCE_SQUAD_COST) and (natives.points >= AI_VENGENCE_SQUAD_COST) and
((getWormCount(regionMap, playerChunk) > 0) or (getNestCount(regionMap, playerChunk) > 0) or (playerChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold))) ((getEnemyStructureCount(regionMap, playerChunk) > 0) or (playerChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold)))
for x=playerChunk.x - PROCESS_PLAYER_BOUND, playerChunk.x + PROCESS_PLAYER_BOUND, 32 do for x=playerChunk.x - PROCESS_PLAYER_BOUND, playerChunk.x + PROCESS_PLAYER_BOUND, 32 do
for y=playerChunk.y - PROCESS_PLAYER_BOUND, playerChunk.y + PROCESS_PLAYER_BOUND, 32 do for y=playerChunk.y - PROCESS_PLAYER_BOUND, playerChunk.y + PROCESS_PLAYER_BOUND, 32 do
local chunk = getChunkByPosition(regionMap, x, y) local chunk = getChunkByXY(regionMap, x, y)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[CHUNK_TICK] ~= tick) then if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[CHUNK_TICK] ~= tick) then
chunk[CHUNK_TICK] = tick chunk[CHUNK_TICK] = tick
@@ -197,10 +195,6 @@ function mapProcessor.scanMap(regionMap, surface, natives)
local offset = {0, 0} local offset = {0, 0}
local chunkBox = {false, offset} local chunkBox = {false, offset}
local playerQuery = {area = chunkBox,
force = "player"}
local resourceQuery = {area = chunkBox,
type = "resource"}
local unitCountQuery = { area = chunkBox, local unitCountQuery = { area = chunkBox,
type = "unit", type = "unit",
force = "enemy", force = "enemy",
@@ -235,25 +229,7 @@ function mapProcessor.scanMap(regionMap, surface, natives)
end end
end end
setResourceGenerator(regionMap, chunk, surface.count_entities_filtered(resourceQuery) * 0.001) analyzeChunk(chunk, natives, surface, regionMap)
local entities = surface.find_entities_filtered(playerQuery)
local playerBaseGenerator = 0
local safeBuildings = natives.safeBuildings
for i=1,#entities do
local entity = entities[i]
local value = BUILDING_PHEROMONES[entity.type]
if safeBuildings then
if natives.safeEntities[entity.type] or natives.safeEntityName[entity.name] then
entity.destructible = false
end
end
if value then
playerBaseGenerator = playerBaseGenerator + value
end
end
setPlayerBaseGenerator(regionMap, chunk, playerBaseGenerator)
end end
if (endIndex == #processQueue) then if (endIndex == #processQueue) then

View File

@@ -25,7 +25,7 @@ local mFloor = math.floor
-- module code -- module code
function mapUtils.getChunkByPosition(regionMap, x, y) function mapUtils.getChunkByXY(regionMap, x, y)
local chunkX = regionMap[x] local chunkX = regionMap[x]
if chunkX then if chunkX then
return chunkX[y] or SENTINEL_IMPASSABLE_CHUNK return chunkX[y] or SENTINEL_IMPASSABLE_CHUNK
@@ -33,6 +33,15 @@ function mapUtils.getChunkByPosition(regionMap, x, y)
return SENTINEL_IMPASSABLE_CHUNK return SENTINEL_IMPASSABLE_CHUNK
end end
function mapUtils.getChunkByPosition(regionMap, position)
local chunkX = regionMap[mFloor(position.x * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE]
if chunkX then
local chunkY = mFloor(position.y * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE
return chunkX[chunkY] or SENTINEL_IMPASSABLE_CHUNK
end
return SENTINEL_IMPASSABLE_CHUNK
end
function mapUtils.positionToChunkXY(position) function mapUtils.positionToChunkXY(position)
local chunkX = mFloor(position.x * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE local chunkX = mFloor(position.x * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE
local chunkY = mFloor(position.y * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE local chunkY = mFloor(position.y * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE
@@ -126,33 +135,32 @@ function mapUtils.getCardinalChunks(regionMap, x, y)
return neighbors return neighbors
end end
function mapUtils.positionFromDirectionAndChunk(direction, startPosition, position, scaling) function mapUtils.positionFromDirectionAndChunk(direction, startPosition, endPosition, scaling)
if (direction == 1) then if (direction == 1) then
position.x = startPosition.x - CHUNK_SIZE * scaling endPosition.x = startPosition.x - CHUNK_SIZE * scaling
position.y = startPosition.y - CHUNK_SIZE * scaling endPosition.y = startPosition.y - CHUNK_SIZE * scaling
elseif (direction == 2) then elseif (direction == 2) then
position.x = startPosition.x endPosition.x = startPosition.x
position.y = startPosition.y - CHUNK_SIZE * scaling endPosition.y = startPosition.y - CHUNK_SIZE * scaling
elseif (direction == 3) then elseif (direction == 3) then
position.x = startPosition.x + CHUNK_SIZE * scaling endPosition.x = startPosition.x + CHUNK_SIZE * scaling
position.y = startPosition.y - CHUNK_SIZE * scaling endPosition.y = startPosition.y - CHUNK_SIZE * scaling
elseif (direction == 4) then elseif (direction == 4) then
position.x = startPosition.x - CHUNK_SIZE * scaling endPosition.x = startPosition.x - CHUNK_SIZE * scaling
position.y = startPosition.y endPosition.y = startPosition.y
elseif (direction == 5) then elseif (direction == 5) then
position.x = startPosition.x + CHUNK_SIZE * scaling endPosition.x = startPosition.x + CHUNK_SIZE * scaling
position.y = startPosition.y endPosition.y = startPosition.y
elseif (direction == 6) then elseif (direction == 6) then
position.x = startPosition.x - CHUNK_SIZE * scaling endPosition.x = startPosition.x - CHUNK_SIZE * scaling
position.y = startPosition.y + CHUNK_SIZE * scaling endPosition.y = startPosition.y + CHUNK_SIZE * scaling
elseif (direction == 7) then elseif (direction == 7) then
position.x = startPosition.x endPosition.x = startPosition.x
position.y = startPosition.y + CHUNK_SIZE * scaling endPosition.y = startPosition.y + CHUNK_SIZE * scaling
elseif (direction == 8) then elseif (direction == 8) then
position.x = startPosition.x + CHUNK_SIZE * scaling endPosition.x = startPosition.x + CHUNK_SIZE * scaling
position.y = startPosition.y + CHUNK_SIZE * scaling endPosition.y = startPosition.y + CHUNK_SIZE * scaling
end end
return position
end end
return mapUtils return mapUtils

View File

@@ -33,7 +33,7 @@ local getChunkByPosition = mapUtils.getChunkByPosition
function nestUtils.buildNest(regionMap, base, surface, targetPosition, name) function nestUtils.buildNest(regionMap, base, surface, targetPosition, name)
local position = surface.find_non_colliding_position(name, targetPosition, DOUBLE_CHUNK_SIZE, 2) local position = surface.find_non_colliding_position(name, targetPosition, DOUBLE_CHUNK_SIZE, 2)
local chunk = getChunkByPosition(regionMap, position.x, position.y) local chunk = getChunkByPosition(regionMap, position)
local nest = nil local nest = nil
if position and (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[NEST_COUNT] < 3) then if position and (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[NEST_COUNT] < 3) then
local biterSpawner = {name=name, position=position} local biterSpawner = {name=name, position=position}

View File

@@ -30,8 +30,7 @@ local PATH_RATING = constants.PATH_RATING
local getCardinalChunks = mapUtils.getCardinalChunks local getCardinalChunks = mapUtils.getCardinalChunks
local mMax = math.max local mMax = math.max
local getNestCount = chunkUtils.getNestCount local getEnemyStructureCount = chunkUtils.getEnemyStructureCount
local getWormCount = chunkUtils.getWormCount
local getPlayerBaseGenerator = chunkUtils.getPlayerBaseGenerator local getPlayerBaseGenerator = chunkUtils.getPlayerBaseGenerator
local getResourceGenerator = chunkUtils.getResourceGenerator local getResourceGenerator = chunkUtils.getResourceGenerator
@@ -40,7 +39,7 @@ local getResourceGenerator = chunkUtils.getResourceGenerator
function pheromoneUtils.scents(regionMap, chunk) function pheromoneUtils.scents(regionMap, chunk)
chunk[BASE_PHEROMONE] = chunk[BASE_PHEROMONE] + getPlayerBaseGenerator(regionMap, chunk) chunk[BASE_PHEROMONE] = chunk[BASE_PHEROMONE] + getPlayerBaseGenerator(regionMap, chunk)
local resourceGenerator = getResourceGenerator(regionMap, chunk) local resourceGenerator = getResourceGenerator(regionMap, chunk)
if (resourceGenerator > 0) and (getNestCount(regionMap, chunk) == 0) and (getWormCount(regionMap, chunk) == 0) then if (resourceGenerator > 0) and (getEnemyStructureCount(regionMap, chunk) == 0) then
chunk[RESOURCE_PHEROMONE] = chunk[RESOURCE_PHEROMONE] + mMax(resourceGenerator * 100, 90) chunk[RESOURCE_PHEROMONE] = chunk[RESOURCE_PHEROMONE] + mMax(resourceGenerator * 100, 90)
end end
end end

View File

@@ -83,11 +83,10 @@ function squadAttack.squadsAttack(regionMap, surface, natives)
local groupState = group.state local groupState = group.state
if (groupState == DEFINES_GROUP_FINISHED) or (groupState == DEFINES_GROUP_GATHERING) or ((groupState == DEFINES_GROUP_MOVING) and (squad.cycles == 0)) then if (groupState == DEFINES_GROUP_FINISHED) or (groupState == DEFINES_GROUP_GATHERING) or ((groupState == DEFINES_GROUP_MOVING) and (squad.cycles == 0)) then
local groupPosition = group.position local groupPosition = group.position
local chunkX, chunkY = positionToChunkXY(groupPosition) local chunk = getChunkByPosition(regionMap, groupPosition)
local chunk = getChunkByPosition(regionMap, chunkX, chunkY)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
local attackChunk, attackDirection = scoreNeighborsForAttack(chunk, local attackChunk, attackDirection = scoreNeighborsForAttack(chunk,
getNeighborChunks(regionMap, chunkX, chunkY), getNeighborChunks(regionMap, chunk.x, chunk.y),
scoreAttackLocation, scoreAttackLocation,
squad) squad)
addMovementPenalty(natives, squad, chunk) addMovementPenalty(natives, squad, chunk)
@@ -136,9 +135,9 @@ function squadAttack.squadsBeginAttack(natives, players)
local squad = squads[i] local squad = squads[i]
local group = squad.group local group = squad.group
if (squad.status == SQUAD_GUARDING) and group.valid then if (squad.status == SQUAD_GUARDING) and group.valid then
local groupPosition = group.position
local kamikazeThreshold = calculateKamikazeThreshold(squad, natives) local kamikazeThreshold = calculateKamikazeThreshold(squad, natives)
local groupPosition = group.position
if playersWithinProximityToPosition(players, groupPosition, 100) then if playersWithinProximityToPosition(players, groupPosition, 100) then
squad.frenzy = true squad.frenzy = true
squad.frenzyPosition.x = groupPosition.x squad.frenzyPosition.x = groupPosition.x

View File

@@ -67,7 +67,7 @@ local function buildTendrilPath(regionMap, tendril, surface, base, tick, natives
return return
end end
local tendrilPosition = tendrilUnit.position local tendrilPosition = tendrilUnit.position
local chunk = getChunkByPosition(regionMap, tendrilPosition.x, tendrilPosition.y) local chunk = getChunkByPosition(regionMap, tendrilPosition)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
local tendrilPath,tendrilDirection = scoreNeighborsForResource(chunk, local tendrilPath,tendrilDirection = scoreNeighborsForResource(chunk,
getNeighborChunks(regionMap, chunk.x, chunk.y), getNeighborChunks(regionMap, chunk.x, chunk.y),

View File

@@ -81,7 +81,5 @@
(define (run) (define (run)
(copyFiles modFolder) (copyFiles modFolder)
;;(copyFiles zipModFolder)
;;(makeZip modFolder) ;;(makeZip modFolder)
) (system*/exit-code "/data/games/factorio/bin/x64/factorio")))
)

99
parseState.rkt Executable file
View File

@@ -0,0 +1,99 @@
(module AiState racket
(provide (all-defined-out))
(struct AiState (chunks
chunksLookup
minMaxes)
#:transparent)
(struct MinMax (min max)
#:transparent)
(struct ChunkRange (x
y
movement
base
player
resource
passable
tick
rating)
#:transparent)
(struct Chunk (x
y
movement
base
player
resource
passable
tick
rating)
#:transparent)
(require threading)
(define (getFile filePath)
(call-with-input-file filePath
(lambda (port)
(port->string port))))
(define (stringToChunk str)
(match-let (((list movement base player resource passable tick rating x y) (string-split (substring str
12
(- (string-length str)
14))
",")))
(apply Chunk
(map (lambda (x)
(string->number (second (string-split x
"="))))
(list x y movement base player resource passable tick rating)))))
(define (chunk->string chunk)
(string-append "x: " (~v (Chunk-x chunk)) "\n"
"y: " (~v (Chunk-y chunk)) "\n"
"movement: " (~v (Chunk-movement chunk)) "\n"
"base: " (~v (Chunk-base chunk)) "\n"
"player: " (~v (Chunk-player chunk)) "\n"
"resource: " (~v (Chunk-resource chunk)) "\n"
"passable: " (~v (Chunk-passable chunk)) "\n"
"tick: " (~v (Chunk-tick chunk)) "\n"
"rating: " (~v (Chunk-rating chunk)) "\n"))
(define (findChunkPropertiesMinMax chunks)
(let ((xs (map Chunk-x chunks))
(ys (map Chunk-y chunks))
(movements (map Chunk-movement chunks))
(bases (map Chunk-base chunks))
(players (map Chunk-player chunks))
(resources (map Chunk-resource chunks))
(passables (map Chunk-passable chunks))
(ticks (map Chunk-tick chunks))
(ratings (map Chunk-rating chunks)))
(ChunkRange (MinMax (apply min xs) (apply max xs))
(MinMax (apply min ys) (apply max ys))
(MinMax (apply min movements) (apply max movements))
(MinMax (apply min bases) (apply max bases))
(MinMax (apply min players) (apply max players))
(MinMax (apply min resources) (apply max resources))
(MinMax (apply min passables) (apply max passables))
(MinMax (apply min ticks) (apply max ticks))
(MinMax (apply min ratings) (apply max ratings)))))
(define (readState filePath)
(let* ((replayChunks (getFile filePath))
(chunks (map stringToChunk (string-split replayChunks "\n")))
(minMaxes (findChunkPropertiesMinMax chunks)))
(AiState chunks
(apply hash
(apply append
(map (lambda (chunk)
(list (list (Chunk-x chunk)
(Chunk-y chunk))
chunk))
chunks)))
minMaxes)))
(define (test)
(readState "/data/games/factorio/script-output/rampantState.txt")))

View File

@@ -189,187 +189,63 @@ data:extend({
}, },
{ {
type = "unit-spawner", type = "container",
name = "chunk-scanner-ew-rampant", name = "chunk-scanner-ns-rampant",
icon = "__base__/graphics/icons/biter-spawner.png", icon = "__base__/graphics/icons/wooden-chest.png",
icon_size = 32, icon_size = 32,
flags = {}, flags = {},
collision_mask = {"player-layer", "object-layer", "water-tile"},
collision_mask = {"player-layer"}, collision_box = {{-0.5, 0}, {0, 32}},
minable = {mining_time = 1, result = "wooden-chest"},
selectable_in_game = false, max_health = 100,
max_health = 350, corpse = "small-remnants",
order="b-b-g", fast_replaceable_group = "container",
subgroup="enemies", selection_box = {{-0.5, -0.5}, {0.5, 0.5}},
resistances = inventory_size = 16,
open_sound = { filename = "__base__/sound/wooden-chest-open.ogg" },
close_sound = { filename = "__base__/sound/wooden-chest-close.ogg" },
vehicle_impact_sound = { filename = "__base__/sound/car-wood-impact.ogg", volume = 1.0 },
picture =
{ {
{ filename = "__base__/graphics/entity/wooden-chest/wooden-chest.png",
type = "physical", priority = "extra-high",
decrease = 2, width = 46,
percent = 15 height = 33,
}, shift = {0.25, 0.015625}
{
type = "explosion",
decrease = 5,
percent = 15,
},
{
type = "fire",
decrease = 3,
percent = 60,
}
}, },
working_sound = { circuit_wire_connection_point = circuit_connector_definitions["chest"].points,
sound = circuit_connector_sprites = circuit_connector_definitions["chest"].sprites,
{ circuit_wire_max_distance = default_circuit_wire_max_distance
{
filename = "__base__/sound/creatures/spawner.ogg",
volume = 1.0
}
},
apparent_volume = 2
},
dying_sound =
{
{
filename = "__base__/sound/creatures/spawner-death-1.ogg",
volume = 1.0
},
{
filename = "__base__/sound/creatures/spawner-death-2.ogg",
volume = 1.0
}
},
healing_per_tick = 0.02,
collision_box = {{0, -0.5}, {32, 0}},
selection_box = {{0, 0}, {32, 1}},
-- in ticks per 1 pu
pollution_absorbtion_absolute = 20,
pollution_absorbtion_proportional = 0.01,
corpse = "biter-spawner-corpse",
dying_explosion = "blood-explosion-huge",
max_count_of_owned_units = 7,
max_friends_around_to_spawn = 5,
animations =
{
spawner_idle_animation(0, biter_spawner_powered_tint),
spawner_idle_animation(1, biter_spawner_powered_tint),
spawner_idle_animation(2, biter_spawner_powered_tint),
spawner_idle_animation(3, biter_spawner_powered_tint)
},
result_units = (function()
local res = {}
res[1] = {"small-biter", {{0.0, 0.3}, {0.6, 0.0}}}
if not data.is_demo then
-- from evolution_factor 0.3 the weight for medium-biter is linearly rising from 0 to 0.3
-- this means for example that when the evolution_factor is 0.45 the probability of spawning
-- a small biter is 66% while probability for medium biter is 33%.
res[2] = {"medium-biter", {{0.2, 0.0}, {0.6, 0.3}, {0.7, 0.1}}}
-- for evolution factor of 1 the spawning probabilities are: small-biter 0%, medium-biter 1/8, big-biter 4/8, behemoth biter 3/8
res[3] = {"big-biter", {{0.5, 0.0}, {1.0, 0.4}}}
res[4] = {"behemoth-biter", {{0.9, 0.0}, {1.0, 0.3}}}
end
return res
end)(),
-- With zero evolution the spawn rate is 6 seconds, with max evolution it is 2.5 seconds
spawning_cooldown = {360, 150},
spawning_radius = 10,
spawning_spacing = 3,
max_spawn_shift = 0,
max_richness_for_spawn_shift = 100,
call_for_help_radius = 50
}, },
{ {
type = "unit-spawner", type = "container",
name = "chunk-scanner-ns-rampant", name = "chunk-scanner-ew-rampant",
icon = "__base__/graphics/icons/biter-spawner.png", icon = "__base__/graphics/icons/wooden-chest.png",
icon_size = 32, icon_size = 32,
flags = {}, flags = {},
collision_box = {{0, -0.5}, {32, 0}},
collision_mask = {"player-layer"}, collision_mask = {"player-layer", "object-layer", "water-tile"},
minable = {mining_time = 1, result = "wooden-chest"},
selectable_in_game = false, max_health = 100,
max_health = 350, corpse = "small-remnants",
order="b-b-g", fast_replaceable_group = "container",
subgroup="enemies", selection_box = {{-0.5, -0.5}, {0.5, 0.5}},
resistances = inventory_size = 16,
open_sound = { filename = "__base__/sound/wooden-chest-open.ogg" },
close_sound = { filename = "__base__/sound/wooden-chest-close.ogg" },
vehicle_impact_sound = { filename = "__base__/sound/car-wood-impact.ogg", volume = 1.0 },
picture =
{ {
{ filename = "__base__/graphics/entity/wooden-chest/wooden-chest.png",
type = "physical", priority = "extra-high",
decrease = 2, width = 46,
percent = 15 height = 33,
}, shift = {0.25, 0.015625}
{
type = "explosion",
decrease = 5,
percent = 15,
},
{
type = "fire",
decrease = 3,
percent = 60,
}
}, },
working_sound = { circuit_wire_connection_point = circuit_connector_definitions["chest"].points,
sound = circuit_connector_sprites = circuit_connector_definitions["chest"].sprites,
{ circuit_wire_max_distance = default_circuit_wire_max_distance
{
filename = "__base__/sound/creatures/spawner.ogg",
volume = 1.0
}
},
apparent_volume = 2
},
dying_sound =
{
{
filename = "__base__/sound/creatures/spawner-death-1.ogg",
volume = 1.0
},
{
filename = "__base__/sound/creatures/spawner-death-2.ogg",
volume = 1.0
}
},
healing_per_tick = 0.02,
collision_box = {{-0.5, 0}, {0, 32}},
selection_box = {{0, 0}, {32, 1}},
-- in ticks per 1 pu
pollution_absorbtion_absolute = 20,
pollution_absorbtion_proportional = 0.01,
corpse = "biter-spawner-corpse",
dying_explosion = "blood-explosion-huge",
max_count_of_owned_units = 7,
max_friends_around_to_spawn = 5,
animations =
{
spawner_idle_animation(0, biter_spawner_powered_tint),
spawner_idle_animation(1, biter_spawner_powered_tint),
spawner_idle_animation(2, biter_spawner_powered_tint),
spawner_idle_animation(3, biter_spawner_powered_tint)
},
result_units = (function()
local res = {}
res[1] = {"small-biter", {{0.0, 0.3}, {0.6, 0.0}}}
if not data.is_demo then
-- from evolution_factor 0.3 the weight for medium-biter is linearly rising from 0 to 0.3
-- this means for example that when the evolution_factor is 0.45 the probability of spawning
-- a small biter is 66% while probability for medium biter is 33%.
res[2] = {"medium-biter", {{0.2, 0.0}, {0.6, 0.3}, {0.7, 0.1}}}
-- for evolution factor of 1 the spawning probabilities are: small-biter 0%, medium-biter 1/8, big-biter 4/8, behemoth biter 3/8
res[3] = {"big-biter", {{0.5, 0.0}, {1.0, 0.4}}}
res[4] = {"behemoth-biter", {{0.9, 0.0}, {1.0, 0.3}}}
end
return res
end)(),
-- With zero evolution the spawn rate is 6 seconds, with max evolution it is 2.5 seconds
spawning_cooldown = {360, 150},
spawning_radius = 10,
spawning_spacing = 3,
max_spawn_shift = 0,
max_richness_for_spawn_shift = 100,
call_for_help_radius = 50
} }
}) })

View File

@@ -230,25 +230,6 @@ function tests.baseStats()
end end
end end
function tests.baseTiles()
local natives = global.natives
for i=1, #natives.bases do
local base = natives.bases[i]
-- local color = "concrete"
-- if (i % 3 == 0) then
-- color = "deepwater"
-- elseif (i % 2 == 0) then
-- color = "water"
-- end
-- for x=1,#base.chunks do
-- local chunk = base.chunks[x]
-- chunkUtils.colorChunk(chunk.pX, chunk.pY, color, game.surfaces[1])
-- end
chunkUtils.colorChunk(base.x, base.y, "deepwater-green", game.surfaces[1])
end
end
function tests.clearBases() function tests.clearBases()
local surface = game.surfaces[1] local surface = game.surfaces[1]
@@ -284,40 +265,17 @@ function tests.clearBases()
end end
end end
function tests.colorResourcePoints()
local chunks = global.regionMap.processQueue
for i=1,#chunks do
local chunk = chunks[i]
local color = "concrete"
if (chunk[constants.RESOURCE_GENERATOR] ~= 0) and (chunk[constants.NEST_COUNT] ~= 0) then
color = "hazard-concrete-left"
elseif (chunk[constants.RESOURCE_GENERATOR] ~= 0) then
color = "deepwater"
elseif (chunk[constants.NEST_COUNT] ~= 0) then
color = "water-green"
end
chunkUtils.colorChunk(chunk.x, chunk.y, color, game.surfaces[1])
end
end
function tests.mergeBases() function tests.mergeBases()
local natives = global.natives local natives = global.natives
baseUtils.mergeBases(natives) baseUtils.mergeBases(natives)
end end
function tests.showMovementGrid() function tests.exportAiState()
game.write_file("rampantState.txt", "", false)
local chunks = global.regionMap.processQueue local chunks = global.regionMap.processQueue
for i=1,#chunks do for i=1,#chunks do
local chunk = chunks[i] local chunk = chunks[i]
local color = "concrete" game.write_file("rampantState.txt", serpent.dump(chunk) .. "\n", true)
if (chunk[constants.PASSABLE] == constants.CHUNK_ALL_DIRECTIONS) then
color = "hazard-concrete-left"
elseif (chunk[constants.PASSABLE] == constants.CHUNK_NORTH_SOUTH) then
color = "deepwater"
elseif (chunk[constants.PASSABLE] == constants.CHUNK_EAST_WEST) then
color = "water-green"
end
chunkUtils.colorChunk(chunk.x, chunk.y, color, game.surfaces[1])
end end
end end

225
visual.rkt Executable file
View File

@@ -0,0 +1,225 @@
(module Visualizer racket
(require "parseState.rkt")
(require racket/gui/base)
(require plot)
(define CHUNK_SIZE 32)
(define windowWidth 1024)
(define windowHeight 1024)
(define INVALID_CHUNK (Chunk -1 -1 0 0 0 0 0 0 0))
(define (normalize v low high)
(/ (- v low)
(- high low)))
(define (roundTo x digits)
(* (floor (/ x digits))
digits))
(define (refresh dc)
(drawFrame dc))
(define frameWithEvents% (class frame%
(define/override (on-subwindow-char r event)
(when (eq? (send event get-key-code) #\c)
(exit))
(super on-subwindow-char r event))
(super-new)))
(define (findChunk chunkLookups x y)
(hash-ref chunkLookups (list x y) INVALID_CHUNK))
(define (chunkX->screenX x minX maxX tileWidth)
(roundTo (* (normalize x minX maxX)
windowWidth)
tileWidth))
(define (chunkY->screenY y minY maxY tileHeight)
(roundTo (+ (- windowHeight
(* (normalize y minY maxY)
windowHeight)))
tileHeight))
(define (screenX->chunkX x minX tileWidth)
(+ (* (ceiling (/ x tileWidth))
CHUNK_SIZE)
minX))
(define (screenY->chunkY y maxY tileHeight)
(- maxY
(* (floor (/ y tileHeight))
CHUNK_SIZE)))
(define (displayHighlight dc x y)
;; (print (list x y))
;; (display "\n")
;; (print (list (screenX->chunkX x) (screenY->chunkY y)))
;; (display "\n---\n")
(let ((chunk (findChunk (screenX->chunkX x)
(screenY->chunkY y))))
(set! activeHighlight chunk))
(refresh dc))
(define canvasWithEvents% (class canvas%
(define/override (on-event event)
(match (send event get-event-type)
((== 'motion) (displayChunk (send event get-x) (send event get-y)))
((== 'left-down) (displayHighlight (send event get-x) (send event get-y)))
((== 'right-down) (begin (set! activeHighlight null)
(refresh )
))
(_ #t)))
(super-new)))
(define (newFrame width height x y [label ""])
(new frameWithEvents%
[label label]
[width width]
[height height]
[x x]
[y y]))
(define activeHighlight null)
(define activeLayer "movement")
(define (showVisual aiState windowX windowY windowWidth windowHeight)
(match-let* (((AiState chunks chunkLookups chunkMinMaxes) aiState)
((ChunkRange (MinMax minX maxX)
(MinMax minY maxY)
(MinMax minMovement maxMovement)
(MinMax minBase maxBase)
(MinMax minPlayer maxPlayer)
(MinMax minResource maxResource)
(MinMax minPassable maxPassable)
(MinMax minTick maxTick)
(MinMax minRating maxRating)) chunkMinMaxes))
;; (pretty-display aiState)
;; (display "\n")
(define templates (list '(250 500 0 0 "controls")
(list windowWidth windowHeight windowX windowY "map")))
(define frames (map (lambda (frame)
(match-let (((list width height x y name) frame))
(newFrame width height x y name)))
templates))
(define mainFrame (first frames))
(define mapFrame (second frames))
(define canvass (map (lambda (frame)
(let ((c (new canvasWithEvents%
[parent frame]
[paint-callback (lambda (canvas dc)
(drawFrame dc))])))
(send c set-canvas-background (make-object color% 0 0 0))
c))
(cdr frames)))
(define dcs (map (lambda (canvas)
(send canvas get-dc))
canvass))
(define dcMap (first dcs))
(define tileWidth (ceiling (/ windowWidth (abs (/ (- maxX minX) CHUNK_SIZE)))))
(define tileHeight (ceiling (/ windowHeight (+ (abs (/ (- maxY minY) CHUNK_SIZE)) 1))))
;; (print (list (exact->inexact tileWidth)
;; (exact->inexact tileHeight)))
;; (display "\n")
(define (drawFrame context)
(send context suspend-flush)
(let ((chunkField (eval (string->symbol (string-append "Chunk-" activeLayer))))
(chunkRangeField (eval (string->symbol (string-append "ChunkRange-" activeLayer)))))
(map (lambda (chunk)
(let ((x (chunkX->screenX (Chunk-x chunk)))
(y (chunkY->screenY (Chunk-y chunk))))
(if (eq? activeHighlight chunk)
(send context set-pen (make-object color% 255 255 255) 1 'solid)
(send context set-pen (make-object color% 0 0 0) 1 'solid))
(define (dcDraw dc property minMax)
(scaleColor dc property (MinMax-min minMax) (MinMax-max minMax))
(send dc draw-rectangle x y tileWidth tileHeight))
(dcDraw context
(chunkField chunk)
(chunkRangeField chunkMinMaxes))))
chunks))
(send context resume-flush))
(define (displayChunk x y)
(send siteBox set-label
(chunk->string (findChunk (screenX->chunkX x)
(screenY->chunkY y)))))
(define (scaleColor dc value low high)
(define v (/ (- value low)
(- high low)))
(define r (if (= v 0)
0
(if (> 0.75 v)
150
(if (> 0.50 v)
100
50))))
(define g (inexact->exact (round (* v 255))))
(send dc set-brush (make-object color% r g 0) 'solid))
(define panel (new panel%
[parent mainFrame]
(alignment '(left top))))
(define statusBox (new message%
[parent panel]
[label (~v "")]
[vert-margin 16]))
(define siteBox (new message%
[parent panel]
[label ""]
[vert-margin 30]))
(new radio-box%
[label "Show Layer"]
[choices (list "movement" "base" "player" "resource" "passable" "tick" "rating")]
[selection 0]
[parent mainFrame]
(callback (lambda (radioButton event)
(set! activeLayer (send radioButton get-item-label (send radioButton get-selection)))
(refresh))))
(new button%
[parent mainFrame]
[label "Refresh"]
(callback (lambda (button event)
(exit))))
(new button%
[parent mainFrame]
[label "Quit"]
(callback (lambda (button event)
(exit))))
(map (lambda (f)
(send f show #t))
frames)
'showing))
(define (test)
(showVisual (readState "/data/games/factorio/script-output/rampantState.txt")
500
0
1024
1024)))