Mi2dRPGamEng/resources/logic/construction.py

119 lines
4.6 KiB
Python

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