W1: choose a worldlet (New World and Host Game pages, kept once an island exists), worldlet shown in the Load list; Godot .uid files
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"_doc": "A worldlet (docs/WORLDLETS_DESIGN.md): its size, and its terrain as building blocks applied in order (scripts/world/terrain.gd: hills, rim, sea_cliff, lake, ocean_edge). The numbers here are the Valley exactly as it was hand-coded: tests/test_worldlets.gd checks it still makes the same height grid.",
|
"_doc": "A worldlet (docs/WORLDLETS_DESIGN.md): its name and description (the New World page), whether a new world can start here (\"start\"; the others are reached on the journey), its size, and its terrain as building blocks applied in order (scripts/world/terrain.gd: hills, rim, sea_cliff, lake, ocean_edge). The numbers here are the Valley exactly as it was hand-coded: tests/test_worldlets.gd checks it still makes the same height grid.",
|
||||||
"id": "valley",
|
"id": "valley",
|
||||||
"name": "Valley",
|
"name": "Valley",
|
||||||
"description": "A sheltered valley ringed by mountains, with a lake in the middle and sea cliffs on one side.",
|
"description": "A sheltered valley ringed by mountains, with a lake in the middle and sea cliffs on one side.",
|
||||||
|
"start": true,
|
||||||
"size": 320,
|
"size": 320,
|
||||||
"resolution": 256,
|
"resolution": 256,
|
||||||
"terrain": [
|
"terrain": [
|
||||||
|
|||||||
1
scripts/building/bed.gd.uid
Normal file
1
scripts/building/bed.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://nx08dmty60ny
|
||||||
1
scripts/building/build_piece.gd.uid
Normal file
1
scripts/building/build_piece.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dsbvhwas6wsxf
|
||||||
1
scripts/building/build_system.gd.uid
Normal file
1
scripts/building/build_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://gj02g6p3xfip
|
||||||
1
scripts/building/door.gd.uid
Normal file
1
scripts/building/door.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dhsyx4mrm28oe
|
||||||
1
scripts/building/forge.gd.uid
Normal file
1
scripts/building/forge.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cl5ip4achfdwo
|
||||||
1
scripts/building/hearth.gd.uid
Normal file
1
scripts/building/hearth.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://mjs6qb5p3q1a
|
||||||
1
scripts/building/jar.gd.uid
Normal file
1
scripts/building/jar.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ciqsy8msggcke
|
||||||
1
scripts/building/lamp.gd.uid
Normal file
1
scripts/building/lamp.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cwvlv0fbxhlgj
|
||||||
1
scripts/building/processor.gd.uid
Normal file
1
scripts/building/processor.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://jvcfwahbmdsm
|
||||||
1
scripts/building/rug.gd.uid
Normal file
1
scripts/building/rug.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://d21if6sepl888
|
||||||
1
scripts/building/stonecutter.gd.uid
Normal file
1
scripts/building/stonecutter.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dk1ge2xxgiev1
|
||||||
1
scripts/core/boot.gd.uid
Normal file
1
scripts/core/boot.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://frm15unnr4w5
|
||||||
1
scripts/core/guide.gd.uid
Normal file
1
scripts/core/guide.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://nva2x7o0ggw1
|
||||||
1
scripts/core/hints.gd.uid
Normal file
1
scripts/core/hints.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cd4pyfn6igpx0
|
||||||
1
scripts/core/music.gd.uid
Normal file
1
scripts/core/music.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://jojnurcejyff
|
||||||
1
scripts/core/narrator.gd.uid
Normal file
1
scripts/core/narrator.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ctvohlyg6yr5m
|
||||||
1
scripts/core/sfx.gd.uid
Normal file
1
scripts/core/sfx.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://br583euuk842q
|
||||||
1
scripts/core/updates.gd.uid
Normal file
1
scripts/core/updates.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://3lflq11qfg00
|
||||||
1
scripts/creatures/bear.gd.uid
Normal file
1
scripts/creatures/bear.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://co3yk3efstys2
|
||||||
1
scripts/creatures/bear_range.gd.uid
Normal file
1
scripts/creatures/bear_range.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ce34amx3oagyx
|
||||||
1
scripts/creatures/creature.gd.uid
Normal file
1
scripts/creatures/creature.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cdxggdwnq1ctn
|
||||||
1
scripts/creatures/creature_meshes.gd.uid
Normal file
1
scripts/creatures/creature_meshes.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dy6utmjegblry
|
||||||
1
scripts/creatures/crow.gd.uid
Normal file
1
scripts/creatures/crow.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bs230fcuvorft
|
||||||
1
scripts/creatures/den.gd.uid
Normal file
1
scripts/creatures/den.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://beng43qyj6rke
|
||||||
1
scripts/creatures/fox.gd.uid
Normal file
1
scripts/creatures/fox.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cfailgwvqv2fc
|
||||||
1
scripts/creatures/guard.gd.uid
Normal file
1
scripts/creatures/guard.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://x0xva7tw6ind
|
||||||
1
scripts/creatures/old_stag.gd.uid
Normal file
1
scripts/creatures/old_stag.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://f88v5i7s5e5c
|
||||||
1
scripts/creatures/projectile.gd.uid
Normal file
1
scripts/creatures/projectile.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ccpmq0ddkoc8g
|
||||||
1
scripts/creatures/spawner.gd.uid
Normal file
1
scripts/creatures/spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dukt8gme03lsb
|
||||||
1
scripts/creatures/thieves.gd.uid
Normal file
1
scripts/creatures/thieves.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cpwa4sffdi3nt
|
||||||
1
scripts/creatures/tutorial_wolf.gd.uid
Normal file
1
scripts/creatures/tutorial_wolf.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b8wfmfnhg2edq
|
||||||
1
scripts/creatures/wolf.gd.uid
Normal file
1
scripts/creatures/wolf.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://024u453fdbml
|
||||||
1
scripts/creatures/wolf_pack.gd.uid
Normal file
1
scripts/creatures/wolf_pack.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cbb3g7cjprhy8
|
||||||
1
scripts/fishing/bobber.gd.uid
Normal file
1
scripts/fishing/bobber.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://br7isyu6hxnsd
|
||||||
1
scripts/fishing/lake_fish.gd.uid
Normal file
1
scripts/fishing/lake_fish.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cs6ucd2nldqmj
|
||||||
1
scripts/net/avatar.gd.uid
Normal file
1
scripts/net/avatar.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bccgr18dkfhad
|
||||||
1
scripts/net/character_mesh.gd.uid
Normal file
1
scripts/net/character_mesh.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://w2qc4a4ai4vt
|
||||||
1
scripts/net/chat.gd.uid
Normal file
1
scripts/net/chat.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cj8ew4d4huen5
|
||||||
1
scripts/net/creature_net.gd.uid
Normal file
1
scripts/net/creature_net.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c3amf3mwhetmf
|
||||||
1
scripts/net/identity.gd.uid
Normal file
1
scripts/net/identity.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://x0plvp658wne
|
||||||
@@ -304,14 +304,14 @@ var _host_out := ""
|
|||||||
var _host_port := DEFAULT_PORT
|
var _host_port := DEFAULT_PORT
|
||||||
|
|
||||||
## Settings for a hosted game: name, password, port, a rules preset
|
## Settings for a hosted game: name, password, port, a rules preset
|
||||||
## (co-op / rivals / adversarial) and the seed for a new island.
|
## (co-op / rivals / adversarial) and the seed and worldlet for a new island.
|
||||||
const PRESETS := {
|
const PRESETS := {
|
||||||
"co-op": {"pvp": "false", "raiding": "false", "locks": "owner_and_friends", "death_penalty": "grave"},
|
"co-op": {"pvp": "false", "raiding": "false", "locks": "owner_and_friends", "death_penalty": "grave"},
|
||||||
"rivals": {"pvp": "true", "raiding": "false", "locks": "owner_and_friends", "death_penalty": "grave"},
|
"rivals": {"pvp": "true", "raiding": "false", "locks": "owner_and_friends", "death_penalty": "grave"},
|
||||||
"adversarial": {"pvp": "true", "raiding": "true", "locks": "owner", "death_penalty": "drop_all"},
|
"adversarial": {"pvp": "true", "raiding": "true", "locks": "owner", "death_penalty": "drop_all"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func host(server_name: String, password: String, port: int, preset: String, seed: int) -> void:
|
func host(server_name: String, password: String, port: int, preset: String, seed: int, worldlet: String = "valley") -> void:
|
||||||
if hosting or state != "offline":
|
if hosting or state != "offline":
|
||||||
return
|
return
|
||||||
if _host_pid != 0 and OS.is_process_running(_host_pid):
|
if _host_pid != 0 and OS.is_process_running(_host_pid):
|
||||||
@@ -324,8 +324,9 @@ func host(server_name: String, password: String, port: int, preset: String, seed
|
|||||||
c.data.password = password
|
c.data.password = password
|
||||||
c.data.port = port
|
c.data.port = port
|
||||||
var fresh := not FileAccess.file_exists(host_config.get_base_dir().path_join("worlds/server-world/meta.json"))
|
var fresh := not FileAccess.file_exists(host_config.get_base_dir().path_join("worlds/server-world/meta.json"))
|
||||||
if fresh:
|
if fresh: # (an island keeps its seed and worldlet once it's made)
|
||||||
c.data.seed = seed
|
c.data.seed = seed
|
||||||
|
c.data.worldlet = worldlet
|
||||||
c.save()
|
c.save()
|
||||||
_host_port = port
|
_host_port = port
|
||||||
var args := PackedStringArray(["--headless"])
|
var args := PackedStringArray(["--headless"])
|
||||||
|
|||||||
1
scripts/net/net.gd.uid
Normal file
1
scripts/net/net.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://d1153ihwyw01t
|
||||||
1
scripts/net/net_sync.gd.uid
Normal file
1
scripts/net/net_sync.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bgyhqtmwppt4g
|
||||||
1
scripts/net/player_net.gd.uid
Normal file
1
scripts/net/player_net.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dim4cfgbtabvf
|
||||||
1
scripts/net/rules.gd.uid
Normal file
1
scripts/net/rules.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://d2fotty6gceeh
|
||||||
1
scripts/net/server_config.gd.uid
Normal file
1
scripts/net/server_config.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cykv4lse4g62a
|
||||||
1
scripts/net/server_player.gd.uid
Normal file
1
scripts/net/server_player.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dkdnvh8f18o2x
|
||||||
1
scripts/net/world_net.gd.uid
Normal file
1
scripts/net/world_net.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cmk7ho2ij6esq
|
||||||
1
scripts/ui/guide.gd.uid
Normal file
1
scripts/ui/guide.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dbijp4r843nyk
|
||||||
1
scripts/ui/hint_card.gd.uid
Normal file
1
scripts/ui/hint_card.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://0npv7fub184o
|
||||||
1
scripts/ui/hut_outline.gd.uid
Normal file
1
scripts/ui/hut_outline.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://87b73wsdjp7t
|
||||||
@@ -370,6 +370,7 @@ func _host_page() -> Control:
|
|||||||
_host_preset.add_item(n)
|
_host_preset.add_item(n)
|
||||||
_host_preset.custom_minimum_size = Vector2(420, 36)
|
_host_preset.custom_minimum_size = Vector2(420, 36)
|
||||||
v.add_child(_host_preset)
|
v.add_child(_host_preset)
|
||||||
|
_host_worldlet = _worldlet_picker(v)
|
||||||
var row := HBoxContainer.new()
|
var row := HBoxContainer.new()
|
||||||
row.add_theme_constant_override("separation", 10)
|
row.add_theme_constant_override("separation", 10)
|
||||||
var c1 := VBoxContainer.new()
|
var c1 := VBoxContainer.new()
|
||||||
@@ -424,6 +425,7 @@ func _fill_host() -> void:
|
|||||||
_host_seed.editable = not existing
|
_host_seed.editable = not existing
|
||||||
_host_seed_label.text = "Seed" if not existing else "Seed (kept)"
|
_host_seed_label.text = "Seed" if not existing else "Seed (kept)"
|
||||||
_host_seed.text = "" if not existing else str(int(c.data.seed))
|
_host_seed.text = "" if not existing else str(int(c.data.seed))
|
||||||
|
_show_host_worldlet(existing, meta_path)
|
||||||
_host_msg.text = "Your hosted island carries on where you left it." if existing else "A new island will be made."
|
_host_msg.text = "Your hosted island carries on where you left it." if existing else "A new island will be made."
|
||||||
_host_msg.add_theme_color_override("font_color", UITheme.TEXT_DIM)
|
_host_msg.add_theme_color_override("font_color", UITheme.TEXT_DIM)
|
||||||
_host_btn.disabled = false
|
_host_btn.disabled = false
|
||||||
@@ -439,7 +441,8 @@ func _start_host() -> void:
|
|||||||
if name.is_empty():
|
if name.is_empty():
|
||||||
name = "%s's island" % Net.get_identity().name
|
name = "%s's island" % Net.get_identity().name
|
||||||
_host_btn.disabled = true
|
_host_btn.disabled = true
|
||||||
Net.host(name, _host_pw.text, int(port_t), PRESET_IDS[maxi(_host_preset.selected, 0)], seed)
|
Net.host(name, _host_pw.text, int(port_t), PRESET_IDS[maxi(_host_preset.selected, 0)], seed,
|
||||||
|
picked_worldlet(_host_worldlet))
|
||||||
|
|
||||||
func _on_host_status(text: String, failed: bool) -> void:
|
func _on_host_status(text: String, failed: bool) -> void:
|
||||||
if not _pages["host"].visible:
|
if not _pages["host"].visible:
|
||||||
@@ -591,6 +594,7 @@ func _new_page() -> Control:
|
|||||||
_rot_pick.add_item(t[1])
|
_rot_pick.add_item(t[1])
|
||||||
_rot_pick.custom_minimum_size = Vector2(360, 36)
|
_rot_pick.custom_minimum_size = Vector2(360, 36)
|
||||||
v.add_child(_rot_pick)
|
v.add_child(_rot_pick)
|
||||||
|
_worldlet_pick = _worldlet_picker(v)
|
||||||
var h := HBoxContainer.new()
|
var h := HBoxContainer.new()
|
||||||
h.add_theme_constant_override("separation", 10)
|
h.add_theme_constant_override("separation", 10)
|
||||||
h.add_child(_button("Create & Play", _create, 170))
|
h.add_child(_button("Create & Play", _create, 170))
|
||||||
@@ -633,7 +637,8 @@ func _fill_list() -> void:
|
|||||||
info.add_child(UITheme.label(w.name, 18))
|
info.add_child(UITheme.label(w.name, 18))
|
||||||
var played := int(w.get("play_time", 0.0))
|
var played := int(w.get("play_time", 0.0))
|
||||||
var when := Time.get_datetime_string_from_unix_time(int(w.get("last_played", 0)), true)
|
var when := Time.get_datetime_string_from_unix_time(int(w.get("last_played", 0)), true)
|
||||||
info.add_child(UITheme.label("seed %d · played %dh %02dm · %s" % [int(w.seed), played / 3600, (played / 60) % 60, when], 13, UITheme.TEXT_DIM))
|
info.add_child(UITheme.label("%s · seed %d · played %dh %02dm · %s" % [Terrain.worldlet_name(String(w.get("worldlet", "valley"))),
|
||||||
|
int(w.seed), played / 3600, (played / 60) % 60, when], 13, UITheme.TEXT_DIM))
|
||||||
h.add_child(info)
|
h.add_child(info)
|
||||||
h.add_child(_button("Play", _play.bind(w.id), 80))
|
h.add_child(_button("Play", _play.bind(w.id), 80))
|
||||||
var del := Button.new()
|
var del := Button.new()
|
||||||
@@ -681,7 +686,68 @@ func _create() -> void:
|
|||||||
var seed_text := _seed_edit.text.strip_edges()
|
var seed_text := _seed_edit.text.strip_edges()
|
||||||
var seed := int(seed_text) if seed_text.is_valid_int() else (seed_text.hash() if not seed_text.is_empty() else randi())
|
var seed := int(seed_text) if seed_text.is_valid_int() else (seed_text.hash() if not seed_text.is_empty() else randi())
|
||||||
var rot: String = ROT_CHOICES[maxi(_rot_pick.selected, 0)][0]
|
var rot: String = ROT_CHOICES[maxi(_rot_pick.selected, 0)][0]
|
||||||
_play(Saves.create_world(world_name, seed, {"rot": rot}))
|
_play(Saves.create_world(world_name, seed, {"rot": rot, "worldlet": picked_worldlet(_worldlet_pick)}))
|
||||||
|
|
||||||
|
# --- worldlets (docs/WORLDLETS_DESIGN.md, W1) -----------------------------------------
|
||||||
|
|
||||||
|
const Terrain = preload("res://scripts/world/terrain.gd")
|
||||||
|
var _worldlet_pick: OptionButton
|
||||||
|
var _host_worldlet: OptionButton
|
||||||
|
|
||||||
|
## "Where": the worldlets a new world can start in, with the chosen one's
|
||||||
|
## description under it. (Only the Valley today: the others are reached on
|
||||||
|
## the journey, so the list is short and the picker sits still.)
|
||||||
|
func _worldlet_picker(v: VBoxContainer) -> OptionButton:
|
||||||
|
var starts: Array = Terrain.starting_worldlets()
|
||||||
|
v.add_child(UITheme.label("Where", 15, UITheme.TEXT_DIM))
|
||||||
|
var pick := OptionButton.new()
|
||||||
|
pick.custom_minimum_size = Vector2(360, 36)
|
||||||
|
pick.focus_mode = Control.FOCUS_NONE
|
||||||
|
for w in starts:
|
||||||
|
pick.add_item(String(w.name))
|
||||||
|
pick.set_item_metadata(pick.item_count - 1, String(w.id))
|
||||||
|
pick.disabled = starts.size() < 2
|
||||||
|
v.add_child(pick)
|
||||||
|
var about := UITheme.label(String(starts[0].description) if not starts.is_empty() else "", 13, UITheme.TEXT_DIM)
|
||||||
|
about.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
about.custom_minimum_size = Vector2(360, 0)
|
||||||
|
v.add_child(about)
|
||||||
|
pick.item_selected.connect(func(i: int):
|
||||||
|
about.text = String(Terrain.load_worldlet(String(pick.get_item_metadata(i))).get("description", "")))
|
||||||
|
return pick
|
||||||
|
|
||||||
|
## The Host page's "Where": a hosted island keeps its worldlet, like its
|
||||||
|
## seed, so once it exists the picker shows that one and stays put.
|
||||||
|
func _show_host_worldlet(existing: bool, meta_path: String) -> void:
|
||||||
|
var id := "valley"
|
||||||
|
if existing:
|
||||||
|
var meta: Variant = JSON.parse_string(FileAccess.get_file_as_string(meta_path))
|
||||||
|
if meta is Dictionary:
|
||||||
|
id = String(meta.get("worldlet", "valley"))
|
||||||
|
var i := select_worldlet(_host_worldlet, id)
|
||||||
|
if i >= 0:
|
||||||
|
_host_worldlet.item_selected.emit(i)
|
||||||
|
_host_worldlet.disabled = existing or _host_worldlet.item_count < 2
|
||||||
|
|
||||||
|
## Selects worldlet `id` in `pick`, adding it if it isn't a starting one
|
||||||
|
## (an island made somewhere else). Returns its index, or -1.
|
||||||
|
static func select_worldlet(pick: OptionButton, id: String) -> int:
|
||||||
|
for i in pick.item_count:
|
||||||
|
if String(pick.get_item_metadata(i)) == id:
|
||||||
|
pick.select(i)
|
||||||
|
return i
|
||||||
|
if Terrain.load_worldlet(id).is_empty():
|
||||||
|
return -1
|
||||||
|
pick.add_item(Terrain.worldlet_name(id))
|
||||||
|
pick.set_item_metadata(pick.item_count - 1, id)
|
||||||
|
pick.select(pick.item_count - 1)
|
||||||
|
return pick.item_count - 1
|
||||||
|
|
||||||
|
## The id of the worldlet chosen in `pick` (the Valley if nothing is).
|
||||||
|
static func picked_worldlet(pick: OptionButton) -> String:
|
||||||
|
if pick == null or pick.item_count == 0 or pick.selected < 0:
|
||||||
|
return "valley"
|
||||||
|
return String(pick.get_item_metadata(pick.selected))
|
||||||
|
|
||||||
## The tutorial (docs/TUTORIAL_DESIGN.md): its own world on the original
|
## The tutorial (docs/TUTORIAL_DESIGN.md): its own world on the original
|
||||||
## island (seed 1337), made the first time and carried on after that.
|
## island (seed 1337), made the first time and carried on after that.
|
||||||
|
|||||||
1
scripts/world/barrow.gd.uid
Normal file
1
scripts/world/barrow.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://5vs0iggnxpuc
|
||||||
1
scripts/world/hazards.gd.uid
Normal file
1
scripts/world/hazards.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bmcefncielej3
|
||||||
1
scripts/world/standing_stones.gd.uid
Normal file
1
scripts/world/standing_stones.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c4dyxek30amhh
|
||||||
@@ -169,9 +169,20 @@ static func worldlets() -> Array:
|
|||||||
if f.ends_with(".json"):
|
if f.ends_with(".json"):
|
||||||
var d := load_worldlet(f.get_basename())
|
var d := load_worldlet(f.get_basename())
|
||||||
if not d.is_empty():
|
if not d.is_empty():
|
||||||
out.append({"id": String(d.id), "name": String(d.get("name", d.id)), "description": String(d.get("description", ""))})
|
out.append({"id": String(d.id), "name": String(d.get("name", d.id)), "description": String(d.get("description", "")),
|
||||||
|
"start": bool(d.get("start", false))})
|
||||||
|
out.sort_custom(func(a, b): return a.id == "valley" or (b.id != "valley" and a.name < b.name))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
## The worldlets a new world can start in (the rest are reached on the journey).
|
||||||
|
static func starting_worldlets() -> Array:
|
||||||
|
return worldlets().filter(func(w): return w.start)
|
||||||
|
|
||||||
|
## A worldlet's display name ("Valley"), or its id if there's no such worldlet.
|
||||||
|
static func worldlet_name(id: String) -> String:
|
||||||
|
var d := load_worldlet(id)
|
||||||
|
return String(d.get("name", id)) if not d.is_empty() else id
|
||||||
|
|
||||||
## Takes on worldlet `id`: its size and every block's numbers. Returns ""
|
## Takes on worldlet `id`: its size and every block's numbers. Returns ""
|
||||||
## on success, else what's wrong (an unknown worldlet or block keeps the old
|
## on success, else what's wrong (an unknown worldlet or block keeps the old
|
||||||
## settings, so the game still has an island).
|
## settings, so the game still has an island).
|
||||||
|
|||||||
1
scripts/world/weather.gd.uid
Normal file
1
scripts/world/weather.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b5x4pld4ecyr4
|
||||||
1
scripts/world/weather_fx.gd.uid
Normal file
1
scripts/world/weather_fx.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ch20dfnan2yed
|
||||||
1
tests/net_bot.gd.uid
Normal file
1
tests/net_bot.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://coc65tapbhoan
|
||||||
1
tests/net_world_bot.gd.uid
Normal file
1
tests/net_world_bot.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c770mv6jl6dbb
|
||||||
@@ -9,7 +9,7 @@ if [ ! -f .godot/global_script_class_cache.cfg ]; then
|
|||||||
echo "Importing the project first (fresh copy)..."
|
echo "Importing the project first (fresh copy)..."
|
||||||
godot --headless --path . --import >/dev/null 2>&1
|
godot --headless --path . --import >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
for t in test_survival_core test_survival_stats test_save_load test_weather_building test_creatures_combat test_wolves test_alpha test_bear test_thieves test_barrow test_boss test_den_sites test_fishing_diet test_food test_hazards test_iron test_stone test_net_lobby test_net_world test_net_shared test_net_life test_net_boss test_net_rules test_host test_tutorial test_tutorial_full test_hints test_updates test_audio test_items_more test_worldlets test_terrain_edges test_boundary; do
|
for t in test_survival_core test_survival_stats test_save_load test_weather_building test_creatures_combat test_wolves test_alpha test_bear test_thieves test_barrow test_boss test_den_sites test_fishing_diet test_food test_hazards test_iron test_stone test_net_lobby test_net_world test_net_shared test_net_life test_net_boss test_net_rules test_host test_tutorial test_tutorial_full test_hints test_updates test_audio test_items_more test_worldlets test_worldlet_pick test_terrain_edges test_boundary; do
|
||||||
extra=""
|
extra=""
|
||||||
[ "$t" = "test_boundary" ] && extra="--fixed-fps 30"
|
[ "$t" = "test_boundary" ] && extra="--fixed-fps 30"
|
||||||
# A suite that waits on a world that failed to load would hang forever,
|
# A suite that waits on a world that failed to load would hang forever,
|
||||||
|
|||||||
1
tests/test_alpha.gd.uid
Normal file
1
tests/test_alpha.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ck4x0gw0w6q54
|
||||||
1
tests/test_audio.gd.uid
Normal file
1
tests/test_audio.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dkwpi1kjp11us
|
||||||
1
tests/test_barrow.gd.uid
Normal file
1
tests/test_barrow.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bveecfelviv2q
|
||||||
1
tests/test_bear.gd.uid
Normal file
1
tests/test_bear.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bwv2gjno475ns
|
||||||
1
tests/test_boss.gd.uid
Normal file
1
tests/test_boss.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://emp4g3jo3n6u
|
||||||
1
tests/test_creatures_combat.gd.uid
Normal file
1
tests/test_creatures_combat.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b181ahffmk3oy
|
||||||
1
tests/test_den_sites.gd.uid
Normal file
1
tests/test_den_sites.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dpmqwgnwum26x
|
||||||
1
tests/test_fishing_diet.gd.uid
Normal file
1
tests/test_fishing_diet.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dlm83u70hui08
|
||||||
1
tests/test_food.gd.uid
Normal file
1
tests/test_food.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ba0oxbbweqnos
|
||||||
1
tests/test_hazards.gd.uid
Normal file
1
tests/test_hazards.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b5jjbmkuhcyu2
|
||||||
1
tests/test_hints.gd.uid
Normal file
1
tests/test_hints.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c2sl304vqgqjm
|
||||||
@@ -46,7 +46,7 @@ func _body() -> void:
|
|||||||
var statuses: Array[String] = []
|
var statuses: Array[String] = []
|
||||||
Net.host_status.connect(func(t, _f): statuses.append(t))
|
Net.host_status.connect(func(t, _f): statuses.append(t))
|
||||||
print("hosting")
|
print("hosting")
|
||||||
Net.host("Alice's island", "", PORT, "rivals", 4242)
|
Net.host("Alice's island", "", PORT, "rivals", 4242, "valley")
|
||||||
check(Net.hosting, "Host Game starts a server (%s)" % ", ".join(statuses))
|
check(Net.hosting, "Host Game starts a server (%s)" % ", ".join(statuses))
|
||||||
await _until(func(): return Net.state == "connected", 40.0)
|
await _until(func(): return Net.state == "connected", 40.0)
|
||||||
check(Net.state == "connected", "and joins it (%s)" % " -> ".join(statuses))
|
check(Net.state == "connected", "and joins it (%s)" % " -> ".join(statuses))
|
||||||
@@ -56,6 +56,7 @@ func _body() -> void:
|
|||||||
"with the name and the rules picked (friendly rivals: PvP on)")
|
"with the name and the rules picked (friendly rivals: PvP on)")
|
||||||
check(bool(Net.players.get(Net.local_peer, {}).get("admin", false)), "and Alice is its admin")
|
check(bool(Net.players.get(Net.local_peer, {}).get("admin", false)), "and Alice is its admin")
|
||||||
check(int(Net.world_info.get("seed", 0)) == 4242, "a new island from the seed given (%d)" % int(Net.world_info.get("seed", 0)))
|
check(int(Net.world_info.get("seed", 0)) == 4242, "a new island from the seed given (%d)" % int(Net.world_info.get("seed", 0)))
|
||||||
|
check(String(Net.world_info.get("worldlet", "")) == "valley", "in the worldlet picked (%s)" % Net.world_info.get("worldlet", "-"))
|
||||||
var addrs: Array[String] = Net.my_addresses()
|
var addrs: Array[String] = Net.my_addresses()
|
||||||
check(not addrs.is_empty(), "the addresses friends can use: %s" % ", ".join(addrs))
|
check(not addrs.is_empty(), "the addresses friends can use: %s" % ", ".join(addrs))
|
||||||
var log_path := ProjectSettings.globalize_path(DIR + "/server.log")
|
var log_path := ProjectSettings.globalize_path(DIR + "/server.log")
|
||||||
@@ -105,10 +106,12 @@ func _body() -> void:
|
|||||||
c.data.backups_kept = 3
|
c.data.backups_kept = 3
|
||||||
c.save()
|
c.save()
|
||||||
statuses.clear()
|
statuses.clear()
|
||||||
Net.host("Alice's island", "", PORT, "co-op", 999)
|
Net.host("Alice's island", "", PORT, "co-op", 999, "cove")
|
||||||
await _until(func(): return Net.state == "connected", 40.0)
|
await _until(func(): return Net.state == "connected", 40.0)
|
||||||
check(Net.state == "connected", "Host Game again: joined (%s)" % " -> ".join(statuses))
|
check(Net.state == "connected", "Host Game again: joined (%s)" % " -> ".join(statuses))
|
||||||
check(int(Net.world_info.get("seed", 0)) == 4242, "the same island (seed %d, not the new one typed)" % int(Net.world_info.get("seed", 0)))
|
check(int(Net.world_info.get("seed", 0)) == 4242, "the same island (seed %d, not the new one typed)" % int(Net.world_info.get("seed", 0)))
|
||||||
|
check(String(load("res://scripts/net/server_config.gd").open(DIR + "/server.json").data.worldlet) == "valley",
|
||||||
|
"and the same worldlet (the Valley, not the new one picked)")
|
||||||
check(not bool(Net.server_info.get("pvp", true)), "with the rules picked this time (co-op: PvP off)")
|
check(not bool(Net.server_info.get("pvp", true)), "with the rules picked this time (co-op: PvP off)")
|
||||||
check(_backups().size() == 3, "start-up made a backup, and only backups_kept (3) are kept (%d)" % _backups().size())
|
check(_backups().size() == 3, "start-up made a backup, and only backups_kept (3) are kept (%d)" % _backups().size())
|
||||||
_finished = true
|
_finished = true
|
||||||
|
|||||||
1
tests/test_host.gd.uid
Normal file
1
tests/test_host.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ci1cmi2muyyhm
|
||||||
1
tests/test_iron.gd.uid
Normal file
1
tests/test_iron.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://diaexq7aujgcy
|
||||||
1
tests/test_items_more.gd.uid
Normal file
1
tests/test_items_more.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c14jw5ojloao8
|
||||||
1
tests/test_net_boss.gd.uid
Normal file
1
tests/test_net_boss.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://clqu3m2ic4h1y
|
||||||
1
tests/test_net_life.gd.uid
Normal file
1
tests/test_net_life.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dxjnvqs81vu4l
|
||||||
1
tests/test_net_lobby.gd.uid
Normal file
1
tests/test_net_lobby.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bf4kcu6iapdmv
|
||||||
1
tests/test_net_rules.gd.uid
Normal file
1
tests/test_net_rules.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cq738mqhslnq6
|
||||||
1
tests/test_net_shared.gd.uid
Normal file
1
tests/test_net_shared.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dypssblmpnt01
|
||||||
1
tests/test_net_world.gd.uid
Normal file
1
tests/test_net_world.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bdcf2xkerrlw1
|
||||||
1
tests/test_stone.gd.uid
Normal file
1
tests/test_stone.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b2jwpmnhtrv2p
|
||||||
1
tests/test_survival_stats.gd.uid
Normal file
1
tests/test_survival_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://rj7p5l0et5vo
|
||||||
1
tests/test_thieves.gd.uid
Normal file
1
tests/test_thieves.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dlh2tpy6p4cub
|
||||||
1
tests/test_tutorial.gd.uid
Normal file
1
tests/test_tutorial.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://d0kc40mfvtcnx
|
||||||
1
tests/test_tutorial_full.gd.uid
Normal file
1
tests/test_tutorial_full.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c2cnl3i4arfv0
|
||||||
1
tests/test_updates.gd.uid
Normal file
1
tests/test_updates.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://34ao8ilbht52
|
||||||
1
tests/test_weather_building.gd.uid
Normal file
1
tests/test_weather_building.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dy1iyx1vqbbrg
|
||||||
1
tests/test_wolves.gd.uid
Normal file
1
tests/test_wolves.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://8mmbdrbtg53y
|
||||||
110
tests/test_worldlet_pick.gd
Normal file
110
tests/test_worldlet_pick.gd
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
extends SceneTree
|
||||||
|
## W1 (docs/WORLDLETS_DESIGN.md): choosing a worldlet. The New World and Host
|
||||||
|
## Game pages have a "Where" picker listing the worldlets a new world can
|
||||||
|
## start in (only the Valley today, so it sits still); a world made from it
|
||||||
|
## remembers it; the Load list says where each world is; and a hosted island
|
||||||
|
## keeps its worldlet, like its seed, once it's made.
|
||||||
|
## Run: godot --headless --path . --script res://tests/test_worldlet_pick.gd
|
||||||
|
|
||||||
|
const DIR := "user://test_worldlet_pick"
|
||||||
|
|
||||||
|
var _fails := 0
|
||||||
|
var _finished := false
|
||||||
|
|
||||||
|
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:
|
||||||
|
var S: Node = root.get_node("Saves")
|
||||||
|
var Net: Node = root.get_node("Net")
|
||||||
|
S.root_dir = DIR + "/worlds"
|
||||||
|
_rm_tree(DIR)
|
||||||
|
DirAccess.make_dir_recursive_absolute(DIR)
|
||||||
|
Net.host_config = DIR + "/server/server.json"
|
||||||
|
var me = load("res://scripts/net/identity.gd").open(DIR + "/me")
|
||||||
|
me.name = "Alice"
|
||||||
|
Net.identity = me
|
||||||
|
await _body(S, Net)
|
||||||
|
check(_finished, "test ran to the end (no script errors)")
|
||||||
|
_rm_tree(DIR)
|
||||||
|
print("RESULT: %s (%d failures)" % ["PASS" if _fails == 0 else "FAIL", _fails])
|
||||||
|
quit(1 if _fails > 0 else 0)
|
||||||
|
|
||||||
|
func _body(S: Node, Net: Node) -> void:
|
||||||
|
var T: Script = load("res://scripts/world/terrain.gd")
|
||||||
|
var TS: Script = load("res://scripts/ui/title_screen.gd")
|
||||||
|
print("the worldlets to start in")
|
||||||
|
var starts: Array = T.starting_worldlets()
|
||||||
|
check(starts.size() == 1 and String(starts[0].id) == "valley", "only the Valley (%s)" % [starts.map(func(w): return w.id)])
|
||||||
|
check(T.worldlets().all(func(w): return w.has("start")), "every worldlet says whether it's a start")
|
||||||
|
check(T.worldlet_name("valley") == "Valley", "the Valley's name, for the menus")
|
||||||
|
check(T.worldlet_name("nowhere") == "nowhere", "(an unknown one: its id)")
|
||||||
|
check(TS.picked_worldlet(null) == "valley", "nothing picked: the Valley")
|
||||||
|
|
||||||
|
print("the New World page")
|
||||||
|
var title: Node = load("res://scenes/title.tscn").instantiate()
|
||||||
|
root.add_child(title)
|
||||||
|
for _i in range(3):
|
||||||
|
await process_frame
|
||||||
|
var pick: OptionButton = title._worldlet_pick
|
||||||
|
check(pick != null and pick.item_count == 1 and pick.get_item_text(0) == "Valley", "a \"Where\" picker with the Valley in it")
|
||||||
|
check(pick.disabled, "which sits still with only one choice")
|
||||||
|
check(TS.picked_worldlet(pick) == "valley", "and gives the Valley")
|
||||||
|
var id: String = S.create_world("Pick test", 4242, {"worldlet": TS.picked_worldlet(pick)})
|
||||||
|
var meta: Dictionary = S.list_worlds().filter(func(w): return w.id == id)[0] if not id.is_empty() else {}
|
||||||
|
check(String(meta.get("worldlet", "")) == "valley", "a world made from it remembers the Valley (%s)" % meta.get("worldlet", "-"))
|
||||||
|
|
||||||
|
print("the Load list")
|
||||||
|
title._show("load")
|
||||||
|
await process_frame
|
||||||
|
check(_texts(title._list).any(func(t): return t.begins_with("Valley · seed 4242")), "says where each world is (%s)" % [_texts(title._list)])
|
||||||
|
|
||||||
|
print("the Host Game page")
|
||||||
|
var hp: OptionButton = title._host_worldlet
|
||||||
|
check(hp != null and hp.get_item_text(hp.selected) == "Valley", "a \"Where\" picker too, on the Valley")
|
||||||
|
title._show("host")
|
||||||
|
await process_frame
|
||||||
|
check(hp.disabled and TS.picked_worldlet(hp) == "valley", "a new island: the Valley (the only start)")
|
||||||
|
# Once the island exists it keeps its worldlet: the picker shows that one.
|
||||||
|
DirAccess.make_dir_recursive_absolute(DIR + "/server/worlds/server-world")
|
||||||
|
var f := FileAccess.open(DIR + "/server/worlds/server-world/meta.json", FileAccess.WRITE)
|
||||||
|
f.store_string(JSON.stringify({"seed": 7, "worldlet": "valley"}))
|
||||||
|
f.close()
|
||||||
|
title._show("host")
|
||||||
|
await process_frame
|
||||||
|
check(hp.disabled and TS.picked_worldlet(hp) == "valley", "an island already made: shows its worldlet, locked")
|
||||||
|
check(title._host_seed_label.text == "Seed (kept)", "(like its seed)")
|
||||||
|
# A worldlet that isn't a start (an island made elsewhere) is still shown.
|
||||||
|
var extra := OptionButton.new()
|
||||||
|
extra.add_item("Valley")
|
||||||
|
extra.set_item_metadata(0, "valley")
|
||||||
|
check(TS.select_worldlet(extra, "valley") == 0, "select_worldlet finds the Valley")
|
||||||
|
check(TS.select_worldlet(extra, "nowhere") == -1 and extra.item_count == 1, "and adds nothing for an unknown one")
|
||||||
|
extra.free()
|
||||||
|
title.queue_free()
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
print("hosting records the worldlet")
|
||||||
|
var C: Script = load("res://scripts/net/server_config.gd")
|
||||||
|
check(String(C.open(DIR + "/server/server.json").data.worldlet) == "valley", "server.json's worldlet is the Valley by default")
|
||||||
|
_finished = true
|
||||||
|
|
||||||
|
func _texts(n: Node) -> Array:
|
||||||
|
var out: Array = []
|
||||||
|
for c in n.find_children("*", "Label", true, false):
|
||||||
|
out.append(String(c.text))
|
||||||
|
return out
|
||||||
|
|
||||||
|
static func _rm_tree(d: String) -> void:
|
||||||
|
if not DirAccess.dir_exists_absolute(d):
|
||||||
|
return
|
||||||
|
for f in DirAccess.get_files_at(d):
|
||||||
|
DirAccess.remove_absolute(d.path_join(f))
|
||||||
|
for s in DirAccess.get_directories_at(d):
|
||||||
|
_rm_tree(d.path_join(s))
|
||||||
|
DirAccess.remove_absolute(d)
|
||||||
1
tests/test_worldlet_pick.gd.uid
Normal file
1
tests/test_worldlet_pick.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bq1s57q65wp3n
|
||||||
1
tests/test_worldlets.gd.uid
Normal file
1
tests/test_worldlets.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://s1e6yemcmng2
|
||||||
1
tools/site/dump_data.gd.uid
Normal file
1
tools/site/dump_data.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://5ok583rm7c4a
|
||||||
1
tools/trailer/trailer.gd.uid
Normal file
1
tools/trailer/trailer.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://xrbxktkso4gb
|
||||||
Reference in New Issue
Block a user