mirror of
https://github.com/veden/Rampant.git
synced 2024-12-26 20:54:12 +02:00
trying to better represent map features
This commit is contained in:
parent
d0ed774587
commit
319fb66845
70
control.lua
70
control.lua
@ -88,8 +88,7 @@ local function onIonCannonFired(event)
|
||||
if (natives.points > AI_MAX_OVERFLOW_POINTS) then
|
||||
natives.points = AI_MAX_OVERFLOW_POINTS
|
||||
end
|
||||
local chunkX, chunkY = positionToChunkXY(event.position)
|
||||
local chunk = getChunkByPosition(regionMap, chunkX, chunkY)
|
||||
local chunk = getChunkByPosition(regionMap, event.position)
|
||||
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
|
||||
rallyUnits(chunk, regionMap, surface, natives, event.tick)
|
||||
end
|
||||
@ -158,10 +157,13 @@ local function rebuildRegionMap()
|
||||
y=0}
|
||||
|
||||
--this is shared between two different queries
|
||||
local sharedFilterArea = {{0, 0}, {0, 0}}
|
||||
regionMap.filteredEntitiesQuery = { area=sharedFilterArea, force=false }
|
||||
regionMap.cliffQuery = { type="cliff", area={{0, 0}, {0, 0}} }
|
||||
regionMap.filteredTilesQuery = { name="", area=sharedFilterArea }
|
||||
regionMap.area = {{0, 0}, {0, 0}}
|
||||
regionMap.countResourcesQuery = { area=regionMap.area, type="resource" }
|
||||
regionMap.filteredEntitiesEnemyQuery = { area=regionMap.area, force="enemy" }
|
||||
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
|
||||
regionMap.logicTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
|
||||
@ -253,28 +255,28 @@ local function onTick(event)
|
||||
processPendingChunks(natives, regionMap, surface, pendingChunks, tick)
|
||||
scanMap(regionMap, surface, natives)
|
||||
|
||||
if (tick == regionMap.logicTick) then
|
||||
regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC
|
||||
-- if (tick == regionMap.logicTick) then
|
||||
-- regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC
|
||||
|
||||
local players = gameRef.players
|
||||
-- local players = gameRef.players
|
||||
|
||||
planning(natives,
|
||||
gameRef.forces.enemy.evolution_factor,
|
||||
tick,
|
||||
surface)
|
||||
-- planning(natives,
|
||||
-- gameRef.forces.enemy.evolution_factor,
|
||||
-- tick,
|
||||
-- surface)
|
||||
|
||||
cleanSquads(natives)
|
||||
regroupSquads(natives)
|
||||
-- cleanSquads(natives)
|
||||
-- regroupSquads(natives)
|
||||
|
||||
processPlayers(players, regionMap, surface, natives, tick)
|
||||
-- processPlayers(players, regionMap, surface, natives, tick)
|
||||
|
||||
if natives.useCustomAI then
|
||||
processBases(regionMap, surface, natives, tick)
|
||||
end
|
||||
-- if natives.useCustomAI then
|
||||
-- processBases(regionMap, surface, natives, tick)
|
||||
-- end
|
||||
|
||||
squadsBeginAttack(natives, players)
|
||||
squadsAttack(regionMap, surface, natives)
|
||||
end
|
||||
-- squadsBeginAttack(natives, players)
|
||||
-- squadsAttack(regionMap, surface, natives)
|
||||
-- end
|
||||
|
||||
processMap(regionMap, surface, natives, tick)
|
||||
end
|
||||
@ -299,19 +301,18 @@ local function onDeath(event)
|
||||
local surface = entity.surface
|
||||
if (surface.index == 1) then
|
||||
local entityPosition = entity.position
|
||||
local chunkX, chunkY = positionToChunkXY(entityPosition)
|
||||
local chunk = getChunkByPosition(regionMap, entityPosition)
|
||||
if (entity.force.name == "enemy") 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
|
||||
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
|
||||
|
||||
retreatUnits(deathChunk,
|
||||
retreatUnits(chunk,
|
||||
entityPosition,
|
||||
convertUnitGroupToSquad(natives, entity.unit_group),
|
||||
regionMap,
|
||||
@ -320,7 +321,7 @@ local function onDeath(event)
|
||||
tick)
|
||||
|
||||
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
|
||||
@ -332,9 +333,8 @@ local function onDeath(event)
|
||||
local creditNatives = false
|
||||
if (event.force ~= nil) and (event.force.name == "enemy") then
|
||||
creditNatives = true
|
||||
local victoryChunk = getChunkByPosition(regionMap, chunkX, chunkY)
|
||||
if (victoryChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
|
||||
victoryScent(victoryChunk, entity.type)
|
||||
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
|
||||
victoryScent(chunk, entity.type)
|
||||
end
|
||||
end
|
||||
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,
|
||||
gaussianRandomTest = tests.gaussianRandomTest,
|
||||
reveal = tests.reveal,
|
||||
showMovementGrid = tests.showMovementGrid,
|
||||
baseStats = tests.baseStats,
|
||||
baseTiles = tests.baseTiles,
|
||||
mergeBases = tests.mergeBases,
|
||||
clearBases = tests.clearBases,
|
||||
getOffsetChunk = tests.getOffsetChunk,
|
||||
registeredNest = tests.registeredNest,
|
||||
colorResourcePoints = tests.colorResourcePoints,
|
||||
stepAdvanceTendrils = tests.stepAdvanceTendrils
|
||||
stepAdvanceTendrils = tests.stepAdvanceTendrils,
|
||||
exportAiState = tests.exportAiState
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -46,7 +46,7 @@ local getRallyTick = chunkUtils.getRallyTick
|
||||
local setRallyTick = chunkUtils.setRallyTick
|
||||
|
||||
local getNeighborChunks = mapUtils.getNeighborChunks
|
||||
local getChunkByPosition = mapUtils.getChunkByPosition
|
||||
local getChunkByXY = mapUtils.getChunkByXY
|
||||
local scoreNeighborsForFormation = movementUtils.scoreNeighborsForFormation
|
||||
local createSquad = unitGroupUtils.createSquad
|
||||
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 y=cY - RALLY_CRY_DISTANCE, cY + RALLY_CRY_DISTANCE, 32 do
|
||||
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
|
||||
aiAttackWave.formSquads(regionMap, surface, natives, rallyChunk, AI_VENGENCE_SQUAD_COST)
|
||||
if (natives.points < AI_VENGENCE_SQUAD_COST) and (#natives.squads < natives.maxSquads) then
|
||||
|
@ -14,22 +14,17 @@ local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK
|
||||
-- imported functions
|
||||
|
||||
local createChunk = chunkUtils.createChunk
|
||||
local checkChunkPassability = chunkUtils.checkChunkPassability
|
||||
local scoreChunk = chunkUtils.scoreChunk
|
||||
local registerChunkEnemies = chunkUtils.registerChunkEnemies
|
||||
local analyzeChunk = chunkUtils.analyzeChunk
|
||||
|
||||
-- module code
|
||||
|
||||
function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendingStack)
|
||||
local processQueue = regionMap.processQueue
|
||||
|
||||
local filteredEntitiesQuery = regionMap.filteredEntitiesQuery
|
||||
local area = regionMap.area
|
||||
|
||||
local topOffset = filteredEntitiesQuery.area[1]
|
||||
local bottomOffset = filteredEntitiesQuery.area[2]
|
||||
|
||||
local filteredTilesQuery = regionMap.filteredTilesQuery
|
||||
local cliffQuery = regionMap.cliffQuery
|
||||
local topOffset = area[1]
|
||||
local bottomOffset = area[2]
|
||||
|
||||
for i=#pendingStack, 1, -1 do
|
||||
local event = pendingStack[i]
|
||||
@ -45,12 +40,9 @@ function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendin
|
||||
bottomOffset[1] = x + 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
|
||||
registerChunkEnemies(regionMap, chunk, surface, filteredEntitiesQuery)
|
||||
scoreChunk(regionMap, chunk, surface, natives, filteredEntitiesQuery)
|
||||
|
||||
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
|
||||
local chunkX = chunk.x
|
||||
|
||||
if regionMap[chunkX] == nil then
|
||||
|
@ -28,7 +28,6 @@ local CHUNK_ALL_DIRECTIONS = constants.CHUNK_ALL_DIRECTIONS
|
||||
local CHUNK_IMPASSABLE = constants.CHUNK_IMPASSABLE
|
||||
|
||||
local CHUNK_TICK = constants.CHUNK_TICK
|
||||
local CHUNK_SIZE = constants.CHUNK_SIZE
|
||||
|
||||
local PATH_RATING = constants.PATH_RATING
|
||||
|
||||
@ -36,42 +35,42 @@ local PASSABLE = constants.PASSABLE
|
||||
|
||||
-- imported functions
|
||||
|
||||
local getChunkByPosition = mapUtils.getChunkByPosition
|
||||
local getChunkByXY = mapUtils.getChunkByXY
|
||||
|
||||
local mFloor = math.floor
|
||||
|
||||
-- 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 passableEastWest = false
|
||||
|
||||
local area = cliffQuery.area
|
||||
local top = area[1]
|
||||
local bottom = area[2]
|
||||
top[2] = y
|
||||
bottom[2] = y + CHUNK_SIZE
|
||||
canPlaceQuery.name = "chunk-scanner-ns-rampant"
|
||||
|
||||
local position = canPlaceQuery.position
|
||||
position[2] = y
|
||||
|
||||
for xi=x, x + 31 do
|
||||
top[1] = xi
|
||||
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
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
top[1] = x
|
||||
bottom[1] = x + CHUNK_SIZE
|
||||
for yi=y, y + 31 do
|
||||
top[2] = yi
|
||||
bottom[2] = yi + 1
|
||||
if (count_entities_filtered(cliffQuery) == 0) then
|
||||
position[1] = x
|
||||
canPlaceQuery.name = "chunk-scanner-ew-rampant"
|
||||
|
||||
for yi=y, y + 32 do
|
||||
position[2] = yi
|
||||
if can_place_entity(canPlaceQuery) then
|
||||
passableEastWest = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return passableNorthSouth, passableEastWest
|
||||
end
|
||||
|
||||
@ -140,7 +139,7 @@ local function getEntityOverlapChunks(regionMap, entity)
|
||||
local leftBottomChunk = SENTINEL_IMPASSABLE_CHUNK
|
||||
local rightBottomChunk = SENTINEL_IMPASSABLE_CHUNK
|
||||
|
||||
if (boundingBox ~= nil) then
|
||||
if boundingBox then
|
||||
local center = entity.position
|
||||
local topXOffset
|
||||
local topYOffset
|
||||
@ -174,15 +173,15 @@ local function getEntityOverlapChunks(regionMap, entity)
|
||||
local rightBottomChunkX = rightTopChunkX
|
||||
local rightBottomChunkY = leftBottomChunkY
|
||||
|
||||
leftTopChunk = getChunkByPosition(regionMap, leftTopChunkX, leftTopChunkY)
|
||||
leftTopChunk = getChunkByXY(regionMap, leftTopChunkX, leftTopChunkY)
|
||||
if (leftTopChunkX ~= rightTopChunkX) then
|
||||
rightTopChunk = getChunkByPosition(regionMap, rightTopChunkX, rightTopChunkY)
|
||||
rightTopChunk = getChunkByXY(regionMap, rightTopChunkX, rightTopChunkY)
|
||||
end
|
||||
if (leftTopChunkY ~= leftBottomChunkY) then
|
||||
leftBottomChunk = getChunkByPosition(regionMap, leftBottomChunkX, leftBottomChunkY)
|
||||
leftBottomChunk = getChunkByXY(regionMap, leftBottomChunkX, leftBottomChunkY)
|
||||
end
|
||||
if (leftTopChunkX ~= rightBottomChunkX) and (leftTopChunkY ~= rightBottomChunkY) then
|
||||
rightBottomChunk = getChunkByPosition(regionMap, rightBottomChunkX, rightBottomChunkY)
|
||||
rightBottomChunk = getChunkByXY(regionMap, rightBottomChunkX, rightBottomChunkY)
|
||||
end
|
||||
end
|
||||
return leftTopChunk, rightTopChunk, leftBottomChunk, rightBottomChunk
|
||||
@ -190,9 +189,9 @@ end
|
||||
|
||||
-- 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_entities_filtered = surface.count_entities_filtered
|
||||
local filteredTilesQuery = regionMap.filteredTilesQuery
|
||||
|
||||
local passScore = 0
|
||||
for i=1,#WATER_TILE_NAMES do
|
||||
@ -204,7 +203,10 @@ function chunkUtils.checkChunkPassability(chunk, surface, filteredTilesQuery, cl
|
||||
|
||||
local pass = CHUNK_IMPASSABLE
|
||||
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
|
||||
pass = CHUNK_ALL_DIRECTIONS
|
||||
elseif passableEastWest then
|
||||
@ -212,7 +214,54 @@ function chunkUtils.checkChunkPassability(chunk, surface, filteredTilesQuery, cl
|
||||
elseif passableNorthSouth then
|
||||
pass = CHUNK_NORTH_SOUTH
|
||||
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
|
||||
|
||||
if (pass == CHUNK_IMPASSABLE) then
|
||||
return SENTINEL_IMPASSABLE_CHUNK
|
||||
else
|
||||
@ -247,16 +296,27 @@ end
|
||||
-- -- end
|
||||
-- end
|
||||
|
||||
function chunkUtils.registerChunkEnemies(regionMap, chunk, surface, filteredEntitiesQuery)
|
||||
filteredEntitiesQuery.force = "enemy"
|
||||
local enemies = surface.find_entities_filtered(filteredEntitiesQuery)
|
||||
function chunkUtils.getNestCount(regionMap, chunk)
|
||||
return regionMap.chunkToNests[chunk] or 0
|
||||
end
|
||||
|
||||
for i=1, #enemies do
|
||||
local enemy = enemies[i]
|
||||
local enemyType = enemy.type
|
||||
if (enemyType == "unit-spawner") or (enemyType == "turret") then
|
||||
addEnemyStructureToChunk(regionMap, chunk, enemy, nil)
|
||||
end
|
||||
function chunkUtils.getWormCount(regionMap, chunk)
|
||||
return regionMap.chunkToWorms[chunk] or 0
|
||||
end
|
||||
|
||||
function chunkUtils.setWormCount(regionMap, chunk, count)
|
||||
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
|
||||
|
||||
@ -288,8 +348,12 @@ function chunkUtils.setRetreatTick(regionMap, chunk, tick)
|
||||
regionMap.chunkToRetreats[chunk] = tick
|
||||
end
|
||||
|
||||
function chunkUtils.setResourceGenerator(regionMap, chunk, playerGenerator)
|
||||
regionMap.chunkToResource[chunk] = playerGenerator
|
||||
function chunkUtils.setResourceGenerator(regionMap, chunk, resourceGenerator)
|
||||
if (resourceGenerator == 0) then
|
||||
regionMap.chunkToResource[chunk] = nil
|
||||
else
|
||||
regionMap.chunkToResource[chunk] = resourceGenerator
|
||||
end
|
||||
end
|
||||
|
||||
function chunkUtils.getResourceGenerator(regionMap, chunk)
|
||||
@ -301,43 +365,17 @@ function chunkUtils.getPlayerBaseGenerator(regionMap, chunk)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
function chunkUtils.addPlayerBaseGenerator(regionMap, chunk, playerGenerator)
|
||||
regionMap.chunkToPlayerBase[chunk] = regionMap.chunkToPlayerBase[chunk] + playerGenerator
|
||||
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)
|
||||
local chunk = {
|
||||
x = topX,
|
||||
@ -354,16 +392,6 @@ function chunkUtils.createChunk(topX, topY)
|
||||
return chunk
|
||||
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)
|
||||
local entityType = entity.type
|
||||
if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then
|
||||
|
@ -18,7 +18,6 @@ constants.VERSION_28 = 28
|
||||
constants.VERSION_33 = 33
|
||||
constants.VERSION_36 = 36
|
||||
|
||||
|
||||
-- misc
|
||||
|
||||
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.SCAN_QUEUE_SIZE = 5
|
||||
constants.ITEM_COLLECTOR_QUEUE_SIZE = 6
|
||||
constants.BASE_QUEUE_SIZE = 1
|
||||
constants.SQUAD_QUEUE_SIZE = 2
|
||||
constants.PROCESS_PLAYER_BOUND = 128
|
||||
|
||||
constants.ITEM_COLLECTOR_MAX_QUEUE_SIZE = 20
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
constants.CHUNK_SIZE = 32
|
||||
@ -75,6 +55,8 @@ constants.CHUNK_IMPASSABLE = 0
|
||||
constants.CHUNK_NORTH_SOUTH = 1
|
||||
constants.CHUNK_EAST_WEST = 2
|
||||
constants.CHUNK_ALL_DIRECTIONS = 3
|
||||
constants.CHUNK_PLAYER_BORDER = 4
|
||||
constants.CHUNK_PLAYER_INTERIOR = 5
|
||||
|
||||
-- ai
|
||||
|
||||
|
@ -42,16 +42,16 @@ local playerScent = pheromoneUtils.playerScent
|
||||
local formSquads = aiAttackWave.formSquads
|
||||
|
||||
local getChunkByPosition = mapUtils.getChunkByPosition
|
||||
local positionToChunkXY = mapUtils.positionToChunkXY
|
||||
local getChunkByXY = mapUtils.getChunkByXY
|
||||
|
||||
local recycleBiters = unitGroupUtils.recycleBiters
|
||||
|
||||
local validPlayer = playerUtils.validPlayer
|
||||
|
||||
local setResourceGenerator = chunkUtils.setResourceGenerator
|
||||
local setPlayerBaseGenerator = chunkUtils.setPlayerBaseGenerator
|
||||
local analyzeChunk = chunkUtils.analyzeChunk
|
||||
|
||||
local getNestCount = chunkUtils.getNestCount
|
||||
local getWormCount = chunkUtils.getWormCount
|
||||
local getEnemyStructureCount = chunkUtils.getEnemyStructureCount
|
||||
|
||||
local canAttack = aiPredicates.canAttack
|
||||
|
||||
@ -141,8 +141,7 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick)
|
||||
for i=1,#playerOrdering do
|
||||
local player = players[playerOrdering[i]]
|
||||
if validPlayer(player) then
|
||||
local chunkX, chunkY = positionToChunkXY(player.character.position)
|
||||
local playerChunk = getChunkByPosition(regionMap, chunkX, chunkY)
|
||||
local playerChunk = getChunkByPosition(regionMap, player.character.position)
|
||||
|
||||
if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
|
||||
playerScent(playerChunk)
|
||||
@ -152,17 +151,16 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick)
|
||||
for i=1,#playerOrdering do
|
||||
local player = players[playerOrdering[i]]
|
||||
if validPlayer(player) then
|
||||
local chunkX, chunkY = positionToChunkXY(player.character.position)
|
||||
local playerChunk = getChunkByPosition(regionMap, chunkX, chunkY)
|
||||
local playerChunk = getChunkByPosition(regionMap, player.character.position)
|
||||
|
||||
if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
|
||||
local vengence = (allowingAttacks 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 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
|
||||
chunk[CHUNK_TICK] = tick
|
||||
@ -197,10 +195,6 @@ function mapProcessor.scanMap(regionMap, surface, natives)
|
||||
|
||||
local offset = {0, 0}
|
||||
local chunkBox = {false, offset}
|
||||
local playerQuery = {area = chunkBox,
|
||||
force = "player"}
|
||||
local resourceQuery = {area = chunkBox,
|
||||
type = "resource"}
|
||||
local unitCountQuery = { area = chunkBox,
|
||||
type = "unit",
|
||||
force = "enemy",
|
||||
@ -235,25 +229,7 @@ function mapProcessor.scanMap(regionMap, surface, natives)
|
||||
end
|
||||
end
|
||||
|
||||
setResourceGenerator(regionMap, chunk, surface.count_entities_filtered(resourceQuery) * 0.001)
|
||||
|
||||
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)
|
||||
analyzeChunk(chunk, natives, surface, regionMap)
|
||||
end
|
||||
|
||||
if (endIndex == #processQueue) then
|
||||
|
@ -25,7 +25,7 @@ local mFloor = math.floor
|
||||
|
||||
-- module code
|
||||
|
||||
function mapUtils.getChunkByPosition(regionMap, x, y)
|
||||
function mapUtils.getChunkByXY(regionMap, x, y)
|
||||
local chunkX = regionMap[x]
|
||||
if chunkX then
|
||||
return chunkX[y] or SENTINEL_IMPASSABLE_CHUNK
|
||||
@ -33,6 +33,15 @@ function mapUtils.getChunkByPosition(regionMap, x, y)
|
||||
return SENTINEL_IMPASSABLE_CHUNK
|
||||
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)
|
||||
local chunkX = mFloor(position.x * 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
|
||||
end
|
||||
|
||||
function mapUtils.positionFromDirectionAndChunk(direction, startPosition, position, scaling)
|
||||
function mapUtils.positionFromDirectionAndChunk(direction, startPosition, endPosition, scaling)
|
||||
if (direction == 1) then
|
||||
position.x = startPosition.x - CHUNK_SIZE * scaling
|
||||
position.y = startPosition.y - CHUNK_SIZE * scaling
|
||||
endPosition.x = startPosition.x - CHUNK_SIZE * scaling
|
||||
endPosition.y = startPosition.y - CHUNK_SIZE * scaling
|
||||
elseif (direction == 2) then
|
||||
position.x = startPosition.x
|
||||
position.y = startPosition.y - CHUNK_SIZE * scaling
|
||||
endPosition.x = startPosition.x
|
||||
endPosition.y = startPosition.y - CHUNK_SIZE * scaling
|
||||
elseif (direction == 3) then
|
||||
position.x = startPosition.x + CHUNK_SIZE * scaling
|
||||
position.y = startPosition.y - CHUNK_SIZE * scaling
|
||||
endPosition.x = startPosition.x + CHUNK_SIZE * scaling
|
||||
endPosition.y = startPosition.y - CHUNK_SIZE * scaling
|
||||
elseif (direction == 4) then
|
||||
position.x = startPosition.x - CHUNK_SIZE * scaling
|
||||
position.y = startPosition.y
|
||||
endPosition.x = startPosition.x - CHUNK_SIZE * scaling
|
||||
endPosition.y = startPosition.y
|
||||
elseif (direction == 5) then
|
||||
position.x = startPosition.x + CHUNK_SIZE * scaling
|
||||
position.y = startPosition.y
|
||||
endPosition.x = startPosition.x + CHUNK_SIZE * scaling
|
||||
endPosition.y = startPosition.y
|
||||
elseif (direction == 6) then
|
||||
position.x = startPosition.x - CHUNK_SIZE * scaling
|
||||
position.y = startPosition.y + CHUNK_SIZE * scaling
|
||||
endPosition.x = startPosition.x - CHUNK_SIZE * scaling
|
||||
endPosition.y = startPosition.y + CHUNK_SIZE * scaling
|
||||
elseif (direction == 7) then
|
||||
position.x = startPosition.x
|
||||
position.y = startPosition.y + CHUNK_SIZE * scaling
|
||||
endPosition.x = startPosition.x
|
||||
endPosition.y = startPosition.y + CHUNK_SIZE * scaling
|
||||
elseif (direction == 8) then
|
||||
position.x = startPosition.x + CHUNK_SIZE * scaling
|
||||
position.y = startPosition.y + CHUNK_SIZE * scaling
|
||||
endPosition.x = startPosition.x + CHUNK_SIZE * scaling
|
||||
endPosition.y = startPosition.y + CHUNK_SIZE * scaling
|
||||
end
|
||||
return position
|
||||
end
|
||||
|
||||
return mapUtils
|
||||
|
@ -33,7 +33,7 @@ local getChunkByPosition = mapUtils.getChunkByPosition
|
||||
|
||||
function nestUtils.buildNest(regionMap, base, surface, targetPosition, name)
|
||||
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
|
||||
if position and (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[NEST_COUNT] < 3) then
|
||||
local biterSpawner = {name=name, position=position}
|
||||
|
@ -30,8 +30,7 @@ local PATH_RATING = constants.PATH_RATING
|
||||
local getCardinalChunks = mapUtils.getCardinalChunks
|
||||
|
||||
local mMax = math.max
|
||||
local getNestCount = chunkUtils.getNestCount
|
||||
local getWormCount = chunkUtils.getWormCount
|
||||
local getEnemyStructureCount = chunkUtils.getEnemyStructureCount
|
||||
local getPlayerBaseGenerator = chunkUtils.getPlayerBaseGenerator
|
||||
local getResourceGenerator = chunkUtils.getResourceGenerator
|
||||
|
||||
@ -40,7 +39,7 @@ local getResourceGenerator = chunkUtils.getResourceGenerator
|
||||
function pheromoneUtils.scents(regionMap, chunk)
|
||||
chunk[BASE_PHEROMONE] = chunk[BASE_PHEROMONE] + getPlayerBaseGenerator(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)
|
||||
end
|
||||
end
|
||||
|
@ -83,11 +83,10 @@ function squadAttack.squadsAttack(regionMap, surface, natives)
|
||||
local groupState = group.state
|
||||
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 chunkX, chunkY = positionToChunkXY(groupPosition)
|
||||
local chunk = getChunkByPosition(regionMap, chunkX, chunkY)
|
||||
local chunk = getChunkByPosition(regionMap, groupPosition)
|
||||
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
|
||||
local attackChunk, attackDirection = scoreNeighborsForAttack(chunk,
|
||||
getNeighborChunks(regionMap, chunkX, chunkY),
|
||||
getNeighborChunks(regionMap, chunk.x, chunk.y),
|
||||
scoreAttackLocation,
|
||||
squad)
|
||||
addMovementPenalty(natives, squad, chunk)
|
||||
@ -136,9 +135,9 @@ function squadAttack.squadsBeginAttack(natives, players)
|
||||
local squad = squads[i]
|
||||
local group = squad.group
|
||||
if (squad.status == SQUAD_GUARDING) and group.valid then
|
||||
local groupPosition = group.position
|
||||
local kamikazeThreshold = calculateKamikazeThreshold(squad, natives)
|
||||
|
||||
|
||||
local groupPosition = group.position
|
||||
if playersWithinProximityToPosition(players, groupPosition, 100) then
|
||||
squad.frenzy = true
|
||||
squad.frenzyPosition.x = groupPosition.x
|
||||
|
@ -67,7 +67,7 @@ local function buildTendrilPath(regionMap, tendril, surface, base, tick, natives
|
||||
return
|
||||
end
|
||||
local tendrilPosition = tendrilUnit.position
|
||||
local chunk = getChunkByPosition(regionMap, tendrilPosition.x, tendrilPosition.y)
|
||||
local chunk = getChunkByPosition(regionMap, tendrilPosition)
|
||||
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
|
||||
local tendrilPath,tendrilDirection = scoreNeighborsForResource(chunk,
|
||||
getNeighborChunks(regionMap, chunk.x, chunk.y),
|
||||
|
4
make.rkt
4
make.rkt
@ -81,7 +81,5 @@
|
||||
|
||||
(define (run)
|
||||
(copyFiles modFolder)
|
||||
;;(copyFiles zipModFolder)
|
||||
;;(makeZip modFolder)
|
||||
)
|
||||
)
|
||||
(system*/exit-code "/data/games/factorio/bin/x64/factorio")))
|
||||
|
99
parseState.rkt
Executable file
99
parseState.rkt
Executable 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")))
|
@ -189,187 +189,63 @@ data:extend({
|
||||
},
|
||||
|
||||
{
|
||||
type = "unit-spawner",
|
||||
name = "chunk-scanner-ew-rampant",
|
||||
icon = "__base__/graphics/icons/biter-spawner.png",
|
||||
type = "container",
|
||||
name = "chunk-scanner-ns-rampant",
|
||||
icon = "__base__/graphics/icons/wooden-chest.png",
|
||||
icon_size = 32,
|
||||
flags = {},
|
||||
|
||||
collision_mask = {"player-layer"},
|
||||
|
||||
selectable_in_game = false,
|
||||
max_health = 350,
|
||||
order="b-b-g",
|
||||
subgroup="enemies",
|
||||
resistances =
|
||||
collision_mask = {"player-layer", "object-layer", "water-tile"},
|
||||
collision_box = {{-0.5, 0}, {0, 32}},
|
||||
minable = {mining_time = 1, result = "wooden-chest"},
|
||||
max_health = 100,
|
||||
corpse = "small-remnants",
|
||||
fast_replaceable_group = "container",
|
||||
selection_box = {{-0.5, -0.5}, {0.5, 0.5}},
|
||||
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 =
|
||||
{
|
||||
{
|
||||
type = "physical",
|
||||
decrease = 2,
|
||||
percent = 15
|
||||
},
|
||||
{
|
||||
type = "explosion",
|
||||
decrease = 5,
|
||||
percent = 15,
|
||||
},
|
||||
{
|
||||
type = "fire",
|
||||
decrease = 3,
|
||||
percent = 60,
|
||||
}
|
||||
filename = "__base__/graphics/entity/wooden-chest/wooden-chest.png",
|
||||
priority = "extra-high",
|
||||
width = 46,
|
||||
height = 33,
|
||||
shift = {0.25, 0.015625}
|
||||
},
|
||||
working_sound = {
|
||||
sound =
|
||||
{
|
||||
{
|
||||
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
|
||||
circuit_wire_connection_point = circuit_connector_definitions["chest"].points,
|
||||
circuit_connector_sprites = circuit_connector_definitions["chest"].sprites,
|
||||
circuit_wire_max_distance = default_circuit_wire_max_distance
|
||||
},
|
||||
|
||||
{
|
||||
type = "unit-spawner",
|
||||
name = "chunk-scanner-ns-rampant",
|
||||
icon = "__base__/graphics/icons/biter-spawner.png",
|
||||
type = "container",
|
||||
name = "chunk-scanner-ew-rampant",
|
||||
icon = "__base__/graphics/icons/wooden-chest.png",
|
||||
icon_size = 32,
|
||||
flags = {},
|
||||
|
||||
collision_mask = {"player-layer"},
|
||||
|
||||
selectable_in_game = false,
|
||||
max_health = 350,
|
||||
order="b-b-g",
|
||||
subgroup="enemies",
|
||||
resistances =
|
||||
collision_box = {{0, -0.5}, {32, 0}},
|
||||
collision_mask = {"player-layer", "object-layer", "water-tile"},
|
||||
minable = {mining_time = 1, result = "wooden-chest"},
|
||||
max_health = 100,
|
||||
corpse = "small-remnants",
|
||||
fast_replaceable_group = "container",
|
||||
selection_box = {{-0.5, -0.5}, {0.5, 0.5}},
|
||||
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 =
|
||||
{
|
||||
{
|
||||
type = "physical",
|
||||
decrease = 2,
|
||||
percent = 15
|
||||
},
|
||||
{
|
||||
type = "explosion",
|
||||
decrease = 5,
|
||||
percent = 15,
|
||||
},
|
||||
{
|
||||
type = "fire",
|
||||
decrease = 3,
|
||||
percent = 60,
|
||||
}
|
||||
filename = "__base__/graphics/entity/wooden-chest/wooden-chest.png",
|
||||
priority = "extra-high",
|
||||
width = 46,
|
||||
height = 33,
|
||||
shift = {0.25, 0.015625}
|
||||
},
|
||||
working_sound = {
|
||||
sound =
|
||||
{
|
||||
{
|
||||
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
|
||||
circuit_wire_connection_point = circuit_connector_definitions["chest"].points,
|
||||
circuit_connector_sprites = circuit_connector_definitions["chest"].sprites,
|
||||
circuit_wire_max_distance = default_circuit_wire_max_distance
|
||||
}
|
||||
|
||||
})
|
||||
|
48
tests.lua
48
tests.lua
@ -230,25 +230,6 @@ function tests.baseStats()
|
||||
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()
|
||||
|
||||
local surface = game.surfaces[1]
|
||||
@ -284,40 +265,17 @@ function tests.clearBases()
|
||||
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()
|
||||
local natives = global.natives
|
||||
baseUtils.mergeBases(natives)
|
||||
end
|
||||
|
||||
function tests.showMovementGrid()
|
||||
function tests.exportAiState()
|
||||
game.write_file("rampantState.txt", "", false)
|
||||
local chunks = global.regionMap.processQueue
|
||||
for i=1,#chunks do
|
||||
local chunk = chunks[i]
|
||||
local color = "concrete"
|
||||
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])
|
||||
game.write_file("rampantState.txt", serpent.dump(chunk) .. "\n", true)
|
||||
end
|
||||
end
|
||||
|
||||
|
225
visual.rkt
Executable file
225
visual.rkt
Executable 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)))
|
Loading…
Reference in New Issue
Block a user