Files
Cinderwild/scripts/building/processor.gd

137 lines
4.7 KiB
GDScript

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},
],
}
## 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))