P4 hazards: food spoilage with rot speed setting, smoking rack and salt, food poisoning, harsher blizzards, hard winters with lake ice, lightning on trees, snow on roofs (protocol 11)
This commit is contained in:
@@ -33,19 +33,103 @@ static func _stack_max(id: String) -> int:
|
||||
|
||||
## A stack can carry "dur": uses left on a tool or weapon that wears out
|
||||
## (ItemDB "durability"). No "dur" means as new.
|
||||
## Food that spoils (ItemDB "keeps", in days) carries "rot": the world time
|
||||
## (seconds since day 1, 00:00) at which it goes rotten. It's stamped when
|
||||
## the food first goes into an inventory; two stacks that merge share the
|
||||
## average. world.gd turns rotten stacks into rotten food (spoil()).
|
||||
static func _norm(stack):
|
||||
if stack == null or int(stack.count) <= 0:
|
||||
return null
|
||||
var out := {"id": String(stack.id), "count": int(stack.count)}
|
||||
if stack.has("dur"):
|
||||
out.dur = int(stack.dur)
|
||||
if stack.has("rot"):
|
||||
out.rot = float(stack.rot)
|
||||
return out
|
||||
|
||||
## Adds a whole stack, keeping what's special about it (a worn tool's "dur").
|
||||
## Returns how many didn't fit.
|
||||
## What's special about a stack, for when it's dropped on the ground (and
|
||||
## kept when it's picked up again): a tool's wear, food's freshness.
|
||||
static func extra_of(stack: Dictionary) -> Dictionary:
|
||||
var out := {}
|
||||
if stack.has("dur"):
|
||||
out.dur = int(stack.dur)
|
||||
if stack.has("rot"):
|
||||
out.rot = float(stack.rot)
|
||||
return out
|
||||
|
||||
# --- spoilage (P4, docs/PROGRESSION_DESIGN.md §5) ---------------------------------
|
||||
|
||||
## The world clock (seconds since day 1, 00:00) and how long a day is, kept
|
||||
## up to date by world.gd. `rot_speed`: 1 normal, 0.5 slow, 0 off (the
|
||||
## world's or server's "rot" setting).
|
||||
static var clock := 0.0
|
||||
static var day_seconds := 1200.0
|
||||
static var rot_speed := 1.0
|
||||
const ROTTEN := "rotten_food"
|
||||
|
||||
static func _db() -> Node:
|
||||
return (Engine.get_main_loop() as SceneTree).root.get_node_or_null("ItemDB")
|
||||
|
||||
## Days `id` keeps before it rots (0: it doesn't).
|
||||
static func keeps_days(id: String) -> float:
|
||||
var db := _db()
|
||||
return float(db.get_item(id).get("keeps", 0.0)) if db != null else 0.0
|
||||
|
||||
## How long `id` keeps, in world seconds, at the current rot speed (0: forever).
|
||||
static func keeps_for(id: String) -> float:
|
||||
var d := keeps_days(id)
|
||||
if d <= 0.0 or rot_speed <= 0.0:
|
||||
return 0.0
|
||||
return d * day_seconds / rot_speed
|
||||
|
||||
## The "rot" stamp for `id` made right now (-1: it doesn't spoil).
|
||||
static func fresh_rot(id: String) -> float:
|
||||
var k := keeps_for(id)
|
||||
# (Whole seconds, so a stamp comes back from a JSON save exactly as it went in.)
|
||||
return roundf(clock + k) if k > 0.0 else -1.0
|
||||
|
||||
## 0..1: how fresh a stack is (1 = just made or doesn't spoil).
|
||||
static func freshness(stack) -> float:
|
||||
if stack == null or not stack.has("rot"):
|
||||
return 1.0
|
||||
var k := keeps_for(String(stack.id))
|
||||
if k <= 0.0:
|
||||
return 1.0
|
||||
return clampf((float(stack.rot) - clock) / k, 0.0, 1.0)
|
||||
|
||||
## In-game hours until a stack rots (INF: never).
|
||||
static func hours_left(stack) -> float:
|
||||
if stack == null or not stack.has("rot") or rot_speed <= 0.0:
|
||||
return INF
|
||||
return maxf(float(stack.rot) - clock, 0.0) / day_seconds * 24.0
|
||||
|
||||
## Stamps food that has no "rot" yet, and turns food past its time into
|
||||
## rotten food. Returns how many items went rotten.
|
||||
func spoil() -> int:
|
||||
if rot_speed <= 0.0:
|
||||
return 0
|
||||
var rotted := 0
|
||||
var touched := false
|
||||
for i in range(slots.size()):
|
||||
var s = slots[i]
|
||||
if s == null or keeps_days(String(s.id)) <= 0.0:
|
||||
continue
|
||||
if not s.has("rot"):
|
||||
s.rot = fresh_rot(String(s.id))
|
||||
touched = true
|
||||
elif clock >= float(s.rot):
|
||||
rotted += int(s.count)
|
||||
slots[i] = {"id": ROTTEN, "count": int(s.count)}
|
||||
touched = true
|
||||
if touched:
|
||||
changed.emit()
|
||||
return rotted
|
||||
|
||||
## Adds a whole stack, keeping what's special about it (a worn tool's "dur",
|
||||
## food's "rot"). Returns how many didn't fit.
|
||||
func add_stack(stack: Dictionary) -> int:
|
||||
if not stack.has("dur"):
|
||||
return add(String(stack.id), int(stack.count))
|
||||
return add(String(stack.id), int(stack.count), float(stack.get("rot", -1.0)))
|
||||
for i in range(slots.size()):
|
||||
if slots[i] == null:
|
||||
slots[i] = _norm(stack)
|
||||
@@ -72,14 +156,18 @@ func space_for(id: String) -> int:
|
||||
return room
|
||||
|
||||
## Adds up to `n` of `id`, topping up existing stacks first. Returns the
|
||||
## leftover that didn't fit (0 = everything fitted).
|
||||
func add(id: String, n: int) -> int:
|
||||
## leftover that didn't fit (0 = everything fitted). Food gets a fresh "rot"
|
||||
## stamp, or `rot` if it's coming from somewhere with one of its own.
|
||||
func add(id: String, n: int, rot: float = -1.0) -> int:
|
||||
var cap := _stack_max(id)
|
||||
var r := rot if rot >= 0.0 else fresh_rot(id)
|
||||
for s in slots:
|
||||
if n <= 0:
|
||||
break
|
||||
if s != null and s.id == id and s.count < cap:
|
||||
var take := mini(cap - s.count, n)
|
||||
if r >= 0.0:
|
||||
s.rot = _mix_rot(s, r, take)
|
||||
s.count += take
|
||||
n -= take
|
||||
for i in range(slots.size()):
|
||||
@@ -88,10 +176,20 @@ func add(id: String, n: int) -> int:
|
||||
if slots[i] == null:
|
||||
var take := mini(cap, n)
|
||||
slots[i] = {"id": id, "count": take}
|
||||
if r >= 0.0:
|
||||
slots[i].rot = r
|
||||
n -= take
|
||||
changed.emit()
|
||||
return n
|
||||
|
||||
## The "rot" of stack `s` once `n` more with `rot` are added to it: the
|
||||
## average, so mixing fresh and old food gives you food in between.
|
||||
static func _mix_rot(s: Dictionary, rot: float, n: int) -> float:
|
||||
if rot < 0.0:
|
||||
return float(s.get("rot", -1.0))
|
||||
var have := float(s.get("rot", rot))
|
||||
return roundf((have * int(s.count) + rot * n) / float(int(s.count) + n))
|
||||
|
||||
## Removes `n` of `id` (from the last stacks first, so the hotbar keeps its
|
||||
## items longest). Returns false and changes nothing if there aren't enough.
|
||||
func remove(id: String, n: int) -> bool:
|
||||
@@ -135,9 +233,16 @@ func quick_move(i: int, other: Inventory) -> void:
|
||||
if s == null:
|
||||
return
|
||||
var left := other.add_stack(s)
|
||||
slots[i] = null if left <= 0 else _norm({"id": s.id, "count": left, "dur": s.dur} if s.has("dur") else {"id": s.id, "count": left})
|
||||
slots[i] = null if left <= 0 else _part(s, left)
|
||||
changed.emit()
|
||||
|
||||
## `n` of stack `s`, with its wear or freshness.
|
||||
static func _part(s: Dictionary, n: int):
|
||||
var out := extra_of(s)
|
||||
out.id = s.id
|
||||
out.count = n
|
||||
return _norm(out)
|
||||
|
||||
## Clicking slot `i` while holding `cursor` (a stack or null). Returns the new
|
||||
## cursor stack. Left click: pick up / put down / merge / swap. Right click:
|
||||
## pick up half, or put down one.
|
||||
@@ -150,23 +255,25 @@ func click(i: int, cursor, right: bool):
|
||||
if right and s.count > 1:
|
||||
var half := int(ceil(s.count / 2.0))
|
||||
s.count -= half
|
||||
result = {"id": s.id, "count": half}
|
||||
result = _part(s, half)
|
||||
else:
|
||||
result = s
|
||||
slots[i] = null
|
||||
elif s == null:
|
||||
if right and cursor.count > 1:
|
||||
slots[i] = {"id": cursor.id, "count": 1}
|
||||
result = _norm({"id": cursor.id, "count": cursor.count - 1})
|
||||
slots[i] = _part(cursor, 1)
|
||||
result = _part(cursor, cursor.count - 1)
|
||||
else:
|
||||
slots[i] = cursor
|
||||
result = null
|
||||
elif s.id == cursor.id:
|
||||
elif s.id == cursor.id and not s.has("dur") and not cursor.has("dur"):
|
||||
var cap := _stack_max(s.id)
|
||||
var move: int = 1 if right else cursor.count
|
||||
move = mini(move, cap - s.count)
|
||||
if move > 0 and cursor.has("rot"):
|
||||
s.rot = _mix_rot(s, float(cursor.rot), move)
|
||||
s.count += move
|
||||
result = _norm({"id": cursor.id, "count": cursor.count - move})
|
||||
result = _part(cursor, cursor.count - move)
|
||||
else:
|
||||
slots[i] = cursor
|
||||
result = s
|
||||
|
||||
Reference in New Issue
Block a user