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("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