184 lines
6.0 KiB
GDScript
184 lines
6.0 KiB
GDScript
extends "res://scripts/building/piece.gd"
|
|
## Campfire: light, warmth and cooking, while it has fuel.
|
|
##
|
|
## Starts with a little kindling. E adds wood (or sticks) from your inventory;
|
|
## it burns down in play time and goes out at zero. While lit it is a
|
|
## "campfire" station (cooking) and a heat source (warmth within ~7 m).
|
|
|
|
const MAX_FUEL := 900.0 ## 15 min
|
|
const START_FUEL := 120.0
|
|
const HEAT_RADIUS := 7.0
|
|
|
|
var fuel := START_FUEL
|
|
var _light: OmniLight3D
|
|
var _fx: Array[CPUParticles3D] = []
|
|
var _t := 0.0
|
|
var _noise := FastNoiseLite.new()
|
|
var _lit := true
|
|
var _roofed_cache := -1.0 ## -1 unknown, 0 open sky, 1 under a roof
|
|
var _roof_timer := 0.0
|
|
|
|
func _build() -> void:
|
|
add_to_group("station:campfire")
|
|
add_to_group("heat")
|
|
_noise.frequency = 2.5
|
|
_light = OmniLight3D.new()
|
|
_light.light_color = Color(1.0, 0.62, 0.3)
|
|
_light.light_energy = 2.2
|
|
_light.omni_range = 11.0
|
|
_light.omni_attenuation = 1.4
|
|
_light.shadow_enabled = true
|
|
_light.position = Vector3(0, 0.7, 0)
|
|
add_child(_light)
|
|
|
|
_fx.append(_particles("Flames", 26, 0.55, Vector3(0, 0.12, 0), 0.5, 1.0, 0.5, 0.9, _gradient([
|
|
[0.0, Color(1.0, 0.8, 0.35, 0.0)], [0.15, Color(1.0, 0.62, 0.2, 0.75)],
|
|
[0.6, Color(0.95, 0.3, 0.06, 0.45)], [1.0, Color(0.5, 0.08, 0.02, 0.0)]]), true, 0.32, 0.12))
|
|
_fx.append(_particles("Embers", 10, 2.0, Vector3(0, 0.3, 0), 0.8, 2.0, 0.5, 1.0, _gradient([
|
|
[0.0, Color(1.0, 0.75, 0.35, 1.0)], [1.0, Color(1.0, 0.3, 0.05, 0.0)]]), true, 0.04, 0.15))
|
|
for p in _fx:
|
|
add_child(p)
|
|
# TODO: smoke. Alpha-blended unshaded particles rendered as an opaque dark
|
|
# ball whatever their ramp colour (additive flames are fine). Needs a small
|
|
# particle shader.
|
|
_set_lit(fuel > 0.0)
|
|
|
|
func display_name() -> String:
|
|
return "Campfire"
|
|
|
|
func is_lit() -> bool:
|
|
return _lit
|
|
|
|
func station_active() -> bool:
|
|
return _lit
|
|
|
|
## 0..1 warmth this fire gives at `pos`.
|
|
func heat_at(pos: Vector3) -> float:
|
|
if not _lit:
|
|
return 0.0
|
|
return 1.0 - smoothstep(1.5, HEAT_RADIUS, pos.distance_to(global_position))
|
|
|
|
func add_fuel(seconds: float) -> void:
|
|
fuel = minf(fuel + seconds, MAX_FUEL)
|
|
_set_lit(true)
|
|
|
|
func interact_prompt(player: Node) -> String:
|
|
var has_fuel: bool = player.inventory.count("wood") + player.inventory.count("stick") + player.inventory.count("bear_fat") > 0
|
|
if _lit:
|
|
var left := "%d:%02d" % [int(fuel) / 60, int(fuel) % 60]
|
|
var hiss := ", hissing in the rain" if burn_rate() > 1.2 else ""
|
|
return "Campfire (%s left%s)%s" % [left, hiss, " [E] Add fuel" if has_fuel else " (add wood or sticks)"]
|
|
return "Campfire (out)%s" % (" [E] Add fuel to light" if has_fuel else " (needs wood or sticks)")
|
|
|
|
func interact(player: Node) -> void:
|
|
Actions.add_fuel(player, self)
|
|
|
|
## How much faster fuel burns right now: rain on an uncovered fire makes it
|
|
## hiss and eat through its wood (up to 4x in a storm). A roof stops that.
|
|
func burn_rate() -> float:
|
|
var w: Node = get_parent().get_parent() if get_parent() != null else null
|
|
if w == null or w.get("weather") == null:
|
|
return 1.0
|
|
var rain: float = w.weather.rain_at(global_position)
|
|
if rain <= 0.05:
|
|
_roofed_cache = -1.0
|
|
return 1.0
|
|
if _roofed_cache < 0.0:
|
|
_roofed_cache = 1.0 if w.build.roofed_at(global_position) else 0.0
|
|
return 1.0 if _roofed_cache > 0.5 else 1.0 + 3.0 * rain
|
|
|
|
func burn(seconds: float) -> void:
|
|
if not _lit:
|
|
return
|
|
fuel -= seconds * burn_rate()
|
|
if fuel <= 0.0:
|
|
fuel = 0.0
|
|
_set_lit(false)
|
|
|
|
func _process(delta: float) -> void:
|
|
if not _lit:
|
|
return
|
|
_roof_timer -= delta
|
|
if _roof_timer <= 0.0:
|
|
_roof_timer = 2.0
|
|
_roofed_cache = -1.0 # re-check the roof now and then (you may build one)
|
|
burn(delta)
|
|
if not _lit:
|
|
return
|
|
_t += delta
|
|
var f := _noise.get_noise_1d(_t * 10.0)
|
|
# Burns lower in its last minute.
|
|
var strength := clampf(fuel / 60.0, 0.35, 1.0)
|
|
_light.light_energy = (2.2 + f * 0.7) * strength
|
|
_light.position = Vector3(f * 0.05, 0.7 + f * 0.05, 0)
|
|
|
|
func _set_lit(on: bool) -> void:
|
|
_lit = on
|
|
if _light != null:
|
|
_light.visible = on
|
|
for p in _fx:
|
|
p.emitting = on
|
|
|
|
func save_extra() -> Dictionary:
|
|
return {"fuel": fuel}
|
|
|
|
func load_extra(d: Dictionary) -> void:
|
|
fuel = float(d.get("fuel", fuel))
|
|
_set_lit(fuel > 0.0)
|
|
|
|
static func _gradient(stops: Array) -> Gradient:
|
|
var g := Gradient.new()
|
|
g.offsets = PackedFloat32Array(stops.map(func(s): return s[0]))
|
|
g.colors = PackedColorArray(stops.map(func(s): return s[1]))
|
|
return g
|
|
|
|
static func _soft_dot() -> GradientTexture2D:
|
|
var g := Gradient.new()
|
|
g.offsets = PackedFloat32Array([0.0, 1.0])
|
|
g.colors = PackedColorArray([Color(1, 1, 1, 1), Color(1, 1, 1, 0)])
|
|
var t := GradientTexture2D.new()
|
|
t.gradient = g
|
|
t.fill = GradientTexture2D.FILL_RADIAL
|
|
t.fill_from = Vector2(0.5, 0.5)
|
|
t.fill_to = Vector2(0.5, 0.0)
|
|
t.width = 64
|
|
t.height = 64
|
|
return t
|
|
|
|
static func _particles(label: String, amount: int, lifetime: float, pos: Vector3, vmin: float, vmax: float,
|
|
smin: float, smax: float, ramp: Gradient, additive: bool, quad_size: float, radius: float) -> CPUParticles3D:
|
|
var p := CPUParticles3D.new()
|
|
p.name = label
|
|
p.amount = amount
|
|
p.lifetime = lifetime
|
|
p.position = pos
|
|
p.direction = Vector3.UP
|
|
p.spread = 12.0
|
|
p.initial_velocity_min = vmin
|
|
p.initial_velocity_max = vmax
|
|
p.gravity = Vector3(0.15, 0.4, 0.05)
|
|
p.emission_shape = CPUParticles3D.EMISSION_SHAPE_SPHERE
|
|
p.emission_sphere_radius = radius
|
|
p.scale_amount_min = smin
|
|
p.scale_amount_max = smax
|
|
p.color_ramp = ramp
|
|
# Flames shrink as they rise.
|
|
var curve := Curve.new()
|
|
curve.add_point(Vector2(0.0, 1.0))
|
|
curve.add_point(Vector2(1.0, 0.35))
|
|
p.scale_amount_curve = curve
|
|
var quad := QuadMesh.new()
|
|
quad.size = Vector2.ONE * quad_size
|
|
var m := StandardMaterial3D.new()
|
|
m.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
m.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
|
|
m.vertex_color_use_as_albedo = true
|
|
m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
m.blend_mode = BaseMaterial3D.BLEND_MODE_ADD if additive else BaseMaterial3D.BLEND_MODE_MIX
|
|
m.albedo_texture = _soft_dot()
|
|
if additive:
|
|
m.albedo_color = Color(1.5, 1.5, 1.5) # mildly HDR so the glow picks it up
|
|
quad.material = m
|
|
p.mesh = quad
|
|
return p
|