The Cove's shore life: crabs up the beach from dusk till dawn (keep to the sand, pinch if you get close, crab meat to cook), gulls after food on the sand and fish on a smoking rack; online too
This commit is contained in:
@@ -9,10 +9,16 @@ extends Node3D
|
||||
## fox.gd). Food in a chest is never touched (that's what chests are for);
|
||||
## the bear (bear_range.gd) is another matter.
|
||||
##
|
||||
## In a land with gulls (the Cove, 0.6.0: its worldlet's "wildlife"), food
|
||||
## left on the sand draws a gull by day instead of a crow (gull.gd), and so
|
||||
## does fish on a smoking rack that nobody's watching: a gull snatches one
|
||||
## and flies off with it.
|
||||
##
|
||||
## On a server this runs for everyone (thieves are wildlife: creature_net.gd).
|
||||
|
||||
const CrowScript = preload("res://scripts/creatures/crow.gd")
|
||||
const FoxScript = preload("res://scripts/creatures/fox.gd")
|
||||
const GullScript = preload("res://scripts/creatures/gull.gd")
|
||||
|
||||
const CHECK_EVERY := 5.0
|
||||
const UNWATCHED := 10.0
|
||||
@@ -25,6 +31,7 @@ signal stolen(thief: Node, id: String, count: int)
|
||||
|
||||
var world: Node3D
|
||||
var enabled := true
|
||||
var gulls := false ## this land has gulls (the Cove)
|
||||
var _acc := 0.0
|
||||
var _unwatched: Dictionary = {} ## pickup instance id -> seconds left alone
|
||||
var _rng := RandomNumberGenerator.new()
|
||||
@@ -32,6 +39,7 @@ var _rng := RandomNumberGenerator.new()
|
||||
func setup(w: Node3D) -> void:
|
||||
world = w
|
||||
_rng.seed = w.terrain.noise_seed + 997
|
||||
gulls = w.has_wildlife("gulls")
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if enabled:
|
||||
@@ -85,14 +93,28 @@ func step(dt: float) -> void:
|
||||
_unwatched[k] = float(_unwatched.get(k, 0.0)) + since
|
||||
if float(_unwatched[k]) >= NOTICE_AFTER and not _targeted(d) and alive().size() < MAX_THIEVES:
|
||||
send(d)
|
||||
if gulls and not night():
|
||||
for r in racks():
|
||||
if not gull_still_there(r):
|
||||
continue
|
||||
var k: int = r.get_instance_id()
|
||||
seen[k] = true
|
||||
if watched(r.global_position, UNWATCHED):
|
||||
_unwatched.erase(k)
|
||||
continue
|
||||
_unwatched[k] = float(_unwatched.get(k, 0.0)) + since
|
||||
if float(_unwatched[k]) >= NOTICE_AFTER and not _targeted(r) and alive().size() < MAX_THIEVES:
|
||||
send(r)
|
||||
for k in _unwatched.keys():
|
||||
if not seen.has(k):
|
||||
_unwatched.erase(k)
|
||||
|
||||
## Sends a thief after `food`: a fox at dusk and night, otherwise a crow.
|
||||
## Sends a thief after `food`: a fox at dusk and night, otherwise a crow (a
|
||||
## gull on the sand, or at a smoking rack, where there are gulls).
|
||||
func send(food: Node3D) -> Node3D:
|
||||
var fox := night()
|
||||
var fp: Vector3 = food.global_position
|
||||
var gull := not fox and gulls and (food.is_in_group("processor") or on_sand(food.global_position))
|
||||
var fp: Vector3 = spot(food)
|
||||
var from := Vector3.INF
|
||||
for _i in range(12):
|
||||
var a := _rng.randf() * TAU
|
||||
@@ -104,8 +126,8 @@ func send(food: Node3D) -> Node3D:
|
||||
break
|
||||
if from == Vector3.INF:
|
||||
return null
|
||||
var t: AnimatableBody3D = (FoxScript if fox else CrowScript).new()
|
||||
t.setup("fox" if fox else "crow", world, from, _rng.randi())
|
||||
var t: AnimatableBody3D = (FoxScript if fox else (GullScript if gull else CrowScript)).new()
|
||||
t.setup("fox" if fox else ("gull" if gull else "crow"), world, from, _rng.randi())
|
||||
t.thieves = self
|
||||
t.food = food
|
||||
t.state = "sneak" if fox else "fly"
|
||||
@@ -148,3 +170,63 @@ func grab(fox: Node, food: Node3D) -> Dictionary:
|
||||
## A thief left with what it took (or empty-handed).
|
||||
func gone(t: Node) -> void:
|
||||
t.queue_free()
|
||||
|
||||
# --- gulls (the Cove) ----------------------------------------------------------------
|
||||
|
||||
func on_sand(pos: Vector3) -> bool:
|
||||
return world.terrain.on_sand(pos.x, pos.z)
|
||||
|
||||
static func is_fish(id: String) -> bool:
|
||||
return Dictionary(ItemDB.get_item(id).get("food", {})).get("diet", {}).has("fish")
|
||||
|
||||
## This land's smoking racks.
|
||||
func racks() -> Array:
|
||||
return get_tree().get_nodes_in_group("processor").filter(func(r): return is_instance_valid(r) and not r.is_queued_for_deletion() \
|
||||
and String(r.item_id) == "smoking_rack" and world.is_ancestor_of(r))
|
||||
|
||||
## Fish on a rack: the first id there is, or "".
|
||||
func _rack_fish(r: Node3D) -> String:
|
||||
for st in r.inventory.slots:
|
||||
if st != null and is_fish(String(st.id)):
|
||||
return String(st.id)
|
||||
return ""
|
||||
|
||||
## Whether a gull's target is still worth it: food on the ground, or a rack
|
||||
## with fish on it.
|
||||
func gull_still_there(t: Node3D) -> bool:
|
||||
if t == null or not is_instance_valid(t) or t.is_queued_for_deletion():
|
||||
return false
|
||||
if t.is_in_group("processor"):
|
||||
return not _rack_fish(t).is_empty()
|
||||
return still_there(t)
|
||||
|
||||
## Where a thief lands for `t`: on food, or on the ground beside a rack.
|
||||
func spot(t: Node3D) -> Vector3:
|
||||
if t.is_in_group("processor"):
|
||||
var p: Vector3 = t.global_position + t.global_basis.z * 0.8
|
||||
return Vector3(p.x, world.terrain.sample_height(p.x, p.z), p.z)
|
||||
return t.global_position
|
||||
|
||||
func gull_landed(_gull: Node, t: Node3D) -> void:
|
||||
if t.is_in_group("processor"):
|
||||
_tell(t.global_position, "A gull's at the fish on your smoking rack!")
|
||||
else:
|
||||
_tell(t.global_position, "A gull's landed by your %s!" % ItemDB.item_name(t.item_id).to_lower())
|
||||
|
||||
## A gull takes one and flies off with it. Returns what it's carrying.
|
||||
func snatch(gull: Node, t: Node3D) -> Dictionary:
|
||||
if not gull_still_there(t):
|
||||
return {}
|
||||
var c := {}
|
||||
if t.is_in_group("processor"):
|
||||
var id := _rack_fish(t)
|
||||
t.inventory.remove(id, 1)
|
||||
c = {"id": id, "count": 1, "extra": {}}
|
||||
else:
|
||||
c = {"id": String(t.item_id), "count": 1, "extra": t.extra.duplicate()}
|
||||
t.amount = int(t.amount) - 1
|
||||
if int(t.amount) <= 0:
|
||||
world.take_dropped(t)
|
||||
_tell(t.global_position, "A gull has snatched your %s!" % ItemDB.item_name(String(c.id)).to_lower())
|
||||
stolen.emit(gull, String(c.id), 1)
|
||||
return c
|
||||
|
||||
Reference in New Issue
Block a user