139 lines
4.9 KiB
GDScript
139 lines
4.9 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},
|
|
{"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))
|