from __future__ import annotations from engine.character import Character from engine.defs import DefRegistry from engine.inventory import Inventory from engine.item import ItemDef, ItemInstance from engine.tile import TILE_LAYERS, Tile, TileLayerInstance from engine.world import World def present_layers(tile: Tile) -> list[str]: """Which of the tile's 4 layers are currently occupied, in stack order - the candidate list for a 'pick a layer' popup when a tool targets a tile with more than one. """ return [layer for layer in TILE_LAYERS if tile.get_layer(layer) is not None] def damage_tile_layer(tile: Tile, layer: str, amount: float) -> bool: """Reduces a layer's integrity; clears it once it hits zero. Returns whether it was destroyed.""" instance = tile.get_layer(layer) if instance is None: return False instance.integrity = max(0.0, instance.integrity - amount) if instance.integrity <= 0.0: tile.set_layer(layer, None) return True return False def destroy_tile_layer(tile: Tile, layer: str) -> bool: """The multi-deconstructor's left-click action: instantly clears a layer, no item gained.""" if tile.get_layer(layer) is None: return False tile.set_layer(layer, None) return True def deconstruct_tile_layer(tile: Tile, layer: str, registry: DefRegistry) -> ItemInstance | None: """The multi-deconstructor's right-click action: clears a layer and yields its item, if any.""" instance = tile.get_layer(layer) if instance is None: return None layer_def = registry.get_tile_layer_def(layer, instance.def_id) tile.set_layer(layer, None) if layer_def is None or layer_def.deconstruct_item_id is None: return None return ItemInstance(item_id=f"deconstructed-{layer}-{instance.def_id}", def_id=layer_def.deconstruct_item_id) def construct_tile_layer( tile: Tile, layer: str, item_def: ItemDef, inventory: Inventory, inventory_item_id: str, ) -> bool: """The constructor tool's action: consumes a matching material item to place a new layer. Fails as a no-op if the item doesn't build this layer, or the layer is already occupied (build on the bare slot first - deconstruct/destroy whatever's there, then construct). """ if item_def.builds_tile_layer != layer or item_def.builds_tile_layer_def_id is None: return False if tile.get_layer(layer) is not None: return False if inventory.remove(inventory_item_id, item_def) is None: return False tile.set_layer(layer, TileLayerInstance(item_def.builds_tile_layer_def_id)) return True def wielded_tool(character: Character, registry: DefRegistry) -> ItemDef | None: for held in character.held_items: item_def = registry.get_item_def(held.item_def_id) if item_def.tool_kind is not None: return item_def return None def interact_with_tile( character: Character, world: World, map_id: str, x: int, y: int, z: int, layer: str, button: str ) -> ItemInstance | bool | None: """Dispatches a tool interaction against one layer of a tile, based on what's wielded. A deconstructor: left-click destroys the layer outright, right-click deconstructs it and (if there's room) drops the yielded item straight into the character's inventory. A constructor: consumes the first matching material found in the character's inventory to build that layer. Returns whatever the underlying action returned, or None if nothing wieldable/applicable was found. """ tool = wielded_tool(character, registry := world.registry) if tool is None: return None tile = world.maps[map_id].get_tile(x, y, z) if tool.tool_kind == "deconstructor": if button == "left": return destroy_tile_layer(tile, layer) if button == "right": item = deconstruct_tile_layer(tile, layer, registry) if item is not None and character.inventory is not None: item_def = registry.get_item_def(item.def_id) slot = character.inventory.find_first_fit(item_def) if slot is not None: character.inventory.place(item, item_def, *slot) return item return None if tool.tool_kind == "constructor": if character.inventory is None: return False for placed in character.inventory.items(): material_def = registry.get_item_def(placed.item.def_id) if material_def.builds_tile_layer == layer: return construct_tile_layer(tile, layer, material_def, character.inventory, placed.item.item_id) return False return None