fix: mine_resource 채굴 안 되면 다른 타일 자동 전환
- Lua에서 가장 가까운 광석 5개를 거리순으로 반환 - 매 10틱마다 진행 체크 (아이템 수 변화 확인) - 3번 연속 진행 없으면 → 다음 타일로 자동 전환 - 최대 5개 타일 순서대로 시도 - 절벽/장애물 때문에 못 닿는 타일은 빠르게 스킵
This commit is contained in:
@@ -2,8 +2,9 @@
|
|||||||
action_executor.py — 순수 AI 플레이 버전 (치트 없음)
|
action_executor.py — 순수 AI 플레이 버전 (치트 없음)
|
||||||
|
|
||||||
핵심 변경:
|
핵심 변경:
|
||||||
- explore 액션 추가: 방향으로 걸으면서 자원 스캔, 발견 시 즉시 멈춤
|
- mine_resource: 채굴 진행 안 되면 다른 광석 타일로 자동 전환
|
||||||
- game.player → game.players[1] (RCON 호환)
|
- Lua에서 가장 가까운 광석 찾기 (거리순 정렬)
|
||||||
|
- 3번까지 다른 타일 시도
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
@@ -23,137 +24,82 @@ class ActionExecutor:
|
|||||||
def execute(self, action: dict) -> tuple[bool, str]:
|
def execute(self, action: dict) -> tuple[bool, str]:
|
||||||
act = action.get("action", "")
|
act = action.get("action", "")
|
||||||
params = action.get("params", {})
|
params = action.get("params", {})
|
||||||
|
|
||||||
handlers = {
|
handlers = {
|
||||||
"move": self.move,
|
"move": self.move, "explore": self.explore,
|
||||||
"explore": self.explore,
|
"mine_resource": self.mine_resource, "craft_item": self.craft_item,
|
||||||
"mine_resource": self.mine_resource,
|
"place_entity": self.place_entity, "place_belt_line": self.place_belt_line,
|
||||||
"craft_item": self.craft_item,
|
"insert_to_entity": self.insert_to_entity, "set_recipe": self.set_recipe,
|
||||||
"place_entity": self.place_entity,
|
"start_research": self.start_research, "wait": self.wait,
|
||||||
"place_belt_line": self.place_belt_line,
|
|
||||||
"insert_to_entity": self.insert_to_entity,
|
|
||||||
"set_recipe": self.set_recipe,
|
|
||||||
"start_research": self.start_research,
|
|
||||||
"wait": self.wait,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handler = handlers.get(act)
|
handler = handlers.get(act)
|
||||||
if not handler:
|
if not handler:
|
||||||
return False, f"알 수 없는 행동: {act}"
|
return False, f"알 수 없는 행동: {act}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = handler(**params)
|
result = handler(**params)
|
||||||
if isinstance(result, tuple) and isinstance(result[1], str):
|
if isinstance(result, tuple) and isinstance(result[1], str):
|
||||||
if "NO_PLAYER" in result[1]:
|
if "NO_PLAYER" in result[1]: return False, "서버에 접속한 플레이어가 없습니다."
|
||||||
return False, "서버에 접속한 플레이어가 없습니다."
|
if "NO_CHARACTER" in result[1]: return False, "플레이어 캐릭터가 없습니다."
|
||||||
if "NO_CHARACTER" in result[1]:
|
|
||||||
return False, "플레이어 캐릭터가 없습니다."
|
|
||||||
return result
|
return result
|
||||||
except TypeError as e:
|
except TypeError as e: return False, f"파라미터 오류: {e}"
|
||||||
return False, f"파라미터 오류: {e}"
|
except Exception as e: return False, f"실행 오류: {e}"
|
||||||
except Exception as e:
|
|
||||||
return False, f"실행 오류: {e}"
|
|
||||||
|
|
||||||
# ── 탐색 (걸으면서 자원 스캔) ────────────────────────────────────
|
# ── 탐색 ─────────────────────────────────────────────────────────
|
||||||
def explore(self, direction: str = "east", max_steps: int = 200) -> tuple[bool, str]:
|
def explore(self, direction: str = "east", max_steps: int = 200) -> tuple[bool, str]:
|
||||||
"""방향으로 걸으면서 매 20틱마다 반경 50에서 자원을 스캔.
|
|
||||||
자원 발견 시 즉시 멈추고 위치 + 자원 정보 반환."""
|
|
||||||
|
|
||||||
dir_map = {
|
dir_map = {
|
||||||
"north": "defines.direction.north",
|
"north": "defines.direction.north", "south": "defines.direction.south",
|
||||||
"south": "defines.direction.south",
|
"east": "defines.direction.east", "west": "defines.direction.west",
|
||||||
"east": "defines.direction.east",
|
"northeast": "defines.direction.northeast", "northwest": "defines.direction.northwest",
|
||||||
"west": "defines.direction.west",
|
"southeast": "defines.direction.southeast", "southwest": "defines.direction.southwest",
|
||||||
"northeast": "defines.direction.northeast",
|
|
||||||
"northwest": "defines.direction.northwest",
|
|
||||||
"southeast": "defines.direction.southeast",
|
|
||||||
"southwest": "defines.direction.southwest",
|
|
||||||
}
|
}
|
||||||
lua_dir = dir_map.get(direction, "defines.direction.east")
|
lua_dir = dir_map.get(direction, "defines.direction.east")
|
||||||
|
|
||||||
# 걷기 시작
|
|
||||||
self.rcon.lua(P + f"p.walking_state = {{walking = true, direction = {lua_dir}}}")
|
self.rcon.lua(P + f"p.walking_state = {{walking = true, direction = {lua_dir}}}")
|
||||||
|
stuck_count, last_pos = 0, None
|
||||||
scan_interval = 20 # 매 20틱(~2초)마다 스캔
|
|
||||||
stuck_count = 0
|
|
||||||
last_pos = None
|
|
||||||
|
|
||||||
for step in range(max_steps):
|
for step in range(max_steps):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
if step % 20 == 0:
|
||||||
# 매 scan_interval 틱마다 자원 스캔
|
|
||||||
if step % scan_interval == 0:
|
|
||||||
result = self.rcon.lua(P + """
|
result = self.rcon.lua(P + """
|
||||||
local ok, data = pcall(function()
|
local ok, data = pcall(function()
|
||||||
local surface = p.surface
|
|
||||||
local pos = p.position
|
local pos = p.position
|
||||||
local resources = surface.find_entities_filtered{position = pos, radius = 50, type = "resource"}
|
local res = p.surface.find_entities_filtered{position = pos, radius = 50, type = "resource"}
|
||||||
if #resources == 0 then
|
if #res == 0 then return "NONE:" .. string.format("%.0f,%.0f", pos.x, pos.y) end
|
||||||
return "NONE:" .. string.format("%.0f,%.0f", pos.x, pos.y)
|
|
||||||
end
|
|
||||||
-- 자원 이름별 카운트
|
|
||||||
local counts = {}
|
local counts = {}
|
||||||
for _, e in ipairs(resources) do
|
for _, e in ipairs(res) do counts[e.name] = (counts[e.name] or 0) + 1 end
|
||||||
counts[e.name] = (counts[e.name] or 0) + 1
|
|
||||||
end
|
|
||||||
local parts = {}
|
local parts = {}
|
||||||
for name, count in pairs(counts) do
|
for name, count in pairs(counts) do parts[#parts+1] = name .. "=" .. count end
|
||||||
parts[#parts+1] = name .. "=" .. count
|
|
||||||
end
|
|
||||||
return "FOUND:" .. string.format("%.0f,%.0f", pos.x, pos.y) .. "|" .. table.concat(parts, ",")
|
return "FOUND:" .. string.format("%.0f,%.0f", pos.x, pos.y) .. "|" .. table.concat(parts, ",")
|
||||||
end)
|
end)
|
||||||
if ok then rcon.print(data) else rcon.print("ERROR") end
|
if ok then rcon.print(data) else rcon.print("ERROR") end
|
||||||
""")
|
""")
|
||||||
|
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
||||||
if not result or result in ("NO_PLAYER", "NO_CHARACTER"):
|
|
||||||
return False, result or "플레이어 없음"
|
|
||||||
|
|
||||||
if result.startswith("FOUND:"):
|
if result.startswith("FOUND:"):
|
||||||
# 멈추기
|
|
||||||
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
||||||
# 파싱: "FOUND:x,y|iron-ore=500,coal=200"
|
|
||||||
parts = result.replace("FOUND:", "").split("|")
|
parts = result.replace("FOUND:", "").split("|")
|
||||||
pos_str = parts[0]
|
return True, f"자원 발견! 위치({parts[0]}), 자원: {parts[1] if len(parts)>1 else ''}"
|
||||||
res_str = parts[1] if len(parts) > 1 else ""
|
|
||||||
return True, f"자원 발견! 위치({pos_str}), 자원: {res_str}"
|
|
||||||
|
|
||||||
if result.startswith("NONE:"):
|
if result.startswith("NONE:"):
|
||||||
pos_str = result.replace("NONE:", "")
|
pos_str = result.replace("NONE:", "")
|
||||||
# stuck 체크
|
if last_pos == pos_str: stuck_count += 1
|
||||||
if last_pos == pos_str:
|
else: stuck_count = 0
|
||||||
stuck_count += 1
|
|
||||||
else:
|
|
||||||
stuck_count = 0
|
|
||||||
last_pos = pos_str
|
last_pos = pos_str
|
||||||
|
|
||||||
if stuck_count >= 3:
|
if stuck_count >= 3:
|
||||||
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
||||||
return False, f"탐색 중 장애물에 막힘 위치({pos_str}) — 다른 방향 시도"
|
return False, f"탐색 중 장애물에 막힘 위치({pos_str}) — 다른 방향 시도"
|
||||||
|
|
||||||
# 시간 초과
|
|
||||||
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
||||||
return False, f"{direction} 방향 {max_steps}틱 탐색했으나 자원 미발견 — 다른 방향 시도"
|
return False, f"{direction} 방향 탐색 자원 미발견 — 다른 방향 시도"
|
||||||
|
|
||||||
# ── 이동 (실제 걷기) ─────────────────────────────────────────────
|
# ── 이동 ─────────────────────────────────────────────────────────
|
||||||
def move(self, x: int, y: int) -> tuple[bool, str]:
|
def move(self, x: int, y: int) -> tuple[bool, str]:
|
||||||
"""8방향 walking_state로 실제 걷기. 도착까지 폴링."""
|
max_ticks, stuck_count, last_dist = 400, 0, 99999
|
||||||
max_ticks = 400
|
|
||||||
stuck_count = 0
|
|
||||||
last_dist = 99999
|
|
||||||
|
|
||||||
for _ in range(max_ticks):
|
for _ in range(max_ticks):
|
||||||
result = self.rcon.lua(P + f"""
|
result = self.rcon.lua(P + f"""
|
||||||
local tx, ty = {x}, {y}
|
local tx, ty = {x}, {y}
|
||||||
local dx = tx - p.position.x
|
local dx = tx - p.position.x
|
||||||
local dy = ty - p.position.y
|
local dy = ty - p.position.y
|
||||||
local dist = math.sqrt(dx*dx + dy*dy)
|
local dist = math.sqrt(dx*dx + dy*dy)
|
||||||
|
|
||||||
if dist < 1.5 then
|
if dist < 1.5 then
|
||||||
p.walking_state = {{walking = false, direction = defines.direction.north}}
|
p.walking_state = {{walking = false, direction = defines.direction.north}}
|
||||||
rcon.print("ARRIVED:" .. math.floor(p.position.x) .. "," .. math.floor(p.position.y))
|
rcon.print("ARRIVED:" .. math.floor(p.position.x) .. "," .. math.floor(p.position.y))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local angle = math.atan2(dy, dx)
|
local angle = math.atan2(dy, dx)
|
||||||
local dir
|
local dir
|
||||||
if angle >= -0.393 and angle < 0.393 then dir = defines.direction.east
|
if angle >= -0.393 and angle < 0.393 then dir = defines.direction.east
|
||||||
@@ -163,81 +109,104 @@ elseif angle >= 1.963 and angle < 2.749 then dir = defines.direction.southwest
|
|||||||
elseif angle >= -1.178 and angle < -0.393 then dir = defines.direction.northeast
|
elseif angle >= -1.178 and angle < -0.393 then dir = defines.direction.northeast
|
||||||
elseif angle >= -1.963 and angle < -1.178 then dir = defines.direction.north
|
elseif angle >= -1.963 and angle < -1.178 then dir = defines.direction.north
|
||||||
elseif angle >= -2.749 and angle < -1.963 then dir = defines.direction.northwest
|
elseif angle >= -2.749 and angle < -1.963 then dir = defines.direction.northwest
|
||||||
else dir = defines.direction.west
|
else dir = defines.direction.west end
|
||||||
end
|
|
||||||
|
|
||||||
p.walking_state = {{walking = true, direction = dir}}
|
p.walking_state = {{walking = true, direction = dir}}
|
||||||
rcon.print("WALK:" .. string.format("%.1f", dist))
|
rcon.print("WALK:" .. string.format("%.1f", dist))
|
||||||
""")
|
""")
|
||||||
if not result or result in ("NO_PLAYER", "NO_CHARACTER"):
|
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
||||||
return False, result or "플레이어 없음"
|
if result.startswith("ARRIVED"): return True, f"({x}, {y})로 도착"
|
||||||
|
|
||||||
if result.startswith("ARRIVED"):
|
|
||||||
return True, f"({x}, {y})로 도착"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dist = float(result.split(":")[1])
|
dist = float(result.split(":")[1])
|
||||||
if abs(dist - last_dist) < 0.3:
|
if abs(dist - last_dist) < 0.3: stuck_count += 1
|
||||||
stuck_count += 1
|
else: stuck_count = 0
|
||||||
else:
|
|
||||||
stuck_count = 0
|
|
||||||
last_dist = dist
|
last_dist = dist
|
||||||
except (ValueError, IndexError):
|
except: pass
|
||||||
pass
|
|
||||||
|
|
||||||
if stuck_count > 30:
|
if stuck_count > 30:
|
||||||
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
||||||
return False, f"({x},{y}) 이동 중 장애물에 막힘 (남은 거리: {last_dist:.0f})"
|
return False, f"({x},{y}) 장애물에 막힘 (남은 거리: {last_dist:.0f})"
|
||||||
|
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
||||||
return False, f"({x},{y})에 도달 못함 (시간 초과, 남은 거리: {last_dist:.0f})"
|
return False, f"({x},{y}) 시간 초과 (남은 거리: {last_dist:.0f})"
|
||||||
|
|
||||||
# ── 자원 채굴 ────────────────────────────────────────────────────
|
# ── 채굴 (진행 안 되면 다른 타일 시도) ────────────────────────────
|
||||||
def mine_resource(self, ore: str = "iron-ore", count: int = 10) -> tuple[bool, str]:
|
def mine_resource(self, ore: str = "iron-ore", count: int = 10) -> tuple[bool, str]:
|
||||||
|
"""가장 가까운 광석부터 시도. 채굴 안 되면 다른 타일로 자동 전환."""
|
||||||
|
before_count = self._get_item_count(ore)
|
||||||
|
target_count = before_count + count
|
||||||
|
|
||||||
|
# Lua에서 가장 가까운 광석 5개 좌표를 거리순으로 반환
|
||||||
find_result = self.rcon.lua(P + f"""
|
find_result = self.rcon.lua(P + f"""
|
||||||
local surface = p.surface
|
local surface = p.surface
|
||||||
local resources = surface.find_entities_filtered{{
|
local pos = p.position
|
||||||
position = p.position, radius = 5, name = "{ore}"
|
local resources = surface.find_entities_filtered{{position = pos, radius = 20, name = "{ore}"}}
|
||||||
}}
|
if #resources == 0 then rcon.print("NOT_FOUND") return end
|
||||||
if #resources == 0 then
|
-- 거리순 정렬
|
||||||
resources = surface.find_entities_filtered{{
|
table.sort(resources, function(a, b)
|
||||||
position = p.position, radius = 15, name = "{ore}"
|
local da = (a.position.x - pos.x)^2 + (a.position.y - pos.y)^2
|
||||||
}}
|
local db = (b.position.x - pos.x)^2 + (b.position.y - pos.y)^2
|
||||||
end
|
return da < db
|
||||||
if #resources > 0 then
|
end)
|
||||||
local r = resources[1]
|
-- 가장 가까운 5개 반환
|
||||||
rcon.print("FOUND:" .. string.format("%.1f,%.1f", r.position.x, r.position.y))
|
local parts = {{}}
|
||||||
else
|
for i = 1, math.min(5, #resources) do
|
||||||
rcon.print("NOT_FOUND")
|
local r = resources[i]
|
||||||
|
parts[#parts+1] = string.format("%.1f,%.1f", r.position.x, r.position.y)
|
||||||
end
|
end
|
||||||
|
rcon.print("FOUND:" .. table.concat(parts, ";"))
|
||||||
""")
|
""")
|
||||||
if not find_result or find_result in ("NO_PLAYER", "NO_CHARACTER"):
|
if not find_result or find_result in ("NO_PLAYER", "NO_CHARACTER"):
|
||||||
return False, find_result or "플레이어 없음"
|
return False, find_result or "플레이어 없음"
|
||||||
if find_result == "NOT_FOUND":
|
if find_result == "NOT_FOUND":
|
||||||
return False, f"근처에 {ore} 없음 — 자원 패치로 move 먼저"
|
return False, f"반경 20 내 {ore} 없음 — explore로 자원 찾기"
|
||||||
|
|
||||||
parts = find_result.replace("FOUND:", "").split(",")
|
# 여러 광석 좌표 파싱
|
||||||
rx, ry = float(parts[0]), float(parts[1])
|
coords_str = find_result.replace("FOUND:", "")
|
||||||
|
coord_list = []
|
||||||
|
for c in coords_str.split(";"):
|
||||||
|
parts = c.split(",")
|
||||||
|
if len(parts) == 2:
|
||||||
|
coord_list.append((float(parts[0]), float(parts[1])))
|
||||||
|
|
||||||
before_count = self._get_item_count(ore)
|
if not coord_list:
|
||||||
target_count = before_count + count
|
return False, f"{ore} 좌표 파싱 실패"
|
||||||
max_ticks = count * 40
|
|
||||||
|
|
||||||
for _ in range(max_ticks):
|
# 각 좌표를 순서대로 시도
|
||||||
self.rcon.lua(P + f"p.mining_state = {{mining = true, position = {{{rx}, {ry}}}}}")
|
total_mined = 0
|
||||||
time.sleep(0.1)
|
for attempt, (rx, ry) in enumerate(coord_list):
|
||||||
current = self._get_item_count(ore)
|
stall_count = 0
|
||||||
if current >= target_count:
|
last_item_count = self._get_item_count(ore)
|
||||||
self.rcon.lua(P + "p.mining_state = {mining = false}")
|
|
||||||
return True, f"{ore} {current - before_count}개 채굴 완료"
|
|
||||||
|
|
||||||
self.rcon.lua(P + "p.mining_state = {mining = false}")
|
ticks_per_tile = min(count * 20, 200) # 타일당 최대 시도 틱
|
||||||
mined = self._get_item_count(ore) - before_count
|
for tick in range(ticks_per_tile):
|
||||||
if mined > 0:
|
self.rcon.lua(P + f"p.mining_state = {{mining = true, position = {{{rx}, {ry}}}}}")
|
||||||
return True, f"{ore} {mined}개 채굴 (목표 {count}개 중 일부)"
|
time.sleep(0.1)
|
||||||
return False, f"{ore} 채굴 실패"
|
|
||||||
|
# 매 10틱마다 진행 체크
|
||||||
|
if tick % 10 == 9:
|
||||||
|
current = self._get_item_count(ore)
|
||||||
|
if current >= target_count:
|
||||||
|
self.rcon.lua(P + "p.mining_state = {mining = false}")
|
||||||
|
total_mined = current - before_count
|
||||||
|
return True, f"{ore} {total_mined}개 채굴 완료"
|
||||||
|
|
||||||
|
if current == last_item_count:
|
||||||
|
stall_count += 1
|
||||||
|
else:
|
||||||
|
stall_count = 0
|
||||||
|
last_item_count = current
|
||||||
|
|
||||||
|
# 3번 연속 진행 없으면 이 타일 포기 → 다음 타일
|
||||||
|
if stall_count >= 3:
|
||||||
|
print(f" [채굴] 타일({rx:.0f},{ry:.0f}) 접근 불가 — 다음 타일 시도 ({attempt+1}/{len(coord_list)})")
|
||||||
|
break
|
||||||
|
|
||||||
|
self.rcon.lua(P + "p.mining_state = {mining = false}")
|
||||||
|
|
||||||
|
# 모든 타일 시도 후
|
||||||
|
total_mined = self._get_item_count(ore) - before_count
|
||||||
|
if total_mined > 0:
|
||||||
|
return True, f"{ore} {total_mined}개 채굴 (목표 {count}개 중 일부 — 접근 가능 타일 부족)"
|
||||||
|
return False, f"{ore} 채굴 실패 — 모든 근처 타일 접근 불가. 다른 위치로 move 필요"
|
||||||
|
|
||||||
# ── 제작 ─────────────────────────────────────────────────────────
|
# ── 제작 ─────────────────────────────────────────────────────────
|
||||||
def craft_item(self, item: str, count: int = 1) -> tuple[bool, str]:
|
def craft_item(self, item: str, count: int = 1) -> tuple[bool, str]:
|
||||||
@@ -246,21 +215,16 @@ local recipe = p.force.recipes["{item}"]
|
|||||||
if not recipe then rcon.print("NO_RECIPE") return end
|
if not recipe then rcon.print("NO_RECIPE") return end
|
||||||
if not recipe.enabled then rcon.print("LOCKED") return end
|
if not recipe.enabled then rcon.print("LOCKED") return end
|
||||||
local crafted = p.begin_crafting{{count={count}, recipe="{item}"}}
|
local crafted = p.begin_crafting{{count={count}, recipe="{item}"}}
|
||||||
if crafted > 0 then rcon.print("CRAFTING:" .. crafted)
|
if crafted > 0 then rcon.print("CRAFTING:" .. crafted) else rcon.print("NO_INGREDIENTS") end
|
||||||
else rcon.print("NO_INGREDIENTS") end
|
|
||||||
""")
|
""")
|
||||||
if not result or result in ("NO_PLAYER", "NO_CHARACTER"):
|
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
||||||
return False, result or "플레이어 없음"
|
|
||||||
if result.startswith("CRAFTING:"):
|
if result.startswith("CRAFTING:"):
|
||||||
crafted = int(result.split(":")[1])
|
crafted = int(result.split(":")[1])
|
||||||
time.sleep(min(crafted * 2, 30))
|
time.sleep(min(crafted * 2, 30))
|
||||||
return True, f"{item} {crafted}개 제작 완료"
|
return True, f"{item} {crafted}개 제작 완료"
|
||||||
elif result == "NO_RECIPE":
|
elif result == "NO_RECIPE": return False, f"{item} 레시피 없음"
|
||||||
return False, f"{item} 레시피 없음"
|
elif result == "LOCKED": return False, f"{item} 레시피 잠김"
|
||||||
elif result == "LOCKED":
|
elif result == "NO_INGREDIENTS": return False, f"{item} 재료 부족"
|
||||||
return False, f"{item} 레시피 잠김"
|
|
||||||
elif result == "NO_INGREDIENTS":
|
|
||||||
return False, f"{item} 재료 부족"
|
|
||||||
return False, f"제작 실패: {result}"
|
return False, f"제작 실패: {result}"
|
||||||
|
|
||||||
# ── 건물 배치 ────────────────────────────────────────────────────
|
# ── 건물 배치 ────────────────────────────────────────────────────
|
||||||
@@ -281,8 +245,7 @@ local built = p.build_from_cursor{{position = {{{x}, {y}}}, direction = {lua_dir
|
|||||||
if built then p.cursor_stack.clear() rcon.print("OK")
|
if built then p.cursor_stack.clear() rcon.print("OK")
|
||||||
else p.cursor_stack.clear() rcon.print("BLOCKED") end
|
else p.cursor_stack.clear() rcon.print("BLOCKED") end
|
||||||
""")
|
""")
|
||||||
if not result or result in ("NO_PLAYER", "NO_CHARACTER"):
|
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
||||||
return False, result or "플레이어 없음"
|
|
||||||
if result == "OK": return True, f"{name} 배치 완료 ({x},{y})"
|
if result == "OK": return True, f"{name} 배치 완료 ({x},{y})"
|
||||||
elif result.startswith("NO_ITEM"): return False, f"인벤토리에 {name} 없음"
|
elif result.startswith("NO_ITEM"): return False, f"인벤토리에 {name} 없음"
|
||||||
elif result.startswith("TOO_FAR"): return False, f"({x},{y})가 너무 멀음 — move 먼저"
|
elif result.startswith("TOO_FAR"): return False, f"({x},{y})가 너무 멀음 — move 먼저"
|
||||||
@@ -310,7 +273,7 @@ else p.cursor_stack.clear() rcon.print("BLOCKED") end
|
|||||||
if ok: placed += 1
|
if ok: placed += 1
|
||||||
else: failed += 1
|
else: failed += 1
|
||||||
if placed == 0: return False, f"벨트 설치 실패 ({failed}개 실패)"
|
if placed == 0: return False, f"벨트 설치 실패 ({failed}개 실패)"
|
||||||
return True, f"벨트 {placed}개 설치 완료" + (f", {failed}개 실패" if failed else "")
|
return True, f"벨트 {placed}개 설치" + (f", {failed}개 실패" if failed else "")
|
||||||
|
|
||||||
# ── 아이템 삽입 ──────────────────────────────────────────────────
|
# ── 아이템 삽입 ──────────────────────────────────────────────────
|
||||||
def insert_to_entity(self, x: int, y: int, item: str = "coal", count: int = 50) -> tuple[bool, str]:
|
def insert_to_entity(self, x: int, y: int, item: str = "coal", count: int = 50) -> tuple[bool, str]:
|
||||||
@@ -381,12 +344,10 @@ else force.research_queue_enabled = true force.add_research("{tech}") rcon.print
|
|||||||
elif result == "NO_TECH": return False, f"{tech} 기술 없음"
|
elif result == "NO_TECH": return False, f"{tech} 기술 없음"
|
||||||
return False, result
|
return False, result
|
||||||
|
|
||||||
# ── 대기 ─────────────────────────────────────────────────────────
|
|
||||||
def wait(self, seconds: int = 3) -> tuple[bool, str]:
|
def wait(self, seconds: int = 3) -> tuple[bool, str]:
|
||||||
time.sleep(min(seconds, 30))
|
time.sleep(min(seconds, 30))
|
||||||
return True, f"{seconds}초 대기 완료"
|
return True, f"{seconds}초 대기 완료"
|
||||||
|
|
||||||
# ── 유틸리티 ─────────────────────────────────────────────────────
|
|
||||||
def _get_item_count(self, item: str) -> int:
|
def _get_item_count(self, item: str) -> int:
|
||||||
result = self.rcon.lua(P + f'rcon.print(tostring(p.get_main_inventory().get_item_count("{item}")))')
|
result = self.rcon.lua(P + f'rcon.print(tostring(p.get_main_inventory().get_item_count("{item}")))')
|
||||||
try: return int(result)
|
try: return int(result)
|
||||||
|
|||||||
Reference in New Issue
Block a user