248 lines
10 KiB
GDScript
248 lines
10 KiB
GDScript
extends SceneTree
|
|
## Uses for every material (the clay jar, bedroll, wolf-pelt rug, fang
|
|
## necklace, bear cloak, pemmican, and rotten food as bait), dropping items
|
|
## (Q), and picking from any bag row with 1-8 (K / M change row).
|
|
## Run: godot --headless --path . --script res://tests/test_items_more.gd
|
|
|
|
var _fails := 0
|
|
var _finished := false
|
|
var A: Node
|
|
var S: Node
|
|
var DB: Node
|
|
var world: Node3D
|
|
var player: CharacterBody3D
|
|
var inv: Inventory
|
|
|
|
func check(cond: bool, what: String) -> void:
|
|
print((" ok " if cond else " FAIL ") + what)
|
|
if not cond:
|
|
_fails += 1
|
|
|
|
func _initialize() -> void:
|
|
_run.call_deferred()
|
|
|
|
func _run() -> void:
|
|
A = root.get_node("Actions")
|
|
S = root.get_node("Saves")
|
|
DB = root.get_node("ItemDB")
|
|
S.root_dir = "user://test_worlds"
|
|
for w in S.list_worlds():
|
|
S.delete_world(w.id)
|
|
await _body()
|
|
check(_finished, "test ran to the end (no script errors)")
|
|
for w in S.list_worlds():
|
|
S.delete_world(w.id)
|
|
DirAccess.remove_absolute(S.root_dir)
|
|
print("RESULT: %s (%d failures)" % ["PASS" if _fails == 0 else "FAIL", _fails])
|
|
quit(1 if _fails > 0 else 0)
|
|
|
|
func _ground(p: Vector3) -> Vector3:
|
|
return Vector3(p.x, world.terrain.sample_height(p.x, p.z), p.z)
|
|
|
|
func _place(id: String, at: Vector3) -> Node3D:
|
|
return world.spawn_piece(DB.get_item(id), Transform3D(Basis(), _ground(at)))
|
|
|
|
func _body() -> void:
|
|
print("every material has a use")
|
|
var items: Dictionary = JSON.parse_string(FileAccess.get_file_as_string("res://data/items.json"))
|
|
var recipes: Dictionary = JSON.parse_string(FileAccess.get_file_as_string("res://data/recipes.json"))
|
|
var used := {}
|
|
for r in recipes.recipes:
|
|
for k in r.inputs:
|
|
used[k] = true
|
|
for i in items.items:
|
|
for k in i.get("repair", {}).get("inputs", {}):
|
|
used[k] = true
|
|
for k in i.get("bow", {}).get("ammo", []):
|
|
used[k] = true
|
|
if i.has("fuel"):
|
|
used[i.id] = true
|
|
# (used by a machine or a place rather than a recipe)
|
|
for k in ["iron_ore", "iron_nugget", "charcoal", "barrow_key", A.BAIT]:
|
|
used[k] = true
|
|
var idle: Array = []
|
|
for i in items.items:
|
|
if i.category == "resource" and not i.get("hidden", false) and not used.has(i.id):
|
|
idle.append(i.id)
|
|
check(idle.is_empty(), "no material is only good for filling your bag %s" % [idle])
|
|
var made := {}
|
|
for r in recipes.recipes:
|
|
made[r.output] = true
|
|
var missing: Array = []
|
|
for id in ["clay_jar", "bedroll", "pelt_rug", "fang_necklace", "bear_cloak", "pemmican"]:
|
|
if not made.has(id) or DB.get_item(id).is_empty() or DB.mesh_for(id) == null:
|
|
missing.append(id)
|
|
check(missing.is_empty(), "the six new things each have a recipe and a model %s" % [missing])
|
|
|
|
S.current_id = S.create_world("Items", 1337, {})
|
|
world = load("res://scenes/world.tscn").instantiate()
|
|
root.add_child(world)
|
|
for _i in range(5):
|
|
await physics_frame
|
|
player = world.player
|
|
player.set_physics_process(false)
|
|
player.survival.set_process(false)
|
|
world.day_night.frozen = true
|
|
world.weather.set_process(false)
|
|
world.weather.force("clear")
|
|
world.creatures.enabled = false
|
|
if world.wolves != null:
|
|
world.wolves.enabled = false
|
|
inv = player.inventory
|
|
var here := player.global_position
|
|
|
|
print("the clay jar")
|
|
var jar := _place("clay_jar", here + Vector3(3, 0, 0))
|
|
var chest := _place("chest", here + Vector3(-3, 0, 0))
|
|
check(jar != null and jar.inventory.size() == 6, "a clay jar holds 6 stacks")
|
|
jar.inventory.add("raw_meat", 1)
|
|
chest.inventory.add("raw_meat", 1)
|
|
jar.inventory.spoil()
|
|
chest.inventory.spoil()
|
|
var keep: float = Inventory.keeps_for("raw_meat")
|
|
var c0 := Inventory.clock
|
|
for k in range(1, 31): # a day and a half, in steps
|
|
Inventory.clock = c0 + keep * 1.5 * k / 30.0
|
|
jar.inventory.spoil()
|
|
chest.inventory.spoil()
|
|
check(chest.inventory.count("rotten_food") == 1, "raw meat in a chest is rotten after 1.5x its keep time")
|
|
check(jar.inventory.count("raw_meat") == 1, "in a clay jar it's still good")
|
|
var left: float = Inventory.hours_left(jar.inventory.get_slot(0))
|
|
# 1.5 keep-times in a jar ages it by half of one: half its life left
|
|
# (counted as if it came out now; in the jar that lasts three times as long).
|
|
var expect: float = keep * 0.5 / Inventory.day_seconds * 24.0
|
|
check(absf(left - expect) < 0.5, "with half its life left (%.0f h, expected %.0f h)" % [left, expect])
|
|
Inventory.clock = c0
|
|
|
|
print("the bedroll")
|
|
var roll := _place("bedroll", here + Vector3(0, 0, 6))
|
|
var bed := _place("bed", here + Vector3(0, 0, -6))
|
|
world.day_night.time = 22.0 / 24.0
|
|
world.day_night.apply(true)
|
|
check(roll.is_in_group("bed") and roll.open_air(), "a bedroll is a bed you can use outdoors")
|
|
check(world.sleep_block_reason(roll).is_empty(), "under the open sky on a clear night: you can sleep")
|
|
check(world.sleep_block_reason(bed).contains("roof"), "(a bed out in the open still needs a roof)")
|
|
world.weather.force("rain")
|
|
for _i in range(3):
|
|
world.weather._process(30.0) if world.weather.has_method("_process") else null
|
|
world.weather.precip = 1.0
|
|
check(not world.sleep_block_reason(roll).is_empty(), "in the rain: not out here")
|
|
world.weather.force("clear")
|
|
world.weather.precip = 0.0
|
|
player.global_position = roll.global_position + Vector3(1.0, 0, 0)
|
|
check(A.use_bed(player, roll) and roll.is_spawn(), "E on it: your spawn point")
|
|
world.day_night.time = 10.0 / 24.0
|
|
world.day_night.apply(true)
|
|
|
|
print("the fang necklace")
|
|
player.survival.health = 100.0
|
|
player.survival.damage(10.0, here, "a wolf")
|
|
check(absf(player.survival.health - 90.0) < 0.01, "without it a wolf's bite is 10")
|
|
inv.add("fang_necklace", 1)
|
|
player.survival.health = 100.0
|
|
player.survival.damage(10.0, here, "a alpha wolf")
|
|
check(absf(player.survival.health - 93.0) < 0.01, "with it in your bag the alpha's is 7")
|
|
player.survival.health = 100.0
|
|
player.survival.damage(10.0, here, "a boar")
|
|
check(absf(player.survival.health - 90.0) < 0.01, "(a boar's isn't softened)")
|
|
inv.remove("fang_necklace", 1)
|
|
|
|
print("the bear cloak, the rug and pemmican")
|
|
inv.add("bear_cloak", 1)
|
|
var slot := -1
|
|
for i in range(inv.size()):
|
|
if inv.get_slot(i) != null and inv.get_slot(i).id == "bear_cloak":
|
|
slot = i
|
|
check(A.wear(player, slot) and player.worn == "bear_cloak", "you can wear the bear cloak")
|
|
var warmest := 0.0
|
|
for i in items.items:
|
|
warmest = maxf(warmest, float(i.get("wear", {}).get("warmth", 0)))
|
|
check(float(DB.get_item("bear_cloak").wear.warmth) == warmest, "the warmest cloak there is")
|
|
check(_place("pelt_rug", here + Vector3(4, 0, 4)).is_in_group("rug"), "a wolf-pelt rug is a rug (Cozy, like the fur rug)")
|
|
player.global_position = here
|
|
var fire := _place("campfire", here + Vector3(1.5, 0, 0))
|
|
fire.fuel = fire.MAX_FUEL
|
|
inv.add("cooked_meat", 2)
|
|
inv.add("berries", 3)
|
|
inv.add("bear_fat", 1)
|
|
check(A.craft(player, "pemmican") and inv.count("pemmican") == 3, "pemmican at the campfire (3 from 2 cooked meat, 3 berries, a lump of fat)")
|
|
var d0: Dictionary = player.survival.diet.duplicate()
|
|
player.survival.hunger = 30.0
|
|
for i in range(inv.size()):
|
|
if inv.get_slot(i) != null and inv.get_slot(i).id == "pemmican":
|
|
A.eat(player, i)
|
|
break
|
|
check(player.survival.diet.meat > d0.meat and player.survival.diet.plants > d0.plants, "it counts as meat and greens")
|
|
|
|
print("lights indoors")
|
|
inv.add("bear_fat", 1)
|
|
inv.add("fiber", 1)
|
|
check(A.craft(player, "tallow_candle") and inv.count("tallow_candle") == 4, "4 tallow candles from a lump of bear fat and some fibre, by hand")
|
|
for id in ["tallow_candle", "torch_stand"]:
|
|
var lamp := _place(id, here + Vector3(-2, 0, 3))
|
|
var lights := lamp.find_children("*", "OmniLight3D", false, false)
|
|
check(lamp.is_in_group("lamp") and lights.size() == 1 and (lights[0] as OmniLight3D).omni_range >= 6.0,
|
|
"a %s, placed, gives a light (%.0f m)" % [DB.get_item(id).name.to_lower(), (lights[0] as OmniLight3D).omni_range if not lights.is_empty() else 0.0])
|
|
player.global_position = lamp.global_position + Vector3(1.0, 0, 0)
|
|
check(A.dismantle(player, lamp) and inv.count(id) >= 1, "and X takes it back up")
|
|
player.global_position = here
|
|
|
|
print("bait")
|
|
var B: Script = load("res://scripts/fishing/bobber.gd")
|
|
var bob: Node3D = B.new()
|
|
bob.player = player
|
|
bob.world = world
|
|
bob.table = {"bite_wait": [10.0, 10.0], "fish": [{"id": "perch", "weight": 1}]}
|
|
var plain: float = bob._next_wait()
|
|
inv.add("rotten_food", 2)
|
|
var baited: float = bob._next_wait()
|
|
check(absf(baited - plain * A.BAIT_WAIT) < 0.01 and bob.baited, "rotten food in your bag: bites come sooner (%.0f s, not %.0f s)" % [baited, plain])
|
|
bob.free()
|
|
|
|
print("dropping (Q)")
|
|
var dropped0: int = world._dropped.get_child_count()
|
|
inv.add("stone", 5)
|
|
for i in range(inv.size()):
|
|
if inv.get_slot(i) != null and inv.get_slot(i).id == "stone":
|
|
slot = i
|
|
break
|
|
check(A.drop(player, slot, 1) and inv.count("stone") == 4, "shift+Q drops one")
|
|
check(A.drop(player, slot) and inv.count("stone") == 0, "Q drops the rest of the stack")
|
|
check(world._dropped.get_child_count() == dropped0 + 2, "both lie on the ground in front of you")
|
|
var rot0: int = inv.count("rotten_food")
|
|
for i in range(inv.size()):
|
|
if inv.get_slot(i) != null and inv.get_slot(i).id == "rotten_food":
|
|
A.drop(player, i)
|
|
check(rot0 > 0 and inv.count("rotten_food") == 0, "and at last you can get rid of rotten food")
|
|
inv.add("stone_axe", 1)
|
|
for i in range(inv.size()):
|
|
if inv.get_slot(i) != null and inv.get_slot(i).id == "stone_axe":
|
|
inv.get_slot(i).dur = 7
|
|
slot = i
|
|
A.drop(player, slot)
|
|
var axe: Node3D = world._dropped.get_child(world._dropped.get_child_count() - 1)
|
|
check(axe.item_id == "stone_axe" and int(axe.extra.get("dur", -1)) == 7, "a worn tool keeps its wear on the ground")
|
|
|
|
print("bag rows on 1-8 (K / M)")
|
|
inv.load_data(Inventory.new(inv.size()).to_data())
|
|
inv.set_slot(11, {"id": "torch", "count": 1})
|
|
player.select(0)
|
|
player.select_in_row(3)
|
|
check(player.selected == 3 and player.row() == 0, "4 on the top row: slot 4")
|
|
player.change_row(1)
|
|
check(player.selected == 11 and player.row() == 1 and player.held_item_id() == "torch", "M: the row below, same key: you're holding its torch")
|
|
player.select_in_row(8)
|
|
check(player.selected == 8, "the wheel wraps round within the row")
|
|
player.select(0)
|
|
player.change_row(-1)
|
|
check(player.row() == player.rows() - 1 and player.rows() == 5, "K from the top row: the bottom one (5 rows)")
|
|
var SP: Script = load("res://scripts/net/server_player.gd")
|
|
var sp: Node = SP.new()
|
|
sp.select(27)
|
|
check(sp.selected == 27, "a server lets you hold from any row")
|
|
sp.free()
|
|
|
|
world.queue_free()
|
|
await process_frame
|
|
_finished = true
|