fix: Factorio 2.0 호환 + 자원 스캔 반경 500 + type 기반 검색
- scan_resources: radius 200→500, type="resource"로 검색 (이름 호환 문제 없음) - get_buildings: name 대신 type 기반 + pcall 안전 감싸기 - summarize_for_ai: 자원 거리 표시 추가
This commit is contained in:
116
state_reader.py
116
state_reader.py
@@ -1,13 +1,16 @@
|
|||||||
"""
|
"""
|
||||||
state_reader.py
|
state_reader.py
|
||||||
RCON을 통해 팩토리오 게임 상태를 읽어오는 모듈
|
RCON을 통해 팩토리오 게임 상태를 읽어오는 모듈
|
||||||
핵심 변경: game.player → game.players[1] (RCON 호환)
|
|
||||||
|
핵심 변경:
|
||||||
|
- game.player → game.players[1] (RCON 호환)
|
||||||
|
- scan_resources 반경 200→500 (초반 탐색 개선)
|
||||||
|
- get_buildings: name 대신 type 기반 검색 (Factorio 2.0 호환)
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
from factorio_rcon import FactorioRCON
|
from factorio_rcon import FactorioRCON
|
||||||
|
|
||||||
|
|
||||||
# 플레이어 참조 헬퍼 (RCON에서 game.player는 nil)
|
|
||||||
P = """local p = game.players[1]
|
P = """local p = game.players[1]
|
||||||
if not p then rcon.print("{}") return end
|
if not p then rcon.print("{}") return end
|
||||||
"""
|
"""
|
||||||
@@ -18,7 +21,6 @@ class StateReader:
|
|||||||
self.rcon = rcon
|
self.rcon = rcon
|
||||||
|
|
||||||
def get_full_state(self) -> dict:
|
def get_full_state(self) -> dict:
|
||||||
"""전체 게임 상태를 한 번에 수집"""
|
|
||||||
return {
|
return {
|
||||||
"player": self.get_player_info(),
|
"player": self.get_player_info(),
|
||||||
"inventory": self.get_inventory(),
|
"inventory": self.get_inventory(),
|
||||||
@@ -27,7 +29,6 @@ class StateReader:
|
|||||||
"tech": self.get_research_status(),
|
"tech": self.get_research_status(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── 플레이어 ────────────────────────────────────────────────────
|
|
||||||
def get_player_info(self) -> dict:
|
def get_player_info(self) -> dict:
|
||||||
lua = P + """
|
lua = P + """
|
||||||
rcon.print(game.table_to_json({
|
rcon.print(game.table_to_json({
|
||||||
@@ -39,7 +40,6 @@ rcon.print(game.table_to_json({
|
|||||||
raw = self.rcon.lua(lua)
|
raw = self.rcon.lua(lua)
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
|
||||||
# ── 인벤토리 ────────────────────────────────────────────────────
|
|
||||||
def get_inventory(self) -> dict:
|
def get_inventory(self) -> dict:
|
||||||
lua = P + """
|
lua = P + """
|
||||||
local inv = p.get_main_inventory()
|
local inv = p.get_main_inventory()
|
||||||
@@ -54,69 +54,78 @@ rcon.print(game.table_to_json(result))
|
|||||||
raw = self.rcon.lua(lua)
|
raw = self.rcon.lua(lua)
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
|
||||||
# ── 자원 패치 스캔 ──────────────────────────────────────────────
|
def scan_resources(self, radius: int = 500) -> dict:
|
||||||
def scan_resources(self, radius: int = 200) -> dict:
|
"""플레이어 주변 자원 패치 위치 탐색 (반경 500타일)"""
|
||||||
"""플레이어 주변 자원 패치 위치 탐색"""
|
|
||||||
lua = P + f"""
|
lua = P + f"""
|
||||||
local surface = p.surface
|
local surface = p.surface
|
||||||
local center = p.position
|
local center = p.position
|
||||||
local resources = {{}}
|
local resources = {{}}
|
||||||
|
|
||||||
local ore_types = {{"iron-ore", "copper-ore", "coal", "stone", "uranium-ore"}}
|
-- type="resource"로 모든 자원을 한번에 검색 (이름 호환 문제 없음)
|
||||||
for _, ore in ipairs(ore_types) do
|
local all_res = surface.find_entities_filtered{{
|
||||||
local entities = surface.find_entities_filtered{{
|
|
||||||
area = {{
|
area = {{
|
||||||
{{center.x - {radius}, center.y - {radius}}},
|
{{center.x - {radius}, center.y - {radius}}},
|
||||||
{{center.x + {radius}, center.y + {radius}}}
|
{{center.x + {radius}, center.y + {radius}}}
|
||||||
}},
|
}},
|
||||||
name = ore
|
type = "resource"
|
||||||
}}
|
}}
|
||||||
if #entities > 0 then
|
|
||||||
local sx, sy = 0, 0
|
-- 자원 이름별로 그룹핑
|
||||||
for _, e in ipairs(entities) do
|
for _, e in ipairs(all_res) do
|
||||||
sx = sx + e.position.x
|
local name = e.name
|
||||||
sy = sy + e.position.y
|
if not resources[name] then
|
||||||
|
resources[name] = {{count = 0, sx = 0, sy = 0}}
|
||||||
end
|
end
|
||||||
resources[ore] = {{
|
local r = resources[name]
|
||||||
count = #entities,
|
r.count = r.count + 1
|
||||||
center_x = math.floor(sx / #entities),
|
r.sx = r.sx + e.position.x
|
||||||
center_y = math.floor(sy / #entities)
|
r.sy = r.sy + e.position.y
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 중심점 계산
|
||||||
|
local result = {{}}
|
||||||
|
for name, r in pairs(resources) do
|
||||||
|
result[name] = {{
|
||||||
|
count = r.count,
|
||||||
|
center_x = math.floor(r.sx / r.count),
|
||||||
|
center_y = math.floor(r.sy / r.count)
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
end
|
rcon.print(game.table_to_json(result))
|
||||||
rcon.print(game.table_to_json(resources))
|
|
||||||
"""
|
"""
|
||||||
raw = self.rcon.lua(lua)
|
raw = self.rcon.lua(lua)
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
|
||||||
# ── 건물 목록 ───────────────────────────────────────────────────
|
|
||||||
def get_buildings(self, radius: int = 300) -> dict:
|
def get_buildings(self, radius: int = 300) -> dict:
|
||||||
|
"""type 기반 검색으로 Factorio 2.0 호환"""
|
||||||
lua = P + f"""
|
lua = P + f"""
|
||||||
local surface = p.surface
|
local surface = p.surface
|
||||||
local center = p.position
|
local center = p.position
|
||||||
|
local area = {{
|
||||||
|
{{center.x - {radius}, center.y - {radius}}},
|
||||||
|
{{center.x + {radius}, center.y + {radius}}}
|
||||||
|
}}
|
||||||
|
|
||||||
local entity_names = {{
|
-- type 기반으로 검색 (엔티티 이름 변경에 안전)
|
||||||
"burner-mining-drill", "electric-mining-drill",
|
local types = {{
|
||||||
"stone-furnace", "steel-furnace", "electric-furnace",
|
"mining-drill", "furnace", "assembling-machine",
|
||||||
"transport-belt", "fast-transport-belt",
|
"transport-belt", "inserter", "electric-pole",
|
||||||
"burner-inserter", "inserter",
|
"generator", "boiler", "offshore-pump",
|
||||||
"small-electric-pole", "medium-electric-pole",
|
"lab", "radar", "container",
|
||||||
"steam-engine", "boiler", "offshore-pump",
|
"ammo-turret", "electric-turret", "wall", "gate",
|
||||||
"assembling-machine-1", "assembling-machine-2",
|
"oil-refinery", "chemical-plant", "mining-drill"
|
||||||
"chest", "iron-chest", "steel-chest"
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
local result = {{}}
|
local result = {{}}
|
||||||
for _, name in ipairs(entity_names) do
|
for _, t in ipairs(types) do
|
||||||
local entities = surface.find_entities_filtered{{
|
local ok, entities = pcall(function()
|
||||||
area = {{
|
return surface.find_entities_filtered{{area = area, type = t}}
|
||||||
{{center.x - {radius}, center.y - {radius}}},
|
end)
|
||||||
{{center.x + {radius}, center.y + {radius}}}
|
if ok and entities and #entities > 0 then
|
||||||
}},
|
-- 이름별로 세분화
|
||||||
name = name
|
for _, e in ipairs(entities) do
|
||||||
}}
|
result[e.name] = (result[e.name] or 0) + 1
|
||||||
if #entities > 0 then
|
end
|
||||||
result[name] = #entities
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rcon.print(game.table_to_json(result))
|
rcon.print(game.table_to_json(result))
|
||||||
@@ -124,7 +133,6 @@ rcon.print(game.table_to_json(result))
|
|||||||
raw = self.rcon.lua(lua)
|
raw = self.rcon.lua(lua)
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
|
||||||
# ── 연구 현황 ───────────────────────────────────────────────────
|
|
||||||
def get_research_status(self) -> dict:
|
def get_research_status(self) -> dict:
|
||||||
lua = P + """
|
lua = P + """
|
||||||
local force = p.force
|
local force = p.force
|
||||||
@@ -145,15 +153,14 @@ rcon.print(game.table_to_json({
|
|||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
|
||||||
def summarize_for_ai(self, state: dict) -> str:
|
def summarize_for_ai(self, state: dict) -> str:
|
||||||
"""AI 프롬프트용 상태 요약 텍스트 생성"""
|
|
||||||
p = state.get("player", {})
|
p = state.get("player", {})
|
||||||
inv = state.get("inventory", {})
|
inv = state.get("inventory", {})
|
||||||
res = state.get("resources", {})
|
res = state.get("resources", {})
|
||||||
bld = state.get("buildings", {})
|
bld = state.get("buildings", {})
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
f"## 현재 게임 상태",
|
"## 현재 게임 상태",
|
||||||
f"### 플레이어",
|
"### 플레이어",
|
||||||
f"- 위치: ({p.get('x', '?')}, {p.get('y', '?')})",
|
f"- 위치: ({p.get('x', '?')}, {p.get('y', '?')})",
|
||||||
f"- 체력: {p.get('health', '?')}",
|
f"- 체력: {p.get('health', '?')}",
|
||||||
"",
|
"",
|
||||||
@@ -175,15 +182,24 @@ rcon.print(game.table_to_json({
|
|||||||
if not any(inv.get(item, 0) > 0 for item in key_items):
|
if not any(inv.get(item, 0) > 0 for item in key_items):
|
||||||
lines.append("- 비어 있음")
|
lines.append("- 비어 있음")
|
||||||
|
|
||||||
lines += ["", "### 주변 자원 패치"]
|
lines += ["", "### 주변 자원 패치 (반경 500타일 스캔)"]
|
||||||
if res:
|
if res:
|
||||||
for ore, info in res.items():
|
# 거리순으로 정렬
|
||||||
|
px = p.get('x', 0)
|
||||||
|
py = p.get('y', 0)
|
||||||
|
sorted_res = sorted(
|
||||||
|
res.items(),
|
||||||
|
key=lambda item: ((item[1]['center_x'] - px)**2 + (item[1]['center_y'] - py)**2)
|
||||||
|
)
|
||||||
|
for ore, info in sorted_res:
|
||||||
|
dist = int(((info['center_x'] - px)**2 + (info['center_y'] - py)**2)**0.5)
|
||||||
lines.append(
|
lines.append(
|
||||||
f"- {ore}: {info['count']}타일 "
|
f"- {ore}: {info['count']}타일 "
|
||||||
f"(중심: {info['center_x']}, {info['center_y']}) "
|
f"(중심: {info['center_x']}, {info['center_y']}) "
|
||||||
|
f"[거리: ~{dist}타일]"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
lines.append("- 탐색된 자원 없음")
|
lines.append("- 반경 500타일 내 자원 없음 — 더 멀리 탐색 필요")
|
||||||
|
|
||||||
lines += ["", "### 건설된 건물"]
|
lines += ["", "### 건설된 건물"]
|
||||||
if bld:
|
if bld:
|
||||||
|
|||||||
Reference in New Issue
Block a user