extends "res://scripts/building/build_piece.gd" ## Door: a wall-sized frame (snaps like a wall) with a hinged panel. E opens ## or closes it. The panel is an AnimatableBody3D so it pushes the player ## out of the way rather than trapping them. ## ## An alpha wolf can break a closed wooden door (bash()): after DOOR_HP worth ## of blows the panel gives way and hangs open, and the door shows as broken ## until you repair it (E, two wood). An iron-banded door (items.json ## place.sturdy) can't be broken. The frame stays either way, so whatever it ## holds up keeps standing. const OPEN_ANGLE := 1.75 ## radians: swings toward -Z, away from where you stood to place it const DOOR_HP := 100.0 const REPAIR_COST := {"wood": 2} var open := false var broken := false var door_hp := DOOR_HP var _hinge: Node3D var _panel: AnimatableBody3D var _panel_shape: CollisionShape3D var _tween: Tween func _visual_mesh() -> Mesh: return ItemDB.mesh_for(String(def.get("mesh", item_id)) + "_frame") # (wood_door_frame, stone_doorway_frame...) func _build() -> void: _hinge = Node3D.new() _hinge.name = "Hinge" _hinge.position = Vector3(-0.54, 0.0, 0.0) add_child(_hinge) _panel = AnimatableBody3D.new() _panel.name = "Panel" # Follow the hinge directly (sync_to_physics would only track the body's # own motion, not its parent rotating). _panel.sync_to_physics = false _panel.collision_layer = 1 | BUILD_LAYER _panel.set_meta("owner_piece", self) _hinge.add_child(_panel) var mi := MeshInstance3D.new() mi.mesh = ItemDB.mesh_for(String(def.place.get("panel", "wood_door_panel"))) mi.material_override = ItemDB.item_material() _panel.add_child(mi) _panel_shape = CollisionShape3D.new() var box := BoxShape3D.new() box.size = Vector3(1.06, 1.86, 0.08) _panel_shape.shape = box _panel_shape.position = Vector3(0.54, 0.95, 0.0) _panel.add_child(_panel_shape) ## Iron-banded: nothing breaks it. func sturdy() -> bool: return bool(def.place.get("sturdy", false)) ## Shut and in one piece (something trying to get in has to get past it). func blocks() -> bool: return not open and not broken func interact_prompt(_player: Node) -> String: if broken: return "Broken door [E] Repair (2 wood)" return "Door [E] %s" % ("Close" if open else "Open") func interact(player: Node) -> void: if broken: Actions.repair_door(player, self) else: Actions.toggle_door(player, self) func set_open(on: bool, animate: bool = true) -> void: if broken: on = true # (it hangs open off one hinge) open = on var target := OPEN_ANGLE if on else 0.0 if _tween != null: _tween.kill() if animate and is_inside_tree(): _tween = create_tween() _tween.tween_property(_hinge, "rotation:y", target, 0.45).set_trans(Tween.TRANS_SINE) else: _hinge.rotation.y = target ## A blow from something trying to get in. Returns "hit", "held" (iron ## bands), "broke" (this blow broke it) or "" (open or already broken). func bash(damage: float) -> String: if not blocks(): return "" if sturdy(): return "held" door_hp -= damage if door_hp > 0.0: return "hit" set_broken(true) return "broke" ## Broken: the panel hangs open, askew, and stops anything. Repaired: shut ## and whole again. func set_broken(on: bool) -> void: broken = on if on: door_hp = 0.0 set_open(true, false) _hinge.rotation = Vector3(0.0, OPEN_ANGLE + 0.35, 0.18) _panel_shape.disabled = true else: door_hp = DOOR_HP _hinge.rotation = Vector3.ZERO _panel_shape.disabled = false set_open(false, false) func save_extra() -> Dictionary: return {"open": open, "broken": broken} func load_extra(d: Dictionary) -> void: set_broken(bool(d.get("broken", false))) if not broken: set_open(bool(d.get("open", false)), false) ## A server telling us this door's state: swing it if it only opened or ## closed, snap it if it broke or was mended. func net_state(d: Dictionary) -> void: if bool(d.get("broken", false)) != broken: load_extra(d) elif not broken: set_open(bool(d.get("open", false)))