extends "res://scripts/building/piece.gd" ## A charcoal kiln or a smelter: E opens it like a chest. Put what it works ## on inside and it turns it into something else over time, on its own, ## while you're away (docs/PROGRESSION_DESIGN.md ยง2). It glows while it works. ## ## - Kiln: 2 wood -> 1 charcoal, 20 s. ## - Smelter: 1 iron ore + 1 charcoal -> 1 iron bar, 30 s; or 3 iron ## nuggets + 1 charcoal -> 1 iron bar. ## ## The output goes into the same slots, for you to take out. On a server it ## runs there (step() is skipped on players' copies) and the contents reach ## everyone like a chest's. Progress is saved. const SLOTS := 6 const RECIPES := { "charcoal_kiln": [{"inputs": {"wood": 2}, "output": "charcoal", "seconds": 20.0}], "smelter": [ {"inputs": {"iron_ore": 1, "charcoal": 1}, "output": "iron_bar", "seconds": 30.0}, {"inputs": {"iron_nugget": 3, "charcoal": 1}, "output": "iron_bar", "seconds": 30.0}, ], # P4: smoked food keeps for days (raw rots in two). "smoking_rack": [ {"inputs": {"raw_meat": 2, "wood": 1}, "output": "smoked_meat", "count": 2, "seconds": 60.0}, {"inputs": {"perch": 2, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0}, {"inputs": {"trout": 1, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0}, {"inputs": {"mackerel": 2, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0}, {"inputs": {"cod": 1, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0}, ], } ## What the prompt says it makes, and what it needs when idle. const MAKES := {"charcoal_kiln": "charcoal", "smelter": "an iron bar", "smoking_rack": "smoked food"} const NEEDS := {"charcoal_kiln": "put in wood", "smelter": "put in iron ore (or 3 nuggets) and charcoal", "smoking_rack": "put in raw meat or fish, and wood for the fire"} var inventory := Inventory.new(SLOTS) var progress := 0.0 ## seconds into the current batch var _light: OmniLight3D var _glow: MeshInstance3D var _t := 0.0 func _build() -> void: add_to_group("processor") inventory.changed.connect(_on_changed) if DisplayServer.get_name() == "headless": return _light = OmniLight3D.new() _light.light_color = Color(1.0, 0.55, 0.25) _light.omni_range = 5.0 _light.light_energy = 0.0 _light.position = Vector3(0, 0.6, 0.5) add_child(_light) # The fire showing through the mouth. _glow = MeshInstance3D.new() var q := QuadMesh.new() q.size = Vector2(0.36, 0.26) _glow.mesh = q var m := StandardMaterial3D.new() m.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED m.albedo_color = Color(1.0, 0.45, 0.12) m.emission_enabled = true m.emission = Color(1.0, 0.45, 0.12) m.emission_energy_multiplier = 3.0 _glow.material_override = m _glow.position = {"smelter": Vector3(0, 0.3, 0.58), "smoking_rack": Vector3(0, 0.12, 0.0)}.get(item_id, Vector3(0, 0.25, 0.72)) if item_id == "smoking_rack": _glow.rotation.x = -PI * 0.5 # (embers on the ground under the rack) _glow.visible = false add_child(_glow) ## The recipe it would work on now, or {} (nothing to do, or no room for ## what it makes). func current() -> Dictionary: for r in RECIPES.get(item_id, []): if inventory.has_all(r.inputs) and inventory.space_for(r.output) > 0: return r return {} func working() -> bool: return not current().is_empty() func _process(delta: float) -> void: if WorldNet.is_client(): _show(working()) # (the server does the work; we just show it) return step(delta) ## Works for `secs` (several batches if there's time and stuff for them). func step(secs: float) -> void: var r := current() if r.is_empty(): progress = 0.0 _show(false) return progress += secs while not r.is_empty() and progress >= float(r.seconds): progress -= float(r.seconds) for id in r.inputs: inventory.remove(id, int(r.inputs[id])) inventory.add(r.output, int(r.get("count", 1))) r = current() if r.is_empty(): progress = 0.0 _show(not r.is_empty()) func _show(on: bool) -> void: if _light == null: return _t += get_process_delta_time() _glow.visible = on _light.light_energy = (1.2 + 0.25 * sin(_t * 9.0) * sin(_t * 3.1)) if on else 0.0 func _on_changed() -> void: if current().is_empty(): progress = 0.0 func dismantle_block_reason() -> String: for s in inventory.slots: if s != null: return "Empty it first" return "" func interact_prompt(_player: Node) -> String: var r := current() var what: String = MAKES.get(item_id, "something") if r.is_empty(): return "%s (idle: %s) [E] Open" % [display_name(), NEEDS.get(item_id, "put something in")] if WorldNet.is_client(): return "%s (making %s) [E] Open" % [display_name(), what] return "%s (making %s, %d s) [E] Open" % [display_name(), what, ceili(float(r.seconds) - progress)] func interact(player: Node) -> void: player.open_inventory(self) func save_extra() -> Dictionary: return {"progress": progress} func load_extra(d: Dictionary) -> void: progress = float(d.get("progress", 0.0))