fix: Factorio 2.0 호환 - type 기반 검색 + pcall 안전 감싸기
- _get_global_summary: name 기반→type 기반 검색 (filter-inserter 에러 해결) - _get_zone_summaries: e.type 기반 분류 - _detect_problems: 전력 부족 체크 제거 (API 변경 대응) - _get_player_info: 모든 아이템 표시 (하드코딩 목록 제거) - 모든 검색에 pcall 적용
This commit is contained in:
@@ -1,48 +1,27 @@
|
|||||||
"""
|
"""
|
||||||
context_compressor.py — 계층적 컨텍스트 압축
|
context_compressor.py — 계층적 컨텍스트 압축
|
||||||
|
|
||||||
중반부 이후 건물이 수백 개가 되면 전체 상태를 프롬프트에
|
핵심 변경:
|
||||||
담을 수 없음. 이 모듈은 공장을 '구역(zone)'으로 나눠
|
- game.player → game.players[1] (RCON 호환)
|
||||||
AI가 소화할 수 있는 크기로 압축한다.
|
- pcall로 엔티티 검색 감싸기 (Factorio 2.0 이름 변경 대응)
|
||||||
|
- type 기반 검색으로 버전 호환성 확보
|
||||||
핵심 변경: game.player → game.players[1] (RCON 호환)
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
from factorio_rcon import FactorioRCON
|
from factorio_rcon import FactorioRCON
|
||||||
|
|
||||||
|
|
||||||
# 공장을 이 크기의 격자로 나눔 (타일 단위)
|
|
||||||
ZONE_SIZE = 64
|
ZONE_SIZE = 64
|
||||||
|
|
||||||
# 모든 Lua 코드 앞에 붙일 플레이어 참조
|
|
||||||
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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ContextCompressor:
|
class ContextCompressor:
|
||||||
"""
|
|
||||||
게임 상태를 AI가 소화 가능한 크기로 압축.
|
|
||||||
|
|
||||||
압축 구조:
|
|
||||||
Level 0 (raw) : 모든 건물 좌표 + 상태 (토큰 수천)
|
|
||||||
Level 1 (zone) : 구역별 건물 수 + 상태 요약 (토큰 ~300)
|
|
||||||
Level 2 (global): 전체 공장 핵심 지표 + 문제 목록 (토큰 ~100)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, rcon: FactorioRCON):
|
def __init__(self, rcon: FactorioRCON):
|
||||||
self.rcon = rcon
|
self.rcon = rcon
|
||||||
|
|
||||||
# ── 공개 API ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def get_compressed_state(self, detail_level: int = 1) -> str:
|
def get_compressed_state(self, detail_level: int = 1) -> str:
|
||||||
"""
|
|
||||||
detail_level:
|
|
||||||
0 = 글로벌 요약만 (후반 대규모 공장, 토큰 ~80)
|
|
||||||
1 = 구역별 요약 (중반, 토큰 ~250) ← 기본값
|
|
||||||
2 = 구역 요약 + 문제 구역 드릴다운 (토큰 ~400)
|
|
||||||
"""
|
|
||||||
global_summary = self._get_global_summary()
|
global_summary = self._get_global_summary()
|
||||||
problems = self._detect_problems()
|
problems = self._detect_problems()
|
||||||
player = self._get_player_info()
|
player = self._get_player_info()
|
||||||
@@ -55,11 +34,10 @@ class ContextCompressor:
|
|||||||
if detail_level == 1:
|
if detail_level == 1:
|
||||||
return self._format_level1(global_summary, zones, problems, player)
|
return self._format_level1(global_summary, zones, problems, player)
|
||||||
|
|
||||||
# level 2: 문제 구역은 드릴다운
|
|
||||||
drilldown = self._drilldown_problem_zones(zones, problems)
|
drilldown = self._drilldown_problem_zones(zones, problems)
|
||||||
return self._format_level2(global_summary, zones, problems, drilldown, player)
|
return self._format_level2(global_summary, zones, problems, drilldown, player)
|
||||||
|
|
||||||
# ── 글로벌 지표 ─────────────────────────────────────────────────
|
# ── 글로벌 지표 (pcall로 안전하게) ──────────────────────────────
|
||||||
|
|
||||||
def _get_global_summary(self) -> dict:
|
def _get_global_summary(self) -> dict:
|
||||||
lua = P + """
|
lua = P + """
|
||||||
@@ -67,31 +45,31 @@ local surface = p.surface
|
|||||||
local force = p.force
|
local force = p.force
|
||||||
local center = p.position
|
local center = p.position
|
||||||
|
|
||||||
-- 전체 건물 수 집계
|
-- type 기반으로 검색 (Factorio 2.0 엔티티 이름 변경에 안전)
|
||||||
local counts = {}
|
local type_list = {
|
||||||
local entity_types = {
|
"mining-drill", "furnace", "assembling-machine",
|
||||||
"burner-mining-drill","electric-mining-drill",
|
"transport-belt", "inserter", "electric-pole",
|
||||||
"stone-furnace","steel-furnace","electric-furnace",
|
"generator", "boiler", "offshore-pump", "pipe",
|
||||||
"assembling-machine-1","assembling-machine-2","assembling-machine-3",
|
|
||||||
"transport-belt","fast-transport-belt","express-transport-belt",
|
|
||||||
"inserter","fast-inserter","filter-inserter",
|
|
||||||
"small-electric-pole","medium-electric-pole","big-electric-pole",
|
|
||||||
"steam-engine","solar-panel","accumulator",
|
|
||||||
"boiler","offshore-pump","pipe",
|
|
||||||
"lab", "rocket-silo", "radar",
|
"lab", "rocket-silo", "radar",
|
||||||
"gun-turret","laser-turret","wall","gate",
|
"ammo-turret", "electric-turret", "wall", "gate",
|
||||||
"oil-refinery","chemical-plant","pumpjack"
|
"oil-refinery", "chemical-plant", "solar-panel", "accumulator",
|
||||||
|
"container"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local counts = {}
|
||||||
local total = 0
|
local total = 0
|
||||||
for _, name in ipairs(entity_types) do
|
for _, t in ipairs(type_list) do
|
||||||
local found = surface.find_entities_filtered{
|
local ok, found = pcall(function()
|
||||||
|
return surface.find_entities_filtered{
|
||||||
area={{center.x-500,center.y-500},{center.x+500,center.y+500}},
|
area={{center.x-500,center.y-500},{center.x+500,center.y+500}},
|
||||||
name=name
|
type=t
|
||||||
}
|
}
|
||||||
if #found > 0 then
|
end)
|
||||||
counts[name] = #found
|
if ok and found then
|
||||||
total = total + #found
|
for _, e in ipairs(found) do
|
||||||
|
counts[e.name] = (counts[e.name] or 0) + 1
|
||||||
|
total = total + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -103,7 +81,6 @@ if force.current_research then
|
|||||||
tech_progress = math.floor(force.research_progress * 100)
|
tech_progress = math.floor(force.research_progress * 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 완료된 연구 수
|
|
||||||
local researched = 0
|
local researched = 0
|
||||||
for _, t in pairs(force.technologies) do
|
for _, t in pairs(force.technologies) do
|
||||||
if t.researched then researched = researched + 1 end
|
if t.researched then researched = researched + 1 end
|
||||||
@@ -119,17 +96,18 @@ rcon.print(game.table_to_json({
|
|||||||
}))
|
}))
|
||||||
"""
|
"""
|
||||||
raw = self.rcon.lua(lua)
|
raw = self.rcon.lua(lua)
|
||||||
|
try:
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
# ── 구역 요약 ───────────────────────────────────────────────────
|
# ── 구역 요약 ───────────────────────────────────────────────────
|
||||||
|
|
||||||
def _get_zone_summaries(self) -> list[dict]:
|
def _get_zone_summaries(self) -> list[dict]:
|
||||||
"""공장을 ZONE_SIZE×ZONE_SIZE 격자로 나눠 구역별 요약"""
|
|
||||||
lua = P + f"""
|
lua = P + f"""
|
||||||
local surface = p.surface
|
local surface = p.surface
|
||||||
local center = p.position
|
local center = p.position
|
||||||
local Z = {ZONE_SIZE}
|
local Z = {ZONE_SIZE}
|
||||||
|
|
||||||
local radius = 512
|
local radius = 512
|
||||||
local zones = {{}}
|
local zones = {{}}
|
||||||
|
|
||||||
@@ -159,15 +137,16 @@ for _, e in ipairs(all_entities) do
|
|||||||
local z = zones[key]
|
local z = zones[key]
|
||||||
z.entities = z.entities + 1
|
z.entities = z.entities + 1
|
||||||
local n = e.name
|
local n = e.name
|
||||||
if n:find("mining%-drill") then z.miners = z.miners + 1
|
local t = e.type
|
||||||
elseif n:find("furnace") then z.furnaces = z.furnaces + 1
|
if t == "mining-drill" then z.miners = z.miners + 1
|
||||||
elseif n:find("assembling") then z.assemblers = z.assemblers + 1
|
elseif t == "furnace" then z.furnaces = z.furnaces + 1
|
||||||
elseif n:find("belt") then z.belts = z.belts + 1
|
elseif t == "assembling-machine" then z.assemblers = z.assemblers + 1
|
||||||
elseif n:find("inserter") then z.inserters = z.inserters + 1
|
elseif t == "transport-belt" then z.belts = z.belts + 1
|
||||||
elseif n:find("pole") then z.poles = z.poles + 1
|
elseif t == "inserter" then z.inserters = z.inserters + 1
|
||||||
elseif n:find("turret") then z.turrets = z.turrets + 1
|
elseif t == "electric-pole" then z.poles = z.poles + 1
|
||||||
|
elseif t == "ammo-turret" or t == "electric-turret" then z.turrets = z.turrets + 1
|
||||||
elseif n == "lab" then z.labs = z.labs + 1
|
elseif n == "lab" then z.labs = z.labs + 1
|
||||||
elseif n == "pumpjack" or n == "offshore-pump" then z.pumps = z.pumps + 1
|
elseif t == "offshore-pump" then z.pumps = z.pumps + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -180,7 +159,10 @@ end
|
|||||||
rcon.print(game.table_to_json(result))
|
rcon.print(game.table_to_json(result))
|
||||||
"""
|
"""
|
||||||
raw = self.rcon.lua(lua)
|
raw = self.rcon.lua(lua)
|
||||||
|
try:
|
||||||
return json.loads(raw) if raw else []
|
return json.loads(raw) if raw else []
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
# ── 문제 감지 ───────────────────────────────────────────────────
|
# ── 문제 감지 ───────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -191,7 +173,7 @@ local force = p.force
|
|||||||
local center = p.position
|
local center = p.position
|
||||||
local problems = {}
|
local problems = {}
|
||||||
|
|
||||||
-- 1. 연료 없는 채굴기/제련소
|
-- 1. 연료 없는 버너 건물
|
||||||
local burner_entities = surface.find_entities_filtered{
|
local burner_entities = surface.find_entities_filtered{
|
||||||
area={{center.x-300,center.y-300},{center.x+300,center.y+300}},
|
area={{center.x-300,center.y-300},{center.x+300,center.y+300}},
|
||||||
type={"mining-drill","furnace"}
|
type={"mining-drill","furnace"}
|
||||||
@@ -224,22 +206,7 @@ if stuck > 5 then
|
|||||||
problems[#problems+1] = "벨트 병목: 삽입기 " .. stuck .. "개가 아이템 들고 멈춤"
|
problems[#problems+1] = "벨트 병목: 삽입기 " .. stuck .. "개가 아이템 들고 멈춤"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 3. 전력 부족
|
-- 3. 자원 고갈 임박
|
||||||
local consumers = surface.find_entities_filtered{
|
|
||||||
area={{center.x-400,center.y-400},{center.x+400,center.y+400}},
|
|
||||||
type={"assembling-machine","lab","electric-mining-drill","electric-furnace"}
|
|
||||||
}
|
|
||||||
local no_power = 0
|
|
||||||
for _, e in ipairs(consumers) do
|
|
||||||
if e.energy < e.electric_buffer_size * 0.1 then
|
|
||||||
no_power = no_power + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if no_power > 0 then
|
|
||||||
problems[#problems+1] = "전력 부족: " .. no_power .. "개 건물 전력 10% 미만"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 4. 자원 고갈 임박한 굴맥
|
|
||||||
local drills = surface.find_entities_filtered{
|
local drills = surface.find_entities_filtered{
|
||||||
area={{center.x-300,center.y-300},{center.x+300,center.y+300}},
|
area={{center.x-300,center.y-300},{center.x+300,center.y+300}},
|
||||||
type="mining-drill"
|
type="mining-drill"
|
||||||
@@ -267,8 +234,6 @@ rcon.print(game.table_to_json(problems))
|
|||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ── 드릴다운 (문제 구역 상세) ────────────────────────────────────
|
|
||||||
|
|
||||||
def _drilldown_problem_zones(self, zones: list, problems: list) -> str:
|
def _drilldown_problem_zones(self, zones: list, problems: list) -> str:
|
||||||
if not problems or not zones:
|
if not problems or not zones:
|
||||||
return ""
|
return ""
|
||||||
@@ -283,28 +248,15 @@ rcon.print(game.table_to_json(problems))
|
|||||||
)
|
)
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
# ── 플레이어 정보 ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
def _get_player_info(self) -> dict:
|
def _get_player_info(self) -> dict:
|
||||||
lua = P + """
|
lua = P + """
|
||||||
local inv = p.get_main_inventory()
|
local inv = p.get_main_inventory()
|
||||||
if not inv then rcon.print("{}") return end
|
if not inv then rcon.print("{}") return end
|
||||||
local contents = inv.get_contents()
|
local contents = inv.get_contents()
|
||||||
|
|
||||||
local key_items = {
|
|
||||||
"iron-plate","copper-plate","steel-plate","iron-ore","copper-ore","coal",
|
|
||||||
"stone","stone-furnace","burner-mining-drill","electric-mining-drill",
|
|
||||||
"transport-belt","inserter","burner-inserter","fast-inserter",
|
|
||||||
"small-electric-pole","medium-electric-pole","pipe",
|
|
||||||
"offshore-pump","boiler","steam-engine","assembling-machine-1",
|
|
||||||
"assembling-machine-2","lab","iron-gear-wheel",
|
|
||||||
"automation-science-pack","logistic-science-pack","chemical-science-pack",
|
|
||||||
"military-science-pack","production-science-pack","utility-science-pack"
|
|
||||||
}
|
|
||||||
local inv_summary = {}
|
local inv_summary = {}
|
||||||
for _, name in ipairs(key_items) do
|
for name, count in pairs(contents) do
|
||||||
local c = contents[name] or 0
|
if count > 0 then inv_summary[name] = count end
|
||||||
if c > 0 then inv_summary[name] = c end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
rcon.print(game.table_to_json({
|
rcon.print(game.table_to_json({
|
||||||
@@ -314,7 +266,10 @@ rcon.print(game.table_to_json({
|
|||||||
}))
|
}))
|
||||||
"""
|
"""
|
||||||
raw = self.rcon.lua(lua)
|
raw = self.rcon.lua(lua)
|
||||||
|
try:
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
# ── 포맷터 ──────────────────────────────────────────────────────
|
# ── 포맷터 ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user