Add dev-mode turret placement
F5 cycles the turret variant (pistol/autocannon), F6 places the selected one in front of the player, hostile to them by default so it's immediately testable. Backed by a new DevTurretMenu mirroring the existing item spawn menu, sourced generically from any entity template with turret_weapon_item_id set. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FyY5qQB6XHRoBFNbsgdRQnmain
parent
e04761f7b5
commit
1099183788
|
|
@ -19,6 +19,8 @@ toggle_dev_mode = F1
|
||||||
dev_cycle_spawn = F2
|
dev_cycle_spawn = F2
|
||||||
dev_spawn_object = F3
|
dev_spawn_object = F3
|
||||||
dev_spawn_npc_menu = F4
|
dev_spawn_npc_menu = F4
|
||||||
|
dev_cycle_turret = F5
|
||||||
|
dev_place_turret = F6
|
||||||
pickup = f
|
pickup = f
|
||||||
|
|
||||||
[hands]
|
[hands]
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ DEFAULT_KEYBINDINGS: dict[str, list[str]] = {
|
||||||
"dev_cycle_spawn": ["F2"],
|
"dev_cycle_spawn": ["F2"],
|
||||||
"dev_spawn_object": ["F3"],
|
"dev_spawn_object": ["F3"],
|
||||||
"dev_spawn_npc_menu": ["F4"],
|
"dev_spawn_npc_menu": ["F4"],
|
||||||
|
"dev_cycle_turret": ["F5"],
|
||||||
|
"dev_place_turret": ["F6"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Human-friendly config names for keys that don't have a printable representation.
|
# Human-friendly config names for keys that don't have a printable representation.
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,32 @@ class DevSpawnMenu:
|
||||||
return False
|
return False
|
||||||
instance = ItemInstance(f"dev-spawn-{uuid.uuid4().hex[:8]}", item_id)
|
instance = ItemInstance(f"dev-spawn-{uuid.uuid4().hex[:8]}", item_id)
|
||||||
return character.inventory.place(instance, item_def, *slot)
|
return character.inventory.place(instance, item_def, *slot)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DevTurretMenu:
|
||||||
|
"""Dev-mode-only cheat tool: cycle through every turret entity template (see entities.json's
|
||||||
|
turret_pistol/turret_autocannon - any template with turret_weapon_item_id set, so a new
|
||||||
|
variant only needs adding to entities.json, not here) and place the selected one into the
|
||||||
|
world - see main.py's dev_place_turret handling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
registry: DefRegistry
|
||||||
|
entity_def_ids: list[str] = field(default_factory=list)
|
||||||
|
selected_index: int = 0
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if not self.entity_def_ids:
|
||||||
|
self.entity_def_ids = sorted(
|
||||||
|
def_id for def_id, entity_def in self.registry.entity_defs.items()
|
||||||
|
if entity_def.turret_weapon_item_id is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
def cycle(self, delta: int) -> None:
|
||||||
|
if self.entity_def_ids:
|
||||||
|
self.selected_index = (self.selected_index + delta) % len(self.entity_def_ids)
|
||||||
|
|
||||||
|
def selected_entity_def_id(self) -> str | None:
|
||||||
|
if not self.entity_def_ids:
|
||||||
|
return None
|
||||||
|
return self.entity_def_ids[self.selected_index]
|
||||||
|
|
|
||||||
22
main.py
22
main.py
|
|
@ -17,7 +17,7 @@ from engine.controls_menu import ControlsMenu
|
||||||
from engine.db import WorldRepository
|
from engine.db import WorldRepository
|
||||||
from engine.defs import DefRegistry
|
from engine.defs import DefRegistry
|
||||||
from engine.dev_config import DevConfig
|
from engine.dev_config import DevConfig
|
||||||
from engine.dev_tools import DevSpawnMenu
|
from engine.dev_tools import DevSpawnMenu, DevTurretMenu
|
||||||
from engine.entity import EntityPosition
|
from engine.entity import EntityPosition
|
||||||
from engine.gamepad import GamepadBindings, GamepadButtonEdgeTracker, GamepadState
|
from engine.gamepad import GamepadBindings, GamepadButtonEdgeTracker, GamepadState
|
||||||
from engine.game_modes import CharacterCreationFlow, MainMenu, Mode, PauseMenu
|
from engine.game_modes import CharacterCreationFlow, MainMenu, Mode, PauseMenu
|
||||||
|
|
@ -63,6 +63,7 @@ from resources.logic.organs import apply_organ_interactions_tick
|
||||||
from resources.logic.pickup import drop_item, try_pickup
|
from resources.logic.pickup import drop_item, try_pickup
|
||||||
from resources.logic.steering import advance_all_ship_rotations, at_helm, try_steer_ship
|
from resources.logic.steering import advance_all_ship_rotations, at_helm, try_steer_ship
|
||||||
from resources.logic.swimming import in_water, try_swim_vertical
|
from resources.logic.swimming import in_water, try_swim_vertical
|
||||||
|
from resources.logic.turrets import spawn_turret
|
||||||
|
|
||||||
ROOT_DIR = Path(__file__).resolve().parent
|
ROOT_DIR = Path(__file__).resolve().parent
|
||||||
DEFS_DIR = ROOT_DIR / "resources" / "defs"
|
DEFS_DIR = ROOT_DIR / "resources" / "defs"
|
||||||
|
|
@ -145,6 +146,7 @@ class AppState:
|
||||||
self.dragging_item: tuple[str, str] | None = None # (item_id, def_id) picked up off the inventory grid
|
self.dragging_item: tuple[str, str] | None = None # (item_id, def_id) picked up off the inventory grid
|
||||||
self.ability_bar = AbilityBar()
|
self.ability_bar = AbilityBar()
|
||||||
self.dev_spawn_menu = DevSpawnMenu(registry)
|
self.dev_spawn_menu = DevSpawnMenu(registry)
|
||||||
|
self.dev_turret_menu = DevTurretMenu(registry)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
|
@ -408,6 +410,23 @@ def main() -> None:
|
||||||
state.npc_spawn_flow = CharacterCreationFlow(CharacterCreator(registry, selectable_species=NPC_SELECTABLE_SPECIES))
|
state.npc_spawn_flow = CharacterCreationFlow(CharacterCreator(registry, selectable_species=NPC_SELECTABLE_SPECIES))
|
||||||
state.mode = Mode.NPC_SPAWN
|
state.mode = Mode.NPC_SPAWN
|
||||||
return
|
return
|
||||||
|
if dev_config.enabled and action == "dev_cycle_turret":
|
||||||
|
state.dev_turret_menu.cycle(1)
|
||||||
|
return
|
||||||
|
if dev_config.enabled and action == "dev_place_turret":
|
||||||
|
player = state.world.player if state.world is not None else None
|
||||||
|
entity_def_id = state.dev_turret_menu.selected_entity_def_id()
|
||||||
|
if isinstance(player, Character) and player.position is not None and entity_def_id is not None and state.world is not None:
|
||||||
|
dx, dy, dz = current_aim_direction(player)
|
||||||
|
position = EntityPosition(
|
||||||
|
player.position.map_id, player.position.x + dx, player.position.y + dy, player.position.z + dz
|
||||||
|
)
|
||||||
|
# hostile to the placing player by default - a dev/debug placement is meant to
|
||||||
|
# be immediately testable (see it track and fire), not require setting up
|
||||||
|
# faction tags first; see resources/logic/turrets.py::is_hostile.
|
||||||
|
turret = spawn_turret(registry, entity_def_id, position, hostile_entity_ids=(player.entity_id,))
|
||||||
|
state.world.add_entity(turret)
|
||||||
|
return
|
||||||
input_state.handle_event(event)
|
input_state.handle_event(event)
|
||||||
|
|
||||||
def handle_character_card_pointer_event(event_type: str, event: dict) -> bool:
|
def handle_character_card_pointer_event(event_type: str, event: dict) -> bool:
|
||||||
|
|
@ -632,6 +651,7 @@ def main() -> None:
|
||||||
if dev_config.enabled:
|
if dev_config.enabled:
|
||||||
draw_text(ui_buckets, "DEV MODE", viewport_w - 130, 10)
|
draw_text(ui_buckets, "DEV MODE", viewport_w - 130, 10)
|
||||||
draw_text(ui_buckets, f"spawn: {state.dev_spawn_menu.selected_item_id()}", viewport_w - 260, 30)
|
draw_text(ui_buckets, f"spawn: {state.dev_spawn_menu.selected_item_id()}", viewport_w - 260, 30)
|
||||||
|
draw_text(ui_buckets, f"turret: {state.dev_turret_menu.selected_entity_def_id()}", viewport_w - 260, 50)
|
||||||
|
|
||||||
renderer.render_frame(context, world, camera, ui_buckets)
|
renderer.render_frame(context, world, camera, ui_buckets)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import pytest
|
||||||
from engine.character import Character
|
from engine.character import Character
|
||||||
from engine.defs import DefRegistry
|
from engine.defs import DefRegistry
|
||||||
from engine.dev_config import DevConfig
|
from engine.dev_config import DevConfig
|
||||||
from engine.dev_tools import DevSpawnMenu
|
from engine.dev_tools import DevSpawnMenu, DevTurretMenu
|
||||||
from engine.inventory import Inventory
|
from engine.inventory import Inventory
|
||||||
|
|
||||||
DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs"
|
DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs"
|
||||||
|
|
@ -66,6 +66,34 @@ def test_spawn_into_fails_when_inventory_is_full(registry):
|
||||||
assert menu.spawn_into(character) is False
|
assert menu.spawn_into(character) is False
|
||||||
|
|
||||||
|
|
||||||
|
# --- DevTurretMenu --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_turret_menu_defaults_to_every_turret_entity_def_sorted(registry):
|
||||||
|
menu = DevTurretMenu(registry)
|
||||||
|
assert menu.entity_def_ids == ["turret_autocannon", "turret_pistol"]
|
||||||
|
assert menu.selected_entity_def_id() == "turret_autocannon"
|
||||||
|
|
||||||
|
|
||||||
|
def test_turret_menu_excludes_non_turret_entity_defs(registry):
|
||||||
|
menu = DevTurretMenu(registry)
|
||||||
|
assert "player" not in menu.entity_def_ids
|
||||||
|
assert "npc_reptilian" not in menu.entity_def_ids
|
||||||
|
assert "companion_dog" not in menu.entity_def_ids
|
||||||
|
|
||||||
|
|
||||||
|
def test_turret_menu_cycle_wraps_around(registry):
|
||||||
|
menu = DevTurretMenu(registry)
|
||||||
|
menu.cycle(-1)
|
||||||
|
assert menu.selected_entity_def_id() == menu.entity_def_ids[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_turret_menu_selected_entity_def_id_is_none_when_empty(registry):
|
||||||
|
menu = DevTurretMenu(registry)
|
||||||
|
menu.entity_def_ids = []
|
||||||
|
assert menu.selected_entity_def_id() is None
|
||||||
|
|
||||||
|
|
||||||
# --- DevConfig ------------------------------------------------------------------------------------
|
# --- DevConfig ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue