189 lines
6.0 KiB
Python
189 lines
6.0 KiB
Python
"""
|
|
state_reader.py
|
|
RCON을 통해 팩토리오 게임 상태를 읽어오는 모듈
|
|
"""
|
|
import json
|
|
from factorio_rcon import FactorioRCON
|
|
|
|
|
|
class StateReader:
|
|
def __init__(self, rcon: FactorioRCON):
|
|
self.rcon = rcon
|
|
|
|
def get_full_state(self) -> dict:
|
|
"""전체 게임 상태를 한 번에 수집"""
|
|
return {
|
|
"player": self.get_player_info(),
|
|
"inventory": self.get_inventory(),
|
|
"resources": self.scan_resources(),
|
|
"buildings": self.get_buildings(),
|
|
"tech": self.get_research_status(),
|
|
}
|
|
|
|
# ── 플레이어 ────────────────────────────────────────────────
|
|
def get_player_info(self) -> dict:
|
|
lua = """
|
|
local p = game.player
|
|
rcon.print(game.table_to_json({
|
|
x = math.floor(p.position.x),
|
|
y = math.floor(p.position.y),
|
|
health = p.character and p.character.health or 100
|
|
}))
|
|
"""
|
|
raw = self.rcon.lua(lua)
|
|
return json.loads(raw) if raw else {}
|
|
|
|
# ── 인벤토리 ────────────────────────────────────────────────
|
|
def get_inventory(self) -> dict:
|
|
lua = """
|
|
local inv = game.player.get_main_inventory()
|
|
local result = {}
|
|
local contents = inv.get_contents()
|
|
for name, count in pairs(contents) do
|
|
result[name] = count
|
|
end
|
|
rcon.print(game.table_to_json(result))
|
|
"""
|
|
raw = self.rcon.lua(lua)
|
|
return json.loads(raw) if raw else {}
|
|
|
|
# ── 자원 패치 스캔 ──────────────────────────────────────────
|
|
def scan_resources(self, radius: int = 200) -> dict:
|
|
"""플레이어 주변 자원 패치 위치 탐색"""
|
|
lua = f"""
|
|
local p = game.player
|
|
local surface = p.surface
|
|
local center = p.position
|
|
local resources = {{}}
|
|
|
|
local ore_types = {{"iron-ore", "copper-ore", "coal", "stone", "uranium-ore"}}
|
|
for _, ore in ipairs(ore_types) do
|
|
local entities = surface.find_entities_filtered{{
|
|
area = {{
|
|
{{center.x - {radius}, center.y - {radius}}},
|
|
{{center.x + {radius}, center.y + {radius}}}
|
|
}},
|
|
name = ore
|
|
}}
|
|
if #entities > 0 then
|
|
-- 패치 중심점 계산
|
|
local sx, sy = 0, 0
|
|
for _, e in ipairs(entities) do
|
|
sx = sx + e.position.x
|
|
sy = sy + e.position.y
|
|
end
|
|
resources[ore] = {{
|
|
count = #entities,
|
|
center_x = math.floor(sx / #entities),
|
|
center_y = math.floor(sy / #entities)
|
|
}}
|
|
end
|
|
end
|
|
rcon.print(game.table_to_json(resources))
|
|
"""
|
|
raw = self.rcon.lua(lua)
|
|
return json.loads(raw) if raw else {}
|
|
|
|
# ── 건물 목록 ───────────────────────────────────────────────
|
|
def get_buildings(self, radius: int = 300) -> list:
|
|
lua = f"""
|
|
local p = game.player
|
|
local surface = p.surface
|
|
local center = p.position
|
|
|
|
local entity_names = {{
|
|
"burner-mining-drill", "electric-mining-drill",
|
|
"stone-furnace", "steel-furnace", "electric-furnace",
|
|
"transport-belt", "fast-transport-belt",
|
|
"burner-inserter", "inserter",
|
|
"small-electric-pole", "medium-electric-pole",
|
|
"steam-engine", "boiler", "offshore-pump",
|
|
"assembling-machine-1", "assembling-machine-2",
|
|
"chest", "iron-chest", "steel-chest"
|
|
}}
|
|
|
|
local result = {{}}
|
|
for _, name in ipairs(entity_names) do
|
|
local entities = surface.find_entities_filtered{{
|
|
area = {{
|
|
{{center.x - {radius}, center.y - {radius}}},
|
|
{{center.x + {radius}, center.y + {radius}}}
|
|
}},
|
|
name = name
|
|
}}
|
|
if #entities > 0 then
|
|
result[name] = #entities
|
|
end
|
|
end
|
|
rcon.print(game.table_to_json(result))
|
|
"""
|
|
raw = self.rcon.lua(lua)
|
|
return json.loads(raw) if raw else {}
|
|
|
|
# ── 연구 현황 ───────────────────────────────────────────────
|
|
def get_research_status(self) -> dict:
|
|
lua = """
|
|
local force = game.player.force
|
|
local completed = {}
|
|
local k = 0
|
|
for name, tech in pairs(force.technologies) do
|
|
if tech.researched then
|
|
k = k + 1
|
|
completed[k] = name
|
|
end
|
|
end
|
|
rcon.print(game.table_to_json({
|
|
current = force.current_research and force.current_research.name or "none",
|
|
completed_count = k
|
|
}))
|
|
"""
|
|
raw = self.rcon.lua(lua)
|
|
return json.loads(raw) if raw else {}
|
|
|
|
def summarize_for_ai(self, state: dict) -> str:
|
|
"""AI 프롬프트용 상태 요약 텍스트 생성"""
|
|
p = state.get("player", {})
|
|
inv = state.get("inventory", {})
|
|
res = state.get("resources", {})
|
|
bld = state.get("buildings", {})
|
|
|
|
lines = [
|
|
f"## 현재 게임 상태",
|
|
f"### 플레이어",
|
|
f"- 위치: ({p.get('x', '?')}, {p.get('y', '?')})",
|
|
f"- 체력: {p.get('health', '?')}",
|
|
"",
|
|
"### 인벤토리 (주요 아이템)",
|
|
]
|
|
|
|
key_items = [
|
|
"iron-ore", "copper-ore", "coal", "stone",
|
|
"iron-plate", "copper-plate", "steel-plate",
|
|
"burner-mining-drill", "electric-mining-drill",
|
|
"stone-furnace", "transport-belt", "inserter",
|
|
"burner-inserter", "small-electric-pole",
|
|
]
|
|
for item in key_items:
|
|
count = inv.get(item, 0)
|
|
if count > 0:
|
|
lines.append(f"- {item}: {count}개")
|
|
|
|
lines += ["", "### 주변 자원 패치"]
|
|
if res:
|
|
for ore, info in res.items():
|
|
lines.append(
|
|
f"- {ore}: {info['count']}타일 "
|
|
f"(중심: {info['center_x']}, {info['center_y']})"
|
|
)
|
|
else:
|
|
lines.append("- 탐색된 자원 없음")
|
|
|
|
lines += ["", "### 건설된 건물"]
|
|
if bld:
|
|
for name, count in bld.items():
|
|
lines.append(f"- {name}: {count}개")
|
|
else:
|
|
lines.append("- 아직 건물 없음")
|
|
|
|
return "\n".join(lines)
|