diff --git a/audio/music/world/tulips.wav b/audio/music/world/tulips.wav deleted file mode 100644 index 9cd4605..0000000 Binary files a/audio/music/world/tulips.wav and /dev/null differ diff --git a/audio/music/world/tulips.wav.import b/audio/music/world/tulips.wav.import deleted file mode 100644 index 0f8cce1..0000000 --- a/audio/music/world/tulips.wav.import +++ /dev/null @@ -1,24 +0,0 @@ -[remap] - -importer="wav" -type="AudioStreamWAV" -uid="uid://5hgmht4bq1my" -path="res://.godot/imported/tulips.wav-7c18a300a62f25396913efb28c2ba3f6.sample" - -[deps] - -source_file="res://audio/music/world/tulips.wav" -dest_files=["res://.godot/imported/tulips.wav-7c18a300a62f25396913efb28c2ba3f6.sample"] - -[params] - -force/8_bit=false -force/mono=false -force/max_rate=false -force/max_rate_hz=44100 -edit/trim=false -edit/normalize=false -edit/loop_mode=0 -edit/loop_begin=0 -edit/loop_end=-1 -compress/mode=2 diff --git a/docs/PORTMASTER.md b/docs/PORTMASTER.md index e8c24f6..e7e3e09 100644 --- a/docs/PORTMASTER.md +++ b/docs/PORTMASTER.md @@ -28,21 +28,18 @@ directory. The templates in `scripts/portmaster/` are authoritative. - `port.json` uses schema version 4, names `netfishing.zip`, and marks the self-contained package ready to run. -- `NETfishing.sh` does not contain a `# PORTMASTER:` signature in the source or - distributable archive. HarbourMaster adds that installation marker when it - installs the port. +- `NETfishing.sh` starts with `# PORTMASTER: netfishing.zip, NETfishing.sh`. - The executable is `netfishing/NETfishing.aarch64`. - The package declares AArch64, two analog sticks, GLIBC 2.28, and `weston_pkg_0.2.squashfs`. - The launcher starts GPTOKEYB with the explicit `netfishing.gptk` no-op map. - Every keyboard and mouse binding is disabled, leaving only GPTOKEYB's + Every keyboard and mouse binding is disabled, leaving only PortMaster's device-specific force-quit chord while Godot and NETfishing's controller - mapping manager handle gameplay input. The explicit map also avoids the - anomalous controller behavior observed on ROCKNIX without a passthrough map. + mapping manager handle gameplay input. - The launcher calls `pm_platform_helper` for the game executable after starting GPTOKEYB and calls `pm_finish` after the game exits. -- NETfishing automatically creates player data for new installations under - `ports/saves/netfishing/` and does not present the in-game folder picker. +- New installations automatically create player data under + `ports/saves/netfishing/` and do not present the in-game folder picker. - Existing installations with a selected data root keep that location when upgraded. Device-local data, configuration, and cache remain under `netfishing/conf/data`, `netfishing/conf/config`, and @@ -106,15 +103,15 @@ Before upgrading an existing installation, preserve device. After installation: 1. Confirm the installed executable and PCK hashes match the staged release. -2. Confirm HarbourMaster added its `# PORTMASTER:` installation signature to - the installed launcher, then confirm the launcher starts GPTOKEYB with - `netfishing.gptk` and calls `pm_platform_helper` for the game executable. +2. Confirm the installed launcher contains the canonical PortMaster header, + starts GPTOKEYB with `netfishing.gptk`, and calls + `pm_platform_helper` for the game executable. 3. Confirm `conf/` and the external save directory were not replaced or removed. 4. Launch the installed port through the normal muOS menu. 5. Verify the displayed game version and controller face-button mapping. -6. Verify GPTOKEYB's device-specific force-quit chord exits the game. This is - Start+Select on most devices. +6. Verify PortMaster's force-quit chord exits the game. This is Start+Select on + most devices. Installing the public PortMaster catalog entry can still install an older catalog build until the upstream `netfishing.zip` is updated. A local release @@ -176,12 +173,12 @@ the game process. This applies the mapping at the same point as the working reconstruction. Do not move the mapping back to either side of `westonwrap.sh`. +Legacy GPTOKEYB builds can load their compiled default keyboard map when no +`-c` file is supplied. That map translates face buttons, D-pad directions, and +sticks into keyboard or mouse events on top of Godot's native controller input. Always pass `netfishing.gptk`, which explicitly unassigns every direct and -hotkey binding with GPTOKEYB's `\"` value. This keeps GPTOKEYB from injecting -keyboard or mouse events on top of Godot's native controller input and retains -the device-specific kill chord. Supplying the explicit passthrough map also -avoids the anomalous controller behavior observed on ROCKNIX when no map is -provided. Do not add gameplay mappings to this file. +hotkey binding with GPTOKEYB's `\"` value. Do not add gameplay mappings to this +file. The Start+Select kill chord remains independent of the no-op bindings. Do not call `Input.add_joy_mapping(..., true)` for a recognized connected muOS controller. Updating this virtual controller after connection can stop Godot diff --git a/docs/README-PLAYTEST.txt b/docs/README-PLAYTEST.txt index 32b07d6..cd65918 100644 --- a/docs/README-PLAYTEST.txt +++ b/docs/README-PLAYTEST.txt @@ -1,6 +1,6 @@ NETfishing -v0.14.0-alpha -Alpha 0.14.0 +v0.13.1-alpha +Alpha 0.13.1 Thank you for trying this early private playtest. diff --git a/economy/fishing_shop_stock.gd b/economy/fishing_shop_stock.gd index b9d3eaf..962ba3a 100644 --- a/economy/fishing_shop_stock.gd +++ b/economy/fishing_shop_stock.gd @@ -12,7 +12,6 @@ const MAGNET_ID: StringName = &"magnet" const BATTERIES_ID: StringName = &"batteries" const CRAB_NET_ID: StringName = &"crab_net" const STANDARD_SHOVEL_ID: StringName = &"standard_shovel" -const FISHING_NET_ID: StringName = &"fishing_net" const ITEM_PRICES: Dictionary[StringName, int] = { &"worms": 1, @@ -31,7 +30,6 @@ const ITEM_PRICES: Dictionary[StringName, int] = { BATTERIES_ID: 15, CRAB_NET_ID: 50, STANDARD_SHOVEL_ID: 75, - FISHING_NET_ID: 250, } const BAIT_UNLOCK_PRICES: Dictionary[StringName, int] = { &"snails": 400, @@ -54,7 +52,6 @@ const ITEM_ORDER: Array[StringName] = [ MAGNET_ID, CRAB_NET_ID, STANDARD_SHOVEL_ID, - FISHING_NET_ID, &"coffee", &"energy_drink", &"snack", @@ -123,7 +120,6 @@ static func is_permanent_unlock( or item_id == MAGNET_ID or item_id == CRAB_NET_ID or item_id == STANDARD_SHOVEL_ID - or item_id == FISHING_NET_ID or item is FishingRodDataType ) ) diff --git a/export_presets.cfg b/export_presets.cfg index a2c2817..ccc81d9 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -9,7 +9,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.14.0-alpha/windows-x86_64/NETfishing.exe" +export_path="builds/v0.13.1-alpha/windows-x86_64/NETfishing.exe" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -35,11 +35,11 @@ application/modify_resources=true application/icon="res://art/exported/system_icons/netfishing.ico" application/console_wrapper_icon="" application/icon_interpolation=4 -application/file_version="0.14.0.0" -application/product_version="0.14.0.0" +application/file_version="0.13.1.0" +application/product_version="0.13.1.0" application/company_name="Woofmeow" application/product_name="NETfishing" -application/file_description="NETfishing v0.14.0-alpha" +application/file_description="NETfishing v0.13.1-alpha" application/copyright="Copyright © 2026 Woofmeow" application/trademarks="NETfishing and Woofmeow branding is reserved; see TRADEMARKS.md" application/export_angle=0 @@ -62,7 +62,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.14.0-alpha/linux-arm64/NETfishing.arm64" +export_path="builds/v0.13.1-alpha/linux-arm64/NETfishing.arm64" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -92,7 +92,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.14.0-alpha/macos/NETfishing.zip" +export_path="builds/v0.13.1-alpha/macos/NETfishing.zip" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -108,8 +108,8 @@ custom_template/release="" application/bundle_identifier="io.woofmeow.netfishing" application/icon="res://art/exported/system_icons/netfishing_1024.png" application/icon_interpolation=0 -application/short_version="0.14.0" -application/version="0.14.0" +application/short_version="0.13.1" +application/version="0.13.1" application/architecture="universal" codesign/enable=false notarization/enable=false @@ -125,7 +125,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*,tests/*" -export_path="builds/v0.14.0-alpha/server-linux-x86_64/NETfishingServer.x86_64" +export_path="builds/v0.13.1-alpha/server-linux-x86_64/NETfishingServer.x86_64" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -155,7 +155,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.14.0-alpha/android/NETfishing.apk" +export_path="builds/v0.13.1-alpha/android/NETfishing.apk" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -176,8 +176,8 @@ architectures/armeabi-v7a=false architectures/arm64-v8a=true architectures/x86=false architectures/x86_64=false -version/code=140000 -version/name="v0.14.0-alpha" +version/code=130100 +version/name="v0.13.1-alpha" package/unique_name="io.woofmeow.netfishing" package/name="NETfishing" package/signed=true @@ -214,7 +214,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.14.0-alpha/linux-x86_64/NETfishing.x86_64" +export_path="builds/v0.13.1-alpha/linux-x86_64/NETfishing.x86_64" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" diff --git a/fish/fish_data.gd b/fish/fish_data.gd index 46d4675..2696d4b 100644 --- a/fish/fish_data.gd +++ b/fish/fish_data.gd @@ -36,12 +36,6 @@ enum LogbookSection { SHELLFISH, } -enum CreatureGroup { - FISH, - INSECT, - SHELLFISH, -} - @export var id: StringName @export var display_name: String @export_category("Developer Catalog") @@ -100,14 +94,6 @@ func is_fishable() -> bool: return is_selectable() and collection_method == CollectionMethod.FISHING -func get_creature_group() -> CreatureGroup: - if logbook_section == LogbookSection.SHELLFISH: - return CreatureGroup.SHELLFISH - if collection_method == CollectionMethod.FISHING: - return CreatureGroup.FISH - return CreatureGroup.INSECT - - func get_habitat_label() -> String: if not habitat_label.strip_edges().is_empty(): return habitat_label.strip_edges().to_lower() diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index 389d40b..e2fdef0 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -111,9 +111,7 @@ const BITE_QUICK_PROBABILITY: float = 0.30 const BITE_TYPICAL_PROBABILITY: float = 0.65 const BITE_LONG_PROBABILITY: float = 0.04 const NETWORK_INPUT_RESEND_INTERVAL_SECONDS: float = 0.1 -const ALTERNATE_REEL_ACTION: StringName = &"reel_alternate" @export_range(0.1, 10.0, 0.1) var cooldown_duration: float = 1.0 -@export_range(0.1, 5.0, 0.05) var minimum_showcase_duration: float = 0.8 @export_category("Selection") @export_range(0.0, 10.0, 0.05) var undiscovered_weight_multiplier: float = 1.5 @@ -174,7 +172,6 @@ var _selected_fish: FishDataType var _pending_catch: FishCatchType var _showcase_ready: bool = false var _put_away_press_armed: bool = false -var _showcase_input_lock_remaining: float = 0.0 var _showcase_restore_generation: int = 0 var _showcase_outcome_completed: bool = false var _network_auto_click_accumulator: float = 0.0 @@ -447,7 +444,6 @@ func _secure_showcase_catch_for_recovery() -> void: _pending_catch = null _showcase_ready = false _put_away_press_armed = false - _showcase_input_lock_remaining = 0.0 showcase_changed.emit("", "", 0.0, 0, false) if _active_player != null: _active_player.end_catch_showcase(Callable(), true) @@ -477,21 +473,14 @@ func _exit_tree() -> void: _selected_fish = null _showcase_ready = false _put_away_press_armed = false - _showcase_input_lock_remaining = 0.0 func _process(delta: float) -> void: if state == FishingState.READY and not Input.is_action_pressed("fish_primary"): _new_cast_press_armed = true - if state == FishingState.SHOWING_CATCH: - _showcase_input_lock_remaining = maxf( - _showcase_input_lock_remaining - delta, - 0.0, - ) if ( state == FishingState.SHOWING_CATCH and _showcase_ready - and _showcase_input_lock_remaining <= 0.0 and not Input.is_action_pressed("fish_primary") ): _put_away_press_armed = true @@ -512,7 +501,7 @@ func _process(delta: float) -> void: _get_effective_reel_speed(), _get_effective_barrier_damage() ) - if not _is_reel_input_pressed(): + if not Input.is_action_pressed("fish_primary"): _catch_controller.set_reel_input(false) else: _resend_network_input_state(delta) @@ -532,11 +521,7 @@ func _unhandled_input(event: InputEvent) -> void: return if _local_player == null or not _local_player.is_local_control_enabled(): return - var primary_action: bool = event.is_action("fish_primary") - var alternate_reel: bool = event.is_action(ALTERNATE_REEL_ACTION) - if not primary_action and not ( - alternate_reel and state == FishingState.FIGHTING - ): + if not event.is_action("fish_primary"): return var selected_item: ItemDataType = _get_active_item() if ( @@ -597,11 +582,7 @@ func _unhandled_input(event: InputEvent) -> void: FishingPresentationType.LineMode.TAUT ) FishingState.SHOWING_CATCH: - if ( - _showcase_ready - and _showcase_input_lock_remaining <= 0.0 - and _put_away_press_armed - ): + if _showcase_ready and _put_away_press_armed: _put_away_catch() else: return @@ -638,23 +619,15 @@ func _unhandled_input(event: InputEvent) -> void: _presentation.set_line_mode(FishingPresentationType.LineMode.SLACK) get_viewport().set_input_as_handled() elif state == FishingState.FIGHTING: - var reel_input_held: bool = _is_reel_input_pressed() if _network_fishing != null: - _network_primary_input_held = reel_input_held + _network_primary_input_held = false _network_input_resend_elapsed = 0.0 - _network_fishing.submit_local_input(reel_input_held, false) + _network_fishing.submit_local_input(false, false) else: - _catch_controller.set_reel_input(reel_input_held) + _catch_controller.set_reel_input(false) get_viewport().set_input_as_handled() -func _is_reel_input_pressed() -> bool: - return ( - Input.is_action_pressed("fish_primary") - or Input.is_action_pressed(ALTERNATE_REEL_ACTION) - ) - - func _begin_aiming(player: PlayerType) -> void: if state != FishingState.READY or _active_player != null: return @@ -783,17 +756,6 @@ func is_returning() -> bool: return state == FishingState.RETURNING -func is_fishing_sequence_active() -> bool: - return state in [ - FishingState.AIMING_CAST, - FishingState.CASTING, - FishingState.WAITING_FOR_BITE, - FishingState.FIGHTING, - FishingState.SHOWING_CATCH, - FishingState.RETURNING, - ] - - func refresh_active_item_status() -> void: if state == FishingState.READY and has_active_fishing_rod(): status_changed.emit("") @@ -818,7 +780,6 @@ func present_external_catch(fish_catch: FishCatchType) -> bool: _showcase_ready = true _showcase_outcome_completed = true _put_away_press_armed = false - _showcase_input_lock_remaining = minimum_showcase_duration _active_player.begin_catch_showcase(fish_catch) showcase_changed.emit( fish_catch.fish.display_name, @@ -1151,7 +1112,7 @@ func _activate_bite(confirmation_override: bool = false) -> void: _pending_catch.quality, ) _catch_controller.set_reel_input( - _is_reel_input_pressed() + Input.is_action_pressed("fish_primary") ) _presentation.show_bite() @@ -1236,7 +1197,6 @@ func _on_catch_completed() -> void: _showcase_ready = false _showcase_outcome_completed = false _put_away_press_armed = false - _showcase_input_lock_remaining = 0.0 _catch_controller.reset() _presentation.set_line_mode(FishingPresentationType.LineMode.TAUT) _presentation.play_outcome(&"catch") @@ -1272,7 +1232,6 @@ func _on_outcome_completed(outcome: StringName) -> void: return _showcase_outcome_completed = true _showcase_ready = true - _showcase_input_lock_remaining = minimum_showcase_duration _active_player.begin_catch_showcase(_pending_catch) showcase_changed.emit( _pending_catch.fish.display_name, @@ -1306,7 +1265,6 @@ func _put_away_catch() -> void: _showcase_ready = false _showcase_outcome_completed = false _put_away_press_armed = false - _showcase_input_lock_remaining = 0.0 showcase_changed.emit("", "", 0.0, 0, false) _showcase_restore_generation += 1 var restore_generation: int = _showcase_restore_generation @@ -1380,7 +1338,6 @@ func _cleanup_attempt( _showcase_ready = false _showcase_outcome_completed = false _put_away_press_armed = false - _showcase_input_lock_remaining = 0.0 showcase_changed.emit("", "", 0.0, 0, false) _catch_controller.reset() state = FishingState.RETURNING @@ -1430,7 +1387,6 @@ func _finalize_attempt_cleanup( _showcase_ready = false _showcase_outcome_completed = false _put_away_press_armed = false - _showcase_input_lock_remaining = 0.0 showcase_changed.emit("", "", 0.0, 0, false) _catch_controller.reset() _presentation.cleanup() @@ -1768,7 +1724,7 @@ func _on_network_bite_started(_attempt_id: String) -> void: if _active_player != null: _active_player.set_fighting_visual(true) _withdrawal_input_held = false - _network_primary_input_held = _is_reel_input_pressed() + _network_primary_input_held = Input.is_action_pressed("fish_primary") _network_input_resend_elapsed = 0.0 _network_auto_click_accumulator = 0.0 _network_active_barrier_index = -1 @@ -1840,7 +1796,7 @@ func _on_network_fishing_snapshot(snapshot: Dictionary) -> void: func _update_network_auto_click(delta: float) -> void: if ( not _catch_controller.auto_click_enabled - or not _is_reel_input_pressed() + or not Input.is_action_pressed("fish_primary") or _network_active_barrier_index < 0 ): _network_auto_click_accumulator = 0.0 @@ -1867,7 +1823,6 @@ func _on_network_catch_received(fish_catch: FishCatchType) -> void: _showcase_ready = false _showcase_outcome_completed = false _put_away_press_armed = false - _showcase_input_lock_remaining = 0.0 catch_display_changed.emit( 0.0, 0.0, PackedFloat32Array(), PackedInt32Array(), PackedInt32Array(), -1, false diff --git a/items/catalog/fishing_net.tres b/items/catalog/fishing_net.tres deleted file mode 100644 index 04b1919..0000000 --- a/items/catalog/fishing_net.tres +++ /dev/null @@ -1,16 +0,0 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] - -[ext_resource type="Script" path="res://items/item_data.gd" id="1_item"] - -[resource] -script = ExtResource("1_item") -item_id = &"fishing_net" -display_name = "fishing net" -description = "a placeable water net reserved for a future passive fishing system." -active = true -category = 1 -stackable = false -max_stack = 1 -usable = false -equippable = false -hotbar_allowed = false diff --git a/items/catalog/item_catalog.tres b/items/catalog/item_catalog.tres index 63ef3ea..1c006f4 100644 --- a/items/catalog/item_catalog.tres +++ b/items/catalog/item_catalog.tres @@ -1,4 +1,4 @@ -[gd_resource type="Resource" script_class="ItemCatalog" load_steps=41 format=3] +[gd_resource type="Resource" script_class="ItemCatalog" load_steps=40 format=3] [ext_resource type="Script" path="res://items/item_catalog.gd" id="1_script"] [ext_resource type="Resource" path="res://items/catalog/basic_fishing_rod.tres" id="2_rod"] @@ -39,8 +39,7 @@ [ext_resource type="Resource" path="res://items/catalog/magnet.tres" id="38_magnet"] [ext_resource type="Resource" path="res://items/catalog/crab_net.tres" id="39_crab_net"] [ext_resource type="Resource" path="res://items/catalog/standard_shovel.tres" id="40_shovel"] -[ext_resource type="Resource" path="res://items/catalog/fishing_net.tres" id="41_fishing_net"] [resource] script = ExtResource("1_script") -items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("14_sardine"), ExtResource("13_anchovy"), ExtResource("15_roe"), ExtResource("16_standby"), ExtResource("17_batteries"), ExtResource("18_cardboard"), ExtResource("19_pond"), ExtResource("20_river"), ExtResource("21_lake"), ExtResource("22_salt"), ExtResource("23_whisker"), ExtResource("24_reef"), ExtResource("25_moonbeam"), ExtResource("26_sun"), ExtResource("27_rain"), ExtResource("28_fog"), ExtResource("29_guide"), ExtResource("30_small"), ExtResource("31_heavy"), ExtResource("32_rocket"), ExtResource("33_lucky"), ExtResource("34_showboat"), ExtResource("35_deep"), ExtResource("36_oddity"), ExtResource("37_aurora"), ExtResource("38_magnet"), ExtResource("39_crab_net"), ExtResource("40_shovel"), ExtResource("41_fishing_net")] +items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("14_sardine"), ExtResource("13_anchovy"), ExtResource("15_roe"), ExtResource("16_standby"), ExtResource("17_batteries"), ExtResource("18_cardboard"), ExtResource("19_pond"), ExtResource("20_river"), ExtResource("21_lake"), ExtResource("22_salt"), ExtResource("23_whisker"), ExtResource("24_reef"), ExtResource("25_moonbeam"), ExtResource("26_sun"), ExtResource("27_rain"), ExtResource("28_fog"), ExtResource("29_guide"), ExtResource("30_small"), ExtResource("31_heavy"), ExtResource("32_rocket"), ExtResource("33_lucky"), ExtResource("34_showboat"), ExtResource("35_deep"), ExtResource("36_oddity"), ExtResource("37_aurora"), ExtResource("38_magnet"), ExtResource("39_crab_net"), ExtResource("40_shovel")] diff --git a/jobs/job_catalog.gd b/jobs/job_catalog.gd index eb3ea02..754263d 100644 --- a/jobs/job_catalog.gd +++ b/jobs/job_catalog.gd @@ -14,9 +14,6 @@ enum Kind { REACH_LEVEL, DISCOVER_SPECIES, MASTER_QUALITIES, - CATCH_CREATURE_GROUP, - DISCOVER_CREATURE_GROUP, - MASTER_CREATURE_GROUP, } const DAILY_JOB_COUNT: int = 4 @@ -114,28 +111,6 @@ static func generate_daily_jobs( 50, {"fish_id": String(selected.id)}, )) - if _has_creature_group(candidates, FishDataType.CreatureGroup.INSECT): - optional.append(_job( - "insect_roundup", - "bug hunt", - "catch 3 insects", - Kind.CATCH_CREATURE_GROUP, - 3, - 40, - 50, - {"creature_group": int(FishDataType.CreatureGroup.INSECT)}, - )) - if _has_creature_group(candidates, FishDataType.CreatureGroup.SHELLFISH): - optional.append(_job( - "shellfish_search", - "shore patrol", - "catch 3 shellfish", - Kind.CATCH_CREATURE_GROUP, - 3, - 45, - 50, - {"creature_group": int(FishDataType.CreatureGroup.SHELLFISH)}, - )) _shuffle(optional, rng) while jobs.size() < DAILY_JOB_COUNT and not optional.is_empty(): jobs.append(optional.pop_back()) @@ -197,8 +172,6 @@ static func generate_weather_schedule( static func lifetime_chains( registered_species_count: int, - registered_insect_count: int = 0, - registered_shellfish_count: int = 0, ) -> Array[Dictionary]: var chains: Array[Dictionary] = [ _chain("catch", Kind.CATCH_TOTAL, [100, 1000, 5000], [ @@ -224,33 +197,15 @@ static func lifetime_chains( [registered_species_count], [[1500, 2000]], )) - _append_creature_lifetime_chains( - chains, - "insect", - FishDataType.CreatureGroup.INSECT, - registered_insect_count, - ) - _append_creature_lifetime_chains( - chains, - "shellfish", - FishDataType.CreatureGroup.SHELLFISH, - registered_shellfish_count, - ) return chains static func visible_lifetime_jobs( registered_species_count: int, claimed_ids: Array[String], - registered_insect_count: int = 0, - registered_shellfish_count: int = 0, ) -> Array[Dictionary]: var visible: Array[Dictionary] = [] - for chain: Dictionary in lifetime_chains( - registered_species_count, - registered_insect_count, - registered_shellfish_count, - ): + for chain: Dictionary in lifetime_chains(registered_species_count): var targets: Array = chain.get("targets", []) var rewards: Array = chain.get("rewards", []) for tier_index: int in targets.size(): @@ -261,9 +216,6 @@ static func visible_lifetime_jobs( if job_id in claimed_ids: continue var reward: Array = rewards[tier_index] - var extra: Dictionary = {} - if chain.has("creature_group"): - extra["creature_group"] = int(chain["creature_group"]) visible.append(_job( job_id, _lifetime_title(str(chain.get("id", "")), int(targets[tier_index])), @@ -274,7 +226,6 @@ static func visible_lifetime_jobs( int(targets[tier_index]), int(reward[0]), int(reward[1]), - extra, )) break return visible @@ -305,10 +256,10 @@ static func is_valid_job(value: Variant) -> bool: and typeof(job.get("description")) == TYPE_STRING and str(job["description"]).length() <= 160 and is_bounded_integer( - job.get("kind"), Kind.CATCH_TOTAL, Kind.MASTER_CREATURE_GROUP + job.get("kind"), Kind.CATCH_TOTAL, Kind.MASTER_QUALITIES ) and kind >= Kind.CATCH_TOTAL - and kind <= Kind.MASTER_CREATURE_GROUP + and kind <= Kind.MASTER_QUALITIES and is_bounded_integer(job.get("target"), 1, 1000000000) and is_bounded_integer( job.get("fish_coin"), 0, MAX_JOB_REWARD_COINS @@ -367,12 +318,6 @@ static func is_valid_job(value: Variant) -> bool: and not str(job.get("fish_id", "")).is_empty() and str(job.get("fish_id", "")).length() <= 96 ) - Kind.CATCH_CREATURE_GROUP, Kind.DISCOVER_CREATURE_GROUP, Kind.MASTER_CREATURE_GROUP: - return is_bounded_integer( - job.get("creature_group"), - FishDataType.CreatureGroup.FISH, - FishDataType.CreatureGroup.SHELLFISH, - ) return true @@ -456,16 +401,13 @@ static func _chain( kind: Kind, targets: Array[int], rewards: Array, - extra: Dictionary = {}, ) -> Dictionary: - var result: Dictionary = { + return { "id": id, "kind": int(kind), "targets": targets, "rewards": rewards, } - result.merge(extra, true) - return result static func _lifetime_title(chain_id: String, target: int) -> String: @@ -480,18 +422,6 @@ static func _lifetime_title(chain_id: String, target: int) -> String: return "complete the catalog" "master": return "quality master" - "insect_catch": - return "seasoned bug catcher" - "insect_discover": - return "complete the insect catalog" - "insect_master": - return "insect quality master" - "shellfish_catch": - return "seasoned beachcomber" - "shellfish_discover": - return "complete the shellfish catalog" - "shellfish_master": - return "shellfish quality master" return "long-term job" @@ -507,67 +437,9 @@ static func _lifetime_description(chain_id: String, target: int) -> String: return "discover all %d cataloged fish" % target "master": return "collect every quality of all %d fish" % target - "insect_catch": - return "catch %d insects" % target - "insect_discover": - return "discover all %d cataloged insects" % target - "insect_master": - return "collect every quality of all %d insects" % target - "shellfish_catch": - return "catch %d shellfish" % target - "shellfish_discover": - return "discover all %d cataloged shellfish" % target - "shellfish_master": - return "collect every quality of all %d shellfish" % target return "keep fishing" -static func _has_creature_group( - candidates: Array[FishDataType], - creature_group: FishDataType.CreatureGroup, -) -> bool: - for fish: FishDataType in candidates: - if ( - fish != null - and fish.is_selectable() - and fish.get_creature_group() == creature_group - ): - return true - return false - - -static func _append_creature_lifetime_chains( - chains: Array[Dictionary], - id_prefix: String, - creature_group: FishDataType.CreatureGroup, - registered_count: int, -) -> void: - if registered_count <= 0: - return - var group_data := {"creature_group": int(creature_group)} - chains.append(_chain( - "%s_catch" % id_prefix, - Kind.CATCH_CREATURE_GROUP, - [10, 50, 250], - [[75, 100], [250, 350], [900, 1100]], - group_data, - )) - chains.append(_chain( - "%s_discover" % id_prefix, - Kind.DISCOVER_CREATURE_GROUP, - [registered_count], - [[250, 350]], - group_data, - )) - chains.append(_chain( - "%s_master" % id_prefix, - Kind.MASTER_CREATURE_GROUP, - [registered_count], - [[750, 1000]], - group_data, - )) - - static func _shuffle(values: Array[Dictionary], rng: RandomNumberGenerator) -> void: for index: int in range(values.size() - 1, 0, -1): var swap_index: int = rng.randi_range(0, index) diff --git a/jobs/player_job_service.gd b/jobs/player_job_service.gd index 7ede866..7b19cc6 100644 --- a/jobs/player_job_service.gd +++ b/jobs/player_job_service.gd @@ -25,7 +25,6 @@ var _session: NetworkSession var _save_manager: PlayerSaveManager var _network_fishing: NetworkFishingService var _network_sale: NetworkSaleService -var _network_world_spawns: NetworkWorldSpawnService var _progression_ready: bool = false var _host_board: Dictionary = {} @@ -36,8 +35,6 @@ var _daily_completions: Dictionary[String, int] = {} var _pending_rewards: Array[Dictionary] = [] var _lifetime_claimed: Array[String] = [] var _total_catches: int = 0 -var _total_insects: int = 0 -var _total_shellfish: int = 0 var _total_sold: int = 0 var _daily_clock_hour: float = WorldTimeService.DEFAULT_START_HOUR @@ -68,11 +65,9 @@ func setup( func bind_authoritative_services( network_fishing: NetworkFishingService, network_sale: NetworkSaleService, - network_world_spawns: NetworkWorldSpawnService, ) -> void: _network_fishing = network_fishing _network_sale = network_sale - _network_world_spawns = network_world_spawns if not _network_fishing.local_catch_received.is_connected( _on_authoritative_catch ): @@ -83,12 +78,6 @@ func bind_authoritative_services( _network_sale.local_sale_finished.connect( _on_authoritative_sale_finished ) - if not _network_world_spawns.local_capture_received.is_connected( - _on_authoritative_catch - ): - _network_world_spawns.local_capture_received.connect( - _on_authoritative_catch - ) func set_save_manager(save_manager: PlayerSaveManager) -> void: @@ -134,10 +123,7 @@ func get_daily_jobs() -> Array[Dictionary]: func get_lifetime_jobs() -> Array[Dictionary]: var result: Array[Dictionary] = JobCatalog.visible_lifetime_jobs( - _registered_species_count(), - _lifetime_claimed, - _registered_creature_count(FishDataType.CreatureGroup.INSECT), - _registered_creature_count(FishDataType.CreatureGroup.SHELLFISH), + _registered_species_count(), _lifetime_claimed ) for job: Dictionary in result: var progress: int = _lifetime_progress(job) @@ -250,8 +236,6 @@ func to_save_data() -> Dictionary: "lifetime_claimed": _lifetime_claimed.duplicate(), "statistics": { "fish_caught": _total_catches, - "insects_caught": _total_insects, - "shellfish_caught": _total_shellfish, "fish_sold": _total_sold, }, } @@ -310,8 +294,6 @@ func restore_from_save_data(data: Dictionary) -> bool: _lifetime_claimed.append(str(value)) var statistics: Dictionary = data.get("statistics", {}) _total_catches = int(statistics.get("fish_caught", 0)) - _total_insects = int(statistics.get("insects_caught", 0)) - _total_shellfish = int(statistics.get("shellfish_caught", 0)) _total_sold = int(statistics.get("fish_sold", 0)) if _session != null and _session.is_host(): _active_board = _host_board.duplicate(true) @@ -336,8 +318,6 @@ func reset_to_defaults() -> void: _pending_rewards.clear() _lifetime_claimed.clear() _total_catches = 0 - _total_insects = 0 - _total_shellfish = 0 _total_sold = 0 _daily_clock_hour = WorldTimeService.DEFAULT_START_HOUR changed.emit() @@ -353,12 +333,7 @@ static func default_save_data() -> Dictionary: "daily_clock_hour": WorldTimeService.DEFAULT_START_HOUR, "pending_rewards": [], "lifetime_claimed": [], - "statistics": { - "fish_caught": 0, - "insects_caught": 0, - "shellfish_caught": 0, - "fish_sold": 0, - }, + "statistics": {"fish_caught": 0, "fish_sold": 0}, } @@ -481,12 +456,6 @@ static func validate_save_data(value: Variant) -> bool: JobCatalog.is_bounded_integer( statistics.get("fish_caught"), 0, MAX_PROGRESS_VALUE ) - and JobCatalog.is_bounded_integer( - statistics.get("insects_caught", 0), 0, MAX_PROGRESS_VALUE - ) - and JobCatalog.is_bounded_integer( - statistics.get("shellfish_caught", 0), 0, MAX_PROGRESS_VALUE - ) and JobCatalog.is_bounded_integer( statistics.get("fish_sold"), 0, MAX_PROGRESS_VALUE ) @@ -545,7 +514,7 @@ func _generate_host_board(cycle: int) -> void: var candidates: Array[FishDataType] = [] if _catalog != null: for fish: FishDataType in _catalog.candidates: - if fish != null and fish.is_selectable(): + if fish != null and fish.is_fishable(): candidates.append(fish) var schedule_anchor_index: int = _current_weather_segment() var allow_weather_jobs: bool = ( @@ -639,13 +608,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void: func _on_authoritative_catch(fish_catch: FishCatchType) -> void: if not _progression_ready or fish_catch == null or not fish_catch.is_valid(): return - match fish_catch.fish.get_creature_group(): - FishDataType.CreatureGroup.FISH: - _total_catches = mini(_total_catches + 1, MAX_PROGRESS_VALUE) - FishDataType.CreatureGroup.INSECT: - _total_insects = mini(_total_insects + 1, MAX_PROGRESS_VALUE) - FishDataType.CreatureGroup.SHELLFISH: - _total_shellfish = mini(_total_shellfish + 1, MAX_PROGRESS_VALUE) + _total_catches = mini(_total_catches + 1, MAX_PROGRESS_VALUE) _update_daily_for_catch(fish_catch) changed.emit() @@ -667,40 +630,23 @@ func _on_authoritative_sale_finished( func _update_daily_for_catch(fish_catch: FishCatchType) -> void: - var creature_group: int = int(fish_catch.fish.get_creature_group()) - var is_fish: bool = creature_group == FishDataType.CreatureGroup.FISH for job: Dictionary in get_daily_jobs(): var matches: bool = false match int(job.get("kind", -1)): JobCatalog.Kind.CATCH_TOTAL: - matches = is_fish + matches = true JobCatalog.Kind.CATCH_WATER: - matches = is_fish and fish_catch.fish.is_allowed_in_water( + matches = fish_catch.fish.is_allowed_in_water( int(job.get("water_type", WaterType.Type.OTHER)) as WaterType.Type ) JobCatalog.Kind.CATCH_QUALITY: - matches = ( - is_fish - and fish_catch.quality >= int(job.get("minimum_quality", 0)) - ) + matches = fish_catch.quality >= int(job.get("minimum_quality", 0)) JobCatalog.Kind.CATCH_PHASE: - matches = ( - is_fish - and int(_world_time.get_phase()) == int(job.get("phase", -1)) - ) + matches = int(_world_time.get_phase()) == int(job.get("phase", -1)) JobCatalog.Kind.CATCH_WEATHER: - matches = ( - is_fish - and int(_world_weather.get_weather()) - == int(job.get("weather", -1)) - ) + matches = int(_world_weather.get_weather()) == int(job.get("weather", -1)) JobCatalog.Kind.CATCH_SPECIES: - matches = ( - is_fish - and String(fish_catch.fish_id) == str(job.get("fish_id", "")) - ) - JobCatalog.Kind.CATCH_CREATURE_GROUP: - matches = creature_group == int(job.get("creature_group", -1)) + matches = String(fish_catch.fish_id) == str(job.get("fish_id", "")) if matches: _advance_daily(job, 1) @@ -795,74 +741,26 @@ func _lifetime_progress(job: Dictionary) -> int: JobCatalog.Kind.REACH_LEVEL: return _experience.get_level() if _experience != null else 0 JobCatalog.Kind.DISCOVER_SPECIES: - return _collection_progress( - FishDataType.CreatureGroup.FISH, false - ) + return _collection.get_discovered_ids().size() if _collection != null else 0 JobCatalog.Kind.MASTER_QUALITIES: - return _collection_progress( - FishDataType.CreatureGroup.FISH, true - ) - JobCatalog.Kind.CATCH_CREATURE_GROUP: - match int(job.get("creature_group", -1)): - FishDataType.CreatureGroup.INSECT: - return _total_insects - FishDataType.CreatureGroup.SHELLFISH: - return _total_shellfish - JobCatalog.Kind.DISCOVER_CREATURE_GROUP: - return _collection_progress( - int(job.get("creature_group", -1)) as FishDataType.CreatureGroup, - false, - ) - JobCatalog.Kind.MASTER_CREATURE_GROUP: - return _collection_progress( - int(job.get("creature_group", -1)) as FishDataType.CreatureGroup, - true, - ) + var mastered: int = 0 + if _collection != null and _catalog != null: + for fish: FishDataType in _catalog.candidates: + if fish != null and _collection.has_mastered(fish.id): + mastered += 1 + return mastered return 0 func _registered_species_count() -> int: - return _registered_creature_count(FishDataType.CreatureGroup.FISH) - - -func _registered_creature_count( - creature_group: FishDataType.CreatureGroup, -) -> int: var count: int = 0 if _catalog != null: for fish: FishDataType in _catalog.candidates: - if ( - fish != null - and fish.is_selectable() - and fish.get_creature_group() == creature_group - ): + if fish != null and fish.is_fishable(): count += 1 return count -func _collection_progress( - creature_group: FishDataType.CreatureGroup, - require_mastery: bool, -) -> int: - var progress: int = 0 - if _collection == null or _catalog == null: - return progress - for fish: FishDataType in _catalog.candidates: - if ( - fish == null - or not fish.is_selectable() - or fish.get_creature_group() != creature_group - ): - continue - if ( - _collection.has_mastered(fish.id) - if require_mastery - else _collection.has_discovered(fish.id) - ): - progress += 1 - return progress - - func _expire_incomplete_daily_jobs() -> void: _daily_progress.clear() _daily_completions.clear() @@ -897,7 +795,7 @@ func _matches_canonical_board(board: Dictionary) -> bool: var candidates: Array[FishDataType] = [] if _catalog != null: for fish: FishDataType in _catalog.candidates: - if fish != null and fish.is_selectable(): + if fish != null and fish.is_fishable(): candidates.append(fish) var anchor_index: int = int(board.get("schedule_anchor_index", -1)) var allow_weather_jobs: bool = ( diff --git a/main/main.gd b/main/main.gd index fa8fc0b..2db40ac 100644 --- a/main/main.gd +++ b/main/main.gd @@ -6,7 +6,6 @@ const FishPoolType = preload("res://fish/fish_pool.gd") const GameUIType = preload("res://ui/game_ui.gd") const PlayerType = preload("res://player/player.gd") const TestWorldType = preload("res://world/test_world.gd") -const WorldLayoutType = preload("res://world/world_layout.gd") const WaterRecoveryControllerType = preload( "res://world/water_recovery_controller.gd" ) @@ -125,7 +124,6 @@ const TITLE_MUSIC_SILENCE_DB: float = -80.0 const TITLE_MUSIC_PATH: String = ( "res://audio/music/title/as_in_four_wolves.ogg" ) -const NEW_GAME_MUSIC_PATH: String = "res://audio/music/world/tulips.wav" const DUSK_MUSIC_PATH: String = "res://audio/music/world/craft.mp3" const TIME_CROSSING_EPSILON_HOURS: float = 0.000001 const PLAYER_MENU_PATTERN_SCALE: float = 0.85 @@ -140,7 +138,6 @@ const SHOP_PATTERN_SCALE: float = 1.75 @export_category("Title Music") @export_range(-40.0, 0.0, 0.5) var title_music_volume_db: float = -6.0 @export_range(0.0, 10.0, 0.05) var title_music_fade_out_seconds: float = 5.0 -@export_range(-40.0, 0.0, 0.5) var new_game_music_volume_db: float = -6.0 @onready var _test_world: TestWorldType = $TestWorld @onready var _player: PlayerType = %Player @@ -157,7 +154,6 @@ const SHOP_PATTERN_SCALE: float = 1.75 ) @onready var _interface_fonts: InterfaceFontController = %InterfaceFontController @onready var _title_music: AudioStreamPlayer = %TitleMusic -@onready var _new_game_music: AudioStreamPlayer = %NewGameMusic @onready var _dusk_music: AudioStreamPlayer = %DuskMusic @onready var _ui_pixelation: UIPixelationPresenterType = %UIPresentation @onready var _pixelation_reset: PixelationResetOverlayType = ( @@ -360,7 +356,6 @@ func _configure_presentation_performance_profile(light_profile: bool) -> void: func _configure_audio_performance_profile(light_profile: bool) -> void: _title_music.stream = _load_optional_audio_stream(TITLE_MUSIC_PATH) - _new_game_music.stream = _load_optional_audio_stream(NEW_GAME_MUSIC_PATH) _dusk_music.stream = _load_optional_audio_stream(DUSK_MUSIC_PATH) if light_profile: _shoreline_ambience.set_audio_enabled(false) @@ -414,25 +409,10 @@ func _start_dedicated_server() -> void: ): _fail_dedicated_server("The server operator list is invalid.") return - if not _network_chat.configure_dedicated_history( - config.chat_logging, - config.chat_log_path, - ): - _fail_dedicated_server( - "The dedicated-server chat privacy configuration is invalid." - ) - return - if not _apply_world( - WorldLayoutType.GENERATED, - config.world_seed, - false, - ): + if not _apply_generated_world(config.world_seed, false): _fail_dedicated_server("The configured world seed could not be generated.") return - if not _network_session.set_host_world( - WorldLayoutType.GENERATED, - config.world_seed, - ): + if not _network_session.set_host_world_seed(config.world_seed): _fail_dedicated_server("The configured world seed is invalid.") return _configure_portable_stores() @@ -656,7 +636,6 @@ func _initialize_application(dedicated: bool) -> void: _world_time, _world_weather, _player_jobs, - _player, ) _player_jobs.set_save_manager(_save_manager) _save_manager.set_autosave_enabled(false) @@ -768,11 +747,7 @@ func _initialize_application(dedicated: bool) -> void: _asset_reservations, _player.inventory_layout, ) - _player_jobs.bind_authoritative_services( - _network_fishing, - _network_sale, - _network_world_spawns, - ) + _player_jobs.bind_authoritative_services(_network_fishing, _network_sale) _network_shop.setup( _network_session, _player_spawn_service, @@ -849,7 +824,6 @@ func _initialize_application(dedicated: bool) -> void: ) _game_ui.setup_data_and_identity( _data_root, - _save_manager, _identity_backups, _player_identity, _host_identity, @@ -1597,10 +1571,7 @@ func _on_natural_time_advanced(advanced_hours: float) -> void: ) ): _dusk_music_played_for_natural_day = true - if ( - _dusk_music.stream != null - and not _new_game_music.playing - ): + if _dusk_music.stream != null: _dusk_music.play(0.0) @@ -1701,34 +1672,26 @@ func _resize_native_overlays() -> void: _shop_backdrop.size = Vector2(get_window().size) -func _on_new_game_requested( - world_layout: StringName, - world_seed: int, -) -> void: +func _on_new_game_requested(world_seed: int) -> void: if _gameplay_started or _quit_in_progress: return - _start_new_game_music() - if not _apply_world(world_layout, world_seed, true): - _show_title_music(true) + if not _apply_generated_world(world_seed, true): _game_ui.get_title_screen().report_network_error( - "Could not prepare that world. Try another seed or the starter island." + "Could not generate that world seed. Try rolling another world." ) return - if not _prepare_host_world(world_layout, world_seed): - _show_title_music(true) + if not _prepare_host_world_seed(world_seed): _game_ui.get_title_screen().report_network_error( - "Could not prepare that world for hosting." + "Could not prepare the generated world for hosting." ) return if not _prepare_private_host(): - _show_title_music(true) return if ( not _save_manager.delete_progression_save() - or not _save_manager.initialize_new_game(world_seed, world_layout) + or not _save_manager.initialize_new_game(world_seed) ): _network_session.disconnect_session("New Game setup failed.") - _show_title_music(true) _game_ui.get_title_screen().report_network_error( "Could not initialize local progression. Existing data was preserved where possible." ) @@ -1744,14 +1707,13 @@ func _on_continue_game_requested() -> void: "Failed to load save. The original was preserved." ) return - var world_layout: StringName = _save_manager.get_world_layout() var world_seed: int = _save_manager.get_world_seed() if ( - not _apply_world(world_layout, world_seed, true) - or not _prepare_host_world(world_layout, world_seed) + not _apply_generated_world(world_seed, true) + or not _prepare_host_world_seed(world_seed) ): _game_ui.get_title_screen().report_network_error( - "The saved world could not be loaded. The save was preserved." + "The saved world could not be generated. The save was preserved." ) return if not _prepare_private_host(): @@ -1778,16 +1740,13 @@ func _prepare_private_host() -> bool: return true -func _prepare_host_world( - world_layout: StringName, - world_seed: int, -) -> bool: +func _prepare_host_world_seed(world_seed: int) -> bool: if _network_session.state in [ NetworkSessionType.State.CONNECTION_FAILED, NetworkSessionType.State.SERVER_LOST, ]: _network_session.reset_failure() - return _network_session.set_host_world(world_layout, world_seed) + return _network_session.set_host_world_seed(world_seed) func _enter_gameplay() -> void: @@ -1991,14 +1950,11 @@ func _on_reset_progress_requested() -> void: pause_menu.report_reset_failure() return var world_seed: int = PlayerSaveManager.roll_world_seed() - if not _save_manager.initialize_new_game( - world_seed, - WorldLayoutType.GENERATED, - ): + if not _save_manager.initialize_new_game(world_seed): pause_menu.report_reset_failure() return _network_session.disconnect_session("Progress reset.") - if not _apply_world(WorldLayoutType.GENERATED, world_seed, true): + if not _apply_generated_world(world_seed, true): pause_menu.report_reset_failure() return pause_menu.close_for_title_transition() @@ -2008,18 +1964,8 @@ func _on_reset_progress_requested() -> void: func _apply_joined_world(server_metadata: Dictionary) -> bool: - var world_layout: StringName = WorldLayoutType.normalized( - server_metadata.get( - "world_layout", - String(WorldLayoutType.GENERATED), - ), - &"", - ) var world_seed: int = int(server_metadata.get("world_seed", 0)) - if ( - not world_layout.is_empty() - and _apply_world(world_layout, world_seed, true) - ): + if _apply_generated_world(world_seed, true): return true _network_session.disconnect_session("World generation failed.") _join_requested_from_title = false @@ -2028,26 +1974,20 @@ func _apply_joined_world(server_metadata: Dictionary) -> bool: var title_screen: TitleScreenType = _game_ui.get_title_screen() title_screen.reopen_to_menu() title_screen.report_network_error( - "The host's world could not be built locally." + "The host's generated world could not be built locally." ) _show_title_music(true) return false -func _apply_world( - world_layout: StringName, - world_seed: int, - reposition_player: bool, -) -> bool: - if not _test_world.activate_world(world_layout, world_seed): +func _apply_generated_world(world_seed: int, reposition_player: bool) -> bool: + if not _test_world.generate_world(world_seed): return false var spawn_transform: Transform3D = _test_world.get_player_spawn_transform() _player_spawn_service.set_spawn_transform(spawn_transform) if reposition_player: _player.global_transform = spawn_transform _player.velocity = Vector3.ZERO - if _application_initialized: - _refresh_active_world_bindings() if _application_initialized and not _dedicated_runtime: _water_recovery.update_world_context( spawn_transform, @@ -2061,51 +2001,6 @@ func _apply_world( return true -func _refresh_active_world_bindings() -> void: - if is_instance_valid(_shop_interaction): - if _shop_interaction.local_player_range_changed.is_connected( - _on_shop_range_changed - ): - _shop_interaction.local_player_range_changed.disconnect( - _on_shop_range_changed - ) - if is_instance_valid(_storage_interaction): - if _storage_interaction.local_player_range_changed.is_connected( - _on_storage_range_changed - ): - _storage_interaction.local_player_range_changed.disconnect( - _on_storage_range_changed - ) - _shop_interaction = _test_world.get_fishing_shop() - _storage_interaction = _test_world.get_player_storage() - _network_sale.set_shop_interaction(_shop_interaction) - _network_shop.set_shop_interaction(_shop_interaction) - if _dedicated_runtime: - return - if _shop_interaction != null: - _shop_interaction.setup_local_player(_player) - _shop_interaction.local_player_range_changed.connect( - _on_shop_range_changed - ) - if _storage_interaction != null: - _storage_interaction.setup_local_player(_player) - _storage_interaction.local_player_range_changed.connect( - _on_storage_range_changed - ) - _game_ui.set_world_interactions( - _shop_interaction, - _storage_interaction, - ) - _on_shop_range_changed( - _shop_interaction != null - and _shop_interaction.is_local_player_in_range() - ) - _on_storage_range_changed( - _storage_interaction != null - and _storage_interaction.is_local_player_in_range() - ) - - func _on_water_recovery_starting() -> void: _local_recovery_attempt_id = ( "recovery:%s" @@ -2225,7 +2120,6 @@ func _on_quit_requested() -> void: func _show_title_music(restart_from_beginning: bool = false) -> void: - _new_game_music.stop() _title_music_requested = true _replace_title_music_transition() if _title_music.stream == null: @@ -2237,19 +2131,6 @@ func _show_title_music(restart_from_beginning: bool = false) -> void: _title_music.play(0.0) -func _start_new_game_music() -> void: - if _new_game_music.stream == null: - return - _title_music_requested = false - _replace_title_music_transition() - _title_music.stop() - _title_music.volume_db = title_music_volume_db - _dusk_music.stop() - _new_game_music.stop() - _new_game_music.volume_db = new_game_music_volume_db - _new_game_music.play(0.0) - - func _fade_out_title_music(on_complete: Callable = Callable()) -> void: _title_music_requested = false var generation: int = _replace_title_music_transition() @@ -2315,9 +2196,6 @@ func _exit_tree() -> void: if is_instance_valid(_title_music): _title_music.stop() _title_music.stream = null - if is_instance_valid(_new_game_music): - _new_game_music.stop() - _new_game_music.stream = null if is_instance_valid(_dusk_music): _dusk_music.stop() _dusk_music.stream = null diff --git a/main/main.tscn b/main/main.tscn index b9ad6fb..e8dba76 100644 --- a/main/main.tscn +++ b/main/main.tscn @@ -361,10 +361,6 @@ unique_name_in_owner = true volume_db = -80.0 bus = &"Music" -[node name="NewGameMusic" type="AudioStreamPlayer" parent="."] -unique_name_in_owner = true -bus = &"Music" - [node name="DuskMusic" type="AudioStreamPlayer" parent="." unique_id=992744241] unique_name_in_owner = true bus = &"Music" diff --git a/network/network_chat_protocol.gd b/network/network_chat_protocol.gd index 2e91432..ef7294d 100644 --- a/network/network_chat_protocol.gd +++ b/network/network_chat_protocol.gd @@ -9,13 +9,6 @@ const MAX_HISTORY: int = 100 const LATE_JOIN_HISTORY: int = 50 const MAX_ID_LENGTH: int = 96 -static var _invisible_edge_pattern: RegEx = RegEx.create_from_string( - "^[\\s\\p{Z}\\p{Cf}\\x{2800}]+|[\\s\\p{Z}\\p{Cf}\\x{2800}]+$" -) -static var _visible_content_pattern: RegEx = RegEx.create_from_string( - "[^\\s\\p{Z}\\p{Cf}\\x{2800}]" -) - enum Kind { PLAYER, SYSTEM } @@ -23,13 +16,8 @@ static func sanitize_body(value: Variant) -> String: if typeof(value) != TYPE_STRING: return "" var result: String = str(value).replace("\r", " ").replace("\n", " ") - result = result.replace("\t", " ") - result = _invisible_edge_pattern.sub(result, "", true) - if ( - result.is_empty() - or _visible_content_pattern.search(result) == null - or result.length() > MAX_VISIBLE_CHARACTERS - ): + result = result.replace("\t", " ").strip_edges() + if result.is_empty() or result.length() > MAX_VISIBLE_CHARACTERS: return "" if result.to_utf8_buffer().size() > MAX_UTF8_BYTES: return "" diff --git a/network/network_chat_service.gd b/network/network_chat_service.gd index b24d558..0502a38 100644 --- a/network/network_chat_service.gd +++ b/network/network_chat_service.gd @@ -4,10 +4,7 @@ extends Node const BURST_COUNT: int = 3 const WINDOW_COUNT: int = 5 const WINDOW_SECONDS: float = 10.0 -const CALL_COOLDOWN_MILLISECONDS: int = 90 -const DEDICATED_CHAT_WARNING: String = ( - "Privacy notice: This dedicated server records and retains chat messages." -) +const CALL_COOLDOWN_MILLISECONDS: int = 180 const CALL_PITCH_VARIANTS: Array[float] = [ 0.96, 1.03, @@ -41,32 +38,6 @@ var _call_variant_indices: Dictionary[int, int] = {} var _sequence: int = 0 var _peer_names: Dictionary[int, String] = {} var _relationships: PlayerRelationshipStore -var _dedicated_history_enabled: bool = false -var _chat_log_path: String = "" - - -func configure_dedicated_history(enabled: bool, log_path: String) -> bool: - _dedicated_history_enabled = false - _chat_log_path = "" - if not enabled: - return true - var normalized_path: String = log_path.strip_edges() - if normalized_path.is_empty() or not normalized_path.is_absolute_path(): - return false - var directory: String = normalized_path.get_base_dir() - if DirAccess.make_dir_recursive_absolute(directory) != OK: - return false - var file: FileAccess - if FileAccess.file_exists(normalized_path): - file = FileAccess.open(normalized_path, FileAccess.READ_WRITE) - else: - file = FileAccess.open(normalized_path, FileAccess.WRITE) - if file == null: - return false - file.close() - _dedicated_history_enabled = true - _chat_log_path = normalized_path - return true func setup(session: NetworkSession) -> void: @@ -317,8 +288,7 @@ func _handle_request(peer_id: int, data: Dictionary) -> void: var request_id: String = data["request_id"] var ledger: Dictionary = _request_ledgers.get(peer_id, {}) if ledger.has(request_id): - if typeof(ledger[request_id]) == TYPE_DICTIONARY: - _send_message(peer_id, ledger[request_id]) + _send_message(peer_id, ledger[request_id]) return if not _consume_rate(peer_id): _send_rejection(peer_id, "Slow down.") @@ -338,11 +308,7 @@ func _handle_request(peer_id: int, data: Dictionary) -> void: message["request_id"] = request_id message["sender_fingerprint"] = data["sender_fingerprint"] message["sender_signature"] = data["sender_signature"] - ledger[request_id] = ( - message.duplicate(true) - if _should_store_host_history() - else true - ) + ledger[request_id] = message.duplicate(true) while ledger.size() > 64: ledger.erase(ledger.keys().front()) _request_ledgers[peer_id] = ledger @@ -438,12 +404,9 @@ func _apply_message( kind == NetworkChatProtocol.Kind.PLAYER and is_sender_filtered(str(data.get("sender_fingerprint", ""))) ) - if _should_store_local_history(): - _history.append(stored_message) - while _history.size() > NetworkChatProtocol.MAX_HISTORY: - _history.pop_front() - if _should_log_message(stored_message): - _append_chat_log(stored_message) + _history.append(stored_message) + while _history.size() > NetworkChatProtocol.MAX_HISTORY: + _history.pop_front() if emit_live_signals and _message_is_visible(stored_message): message_received.emit(stored_message.duplicate(true)) if ( @@ -470,26 +433,8 @@ func _on_peer_authenticated(peer_id: int, display_name: String) -> void: if not _session.is_host(): return _peer_names[peer_id] = display_name - var history: Array[Dictionary] = [] - if _dedicated_history_enabled and _session.is_dedicated_host(): - var start := maxi( - 0, - _history.size() - NetworkChatProtocol.LATE_JOIN_HISTORY, - ) - history = _history.slice(start) - # An explicit empty replacement is intentional for player-hosted rooms and - # dedicated servers without logging. It destroys any stale client scrollback - # instead of replaying another player's live-session chat on reconnect. - receive_chat_history.rpc_id(peer_id, history) - if _dedicated_history_enabled and _session.is_dedicated_host(): - # Deliver the disclosure as a live system message after scrollback. This - # raises the normal unread indicator without creating speech or audio. - _send_message(peer_id, _make_message( - NetworkChatProtocol.Kind.SYSTEM, - 0, - "", - DEDICATED_CHAT_WARNING, - )) + var start := maxi(0, _history.size() - NetworkChatProtocol.LATE_JOIN_HISTORY) + receive_chat_history.rpc_id(peer_id, _history.slice(start)) _broadcast(_make_message( NetworkChatProtocol.Kind.SYSTEM, 0, "", "%s joined." % display_name )) @@ -549,44 +494,6 @@ func _consume_rate(peer_id: int) -> bool: return true -func _should_store_host_history() -> bool: - return ( - _session == null - or not _session.is_dedicated_host() - or _dedicated_history_enabled - ) - - -func _should_store_local_history() -> bool: - return _should_store_host_history() - - -func _should_log_message(message: Dictionary) -> bool: - return ( - _dedicated_history_enabled - and not _chat_log_path.is_empty() - and _session != null - and _session.is_dedicated_host() - and int(message.get("kind", -1)) == NetworkChatProtocol.Kind.PLAYER - ) - - -func _append_chat_log(message: Dictionary) -> void: - var file := FileAccess.open(_chat_log_path, FileAccess.READ_WRITE) - if file == null: - push_error("The configured dedicated-server chat log is unavailable.") - return - file.seek_end() - file.store_line(JSON.stringify({ - "recorded_at_utc": ( - Time.get_datetime_string_from_system(true, false) + "Z" - ), - "sender_display_name": str(message.get("sender_display_name", "")), - "body": str(message.get("body", "")), - })) - file.close() - - func _on_session_state_changed(state: NetworkSession.State) -> void: if state in [ NetworkSession.State.INACTIVE, diff --git a/network/network_protocol.gd b/network/network_protocol.gd index 9a30a27..141756a 100644 --- a/network/network_protocol.gd +++ b/network/network_protocol.gd @@ -1,8 +1,6 @@ class_name NetworkProtocol extends RefCounted -const WorldLayoutType = preload("res://world/world_layout.gd") - const PROTOCOL_VERSION: int = 9 const GAME_BUILD: String = "prealpha" const MAX_GAME_VERSION_LENGTH: int = 64 @@ -31,7 +29,6 @@ const JOBS_CAPABILITY: String = "jobs_v1" const WORLD_SPAWN_CAPABILITY: String = "world_spawn_envelope_v1" const APPEARANCE_PREVIEW_CAPABILITY: String = "appearance_preview_v1" const WORLD_GENERATION_CAPABILITY: String = "world_generation_v1" -const WORLD_LAYOUT_CAPABILITY: String = "world_layout_v1" const DEFAULT_WORLD_SEED: int = 13001 const MAX_WORLD_SEED: int = 2147483646 @@ -185,7 +182,6 @@ static func make_client_hello( APPEARANCE_PREVIEW_CAPABILITY, BACKPACK_SHOP_CAPABILITY, WORLD_GENERATION_CAPABILITY, - WORLD_LAYOUT_CAPABILITY, ]), "cosmetic_snapshot": cosmetic_snapshot, "identity_fingerprint": identity_fingerprint, @@ -290,7 +286,6 @@ static func make_server_hello( max_players: int, server_display_name: String = "NETfishing", world_seed: int = DEFAULT_WORLD_SEED, - world_layout: StringName = WorldLayoutType.GENERATED, ) -> Dictionary: return { "accepted": accepted, @@ -303,7 +298,6 @@ static func make_server_hello( "player_count": player_count, "max_players": max_players, "world_seed": world_seed, - "world_layout": String(world_layout), "capability_flags": PackedStringArray([ "movement_v1", "fishing_v1", @@ -322,7 +316,6 @@ static func make_server_hello( WORLD_SPAWN_CAPABILITY, APPEARANCE_PREVIEW_CAPABILITY, WORLD_GENERATION_CAPABILITY, - WORLD_LAYOUT_CAPABILITY, "chat_v1", "mail_v1", "profile_v1", diff --git a/network/network_sale_service.gd b/network/network_sale_service.gd index 38af917..9207266 100644 --- a/network/network_sale_service.gd +++ b/network/network_sale_service.gd @@ -91,10 +91,6 @@ func setup( _session.state_changed.connect(_on_session_state_changed) -func set_shop_interaction(interaction: FishingShopInteraction) -> void: - _shop_interaction = interaction - - func is_local_sale_pending() -> bool: return not _pending_local_request_id.is_empty() diff --git a/network/network_session.gd b/network/network_session.gd index 21d400c..39796f5 100644 --- a/network/network_session.gd +++ b/network/network_session.gd @@ -86,7 +86,6 @@ var _last_server_player_count: int = 0 var _last_server_display_name: String = "" var _last_server_protocol_version: int = 0 var _last_server_world_seed: int = NetworkProtocol.DEFAULT_WORLD_SEED -var _last_server_world_layout: StringName = WorldLayout.GENERATED var _server_capabilities: PackedStringArray = PackedStringArray() var _profile_ready: bool = false var _player_identity: PlayerIdentityStore @@ -114,7 +113,6 @@ var _session_operator_fingerprints: Dictionary[String, bool] = {} var _operator_peer_ids: Dictionary[int, bool] = {} var _local_operator: bool = false var _host_world_seed: int = NetworkProtocol.DEFAULT_WORLD_SEED -var _host_world_layout: StringName = WorldLayout.GENERATED func _ready() -> void: @@ -277,7 +275,6 @@ func _register_player_host() -> void: NetworkProtocol.WORLD_SPAWN_CAPABILITY, NetworkProtocol.BACKPACK_SHOP_CAPABILITY, NetworkProtocol.WORLD_GENERATION_CAPABILITY, - NetworkProtocol.WORLD_LAYOUT_CAPABILITY, ]), ) _registry.update_appearance(1, _local_appearance_snapshot) @@ -534,35 +531,20 @@ func get_last_server_metadata() -> Dictionary: "player_count": _last_server_player_count, "max_players": _last_server_max_players, "world_seed": _last_server_world_seed, - "world_layout": String(_last_server_world_layout), } func set_host_world_seed(seed: int) -> bool: - return set_host_world(WorldLayout.GENERATED, seed) - - -func set_host_world(world_layout: StringName, seed: int) -> bool: if ( state != State.INACTIVE - or not WorldLayout.is_valid(world_layout) or seed <= 0 or seed > NetworkProtocol.MAX_WORLD_SEED ): return false - _host_world_layout = world_layout _host_world_seed = seed return true -func get_authoritative_world_layout() -> StringName: - return ( - _last_server_world_layout - if is_joined_client() - else _host_world_layout - ) - - func get_authoritative_world_seed() -> int: return _last_server_world_seed if is_joined_client() else _host_world_seed @@ -601,7 +583,6 @@ func supports_server_capability(capability: StringName) -> bool: NetworkProtocol.WORLD_SPAWN_CAPABILITY, NetworkProtocol.APPEARANCE_PREVIEW_CAPABILITY, NetworkProtocol.WORLD_GENERATION_CAPABILITY, - NetworkProtocol.WORLD_LAYOUT_CAPABILITY, "chat_v1", "mail_v1", "profile_v1", @@ -1209,15 +1190,6 @@ func submit_client_hello(data: Dictionary) -> void: NetworkProtocol.RejectionCode.UNSUPPORTED_CLIENT, ) return - if ( - _host_world_layout == WorldLayout.STARTER_ISLAND - and NetworkProtocol.WORLD_LAYOUT_CAPABILITY not in client_capabilities - ): - _reject_peer( - sender_id, - NetworkProtocol.RejectionCode.UNSUPPORTED_CLIENT, - ) - return var identity: Dictionary = _authenticated_identity_cache.get(sender_id, {}) if identity.is_empty(): _reject_peer(sender_id, NetworkProtocol.RejectionCode.INVALID_IDENTITY_PROOF) @@ -1306,7 +1278,6 @@ func submit_client_hello(data: Dictionary) -> void: session_max_players, _session_display_name, _host_world_seed, - _host_world_layout, ) ) receive_spawn_list.rpc_id(sender_id, _build_spawn_list()) @@ -1361,9 +1332,6 @@ func receive_server_hello(data: Dictionary) -> void: or typeof(data.get("game_version")) != TYPE_STRING or typeof(data.get("rejection_code")) != TYPE_INT or typeof(data.get("world_seed")) != TYPE_INT - or typeof( - data.get("world_layout", String(WorldLayout.GENERATED)) - ) not in [TYPE_STRING, TYPE_STRING_NAME] ): _teardown_peer() _fail("The server sent an invalid handshake response.") @@ -1394,14 +1362,6 @@ func receive_server_hello(data: Dictionary) -> void: _teardown_peer() _fail("The server sent an invalid world seed.") return - var received_world_layout := WorldLayout.normalized( - data.get("world_layout", String(WorldLayout.GENERATED)), - &"", - ) - if received_world_layout.is_empty(): - _teardown_peer() - _fail("The server sent an invalid world layout.") - return _session_id = str(data.get("session_id", "")) _last_server_max_players = int(data.get( "max_players", @@ -1413,7 +1373,6 @@ func receive_server_hello(data: Dictionary) -> void: "protocol_version", NetworkProtocol.PROTOCOL_VERSION )) _last_server_world_seed = received_world_seed - _last_server_world_layout = received_world_layout _server_capabilities = PackedStringArray() var advertised_capabilities: Variant = data.get( "capability_flags", PackedStringArray() @@ -1432,13 +1391,6 @@ func receive_server_hello(data: Dictionary) -> void: _teardown_peer() _fail("This server does not support generated worlds.") return - if ( - received_world_layout == WorldLayout.STARTER_ISLAND - and NetworkProtocol.WORLD_LAYOUT_CAPABILITY not in _server_capabilities - ): - _teardown_peer() - _fail("This server does not support selectable world layouts.") - return var local_peer_id: int = multiplayer.get_unique_id() _registry.clear() _registry.add_peer( @@ -1455,7 +1407,6 @@ func receive_server_hello(data: Dictionary) -> void: NetworkProtocol.WORLD_WEATHER_CAPABILITY, NetworkProtocol.BACKPACK_SHOP_CAPABILITY, NetworkProtocol.WORLD_GENERATION_CAPABILITY, - NetworkProtocol.WORLD_LAYOUT_CAPABILITY, ]), ) _registry.update_appearance(local_peer_id, _local_appearance_snapshot) diff --git a/network/network_shop_service.gd b/network/network_shop_service.gd index 6f31c40..f30c1da 100644 --- a/network/network_shop_service.gd +++ b/network/network_shop_service.gd @@ -83,10 +83,6 @@ func setup( _session.state_changed.connect(_on_session_state_changed) -func set_shop_interaction(interaction: FishingShopInteraction) -> void: - _interaction = interaction - - func can_request_purchase() -> bool: return ( _session != null diff --git a/network/player_data_root.gd b/network/player_data_root.gd index 998e380..9e50fe7 100644 --- a/network/player_data_root.gd +++ b/network/player_data_root.gd @@ -188,7 +188,7 @@ func activate_process_root(path: String) -> bool: func path_for(store_owner: StringName) -> String: var relative: String = { - &"player_save": "player/player_save.nfsave", + &"player_save": "player/player_save.json", &"network_profile": "player/network_profile.json", &"player_appearance": "player/player_appearance.json", &"saved_servers": "social/saved_servers.json", @@ -208,10 +208,6 @@ func identity_backup_directory() -> String: return root_path.path_join("identity-backups") -func progression_backup_directory() -> String: - return root_path.path_join("progression-backups") - - func migration_backup_directory() -> String: return root_path.path_join("backups/migrations") @@ -351,7 +347,7 @@ func _test_writable(path: String) -> bool: func _create_layout(path: String, id: String) -> bool: for relative: String in [ "player", "social", "backups/saves", "backups/migrations", - "backups/conflicts", "identity-backups", "progression-backups", + "backups/conflicts", "identity-backups", ]: if DirAccess.make_dir_recursive_absolute(path.path_join(relative)) != OK: return false @@ -373,7 +369,6 @@ func _create_layout(path: String, id: String) -> bool: + "This folder is safe to synchronize with tools such as Syncthing.\n" + "Active private identity keys remain device-local.\n" + "Encrypted identity backups require their passphrase.\n" - + "Progression exports do not contain identity keys or social data.\n" + "Chat and Session Mail are not stored here.\n" + "Do not play the same profile on two devices at the same time.\n" + "Conflicting edits are preserved under backups/conflicts; they are not merged.\n" diff --git a/network/portable_data_migration.gd b/network/portable_data_migration.gd index a74db6d..ab8f21d 100644 --- a/network/portable_data_migration.gd +++ b/network/portable_data_migration.gd @@ -122,7 +122,6 @@ static func migrate_active_to( _remove_tree(staging) return {"ok": false, "message": str(created.get("message", ""))} for relative: String in [ - "player/player_save.nfsave", "player/player_save.json", "player/network_profile.json", "player/player_appearance.json", @@ -135,11 +134,7 @@ static func migrate_active_to( var source: String = data_root.root_path.path_join(relative) if not FileAccess.file_exists(source): continue - if not bool( - _copy_verified_owned_file( - source, staging.path_join(relative) - ).get("ok", false) - ): + if not bool(_copy_verified_json(source, staging.path_join(relative)).get("ok", false)): _remove_tree(staging) return {"ok": false, "message": "Migration validation failed for %s." % relative} if DirAccess.dir_exists_absolute(normalized): @@ -253,30 +248,6 @@ static func _copy_verified_json(source: String, destination: String) -> Dictiona } -static func _copy_verified_owned_file( - source: String, - destination: String, -) -> Dictionary: - if source.get_file() != "player_save.nfsave": - return _copy_verified_json(source, destination) - var decoded: Dictionary = ProgressionSaveCodec.read_local_save(source) - if not bool(decoded.get("ok", false)): - return {"ok": false} - var save_data: Dictionary = decoded["data"] - var version: int = int(save_data.get("save_version", -1)) - if version < 1 or version > PlayerSaveManager.SAVE_VERSION: - return {"ok": false} - var bytes: PackedByteArray = PortableFileGuard.read_bytes(source) - if bytes.is_empty() or not _write_bytes(destination, bytes): - return {"ok": false} - return { - "ok": ( - PortableFileGuard.hash_file(destination) - == PortableFileGuard.hash_bytes(bytes) - ) - } - - static func _valid_owned_data(filename: String, data: Dictionary) -> bool: if filename == "player_save.json": var version: int = int(data.get("save_version", -1)) diff --git a/project.godot b/project.godot index f12f6fd..c09f8a1 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="NETFISHING" -config/version="0.14.0-alpha" +config/version="0.13.1-alpha" run/main_scene="res://main/main.tscn" config/features=PackedStringArray("4.7", "GL Compatibility") config/icon="res://art/exported/system_icons/netfishing_256.png" @@ -107,11 +107,6 @@ fish_primary={ , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":10,"pressure":0.0,"pressed":false,"script":null) ] } -reel_alternate={ -"deadzone": 0.2, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":96,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} camera_drag={ "deadzone": 0.2, "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":32,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null) diff --git a/save/player_save_manager.gd b/save/player_save_manager.gd index 1b4c1d2..7a2e626 100644 --- a/save/player_save_manager.gd +++ b/save/player_save_manager.gd @@ -31,13 +31,9 @@ const WorldTimeServiceType = preload("res://world/world_time_service.gd") const WorldWeatherServiceType = preload( "res://world/world_weather_service.gd" ) -const WorldLayoutType = preload("res://world/world_layout.gd") const PlayerJobServiceType = preload("res://jobs/player_job_service.gd") -const PlayerType = preload("res://player/player.gd") -const SAVE_VERSION: int = 10 -const LEGACY_SAVE_FILENAME := "player_save.json" -const ARCHIVE_EXTENSION := ".nfsave" +const SAVE_VERSION: int = 9 const BASIC_ROD_ID: StringName = &"basic_fishing_rod" const MAX_SAFE_BALANCE: int = 1000000000000 const DEFAULT_WORLD_SEED: int = 13001 @@ -53,8 +49,6 @@ class LoadSnapshot: var next_catch_sequence: int = 1 var bag_items: Array[OwnedItemType] = [] var unlocked_bait_ids: Array[StringName] = [] - var active_bait_id: StringName - var active_lure_id: StringName var hotbar_slots: Array[StringName] = [] var fish_hotbar_slots: Array[StringName] = [] var selected_hotbar_slot: int = 0 @@ -64,7 +58,6 @@ class LoadSnapshot: var cooler_capacity_level: int = 0 var art_unlock_mask: int = 0 var total_experience: int = 0 - var world_layout: StringName = WorldLayoutType.GENERATED var world_seed: int = DEFAULT_WORLD_SEED var world_time_hours: float = WorldTimeServiceType.DEFAULT_START_HOUR var has_world_weather_state: bool = false @@ -94,7 +87,6 @@ var _experience: PlayerExperienceType var _world_time: WorldTimeServiceType var _world_weather: WorldWeatherServiceType var _jobs: PlayerJobServiceType -var _player: PlayerType var _autosave_timer: Timer var _is_configured: bool = false var _is_restoring: bool = false @@ -104,7 +96,6 @@ var _autosave_enabled: bool = false var _save_path := "" var _expected_hash := "" var _data_root: PlayerDataRoot -var _world_layout: StringName = WorldLayoutType.GENERATED var _world_seed: int = DEFAULT_WORLD_SEED @@ -121,21 +112,6 @@ func _backup_path() -> String: return _save_path + ".backup" -func _codec_scratch_path() -> String: - return _save_path + ".codec.tmp" - - -func _legacy_save_path() -> String: - return _save_path.get_base_dir().path_join(LEGACY_SAVE_FILENAME) - - -func _read_path() -> String: - if FileAccess.file_exists(_save_path): - return _save_path - var legacy: String = _legacy_save_path() - return legacy if FileAccess.file_exists(legacy) else "" - - func _ready() -> void: _autosave_timer = Timer.new() _autosave_timer.one_shot = true @@ -159,7 +135,6 @@ func setup( world_time: WorldTimeServiceType, world_weather: WorldWeatherServiceType, jobs: PlayerJobServiceType, - player: PlayerType, ) -> void: _inventory = inventory _collection_log = collection_log @@ -176,7 +151,6 @@ func setup( _world_time = world_time _world_weather = world_weather _jobs = jobs - _player = player _is_configured = ( _inventory != null and _collection_log != null @@ -193,7 +167,6 @@ func setup( and _world_time != null and _world_weather != null and _jobs != null - and _player != null ) if not _is_configured: push_error("PlayerSaveManager setup is missing required references.") @@ -236,10 +209,6 @@ func setup( _experience.experience_changed.connect(_on_experience_changed) if not _jobs.changed.is_connected(_mark_dirty): _jobs.changed.connect(_mark_dirty) - if not _player.active_bait_changed.is_connected(_on_active_tackle_changed): - _player.active_bait_changed.connect(_on_active_tackle_changed) - if not _player.active_lure_changed.is_connected(_on_active_tackle_changed): - _player.active_lure_changed.connect(_on_active_tackle_changed) func load_player_data() -> bool: @@ -247,22 +216,24 @@ func load_player_data() -> bool: return false _automatic_saving_blocked = false _recover_interrupted_write() - var read_path: String = _read_path() - if _save_path.is_empty() or read_path.is_empty(): + if _save_path.is_empty() or not FileAccess.file_exists(_save_path): _is_dirty = false return true - var decoded: Dictionary = ProgressionSaveCodec.read_local_save( - read_path, - read_path == _legacy_save_path(), - ) - if not bool(decoded.get("ok", false)): - _handle_corrupt_save("Player save could not be decoded.", read_path) + + var save_file := FileAccess.open(_save_path, FileAccess.READ) + if save_file == null: + _handle_corrupt_save("Unable to open player save.") return false - _expected_hash = ( - PortableFileGuard.hash_file(_save_path) - if read_path == _save_path else "" - ) - var save_data: Dictionary = decoded["data"] + var json_text: String = save_file.get_as_text() + save_file.close() + _expected_hash = PortableFileGuard.hash_file(_save_path) + + var json := JSON.new() + var parse_error: Error = json.parse(json_text) + if parse_error != OK or typeof(json.data) != TYPE_DICTIONARY: + _handle_corrupt_save("Player save contains malformed JSON.") + return false + var save_data: Dictionary = json.data var version: int = _read_integer(save_data.get("save_version"), -1) if version > SAVE_VERSION: _automatic_saving_blocked = true @@ -279,16 +250,13 @@ func load_player_data() -> bool: save_data = _migrate_save(save_data, version) if save_data.is_empty(): _handle_corrupt_save( - "Player save version %d is unsupported." % version, - read_path, + "Player save version %d is unsupported." % version ) return false var snapshot: LoadSnapshot = _build_load_snapshot(save_data) if snapshot == null: - _handle_corrupt_save( - "Player save failed structural validation.", read_path - ) + _handle_corrupt_save("Player save failed structural validation.") return false _is_restoring = true @@ -309,7 +277,6 @@ func load_player_data() -> bool: _bag.replace_all_items(snapshot.bag_items) and _bag.replace_unlocked_bait_ids(snapshot.unlocked_bait_ids) ) - var tackle_restored: bool = _restore_tackle_selection(snapshot) var upgrades_restored: bool = _fishing_upgrades.restore_levels( snapshot.reel_speed_level, snapshot.barrier_power_level @@ -335,7 +302,6 @@ func load_player_data() -> bool: var world_time_restored: bool = ( _world_time.restore_persistent_time_hours(snapshot.world_time_hours) ) - _world_layout = snapshot.world_layout _world_seed = snapshot.world_seed var world_weather_restored: bool = true if snapshot.has_world_weather_state: @@ -350,7 +316,6 @@ func load_player_data() -> bool: or not collection_restored or not wallet_restored or not bag_restored - or not tackle_restored or not hotbar_restored or not upgrades_restored or not cooler_restored @@ -364,7 +329,7 @@ func load_player_data() -> bool: push_error( ( "Validated player save could not be restored: " - + "inventory=%s collection=%s wallet=%s bag=%s tackle=%s hotbar=%s " + + "inventory=%s collection=%s wallet=%s bag=%s hotbar=%s " + "upgrades=%s cooler=%s layout=%s art=%s experience=%s " + "time=%s weather=%s jobs=%s" ) @@ -373,7 +338,6 @@ func load_player_data() -> bool: collection_restored, wallet_restored, bag_restored, - tackle_restored, hotbar_restored, upgrades_restored, cooler_restored, @@ -388,8 +352,6 @@ func load_player_data() -> bool: return false _is_dirty = false - if read_path == _legacy_save_path(): - _migrate_legacy_plaintext_save(save_data, read_path) print( "Loaded player save version %d with %d catches." % [SAVE_VERSION, snapshot.catches.size()] @@ -400,21 +362,24 @@ func load_player_data() -> bool: func inspect_save() -> SaveInspectionType: var result := SaveInspectionType.new() _recover_interrupted_write() - var read_path: String = _read_path() - result.has_primary_file = not read_path.is_empty() + result.has_primary_file = FileAccess.file_exists(_save_path) if not result.has_primary_file: result.status = SaveInspectionType.Status.MISSING result.message = "no save found." return result - var decoded: Dictionary = ProgressionSaveCodec.read_local_save( - read_path, - read_path == _legacy_save_path(), - ) - if not bool(decoded.get("ok", false)): + var save_file := FileAccess.open(_save_path, FileAccess.READ) + if save_file == null: result.status = SaveInspectionType.Status.IO_ERROR result.message = "the save could not be read." return result - var save_data: Dictionary = decoded["data"] + var json := JSON.new() + var parse_error: Error = json.parse(save_file.get_as_text()) + save_file.close() + if parse_error != OK or typeof(json.data) != TYPE_DICTIONARY: + result.status = SaveInspectionType.Status.MALFORMED + result.message = "the save is corrupt and was preserved." + return result + var save_data: Dictionary = json.data result.detected_version = _read_integer(save_data.get("save_version"), -1) if result.detected_version > SAVE_VERSION: result.status = SaveInspectionType.Status.UNSUPPORTED_VERSION @@ -443,112 +408,18 @@ func inspect_save() -> SaveInspectionType: return result -func export_progression_archive(path: String) -> Dictionary: - if not _is_configured or path.is_empty(): - return {"ok": false, "message": "progression export is unavailable."} - if _is_dirty and not save_if_dirty(): - return {"ok": false, "message": "progression could not be saved first."} - var source_path: String = _read_path() - if source_path.is_empty(): - return {"ok": false, "message": "there is no progression to export."} - var decoded: Dictionary = ProgressionSaveCodec.read_local_save( - source_path, - source_path == _legacy_save_path(), - ) - if not bool(decoded.get("ok", false)): - return {"ok": false, "message": "the active progression could not be read."} - var prepared: Dictionary = _prepare_external_save_data(decoded["data"]) - if not bool(prepared.get("ok", false)): - return prepared - var bytes: PackedByteArray = ProgressionSaveCodec.encode_archive( - prepared["data"], - path + ".codec.tmp", - ) - if bytes.is_empty(): - return {"ok": false, "message": "the progression archive could not be encoded."} - var result: Dictionary = PortableFileGuard.write_guarded( - path, - bytes, - PortableFileGuard.hash_file(path), - _data_root.conflict_directory(), - _data_root.device_id, - ) - if not bool(result.get("ok", false)): - return {"ok": false, "message": "the progression archive could not be written."} - var verified: Dictionary = inspect_progression_archive(path) - if not bool(verified.get("ok", false)): - return {"ok": false, "message": "the progression archive could not be verified."} - verified["message"] = "progression archive created." - return verified - - -func inspect_progression_archive(path: String) -> Dictionary: - var decoded: Dictionary = ProgressionSaveCodec.read_archive(path) - if not bool(decoded.get("ok", false)): - return { - "ok": false, - "message": str(decoded.get("message", "could not open progression archive.")), - } - var prepared: Dictionary = _prepare_external_save_data(decoded["data"]) - if not bool(prepared.get("ok", false)): - return prepared - prepared["game_version"] = str(decoded.get("game_version", "")) - prepared["created_at_unix"] = int(decoded.get("created_at_unix", 0)) - return prepared - - -func import_progression_archive(path: String) -> Dictionary: - if not _is_configured: - return {"ok": false, "message": "progression import is unavailable."} - var inspected: Dictionary = inspect_progression_archive(path) - if not bool(inspected.get("ok", false)): - return inspected - var current_path: String = _read_path() - if not current_path.is_empty() and not _archive_replaced_save(current_path): - return {"ok": false, "message": "the current progression could not be backed up."} - var expected_hash: String = PortableFileGuard.hash_file(_save_path) - var result: Dictionary = _write_current_save_data( - inspected["data"], expected_hash - ) - if not bool(result.get("ok", false)): - return {"ok": false, "message": "the imported progression could not be installed."} - _expected_hash = str(result.get("hash", "")) - _remove_legacy_save_files() - _is_dirty = false - _automatic_saving_blocked = false - return { - "ok": true, - "message": "progression imported.", - "catch_count": inspected["catch_count"], - "wallet_balance": inspected["wallet_balance"], - "discovered_species_count": inspected["discovered_species_count"], - } - - -func initialize_new_game( - world_seed: int = DEFAULT_WORLD_SEED, - world_layout: StringName = WorldLayoutType.GENERATED, -) -> bool: +func initialize_new_game(world_seed: int = DEFAULT_WORLD_SEED) -> bool: if not _is_configured: return false - if ( - world_seed <= 0 - or world_seed > MAX_WORLD_SEED - or not WorldLayoutType.is_valid(world_layout) - ): + if world_seed <= 0 or world_seed > MAX_WORLD_SEED: return false _automatic_saving_blocked = false _restore_defaults() - _world_layout = world_layout _world_seed = world_seed _is_dirty = true return true -func get_world_layout() -> StringName: - return _world_layout - - func get_world_seed() -> int: return _world_seed @@ -560,19 +431,9 @@ static func roll_world_seed() -> int: func delete_progression_save() -> bool: - for primary_path: String in [_save_path, _legacy_save_path()]: - if ( - FileAccess.file_exists(primary_path) - and not _remove_if_present(primary_path) - ): - return false - for path: String in [ - _temp_path(), - _backup_path(), - _codec_scratch_path(), - _legacy_save_path() + ".tmp", - _legacy_save_path() + ".backup", - ]: + if FileAccess.file_exists(_save_path) and not _remove_if_present(_save_path): + return false + for path: String in [_temp_path(), _backup_path()]: if FileAccess.file_exists(path) and not _remove_if_present(path): push_warning("Unable to remove stale player-save auxiliary file.") if _autosave_timer != null: @@ -624,8 +485,16 @@ func save_now() -> bool: var save_data: Dictionary = _build_save_dictionary() if save_data.is_empty(): return false - var result: Dictionary = _write_current_save_data( - save_data, _expected_hash + var json_text: String = JSON.stringify(save_data, "\t") + if json_text.is_empty(): + return false + + var result := PortableFileGuard.write_guarded( + _save_path, + json_text.to_utf8_buffer(), + _expected_hash, + _data_root.conflict_directory(), + _data_root.device_id, ) if bool(result.get("conflict", false)): _data_root.report_conflict( @@ -705,17 +574,6 @@ func _build_save_dictionary() -> Dictionary: ) ): return {} - var active_bait_id: StringName = _player.active_bait_id - var active_lure_id: StringName = _player.active_lure_id - if ( - (not active_bait_id.is_empty() and not _valid_active_tackle( - active_bait_id, true - )) - or (not active_lure_id.is_empty() and not _valid_active_tackle( - active_lure_id, false - )) - ): - return {} return { "save_version": SAVE_VERSION, @@ -734,10 +592,6 @@ func _build_save_dictionary() -> Dictionary: "items": serialized_items, "unlocked_bait_ids": serialized_unlocked_baits, }, - "tackle": { - "active_bait_id": String(active_bait_id), - "active_lure_id": String(active_lure_id), - }, "hotbar": { "selected_slot": _hotbar.get_selected_slot(), "slots": serialized_slots, @@ -749,7 +603,6 @@ func _build_save_dictionary() -> Dictionary: "art": _art_unlocks.to_save_data(), "experience": _experience.to_save_data(), "world": { - "layout": String(_world_layout), "seed": _world_seed, "time_hours": _world_time.get_persistent_time_hours(), "weather": int(_world_weather.get_persistent_weather()), @@ -767,7 +620,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: or typeof(save_data.get("collection")) != TYPE_DICTIONARY or typeof(save_data.get("inventory")) != TYPE_DICTIONARY or typeof(save_data.get("bag")) != TYPE_DICTIONARY - or typeof(save_data.get("tackle")) != TYPE_DICTIONARY or typeof(save_data.get("hotbar")) != TYPE_DICTIONARY or typeof(save_data.get("inventory_layout")) != TYPE_DICTIONARY or typeof(save_data.get("experience")) != TYPE_DICTIONARY @@ -778,7 +630,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: var collection_data: Dictionary = save_data["collection"] var inventory_data: Dictionary = save_data["inventory"] var bag_data: Dictionary = save_data["bag"] - var tackle_data: Dictionary = save_data["tackle"] var hotbar_data: Dictionary = save_data["hotbar"] var inventory_layout_data: Dictionary = save_data["inventory_layout"] var experience_data: Dictionary = save_data["experience"] @@ -814,12 +665,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: and typeof(bag_data.get("unlocked_bait_ids")) != TYPE_ARRAY ) or typeof(hotbar_data.get("slots")) != TYPE_ARRAY - or typeof(tackle_data.get("active_bait_id")) not in [ - TYPE_STRING, TYPE_STRING_NAME, - ] - or typeof(tackle_data.get("active_lure_id")) not in [ - TYPE_STRING, TYPE_STRING_NAME, - ] or not hotbar_data.has("selected_slot") or not experience_data.has("total_experience") ): @@ -839,12 +684,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: return null var snapshot := LoadSnapshot.new() - if world_data.has("layout"): - if not WorldLayoutType.is_valid(world_data["layout"]): - return null - snapshot.world_layout = WorldLayoutType.normalized( - world_data["layout"] - ) if world_data.has("seed"): snapshot.world_seed = _read_integer( world_data["seed"], @@ -1058,16 +897,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: ): seen_unlocked_baits[owned.item_id] = true snapshot.unlocked_bait_ids.append(owned.item_id) - snapshot.active_bait_id = _validated_tackle_id( - StringName(str(tackle_data.get("active_bait_id", ""))), - true, - seen_items, - ) - snapshot.active_lure_id = _validated_tackle_id( - StringName(str(tackle_data.get("active_lure_id", ""))), - false, - seen_items, - ) var slot_values: Array = hotbar_data["slots"] snapshot.hotbar_slots.resize(PlayerHotbarType.SLOT_COUNT) @@ -1166,8 +995,6 @@ func _migrate_save( migrated = _migrate_version_7_to_8(migrated) 8: migrated = _migrate_version_8_to_9(migrated) - 9: - migrated = _migrate_version_9_to_10(migrated) _: return {} if migrated.is_empty(): @@ -1415,16 +1242,6 @@ func _migrate_version_8_to_9(data: Dictionary) -> Dictionary: return migrated -func _migrate_version_9_to_10(data: Dictionary) -> Dictionary: - var migrated: Dictionary = data.duplicate(true) - migrated["tackle"] = { - "active_bait_id": "", - "active_lure_id": "", - } - migrated["save_version"] = 10 - return migrated - - func _mark_dirty() -> void: if ( _is_restoring @@ -1474,169 +1291,33 @@ func _on_experience_changed(_total_experience: int, _level: int) -> void: _mark_dirty() -func _on_active_tackle_changed(_item_id: StringName) -> void: - _mark_dirty() - - func _on_autosave_timeout() -> void: if _is_dirty: save_now() func _recover_interrupted_write() -> void: - _remove_if_present(_codec_scratch_path()) - _recover_path_write(_save_path) - _recover_path_write(_legacy_save_path()) - - -func _recover_path_write(path: String) -> void: - var temporary: String = path + ".tmp" - var backup: String = path + ".backup" - if FileAccess.file_exists(path): - _remove_if_present(temporary) - _remove_if_present(backup) + if FileAccess.file_exists(_save_path): + _remove_if_present(_temp_path()) + _remove_if_present(_backup_path()) return - if FileAccess.file_exists(backup): - _rename_file(backup, path) - _remove_if_present(temporary) + if FileAccess.file_exists(_backup_path()): + _rename_file(_backup_path(), _save_path) + _remove_if_present(_temp_path()) -func _write_current_save_data( - save_data: Dictionary, - expected_hash: String, -) -> Dictionary: - var bytes: PackedByteArray = ProgressionSaveCodec.encode_local_save( - save_data, - _codec_scratch_path(), - ) - if bytes.is_empty(): - return {"ok": false} - return PortableFileGuard.write_guarded( - _save_path, - bytes, - expected_hash, - _data_root.conflict_directory(), - _data_root.device_id, - ) - - -func _prepare_external_save_data(value: Variant) -> Dictionary: - if typeof(value) != TYPE_DICTIONARY: - return {"ok": false, "message": "progression data is malformed."} - var save_data: Dictionary = (value as Dictionary).duplicate(true) - var version: int = _read_integer(save_data.get("save_version"), -1) - if version > SAVE_VERSION: - return { - "ok": false, - "message": "this progression belongs to a newer game version.", - } - if version < 1: - return {"ok": false, "message": "the progression version is unsupported."} - if version != SAVE_VERSION: - save_data = _migrate_save(save_data, version) - if save_data.is_empty(): - return { - "ok": false, - "message": "the progression version is unsupported.", - } - var snapshot: LoadSnapshot = _build_load_snapshot(save_data) - if snapshot == null: - return {"ok": false, "message": "the progression is structurally invalid."} - return { - "ok": true, - "data": save_data, - "catch_count": snapshot.catches.size(), - "wallet_balance": snapshot.wallet_balance, - "discovered_species_count": snapshot.discovered_ids.size(), - "world_layout": String(snapshot.world_layout), - "world_seed": snapshot.world_seed, - } - - -func _migrate_legacy_plaintext_save( - save_data: Dictionary, - legacy_path: String, -) -> void: - var result: Dictionary = _write_current_save_data(save_data, "") - if not bool(result.get("ok", false)): - push_warning("Legacy progression loaded but could not be encrypted yet.") - return - _expected_hash = str(result.get("hash", "")) - if not _archive_replaced_save(legacy_path, "plaintext-migration"): - push_warning( - "Legacy progression was encrypted, but its migration backup failed." - ) - return - _remove_legacy_save_files() - print("Upgraded legacy plaintext progression to the opaque save format.") - - -func _archive_replaced_save( - source_path: String, - reason: String = "before-import", -) -> bool: - if source_path.is_empty() or not FileAccess.file_exists(source_path): - return true - var timestamp: String = Time.get_datetime_string_from_system().replace( - ":", "-" - ) - var destination: String = _data_root.root_path.path_join( - "backups/saves/player-save-%s-%s%s" - % [reason, timestamp, ARCHIVE_EXTENSION] - ) - if FileAccess.file_exists(destination): - destination = destination.trim_suffix(ARCHIVE_EXTENSION) + ( - "-%s%s" % [Time.get_ticks_usec(), ARCHIVE_EXTENSION] - ) - var decoded: Dictionary = ProgressionSaveCodec.read_local_save( - source_path, - source_path == _legacy_save_path(), - ) - var bytes: PackedByteArray - if bool(decoded.get("ok", false)): - var prepared: Dictionary = _prepare_external_save_data(decoded["data"]) - if bool(prepared.get("ok", false)): - bytes = ProgressionSaveCodec.encode_archive( - prepared["data"], destination + ".codec.tmp" - ) - if bytes.is_empty(): - bytes = PortableFileGuard.read_bytes(source_path) - destination = ( - destination.trim_suffix(ARCHIVE_EXTENSION) + ".preserved" - ) - if bytes.is_empty(): - return false - var result: Dictionary = PortableFileGuard.write_guarded( - destination, - bytes, - "", - _data_root.conflict_directory(), - _data_root.device_id, - ) - return bool(result.get("ok", false)) - - -func _remove_legacy_save_files() -> void: - for path: String in [ - _legacy_save_path(), - _legacy_save_path() + ".tmp", - _legacy_save_path() + ".backup", - ]: - _remove_if_present(path) - - -func _handle_corrupt_save(message: String, source_path: String) -> void: +func _handle_corrupt_save(message: String) -> void: push_warning(message) _restore_defaults() - if not FileAccess.file_exists(source_path): + if not FileAccess.file_exists(_save_path): return var timestamp: int = int(Time.get_unix_time_from_system()) var corrupt_path: String = ( - "%s.corrupt-%d" % [source_path, timestamp] + "%s.corrupt-%d" % [_save_path, timestamp] ) if FileAccess.file_exists(corrupt_path): corrupt_path += "-%d" % Time.get_ticks_usec() - if not _rename_file(source_path, corrupt_path): + if not _rename_file(_save_path, corrupt_path): _automatic_saving_blocked = true push_warning( "Corrupt player save was left in place; automatic saving is " @@ -1669,8 +1350,6 @@ func _restore_defaults() -> void: _bag.replace_all_items(default_items) var default_unlocked_baits: Array[StringName] = [] _bag.replace_unlocked_bait_ids(default_unlocked_baits) - _player.unequip_bait() - _player.unequip_lure() _fishing_upgrades.reset_to_defaults() _cooler_capacity.reset_to_defaults() _inventory_layout.reset_to_defaults() @@ -1681,60 +1360,12 @@ func _restore_defaults() -> void: WorldTimeServiceType.DEFAULT_START_HOUR ) _world_weather.reset_persistent_state() - _world_layout = WorldLayoutType.GENERATED _world_seed = DEFAULT_WORLD_SEED _jobs.reset_to_defaults() _is_restoring = false _is_dirty = false -func _valid_active_tackle(item_id: StringName, bait: bool) -> bool: - var item = _item_catalog.get_item_by_id(item_id) - return ( - item != null - and item.is_available() - and (item.is_bait() if bait else item.is_lure()) - and _bag.owns_item(item_id) - ) - - -func _validated_tackle_id( - item_id: StringName, - bait: bool, - seen_items: Dictionary[StringName, bool], -) -> StringName: - if item_id.is_empty(): - return StringName() - var item = _item_catalog.get_item_by_id(item_id) - if ( - item == null - or not item.is_available() - or not seen_items.has(item_id) - or not (item.is_bait() if bait else item.is_lure()) - ): - push_warning("Skipped invalid saved active tackle '%s'." % String(item_id)) - return StringName() - return item_id - - -func _restore_tackle_selection(snapshot: LoadSnapshot) -> bool: - _player.unequip_bait() - _player.unequip_lure() - var bait_restored: bool = ( - snapshot.active_bait_id.is_empty() - or _player.equip_bait( - _item_catalog.get_item_by_id(snapshot.active_bait_id) - ) - ) - var lure_restored: bool = ( - snapshot.active_lure_id.is_empty() - or _player.equip_lure( - _item_catalog.get_item_by_id(snapshot.active_lure_id) - ) - ) - return bait_restored and lure_restored - - func _read_integer( value: Variant, invalid_value: int, diff --git a/save/progression_save_codec.gd b/save/progression_save_codec.gd deleted file mode 100644 index 7844e16..0000000 --- a/save/progression_save_codec.gd +++ /dev/null @@ -1,179 +0,0 @@ -class_name ProgressionSaveCodec -extends RefCounted - -const MAGIC := "NETFISHING_PROGRESSION_CONTAINER" -const FORMAT_VERSION := 1 -const LOCAL_KIND := "local_save" -const ARCHIVE_KIND := "portable_archive" -const MAX_CONTAINER_BYTES := 16 * 1024 * 1024 - -# This key keeps routine progression files opaque to casual editing. Since the -# client is open source, it is intentionally not treated as a security secret. -const CONTAINER_PASSPHRASE := ( - "NETfishing progression container v1 / not an identity credential" -) - - -static func encode_local_save( - save_data: Dictionary, - scratch_path: String, -) -> PackedByteArray: - return _encode_container(LOCAL_KIND, save_data, scratch_path) - - -static func encode_archive( - save_data: Dictionary, - scratch_path: String, -) -> PackedByteArray: - return _encode_container(ARCHIVE_KIND, save_data, scratch_path) - - -static func read_local_save( - path: String, - allow_legacy_plaintext: bool = false, -) -> Dictionary: - if allow_legacy_plaintext and not _looks_encrypted(path): - var legacy: Dictionary = _read_plaintext_dictionary(path) - if bool(legacy.get("ok", false)): - legacy["legacy_plaintext"] = true - return legacy - return _read_container(path, LOCAL_KIND) - - -static func read_archive(path: String) -> Dictionary: - if not _looks_encrypted(path): - return {"ok": false, "message": "progression archive is not recognized."} - return _read_container(path, ARCHIVE_KIND) - - -static func _encode_container( - kind: String, - save_data: Dictionary, - scratch_path: String, -) -> PackedByteArray: - if save_data.is_empty() or scratch_path.is_empty(): - return PackedByteArray() - var payload_json: String = JSON.stringify(save_data) - if payload_json.is_empty(): - return PackedByteArray() - var payload_bytes: PackedByteArray = payload_json.to_utf8_buffer() - var envelope: Dictionary = { - "magic": MAGIC, - "format_version": FORMAT_VERSION, - "kind": kind, - "game_version": str( - ProjectSettings.get_setting("application/config/version", "") - ), - "created_at_unix": int(Time.get_unix_time_from_system()), - "payload_sha256": PortableFileGuard.hash_bytes(payload_bytes), - "payload_json": payload_json, - } - if DirAccess.make_dir_recursive_absolute( - scratch_path.get_base_dir() - ) != OK: - return PackedByteArray() - _remove_if_present(scratch_path) - var file: FileAccess = FileAccess.open_encrypted_with_pass( - scratch_path, - FileAccess.WRITE, - CONTAINER_PASSPHRASE, - ) - if file == null: - return PackedByteArray() - file.store_string(JSON.stringify(envelope)) - file.flush() - var write_ok: bool = file.get_error() == OK - file.close() - if not write_ok: - _remove_if_present(scratch_path) - return PackedByteArray() - var encoded: PackedByteArray = PortableFileGuard.read_bytes( - scratch_path, - MAX_CONTAINER_BYTES, - ) - _remove_if_present(scratch_path) - return encoded - - -static func _read_container(path: String, expected_kind: String) -> Dictionary: - if ( - path.is_empty() - or not FileAccess.file_exists(path) - or FileAccess.get_size(path) > MAX_CONTAINER_BYTES - ): - return {"ok": false, "message": "progression file is unavailable."} - var file: FileAccess = FileAccess.open_encrypted_with_pass( - path, - FileAccess.READ, - CONTAINER_PASSPHRASE, - ) - if file == null: - return {"ok": false, "message": "progression file could not be opened."} - var envelope_text: String = file.get_as_text() - file.close() - var envelope_json := JSON.new() - if ( - envelope_json.parse(envelope_text) != OK - or typeof(envelope_json.data) != TYPE_DICTIONARY - ): - return {"ok": false, "message": "progression container is malformed."} - var envelope: Dictionary = envelope_json.data - if ( - envelope.get("magic") != MAGIC - or int(envelope.get("format_version", -1)) != FORMAT_VERSION - or str(envelope.get("kind", "")) != expected_kind - ): - return {"ok": false, "message": "progression container is unsupported."} - var payload_json: String = str(envelope.get("payload_json", "")) - var payload_bytes: PackedByteArray = payload_json.to_utf8_buffer() - if ( - payload_json.is_empty() - or str(envelope.get("payload_sha256", "")) - != PortableFileGuard.hash_bytes(payload_bytes) - ): - return {"ok": false, "message": "progression container failed validation."} - var payload_parser := JSON.new() - if ( - payload_parser.parse(payload_json) != OK - or typeof(payload_parser.data) != TYPE_DICTIONARY - ): - return {"ok": false, "message": "progression payload is malformed."} - return { - "ok": true, - "data": payload_parser.data, - "game_version": str(envelope.get("game_version", "")), - "created_at_unix": int(envelope.get("created_at_unix", 0)), - "legacy_plaintext": false, - } - - -static func _read_plaintext_dictionary(path: String) -> Dictionary: - var bytes: PackedByteArray = PortableFileGuard.read_bytes( - path, - MAX_CONTAINER_BYTES, - ) - if bytes.is_empty(): - return {"ok": false} - var text: String = bytes.get_string_from_utf8() - if not text.strip_edges().begins_with("{"): - return {"ok": false} - var parser := JSON.new() - if parser.parse(text) != OK or typeof(parser.data) != TYPE_DICTIONARY: - return {"ok": false} - return {"ok": true, "data": parser.data} - - -static func _looks_encrypted(path: String) -> bool: - if not FileAccess.file_exists(path) or FileAccess.get_size(path) < 4: - return false - var file := FileAccess.open(path, FileAccess.READ) - if file == null: - return false - var header: PackedByteArray = file.get_buffer(4) - file.close() - return header == PackedByteArray([0x47, 0x44, 0x45, 0x43]) - - -static func _remove_if_present(path: String) -> void: - if FileAccess.file_exists(path): - DirAccess.remove_absolute(path) diff --git a/save/progression_save_codec.gd.uid b/save/progression_save_codec.gd.uid deleted file mode 100644 index 6db5823..0000000 --- a/save/progression_save_codec.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://lad8mvedktww diff --git a/scripts/build_playtest.sh b/scripts/build_playtest.sh index d409d7a..a1874c4 100755 --- a/scripts/build_playtest.sh +++ b/scripts/build_playtest.sh @@ -4,14 +4,14 @@ set -euo pipefail readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" -readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.14.0-alpha" +readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.13.1-alpha" readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64" readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64" readonly README_SOURCE="${PROJECT_ROOT}/docs/README-PLAYTEST.txt" readonly SOURCE_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)" readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/netfishing" -readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.14.0-alpha-windows-x86_64.zip" -readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.14.0-alpha-linux-x86_64.zip" +readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.13.1-alpha-windows-x86_64.zip" +readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.13.1-alpha-linux-x86_64.zip" readonly GODOT_BIN="${GODOT_BIN:-godot}" if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then diff --git a/scripts/build_portmaster.sh b/scripts/build_portmaster.sh index 833305b..b5ad5ae 100755 --- a/scripts/build_portmaster.sh +++ b/scripts/build_portmaster.sh @@ -223,7 +223,7 @@ music while omitting rain and shoreline ambience in both profiles. See ## Controls -NETfishing reads the handheld controller directly. GPTOKEYB's device-specific +NETfishing reads the handheld controller directly. PortMaster's system force-quit chord remains available; this is Start+Select on most devices. EOF @@ -249,11 +249,8 @@ if unzip -Z1 "${ARCHIVE}" | grep -qx 'port.json'; then echo "port.json must be inside netfishing/, not at the archive root." >&2 exit 1 fi -if grep -Eq '^[[:space:]]*#[[:space:]]*PORTMASTER:' \ - "${STAGE_ROOT}/NETfishing.sh"; then - echo "The distributable launcher contains an installed PortMaster signature." >&2 - exit 1 -fi +grep -q '^# PORTMASTER: netfishing.zip, NETfishing.sh$' \ + "${STAGE_ROOT}/NETfishing.sh" grep -Fq 'PROFILE_PATH="$CONFDIR/performance_profile"' \ "${STAGE_ROOT}/NETfishing.sh" grep -Fq 'NETFISHING_PERFORMANCE_PROFILE=light' \ diff --git a/scripts/portmaster/NETfishing.sh b/scripts/portmaster/NETfishing.sh index 8e118da..fe6d778 100755 --- a/scripts/portmaster/NETfishing.sh +++ b/scripts/portmaster/NETfishing.sh @@ -1,4 +1,5 @@ #!/bin/bash +# PORTMASTER: netfishing.zip, NETfishing.sh XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} @@ -12,11 +13,19 @@ else controlfolder="/roms/ports/PortMaster" fi +if [ ! -f "$controlfolder/control.txt" ]; then + echo "PortMaster control.txt was not found at $controlfolder" >&2 + exit 1 +fi + source "$controlfolder/control.txt" [ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" get_controls GAMEDIR="/${directory}/ports/netfishing" +if [ ! -d "$GAMEDIR" ] && [ -d "/mnt/mmc/ports/netfishing" ]; then + GAMEDIR="/mnt/mmc/ports/netfishing" +fi CONFDIR="$GAMEDIR/conf" PORTS_ROOT="${GAMEDIR%/netfishing}" @@ -29,13 +38,16 @@ CONTROLLER_MAPPING_FILE="$CONFDIR/cache/controller_mapping.txt" WESTON_DIR="/tmp/netfishing-weston" WESTON_RUNTIME="weston_pkg_0.2" HARBOURMASTER="$controlfolder/harbourmaster" +if [ ! -x "$HARBOURMASTER" ] && [ -x "/mnt/mmc/MUOS/PortMaster/harbourmaster" ]; then + HARBOURMASTER="/mnt/mmc/MUOS/PortMaster/harbourmaster" +fi mkdir -p "$CONFDIR/data" "$CONFDIR/config" "$CONFDIR/cache" "$WESTON_DIR" chmod +x "$GAME_EXECUTABLE" -# NETfishing uses a predictable save directory beside the port instead of -# presenting a folder picker on a small screen. Preserve an established -# data-root choice when upgrading an existing installation. +# New PortMaster installs use a predictable save directory beside the port +# instead of presenting a folder picker on a small screen. Preserve an +# established data-root choice when upgrading an existing installation. NETFISHING_DATA_ENVIRONMENT=() if [ -f "$SAVEDIR/netfishing_data.json" ] || { [ ! -f "$DATA_BOOTSTRAP_DIR/data_root_bootstrap.json" ] && @@ -127,9 +139,10 @@ case "$PERFORMANCE_PROFILE" in ;; esac -# NETfishing reads gameplay input natively. The explicit no-op map prevents -# GPTOKEYB from injecting duplicate keyboard or mouse input while retaining -# its device-specific force-quit chord, including on ROCKNIX. +# Keep NETfishing's native controller input separate from PortMaster's exit +# handling. Legacy GPTOKEYB builds can load their default keyboard map when no +# config is supplied, so the explicit no-op map preserves only the +# device-specific force-quit chord. $GPTOKEYB "NETfishing.aarch64" -c "$GPTOKEYB_CONFIG" & pm_platform_helper "$GAME_EXECUTABLE" diff --git a/scripts/portmaster/netfishing.gptk b/scripts/portmaster/netfishing.gptk index ff1ad36..ac03270 100644 --- a/scripts/portmaster/netfishing.gptk +++ b/scripts/portmaster/netfishing.gptk @@ -1,6 +1,5 @@ -# NETfishing reads the controller directly. GPTOKEYB is retained only for its -# device-specific force-quit chord, so every keyboard and mouse binding is -# disabled to prevent duplicate input. +# NETfishing reads the controller directly. GPTOKEYB is retained only for the +# PortMaster force-quit chord, so every keyboard and mouse binding is disabled. back = \" guide = \" diff --git a/scripts/run_validations.sh b/scripts/run_validations.sh index 6bf5b1f..67b8abe 100755 --- a/scripts/run_validations.sh +++ b/scripts/run_validations.sh @@ -15,7 +15,6 @@ readonly -a QUICK_TESTS=( "tests/android_readiness_validation.gd" "tests/camera_drag_validation.gd" "tests/character_rig_validation.gd" - "tests/chat_validation.gd" "tests/controller_focus_presentation_validation.gd" "tests/controller_menu_accessibility_validation.gd" "tests/controller_mapping_validation.gd" @@ -23,7 +22,6 @@ readonly -a QUICK_TESTS=( "tests/controller_ui_navigation_validation.gd" "tests/dedicated_server_config_validation.gd" "tests/digging_prototype_validation.gd" - "tests/equipment_catalog_validation.gd" "tests/exported_decal_hotfix_validation.gd" "tests/file_dialog_controller_navigation_validation.gd" "tests/fish_catalog_content_validation.gd" @@ -52,7 +50,6 @@ readonly -a QUICK_TESTS=( "tests/tree_gathering_prototype_validation.gd" "tests/unified_inventory_validation.gd" "tests/world_time_validation.gd" - "tests/world_layout_validation.gd" "tests/world_spawn_protocol_validation.gd" "tests/world_weather_validation.gd" ) @@ -75,12 +72,10 @@ readonly -a HOST_TESTS=( "tests/gathering_showcase_validation.gd" "tests/inventory_storage_persistence_validation.gd" "tests/job_system_validation.gd" - "tests/progression_archive_validation.gd" "tests/surface_drawing_runtime_validation.gd" ) readonly -a NETWORK_TESTS=( - "tests/chat_privacy_multiplayer_validation.gd" "tests/economy_regression_validation.gd" "tests/fish_showcase_multiplayer_validation.gd" "tests/fishing_multiplayer_validation.gd" @@ -90,7 +85,6 @@ readonly -a NETWORK_TESTS=( "tests/profile_multiplayer_validation.gd" "tests/surface_drawing_multiplayer_validation.gd" "tests/world_time_multiplayer_validation.gd" - "tests/world_layout_multiplayer_validation.gd" "tests/world_spawn_multiplayer_validation.gd" ) @@ -146,10 +140,7 @@ wait_for_network_host() { sleep 2 return 0 fi - # A cold generated-world startup can take longer than ten seconds before - # NetworkSession is ready to bind. Keep the client launch tied to the actual - # listener instead of treating that normal startup work as a host failure. - for _attempt in {1..600}; do + for _attempt in {1..100}; do if ! kill -0 "${host_pid}" 2>/dev/null; then return 1 fi @@ -235,7 +226,7 @@ run_session_switch_network_test() { --headless --path "${PROJECT_ROOT}" --script "${script}" -- second_host \ >"${second_root}/output.log" 2>&1 & second_pid=$! - for _attempt in {1..600}; do + for _attempt in {1..100}; do if ! kill -0 "${first_pid}" 2>/dev/null \ || ! kill -0 "${second_pid}" 2>/dev/null; then break diff --git a/server/dedicated_server_config.gd b/server/dedicated_server_config.gd index 2f515c4..7812800 100644 --- a/server/dedicated_server_config.gd +++ b/server/dedicated_server_config.gd @@ -17,8 +17,6 @@ var world_seed: int = DEFAULT_WORLD_SEED var public_listing: bool = false var discovery_url: String = "" var data_directory: String = "" -var chat_logging: bool = false -var chat_log_path: String = "" var operator_fingerprints: PackedStringArray = PackedStringArray() var error_message: String = "" @@ -73,12 +71,6 @@ func _load_file(path: String) -> bool: discovery_url = str(file.get_value( "discovery", "url", discovery_url )) - chat_logging = bool(file.get_value( - "privacy", "chat_logging", chat_logging - )) - chat_log_path = str(file.get_value( - "privacy", "chat_log_path", chat_log_path - )) operator_fingerprints = _parse_fingerprint_list(file.get_value( "moderation", "operators", operator_fingerprints )) @@ -108,12 +100,6 @@ func _apply_environment() -> void: data_directory = _environment_string( "NETFISHING_DATA_DIR", data_directory ) - chat_logging = _environment_bool( - "NETFISHING_CHAT_LOGGING", chat_logging - ) - chat_log_path = _environment_string( - "NETFISHING_CHAT_LOG_PATH", chat_log_path - ) if OS.has_environment("NETFISHING_SERVER_OPERATORS"): operator_fingerprints = _parse_fingerprint_list( OS.get_environment("NETFISHING_SERVER_OPERATORS") @@ -144,16 +130,10 @@ func _apply_arguments(arguments: PackedStringArray) -> void: operator_fingerprints = _parse_fingerprint_list( argument.trim_prefix("--operators=") ) - elif argument.begins_with("--chat-log-path="): - chat_log_path = argument.trim_prefix("--chat-log-path=") elif argument == "--public": public_listing = true elif argument == "--private": public_listing = false - elif argument == "--chat-logging": - chat_logging = true - elif argument == "--no-chat-logging": - chat_logging = false func _validate() -> void: @@ -161,9 +141,6 @@ func _validate() -> void: bind_address = bind_address.strip_edges() discovery_url = discovery_url.strip_edges().trim_suffix("/") data_directory = data_directory.strip_edges() - chat_log_path = chat_log_path.strip_edges() - if chat_logging and chat_log_path.is_empty() and not data_directory.is_empty(): - chat_log_path = data_directory.path_join("logs/chat.jsonl") operator_fingerprints = _normalized_fingerprints(operator_fingerprints) if server_name.is_empty(): error_message = "Server name cannot be empty." @@ -179,12 +156,6 @@ func _validate() -> void: error_message = "World seed must be between 1 and %d." % MAX_WORLD_SEED elif not data_directory.is_empty() and not data_directory.is_absolute_path(): error_message = "Server data directory must be an absolute path." - elif chat_logging and ( - chat_log_path.is_empty() or not chat_log_path.is_absolute_path() - ): - error_message = ( - "Chat logging requires an absolute chat log path." - ) elif public_listing and not ( discovery_url.begins_with("https://") or discovery_url.begins_with("http://") diff --git a/settings/keyboard_mouse_mapping_manager.gd b/settings/keyboard_mouse_mapping_manager.gd index d9cb03d..a8d4f09 100644 --- a/settings/keyboard_mouse_mapping_manager.gd +++ b/settings/keyboard_mouse_mapping_manager.gd @@ -3,8 +3,7 @@ extends Node signal mapping_changed -const FORMAT_VERSION: int = 2 -const LEGACY_FORMAT_VERSION: int = 1 +const FORMAT_VERSION: int = 1 const MAPPING_PATH: String = "user://keyboard_mouse_bindings.json" const MAPPING_TEMP_PATH: String = "user://keyboard_mouse_bindings.json.tmp" const MAPPING_BACKUP_PATH: String = ( @@ -22,7 +21,6 @@ const ROLE_SNEAK: StringName = &"sneak" const ROLE_SLOW_WALK: StringName = &"slow_walk" const ROLE_INTERACT: StringName = &"interact" const ROLE_PRIMARY_ACTION: StringName = &"fish_primary" -const ROLE_ALTERNATE_REEL: StringName = &"reel_alternate" const ROLE_CAMERA_DRAG: StringName = &"camera_drag" const ROLE_CAMERA_ZOOM_IN: StringName = &"camera_zoom_in" const ROLE_CAMERA_ZOOM_OUT: StringName = &"camera_zoom_out" @@ -48,7 +46,6 @@ const ROLE_ORDER: Array[StringName] = [ ROLE_SLOW_WALK, ROLE_INTERACT, ROLE_PRIMARY_ACTION, - ROLE_ALTERNATE_REEL, ROLE_CAMERA_DRAG, ROLE_CAMERA_ZOOM_IN, ROLE_CAMERA_ZOOM_OUT, @@ -84,7 +81,6 @@ const ROLE_LABELS: Dictionary = { ROLE_SLOW_WALK: "slow walk", ROLE_INTERACT: "interact", ROLE_PRIMARY_ACTION: "primary action", - ROLE_ALTERNATE_REEL: "alternate reel", ROLE_CAMERA_DRAG: "rotate camera", ROLE_CAMERA_ZOOM_IN: "camera zoom in", ROLE_CAMERA_ZOOM_OUT: "camera zoom out", @@ -142,28 +138,19 @@ func load_mapping() -> bool: _bindings = {} return false var data := json.data as Dictionary - var format_version: int = int(data.get("format_version", -1)) - if format_version not in [LEGACY_FORMAT_VERSION, FORMAT_VERSION]: + if int(data.get("format_version", -1)) != FORMAT_VERSION: push_warning("Keyboard binding version is unsupported; using defaults.") _bindings = {} return false var raw_bindings: Variant = data.get("bindings", {}) if ( typeof(raw_bindings) != TYPE_DICTIONARY + or not _validate_complete_bindings(raw_bindings as Dictionary) ): push_warning("Keyboard bindings are incomplete; using defaults.") _bindings = {} return false - var loaded_bindings := (raw_bindings as Dictionary).duplicate(true) - if format_version == LEGACY_FORMAT_VERSION: - loaded_bindings[str(ROLE_ALTERNATE_REEL)] = ( - _default_bindings[str(ROLE_ALTERNATE_REEL)].duplicate(true) - ) - if not _validate_complete_bindings(loaded_bindings): - push_warning("Keyboard bindings are incomplete; using defaults.") - _bindings = {} - return false - _bindings = loaded_bindings + _bindings = (raw_bindings as Dictionary).duplicate(true) return true diff --git a/tests/art_tools_validation.gd b/tests/art_tools_validation.gd index aeebe05..78a020f 100644 --- a/tests/art_tools_validation.gd +++ b/tests/art_tools_validation.gd @@ -86,11 +86,6 @@ func _run() -> void: var chat_ui := game_ui.get_node("%ChatUI") as ChatUI assert(player != null and service != null and toolbar != null) assert(chat_ui != null) - assert(toolbar.get_parent() == chat_ui.get_parent()) - assert( - toolbar.get_index() > chat_ui.get_index(), - "The art-kit toolbar must receive overlapping pointer input before Chat.", - ) assert(bool(game_ui.call("_can_start_virtual_mouse"))) var virtual_pointer_states: Array[bool] = [] game_ui.virtual_pointer_mode_changed.connect( @@ -322,22 +317,6 @@ func _run() -> void: assert(not ( game_ui.get_node("%ExperiencePresentation") as Control ).visible) - var hud_fishing_spot := main.get_node("%FishingSpot") as FishingSpot - assert(hud_fishing_spot != null) - hud_fishing_spot.state = FishingSpot.FishingState.SHOWING_CATCH - game_ui.call("_refresh_gameplay_hud_visibility") - assert(( - game_ui.get_node("%GameplayTransientHUD") as Control - ).visible) - assert(not ( - game_ui.get_node("%ExperiencePresentation") as Control - ).visible) - assert(chat_ui.is_hud_hidden()) - hud_fishing_spot.state = FishingSpot.FishingState.READY - game_ui.call("_refresh_gameplay_hud_visibility") - assert(not ( - game_ui.get_node("%GameplayTransientHUD") as Control - ).visible) quick_menu.open_menu(true) assert(quick_menu.visible and quick_menu.is_open()) quick_menu.close_menu() @@ -386,12 +365,6 @@ func _run() -> void: await process_frame assert(service.is_active() and not service.is_placement_mode()) assert(toolbar.visible) - chat_ui.open_chat() - assert(chat_ui.is_open()) - assert(not service.is_active() and not toolbar.visible) - chat_ui.close_chat() - assert(not chat_ui.is_open()) - assert(service.is_active() and toolbar.visible) var ui_root := game_ui.get_node("%UIRoot") as Control assert(toolbar.get_parent() == ui_root) assert(is_zero_approx(toolbar.position.y)) diff --git a/tests/chat_privacy_multiplayer_validation.gd b/tests/chat_privacy_multiplayer_validation.gd deleted file mode 100644 index 5d38b08..0000000 --- a/tests/chat_privacy_multiplayer_validation.gd +++ /dev/null @@ -1,120 +0,0 @@ -extends SceneTree - -const MainScene: PackedScene = preload("res://main/main.tscn") -const TEST_PORT: int = 18152 -const PREJOIN_MESSAGE: String = "private prejoin message" -const LIVE_MESSAGE: String = "live message after join" - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - var arguments: PackedStringArray = OS.get_cmdline_user_args() - if arguments.has("host"): - await _run_host() - return - if arguments.has("client"): - await _run_client() - return - push_error("Chat privacy validation needs host or client mode.") - quit(1) - - -func _run_host() -> void: - var main: Node = await _create_initialized_main() - var session := main.get_node("%NetworkSession") as NetworkSession - var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(session.start_private_host(TEST_PORT)) - assert(save_manager.initialize_new_game()) - main.call("_enter_gameplay") - for _frame: int in 4: - await physics_frame - assert(session.set_host_open(true)) - var chat_service := main.get_node( - "%NetworkChatService" - ) as NetworkChatService - assert(chat_service.send_local_message(PREJOIN_MESSAGE)) - var remote_peer_id: int = await _wait_for_remote_peer(session) - assert(remote_peer_id > 1) - await create_timer(1.0).timeout - assert(chat_service.send_local_message(LIVE_MESSAGE)) - var disconnect_deadline: int = Time.get_ticks_msec() + 8000 - while ( - Time.get_ticks_msec() < disconnect_deadline - and session.is_authenticated_peer(remote_peer_id) - ): - await process_frame - assert(not session.is_authenticated_peer(remote_peer_id)) - print("Chat privacy multiplayer host validation: PASS") - await _session_cleanup(main, session) - - -func _run_client() -> void: - var main: Node = await _create_initialized_main() - main.call("_on_title_join_game_requested", "127.0.0.1:%d" % TEST_PORT) - var session := main.get_node("%NetworkSession") as NetworkSession - var join_deadline: int = Time.get_ticks_msec() + 20000 - while Time.get_ticks_msec() < join_deadline: - await process_frame - if session.state == NetworkSession.State.VERIFYING_SERVER_IDENTITY: - main.call("_confirm_server_trust") - if session.is_joined_client() and bool(main.get("_gameplay_started")): - break - assert(session.is_joined_client()) - var chat_service := main.get_node( - "%NetworkChatService" - ) as NetworkChatService - await create_timer(0.5).timeout - assert(not _history_contains(chat_service, PREJOIN_MESSAGE)) - var live_deadline: int = Time.get_ticks_msec() + 8000 - while ( - Time.get_ticks_msec() < live_deadline - and not _history_contains(chat_service, LIVE_MESSAGE) - ): - await process_frame - assert(_history_contains(chat_service, LIVE_MESSAGE)) - assert(not _history_contains(chat_service, PREJOIN_MESSAGE)) - print("Chat privacy multiplayer client validation: PASS") - await _session_cleanup(main, session) - - -func _history_contains(service: NetworkChatService, body: String) -> bool: - return service.get_history().any( - func(message: Dictionary) -> bool: - return str(message.get("body", "")) == body - ) - - -func _create_initialized_main() -> Node: - root.size = Vector2i(1280, 720) - var main: Node = MainScene.instantiate() - root.add_child(main) - for _frame: int in 4: - await process_frame - if not bool(main.get("_application_initialized")): - main.call("_activate_selected_data_path", "", true) - for _frame: int in 8: - await process_frame - assert(bool(main.get("_application_initialized"))) - return main - - -func _wait_for_remote_peer(session: NetworkSession) -> int: - var deadline: int = Time.get_ticks_msec() + 20000 - while Time.get_ticks_msec() < deadline: - await process_frame - for peer_id: int in session.get_authenticated_peer_ids(): - if peer_id != session.get_local_peer_id(): - return peer_id - return 0 - - -func _session_cleanup(main: Node, session: NetworkSession) -> void: - session.disconnect_session("") - main.queue_free() - for _frame: int in 4: - await process_frame - await create_timer(0.1).timeout - quit() diff --git a/tests/chat_privacy_multiplayer_validation.gd.uid b/tests/chat_privacy_multiplayer_validation.gd.uid deleted file mode 100644 index 33b210d..0000000 --- a/tests/chat_privacy_multiplayer_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ivtqrnppnmli diff --git a/tests/chat_validation.gd b/tests/chat_validation.gd deleted file mode 100644 index fa0cd8e..0000000 --- a/tests/chat_validation.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends SceneTree - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - assert(NetworkChatProtocol.sanitize_body(42).is_empty()) - assert(NetworkChatProtocol.sanitize_body(" a ") == "a") - assert(NetworkChatProtocol.sanitize_body("a b") == "a b") - assert(NetworkChatProtocol.sanitize_body("a\tb\nc") == "a b c") - assert(NetworkChatProtocol.sanitize_body(" !hello!? ") == "!hello!?") - assert(NetworkChatProtocol.sanitize_body("!!!") == "!!!") - - const em_space := " " # U+2003 EM SPACE - const zero_width_space := "​" # U+200B ZERO WIDTH SPACE - const braille_blank := "⠀" # U+2800 BRAILLE PATTERN BLANK - const invisible_padding := em_space + zero_width_space + braille_blank - assert( - NetworkChatProtocol.sanitize_body(invisible_padding + "a" + invisible_padding) - == "a" - ) - assert(NetworkChatProtocol.sanitize_body(invisible_padding).is_empty()) - assert(NetworkChatProtocol.sanitize_body(" \t\n" + invisible_padding).is_empty()) - - assert( - NetworkChatProtocol.sanitize_body("a".repeat( - NetworkChatProtocol.MAX_VISIBLE_CHARACTERS - )).length() == NetworkChatProtocol.MAX_VISIBLE_CHARACTERS - ) - assert( - NetworkChatProtocol.sanitize_body("a".repeat( - NetworkChatProtocol.MAX_VISIBLE_CHARACTERS + 1 - )).is_empty() - ) - - print("Chat validation: PASS") - quit(0) diff --git a/tests/chat_validation.gd.uid b/tests/chat_validation.gd.uid deleted file mode 100644 index 54e6823..0000000 --- a/tests/chat_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ohemhv2sf5g4 diff --git a/tests/controller_menu_accessibility_validation.gd b/tests/controller_menu_accessibility_validation.gd index 3e37223..975ce79 100644 --- a/tests/controller_menu_accessibility_validation.gd +++ b/tests/controller_menu_accessibility_validation.gd @@ -353,8 +353,6 @@ func _validate_data_settings_navigation() -> void: "Open Data Folder left a controller-activatable dialog behind.", ) var change_data_folder := panel.get_node("%ChangeDataFolder") as Button - var export_progression := panel.get_node("%ExportProgression") as Button - var import_progression := panel.get_node("%ImportProgression") as Button var copy_fingerprint := panel.get_node("%CopyPlayerFingerprint") as Button var export_player := panel.get_node("%ExportPlayerIdentity") as Button var import_player := panel.get_node("%ImportPlayerIdentity") as Button @@ -364,8 +362,6 @@ func _validate_data_settings_navigation() -> void: data_tab, open_data_folder, change_data_folder, - export_progression, - import_progression, copy_fingerprint, export_player, import_player, @@ -389,7 +385,7 @@ func _validate_data_settings_navigation() -> void: _assert_neighbor( open_data_folder, &"focus_neighbor_bottom", - export_progression, + copy_fingerprint, ) _assert_neighbor( change_data_folder, @@ -399,43 +395,13 @@ func _validate_data_settings_navigation() -> void: _assert_neighbor( change_data_folder, &"focus_neighbor_bottom", - import_progression, + copy_fingerprint, ) _assert_neighbor( - export_progression, + copy_fingerprint, &"focus_neighbor_top", open_data_folder, ) - _assert_neighbor( - export_progression, - &"focus_neighbor_right", - import_progression, - ) - _assert_neighbor( - export_progression, - &"focus_neighbor_bottom", - copy_fingerprint, - ) - _assert_neighbor( - import_progression, - &"focus_neighbor_top", - change_data_folder, - ) - _assert_neighbor( - import_progression, - &"focus_neighbor_left", - export_progression, - ) - _assert_neighbor( - import_progression, - &"focus_neighbor_bottom", - copy_fingerprint, - ) - _assert_neighbor( - copy_fingerprint, - &"focus_neighbor_top", - export_progression, - ) _assert_neighbor( copy_fingerprint, &"focus_neighbor_bottom", diff --git a/tests/dedicated_server_config_validation.gd b/tests/dedicated_server_config_validation.gd index 02a881d..c9a82c0 100644 --- a/tests/dedicated_server_config_validation.gd +++ b/tests/dedicated_server_config_validation.gd @@ -19,12 +19,6 @@ func _run() -> void: defaults.call("_validate") assert(defaults.is_valid()) assert(not bool(defaults.get("public_listing"))) - assert(not bool(defaults.get("chat_logging"))) - assert(str(defaults.get("chat_log_path")).is_empty()) - assert(NetworkChatService.BURST_COUNT == 3) - assert(NetworkChatService.WINDOW_COUNT == 5) - assert(is_equal_approx(NetworkChatService.WINDOW_SECONDS, 10.0)) - assert(NetworkChatService.CALL_COOLDOWN_MILLISECONDS == 90) var path: String = ProjectSettings.globalize_path( "user://dedicated-server-validation.cfg" @@ -38,12 +32,6 @@ func _run() -> void: file.set_value("server", "public", true) file.set_value("server", "data_directory", "/tmp/configured-server") file.set_value("discovery", "url", "https://discovery.netfishing.org/") - file.set_value("privacy", "chat_logging", true) - file.set_value( - "privacy", - "chat_log_path", - "/tmp/configured-server/logs/chat.jsonl", - ) file.set_value( "moderation", "operators", PackedStringArray([OPERATOR_B, OPERATOR_A]) ) @@ -60,11 +48,6 @@ func _run() -> void: assert(int(configured.get("world_seed")) == 928374) assert(bool(configured.get("public_listing"))) assert(str(configured.get("discovery_url")) == "https://discovery.netfishing.org") - assert(bool(configured.get("chat_logging"))) - assert( - str(configured.get("chat_log_path")) - == "/tmp/configured-server/logs/chat.jsonl" - ) assert( configured.get("operator_fingerprints") == PackedStringArray([OPERATOR_A, OPERATOR_B]) @@ -106,51 +89,6 @@ func _run() -> void: invalid_seed.call("_validate") assert(not invalid_seed.is_valid()) - var default_chat_path := ConfigType.new() - default_chat_path.set("data_directory", "/tmp/default-chat-path-server") - default_chat_path.set("chat_logging", true) - default_chat_path.call("_validate") - assert(default_chat_path.is_valid()) - assert( - str(default_chat_path.get("chat_log_path")) - == "/tmp/default-chat-path-server/logs/chat.jsonl" - ) - - var invalid_chat_path := ConfigType.new() - invalid_chat_path.set("data_directory", "/tmp/invalid-chat-path-server") - invalid_chat_path.set("chat_logging", true) - invalid_chat_path.set("chat_log_path", "relative/chat.jsonl") - invalid_chat_path.call("_validate") - assert(not invalid_chat_path.is_valid()) - - var chat_log_validation_path: String = ProjectSettings.globalize_path( - "user://dedicated-chat-log-validation.jsonl" - ) - var chat_service := NetworkChatService.new() - assert(not chat_service.configure_dedicated_history( - true, - "relative/chat.jsonl", - )) - assert(chat_service.configure_dedicated_history( - true, - chat_log_validation_path, - )) - assert(FileAccess.file_exists(chat_log_validation_path)) - chat_service.call("_append_chat_log", { - "sender_display_name": "Privacy Tester", - "body": "explicitly retained message", - }) - var logged_message: Variant = JSON.parse_string( - FileAccess.get_file_as_string(chat_log_validation_path).strip_edges() - ) - assert(typeof(logged_message) == TYPE_DICTIONARY) - assert(str(logged_message.get("recorded_at_utc", "")).ends_with("Z")) - assert(logged_message.get("sender_display_name") == "Privacy Tester") - assert(logged_message.get("body") == "explicitly retained message") - assert(chat_service.configure_dedicated_history(false, "")) - assert(DirAccess.remove_absolute(chat_log_validation_path) == OK) - chat_service.free() - var discovery := DiscoveryClient.new() assert( str(discovery.call("_default_room_name", "River")) diff --git a/tests/equipment_catalog_validation.gd b/tests/equipment_catalog_validation.gd index bc549aa..a74d066 100644 --- a/tests/equipment_catalog_validation.gd +++ b/tests/equipment_catalog_validation.gd @@ -54,21 +54,6 @@ func _initialize() -> void: assert(FishingShopStockType.get_price(&"magnet") == 250) assert(FishingShopStockType.get_stock_item_ids().has(&"magnet")) assert(FishingShopStockType.is_permanent_unlock(&"magnet", magnet)) - var fishing_net: ItemDataType = ( - ItemCatalogResource.get_available_item_by_id(&"fishing_net") - ) - assert(fishing_net != null) - assert(fishing_net.category == ItemDataType.Category.TOOL) - assert(fishing_net.icon == null) - assert(not fishing_net.usable) - assert(not fishing_net.equippable) - assert(not fishing_net.hotbar_allowed) - assert(FishingShopStockType.get_price(&"fishing_net") == 250) - assert(FishingShopStockType.get_stock_item_ids().has(&"fishing_net")) - assert(FishingShopStockType.is_permanent_unlock( - &"fishing_net", fishing_net - )) - assert(not FishingShopStockType.is_gathering_tool(&"fishing_net")) var crab_net: ItemDataType = ItemCatalogResource.get_available_item_by_id( &"crab_net" ) diff --git a/tests/fish_hotbar_showcase_validation.gd b/tests/fish_hotbar_showcase_validation.gd index 9421816..d61d002 100644 --- a/tests/fish_hotbar_showcase_validation.gd +++ b/tests/fish_hotbar_showcase_validation.gd @@ -98,27 +98,29 @@ func _run() -> void: assert(save_manager.save_now()) var save_path: String = str(save_manager.get("_save_path")) - var decoded: Dictionary = ProgressionSaveCodec.read_local_save(save_path) - assert(bool(decoded.get("ok", false))) - var parsed: Dictionary = decoded["data"] - var hotbar_data: Dictionary = parsed["hotbar"] + var save_file := FileAccess.open(save_path, FileAccess.READ) + assert(save_file != null) + var parsed: Variant = JSON.parse_string(save_file.get_as_text()) + save_file.close() + assert(typeof(parsed) == TYPE_DICTIONARY) + var hotbar_data: Dictionary = (parsed as Dictionary)["hotbar"] assert(typeof(hotbar_data.get("fish_slots")) == TYPE_ARRAY) assert(str((hotbar_data["fish_slots"] as Array)[1]) == fish_catch.catch_id) - assert(int(parsed["save_version"]) == 10) + assert(int((parsed as Dictionary)["save_version"]) == 9) assert( - int(parsed["experience"]["total_experience"]) + int((parsed as Dictionary)["experience"]["total_experience"]) == 125 ) assert(absf( - float(parsed["world"]["time_hours"]) - saved_time_hours + float((parsed as Dictionary)["world"]["time_hours"]) - saved_time_hours ) < 0.01) assert( - int(parsed["world"]["weather"]) + int((parsed as Dictionary)["world"]["weather"]) == int(saved_weather) ) assert(absf( float( - parsed["world"][ + (parsed as Dictionary)["world"][ "weather_seconds_remaining" ] ) - saved_weather_seconds diff --git a/tests/fish_quality_validation.gd b/tests/fish_quality_validation.gd index 9c5d338..31e4cc1 100644 --- a/tests/fish_quality_validation.gd +++ b/tests/fish_quality_validation.gd @@ -433,7 +433,7 @@ func _validate_version_four_migration() -> void: version_four, 4, ) - assert(int(migrated.get("save_version", -1)) == 10) + assert(int(migrated.get("save_version", -1)) == 9) assert(int((migrated["experience"] as Dictionary)["total_experience"]) == 0) assert( is_equal_approx( diff --git a/tests/fishing_multiplayer_validation.gd b/tests/fishing_multiplayer_validation.gd index 7965af6..e19a92e 100644 --- a/tests/fishing_multiplayer_validation.gd +++ b/tests/fishing_multiplayer_validation.gd @@ -2,7 +2,6 @@ extends SceneTree const MainScene = preload("res://main/main.tscn") const FishingSpotType = preload("res://fishing/fishing_spot.gd") -const TEST_SEED: int = 864209 func _initialize() -> void: @@ -23,23 +22,9 @@ func _run() -> void: func _run_host() -> void: var main: Node = await _create_initialized_main() - assert(bool(main.call( - "_apply_world", - WorldLayout.STARTER_ISLAND, - TEST_SEED, - true, - ))) - assert(bool(main.call( - "_prepare_host_world", - WorldLayout.STARTER_ISLAND, - TEST_SEED, - ))) assert(bool(main.call("_prepare_private_host"))) var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(save_manager.initialize_new_game( - TEST_SEED, - WorldLayout.STARTER_ISLAND, - )) + assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") var session := main.get_node("%NetworkSession") as NetworkSession assert(session.set_host_open(true)) @@ -232,9 +217,7 @@ func _run_client() -> void: int(player.get("_fishing_visual_phase")) == Player.FishingVisualPhase.CASTING ) - # Keep the transient pull-back pose visible long enough for the separate host - # process to observe it even when a cold validation run is CPU-bound. - for _frame: int in 120: + for _frame: int in 30: await physics_frame player.set_fishing_visual(false) player.call("_set_sitting", true, true) diff --git a/tests/gathering_showcase_validation.gd b/tests/gathering_showcase_validation.gd index e565beb..0138bad 100644 --- a/tests/gathering_showcase_validation.gd +++ b/tests/gathering_showcase_validation.gd @@ -32,7 +32,6 @@ func _run() -> void: var fishing_spot := main.get_node("%FishingSpot") as FishingSpot var catalog := main.get("fish_catalog") as FishPool assert(player != null and fishing_spot != null and catalog != null) - fishing_spot.minimum_showcase_duration = 0.15 assert(player.bag.add_item(&"crab_net")) assert(player.hotbar.assign_item(0, &"crab_net")) assert(player.hotbar.select_slot(0)) @@ -61,20 +60,13 @@ func _run() -> void: assert(not player.is_movement_enabled()) Input.action_release("fish_primary") await process_frame - assert(not bool(fishing_spot.get("_put_away_press_armed"))) + assert(bool(fishing_spot.get("_put_away_press_armed"))) var pocket_event := InputEventAction.new() pocket_event.action = &"fish_primary" pocket_event.pressed = true Input.parse_input_event(pocket_event) await process_frame - assert(fishing_spot.get("_pending_catch") == crab_catch) - Input.action_release("fish_primary") - await create_timer(0.2).timeout - await process_frame - assert(bool(fishing_spot.get("_put_away_press_armed"))) - Input.parse_input_event(pocket_event) - await process_frame assert(fishing_spot.get("_pending_catch") == null) var pocket_deadline: int = Time.get_ticks_msec() + 6000 diff --git a/tests/generated_world_runtime_validation.gd b/tests/generated_world_runtime_validation.gd index 501deb9..c6ac945 100644 --- a/tests/generated_world_runtime_validation.gd +++ b/tests/generated_world_runtime_validation.gd @@ -82,42 +82,18 @@ func _validate_generated_region( generator: TerrainChunkGenerator, ) -> void: var placements := generator.placement_keys() - var expected_chunk_count := generator.grid_size.x * generator.grid_size.y - var center := Vector2i( - generator.grid_size.x / 2, - generator.grid_size.y / 2, - ) - var center_index := center.y * generator.grid_size.x + center.x - assert(placements.size() == expected_chunk_count) - assert(placements[center_index].begins_with("chunk_spawn@")) + assert(placements.size() == 49) + assert(placements[24].begins_with("chunk_spawn@")) assert(_placement_count(placements, "chunk_spawn") == 1) assert(_placement_count(placements, "chunk_0001") >= 1) assert(_placement_count(placements, "chunk_0002") >= 1) assert(_placement_count(placements, "chunk_0003") >= 1) - assert(_placement_count(placements, "chunk_0016") >= 1) assert(_placement_count(placements, "chunk_0004") >= 1) assert(_placement_count(placements, "chunk_0005") >= 1) assert(_placement_count(placements, "chunk_0006") >= 1) assert(_placement_count(placements, "chunk_0007") >= 1) - assert(_placement_count(placements, "chunk_0008") >= 1) - assert(_placement_count(placements, "chunk_0013") >= 1) - assert(_placement_count(placements, "chunk_0009") == 1) - assert(_placement_count(placements, "chunk_0010") == 4) - assert( - _placement_count(placements, "chunk_0011") - + _placement_count(placements, "chunk_0012") - == 4 - ) - assert(_placement_count(placements, "chunk_0012") <= 1) - assert( - generator.get_generated_chunks_root().get_child_count() - == expected_chunk_count - ) - assert( - generator.get_primary_terrain_meshes().size() - == expected_chunk_count - ) - _validate_elevated_cliff_feature(generator) + assert(generator.get_generated_chunks_root().get_child_count() == 49) + assert(generator.get_primary_terrain_meshes().size() == 49) var shop := region.get_node("Interactables/FishingShopWorld") as Node3D var storage := region.get_node("Interactables/PlayerStorageBox") as Node3D @@ -135,7 +111,6 @@ func _validate_generated_region( var fresh_root := region.get_node("WaterBodies/FreshWaterBodies") as Node3D assert(ocean != null and fresh_root != null) assert(ocean.water_type == WaterType.Type.SALT_WATER) - assert(is_equal_approx(ocean.position.y, GeneratedWorldRegion.WATER_HEIGHT)) var fresh_placement_count := 0 for record: Dictionary in generator.placement_records(): var tags: PackedStringArray = record.get("tags", PackedStringArray()) @@ -148,7 +123,6 @@ func _validate_generated_region( var fresh := child as WaterBodyAuthoring assert(fresh != null) assert(fresh.water_type == WaterType.Type.FRESH_WATER) - assert(is_equal_approx(fresh.position.y, GeneratedWorldRegion.WATER_HEIGHT)) assert(fresh.visible) assert(fresh.surface_size.x < 10.0 or fresh.surface_size.y < 10.0) assert(not fresh.visual_surface_enabled) @@ -181,13 +155,13 @@ func _validate_generated_region( &"terrain_chunk_coordinate", Vector2i.ZERO, ) - var spawn_coordinate := Vector2i( + var center := Vector2i( generator.grid_size.x / 2, generator.grid_size.y / 2, ) var spawn_distance := ( - absi(coordinate.x - spawn_coordinate.x) - + absi(coordinate.y - spawn_coordinate.y) + absi(coordinate.x - center.x) + + absi(coordinate.y - center.y) ) assert(spawn_distance >= definition.minimum_spawn_chunk_distance) _validate_decoration_transform( @@ -195,13 +169,6 @@ func _validate_generated_region( child as Node3D, definition, ) - if prop_id in [&"prop_tree_1", &"prop_tree_2", &"prop_tree_3"]: - assert( - _has_material_variant_override( - child.get_node_or_null("Visual"), - definition.material_variants, - ) - ) assert(_decoration_group_count(region, &"grass_tree") > 0) assert(_decoration_group_count(region, &"sand_tree") > 0) assert( @@ -220,69 +187,6 @@ func _validate_generated_region( ) -func _validate_elevated_cliff_feature( - generator: TerrainChunkGenerator, -) -> void: - var elevated_coordinates: Dictionary[Vector2i, StringName] = {} - var top_coordinate := Vector2i(-1, -1) - for record: Dictionary in generator.placement_records(): - var stable_id: StringName = record.get("stable_id", &"") - if stable_id not in [ - &"chunk_0009", - &"chunk_0010", - &"chunk_0011", - &"chunk_0012", - ]: - continue - var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO) - assert(coordinate.x > 0 and coordinate.x < generator.grid_size.x - 1) - assert(coordinate.y > 0 and coordinate.y < generator.grid_size.y - 1) - elevated_coordinates[coordinate] = stable_id - if stable_id == &"chunk_0009": - top_coordinate = coordinate - assert(elevated_coordinates.size() == 9) - assert(top_coordinate != Vector2i(-1, -1)) - for row_offset: int in range(-1, 2): - for column_offset: int in range(-1, 2): - assert( - elevated_coordinates.has( - top_coordinate + Vector2i(column_offset, row_offset) - ) - ) - - var generated := generator.get_generated_chunks_root() - assert(generated != null) - var layered_count := 0 - for chunk_root: Node in generated.get_children(): - var stable_id := StringName( - chunk_root.get_meta(&"terrain_chunk_id", &"") - ) - if stable_id not in [ - &"chunk_0009", - &"chunk_0010", - &"chunk_0011", - &"chunk_0012", - ]: - continue - layered_count += 1 - var base_layer := chunk_root.get_node_or_null("TerrainBaseLayer") - var overlay := chunk_root.get_node_or_null("TerrainOverlay") - assert(base_layer != null and overlay != null) - var base_mesh := TerrainChunkAnalyzer.find_primary_mesh( - base_layer, - &"chunk_0000", - ) - var overlay_mesh := TerrainChunkAnalyzer.find_primary_mesh( - overlay, - stable_id, - ) - assert(base_mesh != null and base_mesh.mesh != null) - assert(overlay_mesh != null and overlay_mesh.mesh != null) - assert(base_mesh.has_node("TerrainBaseLayerCollision")) - assert(overlay_mesh.has_node("TerrainCollision")) - assert(layered_count == 9) - - func _validate_ocean_facing_record( record: Dictionary, grid_size: Vector2i, @@ -320,23 +224,6 @@ func _validate_prop_catalog(region: GeneratedWorldRegion) -> void: assert(instance.position.is_zero_approx()) assert(_find_mesh_instance(instance) != null) instance.free() - var mushroom := catalog.definition_for_id(&"prop_mushroom") - assert(mushroom != null and mushroom.has_collision()) - assert(mushroom.visual_offset.y < 0.0) - for tree_id: StringName in [ - &"prop_tree_1", - &"prop_tree_2", - &"prop_tree_3", - ]: - var tree := catalog.definition_for_id(tree_id) - assert(tree != null) - assert(tree.minimum_visual_scale < tree.maximum_visual_scale) - assert(tree.material_variants.size() >= 3) - assert(not tree.variant_material_slot_names.is_empty()) - var pine := catalog.definition_for_id(&"prop_pine") - assert(pine != null) - assert(pine.minimum_visual_scale < pine.maximum_visual_scale) - assert(pine.material_variants.is_empty()) func _validate_biome_catalog( @@ -352,9 +239,7 @@ func _validate_biome_catalog( var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO) var biome_id := region.get_biome_at(coordinate) var tags: PackedStringArray = record.get("tags", PackedStringArray()) - if "mixed_surface" in tags: - assert(biome_id == &"") - elif "grass" in tags or "sand" in tags: + if "grass" in tags or "sand" in tags: assert(biome_id != &"") var biome := catalog.definition_for_id(biome_id) assert(biome != null and biome.supports_chunk_tags(tags)) @@ -391,16 +276,7 @@ func _validate_decoration_transform( var collision := prop.get_node_or_null( "TrunkCollision/CollisionShape" ) as CollisionShape3D - assert(visual_root != null) - assert(visual_root.position.is_equal_approx(definition.visual_offset)) - var visual_scale := float( - prop.get_meta(&"terrain_prop_visual_scale", 1.0) - ) - assert(visual_scale >= definition.minimum_visual_scale - 0.001) - assert(visual_scale <= definition.maximum_visual_scale + 0.001) - assert( - visual_root.scale.is_equal_approx(Vector3.ONE * visual_scale) - ) + assert(visual_root != null and visual_root.position.is_zero_approx()) assert(visual != null and visual.mesh != null) if definition.has_collision(): assert(collision != null) @@ -443,40 +319,16 @@ func _find_mesh_instance(root_node: Node) -> MeshInstance3D: return null -func _has_material_variant_override( - root_node: Node, - variants: Array[Material], -) -> bool: - if root_node == null: - return false - var mesh_instance := root_node as MeshInstance3D - if mesh_instance != null and mesh_instance.mesh != null: - for surface_index: int in mesh_instance.mesh.get_surface_count(): - var override := mesh_instance.get_surface_override_material( - surface_index - ) - if override != null and override in variants: - return true - for child: Node in root_node.get_children(): - if _has_material_variant_override(child, variants): - return true - return false - - func _validate_authored_chunk_surfaces( region: GeneratedWorldRegion, generator: TerrainChunkGenerator, ) -> void: var generated := generator.get_generated_chunks_root() var found_beach := false - var found_stream := false - var found_alternate_stream := false var found_pond := false var found_grass_ocean_edge := false var found_grass_beach_transition := false var found_grass_ocean_corner := false - var found_beach_ocean_corner := false - var found_grass_sand_diagonal := false for chunk_root: Node in generated.get_children(): if chunk_root.name.begins_with("chunk_0002r"): var beach := chunk_root.find_child( @@ -486,27 +338,6 @@ func _validate_authored_chunk_surfaces( var material := beach.get_active_material(0) assert(material != null and material.resource_name == "sand") found_beach = true - elif ( - chunk_root.name.begins_with("chunk_0003r") - or chunk_root.name.begins_with("chunk_0016r") - ): - var stream_id := ( - &"chunk_0016" - if chunk_root.name.begins_with("chunk_0016r") - else &"chunk_0003" - ) - var stream := chunk_root.find_child( - String(stream_id), true, false - ) as MeshInstance3D - assert(stream != null and stream.mesh != null) - var stream_materials := _material_names(stream) - assert("dirt" in stream_materials) - assert("grass_lite" in stream_materials) - assert("dirt_wall" in stream_materials) - if stream_id == &"chunk_0016": - found_alternate_stream = true - else: - found_stream = true elif chunk_root.name.begins_with("chunk_0004r"): var pond := chunk_root.find_child( "chunk_0004", true, false @@ -552,32 +383,11 @@ func _validate_authored_chunk_surfaces( assert("grass_lite" in corner_materials) assert("cliff_wall" in corner_materials) found_grass_ocean_corner = true - elif chunk_root.name.begins_with("chunk_0008r"): - var beach_ocean_corner := chunk_root.find_child( - "chunk_0008", true, false - ) as MeshInstance3D - assert(beach_ocean_corner != null and beach_ocean_corner.mesh != null) - var beach_corner_materials := _material_names(beach_ocean_corner) - assert("sand" in beach_corner_materials) - found_beach_ocean_corner = true - elif chunk_root.name.begins_with("chunk_0013r"): - var grass_sand_diagonal := chunk_root.find_child( - "chunk_0013", true, false - ) as MeshInstance3D - assert(grass_sand_diagonal != null and grass_sand_diagonal.mesh != null) - var diagonal_materials := _material_names(grass_sand_diagonal) - assert("grass_lite" in diagonal_materials) - assert("sand" in diagonal_materials) - found_grass_sand_diagonal = true assert(found_beach) - assert(found_stream) - assert(found_alternate_stream) assert(found_pond) assert(found_grass_ocean_edge) assert(found_grass_beach_transition) assert(found_grass_ocean_corner) - assert(found_beach_ocean_corner) - assert(found_grass_sand_diagonal) func _material_names(mesh_instance: MeshInstance3D) -> PackedStringArray: diff --git a/tests/inventory_storage_persistence_validation.gd b/tests/inventory_storage_persistence_validation.gd index 794f438..013ebbf 100644 --- a/tests/inventory_storage_persistence_validation.gd +++ b/tests/inventory_storage_persistence_validation.gd @@ -1,9 +1,6 @@ extends SceneTree const MainScene: PackedScene = preload("res://main/main.tscn") -const ItemCatalogResource: ItemCatalog = preload( - "res://items/catalog/item_catalog.tres" -) func _initialize() -> void: @@ -26,43 +23,33 @@ func _run() -> void: var player := main.get("_player") as Player assert(save_manager != null and player != null) const TEST_WORLD_SEED := 918273 - assert(save_manager.initialize_new_game( - TEST_WORLD_SEED, - WorldLayout.STARTER_ISLAND, - )) + assert(save_manager.initialize_new_game(TEST_WORLD_SEED)) save_manager.set_autosave_enabled(true) assert(player.bag.add_item(&"art_kit")) assert(player.bag.add_item(&"coffee")) - assert(player.bag.add_item(&"worms")) - assert(player.bag.add_item(&"the_standby")) - assert(player.equip_bait(ItemCatalogResource.get_item_by_id(&"worms"))) - assert(player.equip_lure(ItemCatalogResource.get_item_by_id(&"the_standby"))) assert(player.bag.move_item_to_storage_slot(&"basic_fishing_rod", 14)) assert(player.bag.move_item_to_storage_slot(&"coffee", 12)) assert(save_manager.save_now()) - var decoded: Dictionary = ProgressionSaveCodec.read_local_save( - str(save_manager.get("_save_path")) + var save_file := FileAccess.open( + str(save_manager.get("_save_path")), + FileAccess.READ, ) - assert(bool(decoded.get("ok", false))) - var parsed: Dictionary = decoded["data"] - var records: Array = parsed["bag"]["items"] - assert(str(parsed["world"]["layout"]) == String(WorldLayout.STARTER_ISLAND)) - assert(int(parsed["world"]["seed"]) == TEST_WORLD_SEED) + assert(save_file != null) + var parsed: Variant = JSON.parse_string(save_file.get_as_text()) + save_file.close() + assert(typeof(parsed) == TYPE_DICTIONARY) + var records: Array = (parsed as Dictionary)["bag"]["items"] + assert(int((parsed as Dictionary)["world"]["seed"]) == TEST_WORLD_SEED) assert(_saved_slot(records, &"basic_fishing_rod") == 14) assert(_saved_slot(records, &"coffee") == 12) assert(player.bag.move_item_to_storage_slot(&"basic_fishing_rod", 0)) assert(player.bag.move_item_to_storage_slot(&"coffee", 0)) - player.unequip_bait() - player.unequip_lure() assert(save_manager.load_player_data()) - assert(save_manager.get_world_layout() == WorldLayout.STARTER_ISLAND) assert(save_manager.get_world_seed() == TEST_WORLD_SEED) assert(player.bag.get_storage_slot(&"basic_fishing_rod") == 14) assert(player.bag.get_storage_slot(&"coffee") == 12) - assert(player.active_bait_id == &"worms") - assert(player.active_lure_id == &"the_standby") main.queue_free() for _frame: int in 4: diff --git a/tests/job_system_validation.gd b/tests/job_system_validation.gd index 1526e44..d2f2ae7 100644 --- a/tests/job_system_validation.gd +++ b/tests/job_system_validation.gd @@ -49,7 +49,6 @@ func _run() -> void: _validate_weather_guarantees(board) _validate_late_board_weather_fallback() _validate_remote_tamper_rejection(jobs, board) - _validate_creature_jobs(jobs) var sell_job: Dictionary = _find_job( jobs.get_daily_jobs(), JobCatalog.Kind.SELL_TOTAL @@ -196,12 +195,15 @@ func _run() -> void: assert(session.set_host_open(false)) assert(save_manager.save_now()) - var decoded: Dictionary = ProgressionSaveCodec.read_local_save( - str(save_manager.get("_save_path")) + var save_file := FileAccess.open( + str(save_manager.get("_save_path")), FileAccess.READ ) - assert(bool(decoded.get("ok", false))) - var save_data: Dictionary = decoded["data"] - assert(int(save_data.get("save_version", -1)) == 10) + assert(save_file != null) + var parsed: Variant = JSON.parse_string(save_file.get_as_text()) + save_file.close() + assert(typeof(parsed) == TYPE_DICTIONARY) + var save_data: Dictionary = parsed + assert(int(save_data.get("save_version", -1)) == 9) assert(PlayerJobService.validate_save_data(save_data.get("jobs", {}))) _validate_pause_session_switch(main, session) @@ -261,100 +263,6 @@ func _validate_unavailable_fishnet_layout() -> void: await process_frame -func _validate_creature_jobs(jobs: PlayerJobService) -> void: - var fish_count: int = 0 - var insect_count: int = 0 - var shellfish_count: int = 0 - var candidates: Array[FishData] = [] - for fish: FishData in Catalog.candidates: - if fish == null or not fish.is_selectable(): - continue - candidates.append(fish) - match fish.get_creature_group(): - FishData.CreatureGroup.FISH: - fish_count += 1 - FishData.CreatureGroup.INSECT: - insect_count += 1 - FishData.CreatureGroup.SHELLFISH: - shellfish_count += 1 - assert(fish_count > 0 and insect_count > 0 and shellfish_count > 0) - - var lifetime: Array[Dictionary] = jobs.get_lifetime_jobs() - var fish_discovery: Dictionary = _find_job( - lifetime, JobCatalog.Kind.DISCOVER_SPECIES - ) - assert(int(fish_discovery.get("target", -1)) == fish_count) - for creature_group: int in [ - FishData.CreatureGroup.INSECT, - FishData.CreatureGroup.SHELLFISH, - ]: - assert(not _find_creature_job( - lifetime, - JobCatalog.Kind.CATCH_CREATURE_GROUP, - creature_group, - ).is_empty()) - assert(not _find_creature_job( - lifetime, - JobCatalog.Kind.DISCOVER_CREATURE_GROUP, - creature_group, - ).is_empty()) - assert(not _find_creature_job( - lifetime, - JobCatalog.Kind.MASTER_CREATURE_GROUP, - creature_group, - ).is_empty()) - - var daily_groups: Dictionary[int, bool] = {} - for seed_index: int in 64: - for job: Dictionary in JobCatalog.generate_daily_jobs( - "creature-job-validation-%d" % seed_index, - candidates, - true, - ): - if int(job.get("kind", -1)) == JobCatalog.Kind.CATCH_CREATURE_GROUP: - daily_groups[int(job.get("creature_group", -1))] = true - assert(daily_groups.has(FishData.CreatureGroup.INSECT)) - assert(daily_groups.has(FishData.CreatureGroup.SHELLFISH)) - - var beetle: FishData = Catalog.get_fish_by_id(&"beetle_stag_common") - assert(beetle != null) - var beetle_catch := _make_test_catch(beetle, &"job-beetle") - jobs.call("_on_authoritative_catch", beetle_catch) - var statistics: Dictionary = jobs.to_save_data().get("statistics", {}) - assert(int(statistics.get("fish_caught", -1)) == 0) - assert(int(statistics.get("insects_caught", -1)) == 1) - - -func _find_creature_job( - jobs: Array[Dictionary], - kind: JobCatalog.Kind, - creature_group: int, -) -> Dictionary: - for job: Dictionary in jobs: - if ( - int(job.get("kind", -1)) == kind - and int(job.get("creature_group", -1)) == creature_group - ): - return job - return {} - - -func _make_test_catch(fish: FishData, catch_id: StringName) -> FishCatch: - var fish_catch := FishCatch.new() - fish_catch.fish = fish - fish_catch.fish_id = fish.id - fish_catch.catch_id = catch_id - fish_catch.catch_sequence = 1 - fish_catch.weight_lb = fish.get_minimum_weight() - fish_catch.display_scale = fish.get_display_scale_for_weight( - fish_catch.weight_lb - ) - fish_catch.quality = FishQuality.Tier.BORING - fish_catch.sale_value = fish.get_sale_value_for_weight(fish_catch.weight_lb) - assert(fish_catch.is_valid()) - return fish_catch - - func _validate_weather_guarantees(board: Dictionary) -> void: var jobs: Array = board.get("jobs", []) var schedule: Array = board.get("weather_schedule", []) diff --git a/tests/keyboard_mouse_mapping_validation.gd b/tests/keyboard_mouse_mapping_validation.gd index 7d02736..cca2ac2 100644 --- a/tests/keyboard_mouse_mapping_validation.gd +++ b/tests/keyboard_mouse_mapping_validation.gd @@ -7,12 +7,6 @@ const KeyboardMouseMappingPanelType = preload( "res://ui/keyboard_mouse_mapping_panel.gd" ) const SettingsPanelScene = preload("res://ui/settings_panel.tscn") -const FishingSpotScene = preload("res://fishing/fishing_spot.tscn") -const FishingSpotType = preload("res://fishing/fishing_spot.gd") -const CatchDifficultyProfileType = preload( - "res://fishing/catch_difficulty_profile.gd" -) -const PlayerType = preload("res://player/player.gd") func _initialize() -> void: @@ -33,13 +27,6 @@ func _run() -> void: str(KeyboardMouseMappingManagerType.ROLE_PRIMARY_ACTION) ].get("kind", "")) == "mouse_button" ) - assert( - str(defaults[ - str(KeyboardMouseMappingManagerType.ROLE_ALTERNATE_REEL) - ].get("kind", "")) == "key" - ) - assert(_has_physical_key(&"reel_alternate", KEY_QUOTELEFT)) - assert(_event_count(&"reel_alternate", true) == 0) assert( str(defaults[ str(KeyboardMouseMappingManagerType.ROLE_CHAT) @@ -73,15 +60,6 @@ func _run() -> void: )) assert(_has_mouse_button(&"fish_primary", MOUSE_BUTTON_XBUTTON1)) assert(not _has_mouse_button(&"fish_primary", MOUSE_BUTTON_LEFT)) - var alternate_reel_key := InputEventKey.new() - alternate_reel_key.physical_keycode = KEY_G - alternate_reel_key.pressed = true - assert(manager.set_binding( - KeyboardMouseMappingManagerType.ROLE_ALTERNATE_REEL, - manager.binding_from_event(alternate_reel_key), - )) - assert(_has_physical_key(&"reel_alternate", KEY_G)) - assert(_event_count(&"reel_alternate", true) == 0) var joypad_event := InputEventJoypadButton.new() joypad_event.button_index = JOY_BUTTON_A @@ -139,64 +117,11 @@ func _run() -> void: )) assert(_has_physical_key(&"jump", KEY_SPACE)) assert(_has_mouse_button(&"fish_primary", MOUSE_BUTTON_LEFT)) - assert(_has_physical_key(&"reel_alternate", KEY_QUOTELEFT)) - assert(_event_count(&"reel_alternate", true) == 0) assert(_has_physical_key(&"open_chat", KEY_T)) assert(_event_count(&"jump", true) == joypad_events_before) - var legacy_bindings: Dictionary = manager.get_active_bindings() - legacy_bindings.erase(str( - KeyboardMouseMappingManagerType.ROLE_ALTERNATE_REEL - )) - legacy_bindings[str(KeyboardMouseMappingManagerType.ROLE_JUMP)] = ( - manager.binding_from_event(rebind_key) - ) - var legacy_file := FileAccess.open( - KeyboardMouseMappingManagerType.MAPPING_PATH, - FileAccess.WRITE, - ) - assert(legacy_file != null) - legacy_file.store_string(JSON.stringify({ - "format_version": KeyboardMouseMappingManagerType.LEGACY_FORMAT_VERSION, - "bindings": legacy_bindings, - })) - legacy_file.close() - var migrated_manager := KeyboardMouseMappingManagerType.new() - root.add_child(migrated_manager) - await process_frame - assert(migrated_manager.has_custom_mapping()) - assert(_has_physical_key(&"jump", KEY_R)) - assert(_has_physical_key(&"reel_alternate", KEY_QUOTELEFT)) - assert(migrated_manager.reset_mapping()) - - var fishing_spot := FishingSpotScene.instantiate() as FishingSpotType - root.add_child(fishing_spot) - await process_frame - var local_player := PlayerType.new() - fishing_spot.set("_local_player", local_player) - fishing_spot.set("_gameplay_input_enabled", true) - fishing_spot.state = FishingSpotType.FishingState.FIGHTING - var catch_controller := fishing_spot.get_node("%CatchController") - var catch_profile := CatchDifficultyProfileType.new() - catch_profile.barrier_count_min = 0 - catch_profile.barrier_count_max = 0 - catch_controller.start_encounter(catch_profile, 1.0, 1) - var alternate_press := InputEventAction.new() - alternate_press.action = &"reel_alternate" - alternate_press.pressed = true - fishing_spot._unhandled_input(alternate_press) - assert(bool(catch_controller.get("_reel_input_held"))) - var alternate_release := InputEventAction.new() - alternate_release.action = &"reel_alternate" - alternate_release.pressed = false - fishing_spot._unhandled_input(alternate_release) - assert(not bool(catch_controller.get("_reel_input_held"))) - fishing_spot.free() - local_player.free() - settings_panel.queue_free() panel.queue_free() - migrated_manager.queue_free() manager.queue_free() print("Keyboard and mouse mapping validation: PASS") quit(0) diff --git a/tests/light_performance_profile_validation.gd b/tests/light_performance_profile_validation.gd index 4533abb..9421901 100644 --- a/tests/light_performance_profile_validation.gd +++ b/tests/light_performance_profile_validation.gd @@ -43,7 +43,6 @@ func _run() -> void: await process_frame assert(bool(normal_main.get("_application_initialized"))) _validate_normal_profile(normal_main) - _validate_new_game_music_transition(normal_main) _stop_audio_players(normal_main) normal_main.queue_free() for _frame: int in 8: @@ -116,7 +115,6 @@ func _validate_main_profile(main: Node) -> void: assert(player_menu != null) assert(not player_menu.is_cooler_water_effect_enabled()) assert((main.get_node("%TitleMusic") as AudioStreamPlayer).stream != null) - assert((main.get_node("%NewGameMusic") as AudioStreamPlayer).stream != null) assert((main.get_node("%DuskMusic") as AudioStreamPlayer).stream != null) assert( (main.get_node("%WavesAudio") as AudioStreamPlayer).stream == null @@ -185,7 +183,6 @@ func _validate_normal_profile(main: Node) -> void: assert(clouds.get_node_or_null("CloudField") is MultiMeshInstance3D) assert(clouds.get_node_or_null("CloudCeiling") == null) assert((main.get_node("%TitleMusic") as AudioStreamPlayer).stream != null) - assert((main.get_node("%NewGameMusic") as AudioStreamPlayer).stream != null) assert((main.get_node("%DuskMusic") as AudioStreamPlayer).stream != null) assert( (main.get_node("%WavesAudio") as AudioStreamPlayer).stream != null @@ -195,18 +192,6 @@ func _validate_normal_profile(main: Node) -> void: ) -func _validate_new_game_music_transition(main: Node) -> void: - var title_music := main.get_node("%TitleMusic") as AudioStreamPlayer - var new_game_music := main.get_node("%NewGameMusic") as AudioStreamPlayer - assert(title_music.playing) - main.call("_start_new_game_music") - assert(not title_music.playing) - assert(new_game_music.playing) - main.call("_show_title_music", true) - assert(not new_game_music.playing) - assert(title_music.playing) - - func _first_fresh_water_visual(main: Node) -> MeshInstance3D: var root := main.get_node( "TestWorld/Regions/GeneratedWorldRegion/WaterBodies/FreshWaterBodies" diff --git a/tests/manual/terrain_chunk_generator/terrain_chunk_compatibility_report.gd b/tests/manual/terrain_chunk_generator/terrain_chunk_compatibility_report.gd index 94b557e..64d5f1f 100644 --- a/tests/manual/terrain_chunk_generator/terrain_chunk_compatibility_report.gd +++ b/tests/manual/terrain_chunk_generator/terrain_chunk_compatibility_report.gd @@ -119,8 +119,6 @@ func _make_generator() -> TerrainChunkGenerator: "chunk_0005", "chunk_0006", "chunk_0007", - "chunk_0008", ] ) - generator.grass_sand_smoothing_enabled = true return generator diff --git a/tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.tscn b/tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.tscn index 34a74e2..964da9a 100644 --- a/tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.tscn +++ b/tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.tscn @@ -21,14 +21,12 @@ shadow_enabled = true [node name="TerrainChunkGenerator" type="Node3D" parent="."] script = ExtResource("2_generator") catalog = ExtResource("3_catalog") -grid_size = Vector2i(11, 9) +grid_size = Vector2i(7, 7) generation_seed = 13001 build_collision = true show_chunk_labels = false force_center_chunk_id = &"chunk_spawn" -required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007", "chunk_0008") -grass_sand_smoothing_enabled = true -elevated_cliff_feature_enabled = true +required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007") [node name="Player" parent="." instance=ExtResource("4_player")] position = Vector3(0, 0.3, 0) diff --git a/tests/player_experience_validation.gd b/tests/player_experience_validation.gd index 5e9dbef..0c1a9f7 100644 --- a/tests/player_experience_validation.gd +++ b/tests/player_experience_validation.gd @@ -143,7 +143,7 @@ func _validate_save_migration() -> void: version_five, 5, ) - assert(int(migrated.get("save_version", -1)) == 10) + assert(int(migrated.get("save_version", -1)) == 9) var experience_data: Dictionary = migrated.get("experience", {}) assert(int(experience_data.get("total_experience", -1)) == 0) var world_data: Dictionary = migrated.get("world", {}) diff --git a/tests/profile_multiplayer_validation.gd b/tests/profile_multiplayer_validation.gd index ec7a669..669c155 100644 --- a/tests/profile_multiplayer_validation.gd +++ b/tests/profile_multiplayer_validation.gd @@ -152,10 +152,15 @@ func _run_client() -> void: var chat_service := main.get_node( "%NetworkChatService" ) as NetworkChatService - # Player-hosted rooms deliberately send no prior scrollback to joining or - # reconnecting clients. Live messages sent after authentication still work. - await create_timer(0.5).timeout - assert(not chat_service.get_history().any( + var history_deadline: int = Time.get_ticks_msec() + 8000 + while Time.get_ticks_msec() < history_deadline: + if chat_service.get_history().any( + func(message: Dictionary) -> bool: + return str(message.get("body", "")) == HOST_HISTORY_MESSAGE + ): + break + await process_frame + assert(chat_service.get_history().any( func(message: Dictionary) -> bool: return str(message.get("body", "")) == HOST_HISTORY_MESSAGE )) diff --git a/tests/progression_archive_validation.gd b/tests/progression_archive_validation.gd deleted file mode 100644 index 11fc51c..0000000 --- a/tests/progression_archive_validation.gd +++ /dev/null @@ -1,135 +0,0 @@ -extends SceneTree - -const MainScene: PackedScene = preload("res://main/main.tscn") - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - root.size = Vector2i(1280, 720) - var main: Node = MainScene.instantiate() - root.add_child(main) - for _frame: int in 4: - await process_frame - if not bool(main.get("_application_initialized")): - main.call("_activate_selected_data_path", "", true) - for _frame: int in 8: - await process_frame - assert(bool(main.get("_application_initialized"))) - - var save_manager := main.get("_save_manager") as PlayerSaveManager - var player := main.get("_player") as Player - var data_root := main.get("_data_root") as PlayerDataRoot - assert(save_manager != null and player != null and data_root != null) - assert(save_manager.initialize_new_game(864209)) - save_manager.set_autosave_enabled(true) - assert(player.wallet.restore_balance(4321)) - assert(player.bag.add_item(&"coffee")) - assert(save_manager.save_now()) - - var save_path: String = str(save_manager.get("_save_path")) - assert(save_path.ends_with("player/player_save.nfsave")) - assert(FileAccess.file_exists(save_path)) - assert(not FileAccess.file_exists(save_path.get_base_dir().path_join( - "player_save.json" - ))) - _assert_opaque(save_path) - var decoded: Dictionary = ProgressionSaveCodec.read_local_save(save_path) - assert(bool(decoded.get("ok", false))) - assert(int((decoded["data"] as Dictionary)["save_version"]) == 10) - - var archive_path: String = data_root.progression_backup_directory().path_join( - "progression-test.nfsave" - ) - var exported: Dictionary = save_manager.export_progression_archive( - archive_path - ) - assert(bool(exported.get("ok", false))) - assert(FileAccess.file_exists(archive_path)) - _assert_opaque(archive_path) - var inspected: Dictionary = save_manager.inspect_progression_archive( - archive_path - ) - assert(bool(inspected.get("ok", false))) - assert(int(inspected.get("wallet_balance", -1)) == 4321) - assert(int(inspected.get("world_seed", -1)) == 864209) - - assert(player.wallet.restore_balance(7)) - assert(save_manager.save_now()) - var imported: Dictionary = save_manager.import_progression_archive( - archive_path - ) - assert(bool(imported.get("ok", false))) - assert(save_manager.load_player_data()) - assert(player.wallet.get_balance() == 4321) - assert(save_manager.get_world_seed() == 864209) - - var legacy_data: Dictionary = ( - ProgressionSaveCodec.read_local_save(save_path)["data"] - ) - assert(save_manager.delete_progression_save()) - var legacy_path: String = save_path.get_base_dir().path_join( - "player_save.json" - ) - var legacy_file := FileAccess.open(legacy_path, FileAccess.WRITE) - assert(legacy_file != null) - legacy_file.store_string(JSON.stringify(legacy_data, "\t")) - legacy_file.close() - assert(save_manager.load_player_data()) - assert(FileAccess.file_exists(save_path)) - assert(not FileAccess.file_exists(legacy_path)) - _assert_opaque(save_path) - - var settings_panels: Array[Node] = main.find_children( - "*", "SettingsPanel", true, false - ) - assert(settings_panels.size() == 2) - for settings_panel: SettingsPanel in settings_panels: - var export_button := settings_panel.get_node( - "%ExportProgression" - ) as Button - var import_button := settings_panel.get_node( - "%ImportProgression" - ) as Button - assert(export_button != null and import_button != null) - assert(not export_button.disabled and not import_button.disabled) - assert( - export_button.get_node(export_button.focus_neighbor_right) - == import_button - ) - assert( - import_button.get_node(import_button.focus_neighbor_left) - == export_button - ) - - var migrated_root: String = data_root.root_path.get_base_dir().path_join( - "progression-migrated-data" - ) - var data_migration: Dictionary = PortableDataMigration.migrate_active_to( - data_root, migrated_root - ) - assert(bool(data_migration.get("ok", false))) - var migrated_save: String = migrated_root.path_join( - "player/player_save.nfsave" - ) - assert(FileAccess.file_exists(migrated_save)) - assert(bool( - ProgressionSaveCodec.read_local_save(migrated_save).get("ok", false) - )) - - main.queue_free() - for _frame: int in 4: - await process_frame - await create_timer(0.1).timeout - print("Progression archive validation: PASS") - quit() - - -func _assert_opaque(path: String) -> void: - var bytes: PackedByteArray = PortableFileGuard.read_bytes(path) - assert(not bytes.is_empty()) - var encoded_hex: String = bytes.hex_encode() - assert("save_version".to_utf8_buffer().hex_encode() not in encoded_hex) - assert("wallet".to_utf8_buffer().hex_encode() not in encoded_hex) diff --git a/tests/progression_archive_validation.gd.uid b/tests/progression_archive_validation.gd.uid deleted file mode 100644 index 59699fd..0000000 --- a/tests/progression_archive_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ccl47ftc1cu5l diff --git a/tests/terrain_chunk_generator_validation.gd b/tests/terrain_chunk_generator_validation.gd index b583a1f..0d70816 100644 --- a/tests/terrain_chunk_generator_validation.gd +++ b/tests/terrain_chunk_generator_validation.gd @@ -12,15 +12,6 @@ const EXPECTED_IDS: Array[String] = [ "chunk_0005", "chunk_0006", "chunk_0007", - "chunk_0008", - "chunk_0009", - "chunk_0010", - "chunk_0011", - "chunk_0012", - "chunk_0013", - "chunk_0014", - "chunk_0015", - "chunk_0016", "chunk_spawn", ] const REQUIRED_IDS: Array[String] = [ @@ -32,11 +23,10 @@ const REQUIRED_IDS: Array[String] = [ "chunk_0005", "chunk_0006", "chunk_0007", - "chunk_0008", ] const TEST_SEED := 13001 const CONNECTOR_STRESS_SEED := 287 -const EXPECTED_SOLVER_VARIANT_COUNT := 35 +const EXPECTED_SOLVER_VARIANT_COUNT := 27 const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = { &"chunk_0000": 4, &"chunk_0001": 4, @@ -46,15 +36,6 @@ const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = { &"chunk_0005": 4, &"chunk_0006": 4, &"chunk_0007": 4, - &"chunk_0008": 4, - &"chunk_0009": 4, - &"chunk_0010": 4, - &"chunk_0011": 4, - &"chunk_0012": 4, - &"chunk_0013": 4, - &"chunk_0014": 4, - &"chunk_0015": 4, - &"chunk_0016": 4, &"chunk_spawn": 1, } @@ -68,7 +49,6 @@ func _initialize() -> void: func _run() -> void: _validate_catalog() _validate_profiles() - _validate_second_tier_coastal_layout() _validate_generation() _finish() @@ -95,15 +75,6 @@ func _validate_catalog() -> void: var grass_ocean_edge := CATALOG.definition_for_id(&"chunk_0005") var grass_beach_transition := CATALOG.definition_for_id(&"chunk_0006") var grass_ocean_corner := CATALOG.definition_for_id(&"chunk_0007") - var beach_ocean_corner := CATALOG.definition_for_id(&"chunk_0008") - var grass_sand_diagonal := CATALOG.definition_for_id(&"chunk_0013") - var cliff_sea_edge := CATALOG.definition_for_id(&"chunk_0014") - var cliff_sea_corner := CATALOG.definition_for_id(&"chunk_0015") - var stream_variant := CATALOG.definition_for_id(&"chunk_0016") - var cliff_top := CATALOG.definition_for_id(&"chunk_0009") - var cliff_corner := CATALOG.definition_for_id(&"chunk_0010") - var cliff_edge := CATALOG.definition_for_id(&"chunk_0011") - var cliff_ramp := CATALOG.definition_for_id(&"chunk_0012") var spawn := CATALOG.definition_for_id(&"chunk_spawn") _check( ( @@ -113,15 +84,6 @@ func _validate_catalog() -> void: and grass_ocean_edge != null and grass_beach_transition != null and grass_ocean_corner != null - and beach_ocean_corner != null - and grass_sand_diagonal != null - and cliff_sea_edge != null - and cliff_sea_corner != null - and stream_variant != null - and cliff_top != null - and cliff_corner != null - and cliff_edge != null - and cliff_ramp != null and spawn != null ), "The authored biome-rule definitions must be available.", @@ -133,15 +95,6 @@ func _validate_catalog() -> void: and grass_ocean_edge != null and grass_beach_transition != null and grass_ocean_corner != null - and beach_ocean_corner != null - and grass_sand_diagonal != null - and cliff_sea_edge != null - and cliff_sea_corner != null - and stream_variant != null - and cliff_top != null - and cliff_corner != null - and cliff_edge != null - and cliff_ramp != null and spawn != null ): _check( @@ -240,124 +193,6 @@ func _validate_catalog() -> void: ), "Both corner land seams must accept straight grass ocean edges.", ) - _check( - beach_ocean_corner.ocean_facing_edges - == ( - (1 << int(TerrainChunkTopology.Edge.EAST)) - | (1 << int(TerrainChunkTopology.Edge.SOUTH)) - ), - "The authored beach corner must expose east and south to ocean.", - ) - _check( - beach_ocean_corner.minimum_required_neighbors == 2 - and ( - "beach_ocean_edge" - in beach_ocean_corner.required_neighbor_tags - ), - "The beach corner must join two straight beach ocean edges.", - ) - _check( - grass_sand_diagonal.must_be_interior - and not grass_sand_diagonal.participates_in_base_solver - and "mixed_surface" in grass_sand_diagonal.tags, - ( - "The grass-sand diagonal must remain an inland post-process " - + "mixed-surface chunk." - ), - ) - _check( - grass_sand_diagonal.surface_tags_for_edge( - TerrainChunkTopology.Edge.NORTH - ) == PackedStringArray(["sand"]) - and grass_sand_diagonal.surface_tags_for_edge( - TerrainChunkTopology.Edge.WEST - ) == PackedStringArray(["sand"]) - and grass_sand_diagonal.surface_tags_for_edge( - TerrainChunkTopology.Edge.EAST - ) == PackedStringArray(["grass"]) - and grass_sand_diagonal.surface_tags_for_edge( - TerrainChunkTopology.Edge.SOUTH - ) == PackedStringArray(["grass"]), - "The diagonal's authored edges must retain two sand and two grass sides.", - ) - var stream := CATALOG.definition_for_id(&"chunk_0003") - _check( - stream != null - and is_equal_approx(stream.selection_weight, 0.3) - and is_equal_approx(stream_variant.selection_weight, 0.3) - and stream_variant.tags == stream.tags - and stream_variant.allowed_neighbor_tags - == stream.allowed_neighbor_tags - and stream_variant.water_inlet_edges == stream.water_inlet_edges - and stream_variant.water_outlet_edges == stream.water_outlet_edges - and stream_variant.water_surface_size == stream.water_surface_size - and stream_variant.water_surface_offset - == stream.water_surface_offset, - ( - "Both stream visuals must share one combined selection weight " - + "and identical terrain/water behavior." - ), - ) - for cliff_definition: TerrainChunkDefinition in [ - cliff_top, - cliff_corner, - cliff_edge, - cliff_ramp, - ]: - _check( - cliff_definition.overlay_only - and cliff_definition.must_be_interior - and cliff_definition.base_layer_scene != null - and cliff_definition.base_layer_mesh_name == &"chunk_0000", - "Every second-tier piece must overlay level-one grass inland.", - ) - for coastal_cliff: TerrainChunkDefinition in [ - cliff_sea_edge, - cliff_sea_corner, - ]: - _check( - coastal_cliff.overlay_only - and coastal_cliff.base_layer_scene == null - and not coastal_cliff.must_be_interior - and coastal_cliff.prefers_map_boundary - and "coast" in coastal_cliff.tags - and "elevation_2_sea_edge" in coastal_cliff.tags, - ( - "Each second-tier sea piece must be a complete coastal " - + "overlay without an inland grass base." - ), - ) - _check( - cliff_sea_edge.ocean_facing_edges - == (1 << int(TerrainChunkTopology.Edge.EAST)), - "The second-tier sea edge must face east before rotation.", - ) - _check( - cliff_sea_corner.ocean_facing_edges - == ( - (1 << int(TerrainChunkTopology.Edge.EAST)) - | (1 << int(TerrainChunkTopology.Edge.SOUTH)) - ), - "The second-tier sea corner must face east and south before rotation.", - ) - _check( - cliff_sea_corner.minimum_required_neighbors == 2 - and ( - "elevation_2_sea_edge" - in cliff_sea_corner.required_neighbor_tags - ), - "The second-tier sea corner must join two straight sea edges.", - ) - _check( - cliff_top.minimum_required_neighbors == 4 - and "elevation_2" in cliff_top.required_neighbor_tags, - "The second-tier top must be enclosed on all four sides.", - ) - _check( - "ramp" in cliff_ramp.tags - and cliff_ramp.maximum_placements > 0, - "The second-tier ramp must remain available without being required.", - ) func _validate_profiles() -> void: @@ -393,91 +228,6 @@ func _validate_profiles() -> void: ) -func _validate_second_tier_coastal_layout() -> void: - var generator := TerrainChunkGenerator.new() - generator.catalog = CATALOG - generator.grid_size = Vector2i(4, 4) - generator.generate_on_ready = false - generator.build_collision = false - generator.force_center_chunk_id = &"" - root.add_child(generator) - var keys := PackedStringArray() - for row: int in generator.grid_size.y: - for column: int in generator.grid_size.x: - var at_north := row == 0 - var at_east := column == generator.grid_size.x - 1 - var at_south := row == generator.grid_size.y - 1 - var at_west := column == 0 - if at_north and at_west: - keys.append("chunk_0015@2") - elif at_north and at_east: - keys.append("chunk_0015@1") - elif at_south and at_east: - keys.append("chunk_0015@0") - elif at_south and at_west: - keys.append("chunk_0015@3") - elif at_north: - keys.append("chunk_0014@1") - elif at_east: - keys.append("chunk_0014@0") - elif at_south: - keys.append("chunk_0014@3") - elif at_west: - keys.append("chunk_0014@2") - else: - keys.append("chunk_0009@0") - _check( - generator.generate_from_placement_keys(keys), - ( - "The second-tier sea edge and corner must form a complete " - + "raised coastal perimeter." - ), - ) - if generator.placement_keys().size() == keys.size(): - _check( - _all_neighbor_edges_match(generator), - "Every raised coastal-perimeter seam must match.", - ) - _check( - _count_placements(generator.placement_keys(), "chunk_0014") == 8 - and _count_placements( - generator.placement_keys(), "chunk_0015" - ) == 4, - "The raised coastal perimeter must use its straight and corner pieces.", - ) - for record: Dictionary in generator.placement_records(): - var stable_id: StringName = record.get("stable_id", &"") - if stable_id not in [&"chunk_0014", &"chunk_0015"]: - continue - var coordinate: Vector2i = record.get( - "coordinate", Vector2i.ZERO - ) - var variant := _find_variant( - generator, - stable_id, - int(record.get("rotation_quarters", 0)), - ) - _check( - variant != null - and generator._variant_respects_ocean_boundary( - variant, coordinate - ), - "Every second-tier sea opening must face outside the terrain grid.", - ) - var generated := generator.get_generated_chunks_root() - if generated != null: - for child: Node in generated.get_children(): - var stable_id := StringName( - child.get_meta(&"terrain_chunk_id", &"") - ) - if stable_id in [&"chunk_0014", &"chunk_0015"]: - _check( - child.get_node_or_null("TerrainBaseLayer") == null, - "Second-tier sea pieces must not render inland grass over ocean.", - ) - generator.free() - - func _validate_generation() -> void: var first := _make_generator() root.add_child(first) @@ -493,10 +243,6 @@ func _validate_generation() -> void: _placements_contain(first_keys, stable_id), "Generated diagnostic must contain %s." % stable_id, ) - _check( - _placements_contain(first_keys, "chunk_0013"), - "Generated terrain must replace sharp grass-sand corners with diagonals.", - ) _check( _all_neighbor_edges_match(first), "Every generated neighboring edge must match.", @@ -605,12 +351,8 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: ) var stream := CATALOG.definition_for_id(&"chunk_0003") - var alternate_stream := CATALOG.definition_for_id(&"chunk_0016") - _check( - stream != null and alternate_stream != null, - "Both stream visual definitions must be available.", - ) - if stream == null or alternate_stream == null: + _check(stream != null, "The stream definition must be available.") + if stream == null: return for quarter_turns: int in 4: var stream_variant := _find_variant( @@ -622,16 +364,7 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: stream_variant != null, "The stream must support rotation %d." % quarter_turns, ) - var alternate_variant := _find_variant( - generator, - &"chunk_0016", - quarter_turns, - ) - _check( - alternate_variant != null, - "The alternate stream must support rotation %d." % quarter_turns, - ) - if stream_variant == null or alternate_variant == null: + if stream_variant == null: continue var expected_inlet := 1 << int(TerrainChunkTopology.rotated_edge( TerrainChunkTopology.Edge.NORTH, @@ -651,25 +384,6 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: == expected_outlet, "The rotated stream outlet must follow its authored orientation.", ) - _check( - alternate_variant.rotated_edge_mask( - alternate_stream.water_inlet_edges - ) == expected_inlet - and alternate_variant.rotated_edge_mask( - alternate_stream.water_outlet_edges - ) == expected_outlet, - "The alternate stream connectors must rotate exactly like the original.", - ) - for edge_value: int in TerrainChunkTopology.Edge.values(): - var edge := edge_value as TerrainChunkTopology.Edge - _check( - stream_variant.profile(edge).signature( - TerrainChunkGenerator.CONSTRAINT_PROFILE_QUANTIZATION - ) == alternate_variant.profile(edge).signature( - TerrainChunkGenerator.CONSTRAINT_PROFILE_QUANTIZATION - ), - "Both stream visuals must expose identical terrain edge profiles.", - ) var beach := CATALOG.definition_for_id(&"chunk_0002") _check(beach != null, "The beach definition must be available.") @@ -729,13 +443,11 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: var grass_ocean_edge := CATALOG.definition_for_id(&"chunk_0005") var grass_beach_transition := CATALOG.definition_for_id(&"chunk_0006") var grass_ocean_corner := CATALOG.definition_for_id(&"chunk_0007") - var beach_ocean_corner := CATALOG.definition_for_id(&"chunk_0008") _check( ( grass_ocean_edge != null and grass_beach_transition != null and grass_ocean_corner != null - and beach_ocean_corner != null ), "The authored coastline additions must be available.", ) @@ -743,7 +455,6 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: grass_ocean_edge == null or grass_beach_transition == null or grass_ocean_corner == null - or beach_ocean_corner == null ): return var beach_zero := _find_variant(generator, &"chunk_0002", 0) @@ -751,14 +462,12 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: var grass_edge_south := _find_variant(generator, &"chunk_0005", 3) var transition_zero := _find_variant(generator, &"chunk_0006", 0) var corner_zero := _find_variant(generator, &"chunk_0007", 0) - var beach_corner_zero := _find_variant(generator, &"chunk_0008", 0) _check( beach_zero != null and grass_edge_zero != null and grass_edge_south != null and transition_zero != null - and corner_zero != null - and beach_corner_zero != null, + and corner_zero != null, "The unrotated coastline variants must be available.", ) if ( @@ -767,7 +476,6 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: and grass_edge_south != null and transition_zero != null and corner_zero != null - and beach_corner_zero != null ): _check( grass_edge_zero.profile( @@ -814,80 +522,6 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: ), "The corner's west side must join a straight grass ocean edge.", ) - _check( - beach_corner_zero.profile(TerrainChunkTopology.Edge.NORTH).matches( - beach_zero.profile(TerrainChunkTopology.Edge.SOUTH), - generator.edge_match_tolerance, - ), - "The beach corner's north side must join a straight beach.", - ) - var beach_south := _find_variant(generator, &"chunk_0002", 3) - _check( - beach_south != null - and beach_corner_zero.profile( - TerrainChunkTopology.Edge.WEST - ).matches( - beach_south.profile(TerrainChunkTopology.Edge.EAST), - generator.edge_match_tolerance, - ), - "The beach corner's west side must join a rotated straight beach.", - ) - var cliff_top_zero := _find_variant(generator, &"chunk_0009", 0) - var cliff_sea_edge_zero := _find_variant(generator, &"chunk_0014", 0) - var cliff_sea_edge_south := _find_variant(generator, &"chunk_0014", 3) - var cliff_sea_corner_zero := _find_variant(generator, &"chunk_0015", 0) - _check( - cliff_top_zero != null - and cliff_sea_edge_zero != null - and cliff_sea_edge_south != null - and cliff_sea_corner_zero != null, - "The second-tier sea edge variants must be available.", - ) - if ( - cliff_top_zero != null - and cliff_sea_edge_zero != null - and cliff_sea_edge_south != null - and cliff_sea_corner_zero != null - ): - _check( - cliff_sea_edge_zero.profile( - TerrainChunkTopology.Edge.EAST - ).points.is_empty(), - "The second-tier sea edge must remain open on its ocean side.", - ) - _check( - cliff_sea_corner_zero.profile( - TerrainChunkTopology.Edge.EAST - ).points.is_empty() - and cliff_sea_corner_zero.profile( - TerrainChunkTopology.Edge.SOUTH - ).points.is_empty(), - "The second-tier sea corner must retain both ocean openings.", - ) - _check( - generator._edges_are_compatible( - cliff_sea_edge_zero, - TerrainChunkTopology.Edge.WEST, - cliff_top_zero, - TerrainChunkTopology.Edge.EAST, - ), - "The second-tier sea edge must join the raised grass top inland.", - ) - _check( - generator._edges_are_compatible( - cliff_sea_corner_zero, - TerrainChunkTopology.Edge.NORTH, - cliff_sea_edge_zero, - TerrainChunkTopology.Edge.SOUTH, - ) - and generator._edges_are_compatible( - cliff_sea_corner_zero, - TerrainChunkTopology.Edge.WEST, - cliff_sea_edge_south, - TerrainChunkTopology.Edge.EAST, - ), - "The second-tier sea corner must join two straight sea edges.", - ) var flat_grass := _find_variant(generator, &"chunk_0000", 0) var flat_sand := _find_variant(generator, &"chunk_0001", 0) _check( @@ -926,58 +560,7 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: ), "No transition rotation may accept sand behind it.", ) - for quarter_turns: int in 4: - var diagonal := _find_variant( - generator, - &"chunk_0013", - quarter_turns, - ) - _check( - diagonal != null, - "The grass-sand diagonal must support rotation %d." - % quarter_turns, - ) - if diagonal == null: - continue - for source_edge_value: int in TerrainChunkTopology.Edge.values(): - var source_edge := source_edge_value as TerrainChunkTopology.Edge - var edge := TerrainChunkTopology.rotated_edge( - source_edge, - quarter_turns, - ) - var neighbor_edge := TerrainChunkTopology.opposite_edge(edge) - var expects_sand := source_edge in [ - TerrainChunkTopology.Edge.NORTH, - TerrainChunkTopology.Edge.WEST, - ] - var expected := flat_sand if expects_sand else flat_grass - var rejected := flat_grass if expects_sand else flat_sand - _check( - generator._edges_are_compatible( - diagonal, - edge, - expected, - neighbor_edge, - ), - "Each rotated diagonal edge must accept its authored surface.", - ) - _check( - not generator._edges_are_compatible( - diagonal, - edge, - rejected, - neighbor_edge, - ), - "No rotated diagonal edge may accept the opposite surface.", - ) - for stable_id: StringName in [ - &"chunk_0005", - &"chunk_0006", - &"chunk_0007", - &"chunk_0008", - &"chunk_0014", - &"chunk_0015", - ]: + for stable_id: StringName in [&"chunk_0005", &"chunk_0006", &"chunk_0007"]: for quarter_turns: int in 4: var coast_variant := _find_variant( generator, @@ -997,12 +580,11 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: ), "%s must never place its ocean edge inside the map." % stable_id, ) - var coast_definition := CATALOG.definition_for_id(stable_id) - if coast_definition == null: + if stable_id != &"chunk_0007": continue var boundary_coordinate := Vector2i(3, 3) var ocean_edges := coast_variant.rotated_edge_mask( - coast_definition.ocean_facing_edges + grass_ocean_corner.ocean_facing_edges ) for edge_value: int in TerrainChunkTopology.Edge.values(): if (ocean_edges & (1 << edge_value)) == 0: @@ -1021,15 +603,15 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: coast_variant, boundary_coordinate, ), - "Each coast rotation must fit its matching open map boundary.", + "Each grass corner rotation must fit its matching map corner.", ) func _validate_generation_summary(generator: TerrainChunkGenerator) -> void: var summary := generator._build_summary() _check( - int(summary.get("variant_count", 0)) == 69, - "The current catalog must expose all 69 authored rotations.", + int(summary.get("variant_count", 0)) == 33, + "The current catalog must expose all 33 authored rotations.", ) _check( int(summary.get("solver_variant_count", 0)) @@ -1082,7 +664,6 @@ func _make_generator() -> TerrainChunkGenerator: generator.build_collision = false generator.force_center_chunk_id = &"chunk_spawn" generator.required_chunk_ids = PackedStringArray(REQUIRED_IDS) - generator.grass_sand_smoothing_enabled = true generator.maximum_backtracks = 100000 return generator @@ -1230,27 +811,6 @@ func _validate_layout_rules(generator: TerrainChunkGenerator) -> void: straight_edge_neighbors == 2, "Every grass corner must join two straight grass ocean edges.", ) - if placement.definition.stable_id == &"chunk_0008": - var straight_beach_neighbors := 0 - for edge_value: int in TerrainChunkTopology.Edge.values(): - var neighbor_coordinate := ( - coordinate - + TerrainChunkTopology.grid_offset( - edge_value as TerrainChunkTopology.Edge - ) - ) - if not generator._coordinate_is_inside_grid(neighbor_coordinate): - continue - var neighbor := generator._placements[ - neighbor_coordinate.y * generator.grid_size.x - + neighbor_coordinate.x - ] - if neighbor.definition.stable_id == &"chunk_0002": - straight_beach_neighbors += 1 - _check( - straight_beach_neighbors == 2, - "Every beach corner must join two straight beach ocean edges.", - ) if placement.definition.stable_id != &"chunk_0001": continue var touches_coast := false diff --git a/tests/world_layout_multiplayer_validation.gd b/tests/world_layout_multiplayer_validation.gd deleted file mode 100644 index 51e7be6..0000000 --- a/tests/world_layout_multiplayer_validation.gd +++ /dev/null @@ -1,126 +0,0 @@ -extends SceneTree - -const MainScene: PackedScene = preload("res://main/main.tscn") -const TEST_PORT: int = 18155 -const TEST_SEED: int = 86421357 - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - var arguments: PackedStringArray = OS.get_cmdline_user_args() - if arguments.has("host"): - await _run_host() - return - if arguments.has("client"): - await _run_client() - return - push_error("World layout multiplayer validation needs host or client mode.") - quit(1) - - -func _run_host() -> void: - var main: Node = await _create_initialized_main() - var session := main.get_node("%NetworkSession") as NetworkSession - var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(bool(main.call( - "_apply_world", - WorldLayout.STARTER_ISLAND, - TEST_SEED, - true, - ))) - assert(bool(main.call( - "_prepare_host_world", - WorldLayout.STARTER_ISLAND, - TEST_SEED, - ))) - assert(save_manager.initialize_new_game( - TEST_SEED, - WorldLayout.STARTER_ISLAND, - )) - assert(session.start_private_host(TEST_PORT)) - main.call("_enter_gameplay") - assert(session.set_host_open(true)) - var remote_peer_id: int = await _wait_for_remote_peer(session) - assert(remote_peer_id > 1) - assert(session.peer_supports_capability( - remote_peer_id, - NetworkProtocol.WORLD_LAYOUT_CAPABILITY, - )) - var disconnect_deadline: int = Time.get_ticks_msec() + 12000 - while ( - Time.get_ticks_msec() < disconnect_deadline - and session.is_authenticated_peer(remote_peer_id) - ): - await process_frame - assert(not session.is_authenticated_peer(remote_peer_id)) - print("World layout multiplayer host validation: PASS") - await _cleanup(main, session) - - -func _run_client() -> void: - var main: Node = await _create_initialized_main() - main.call( - "_on_title_join_game_requested", - "127.0.0.1:%d" % TEST_PORT, - ) - var session := main.get_node("%NetworkSession") as NetworkSession - var deadline: int = Time.get_ticks_msec() + 20000 - while Time.get_ticks_msec() < deadline: - await process_frame - if session.state == NetworkSession.State.VERIFYING_SERVER_IDENTITY: - main.call("_confirm_server_trust") - if session.is_joined_client() and bool(main.get("_gameplay_started")): - break - assert(session.is_joined_client()) - assert(session.supports_server_capability( - NetworkProtocol.WORLD_LAYOUT_CAPABILITY - )) - var metadata: Dictionary = session.get_last_server_metadata() - assert(str(metadata.get("world_layout", "")) == String( - WorldLayout.STARTER_ISLAND - )) - assert(int(metadata.get("world_seed", 0)) == TEST_SEED) - var world := main.get("_test_world") as TestWorld - assert(world.get_world_layout() == WorldLayout.STARTER_ISLAND) - assert(world.get_generation_seed() == TEST_SEED) - assert(world.get_node_or_null("Regions/StarterIslandRegion") != null) - assert(world.get_fishing_shop() != null) - assert(world.get_player_storage() != null) - print("World layout multiplayer client validation: PASS") - await _cleanup(main, session) - - -func _create_initialized_main() -> Node: - root.size = Vector2i(1280, 720) - var main: Node = MainScene.instantiate() - root.add_child(main) - for _frame: int in 4: - await process_frame - if not bool(main.get("_application_initialized")): - main.call("_activate_selected_data_path", "", true) - for _frame: int in 8: - await process_frame - assert(bool(main.get("_application_initialized"))) - return main - - -func _wait_for_remote_peer(session: NetworkSession) -> int: - var deadline: int = Time.get_ticks_msec() + 20000 - while Time.get_ticks_msec() < deadline: - await process_frame - for peer_id: int in session.get_authenticated_peer_ids(): - if peer_id != session.get_local_peer_id(): - return peer_id - return 0 - - -func _cleanup(main: Node, session: NetworkSession) -> void: - session.disconnect_session("") - main.queue_free() - for _frame: int in 4: - await process_frame - await create_timer(0.1).timeout - quit() diff --git a/tests/world_layout_multiplayer_validation.gd.uid b/tests/world_layout_multiplayer_validation.gd.uid deleted file mode 100644 index e666cb2..0000000 --- a/tests/world_layout_multiplayer_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bg52pbgv5ykjc diff --git a/tests/world_layout_validation.gd b/tests/world_layout_validation.gd deleted file mode 100644 index 63f03ae..0000000 --- a/tests/world_layout_validation.gd +++ /dev/null @@ -1,125 +0,0 @@ -extends SceneTree - -const TestWorldScene: PackedScene = preload("res://world/test_world.tscn") -const SetupPageScene: PackedScene = preload( - "res://ui/new_game_setup_page.tscn" -) - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - root.size = Vector2i(1280, 720) - _validate_layout_values() - await _validate_setup_page() - await _validate_world_switching() - _validate_network_metadata() - print("World layout validation: PASS") - quit() - - -func _validate_layout_values() -> void: - assert(WorldLayout.is_valid(WorldLayout.GENERATED)) - assert(WorldLayout.is_valid(String(WorldLayout.STARTER_ISLAND))) - assert(not WorldLayout.is_valid(&"unknown")) - assert(WorldLayout.normalized("unknown") == WorldLayout.GENERATED) - assert(WorldLayout.display_name(WorldLayout.STARTER_ISLAND) == "starter island") - - -func _validate_setup_page() -> void: - assert(NewGameSetupPage.parse_seed_text(" 24680 ") == 24680) - assert(NewGameSetupPage.parse_seed_text("0") == 0) - assert(NewGameSetupPage.parse_seed_text("2147483647") == 0) - assert(NewGameSetupPage.parse_seed_text("") == 0) - var text_seed: int = NewGameSetupPage.parse_seed_text("voyager island") - assert(text_seed > 0 and text_seed <= PlayerSaveManager.MAX_WORLD_SEED) - assert(NewGameSetupPage.parse_seed_text(" voyager island ") == text_seed) - assert(NewGameSetupPage.parse_seed_text("voyager island") == text_seed) - assert(NewGameSetupPage.parse_seed_text("Voyager Island") != text_seed) - var page := SetupPageScene.instantiate() as NewGameSetupPage - root.add_child(page) - await process_frame - page.open_page(true) - await process_frame - assert(page.visible) - var paper := page.get_node("%Paper") as PanelContainer - assert(paper.get_combined_minimum_size().x <= paper.size.x) - assert(paper.get_combined_minimum_size().y <= paper.size.y) - assert(page.get_selected_world_layout() == WorldLayout.GENERATED) - assert(page.get_selected_world_seed() > 0) - assert((page.get_node("%OverwriteWarning") as Label).visible) - var generated_button := page.get_node("%GeneratedButton") as Button - var starter_button := page.get_node("%StarterButton") as Button - var random_button := page.get_node("%RandomSeedButton") as Button - var custom_button := page.get_node("%CustomSeedButton") as Button - var seed_edit := page.get_node("%SeedEdit") as LineEdit - var start_button := page.get_node("%StartButton") as Button - var back_button := page.get_node("%BackButton") as Button - assert(generated_button.get_node(generated_button.focus_neighbor_right) == starter_button) - assert(generated_button.get_node(generated_button.focus_neighbor_bottom) == random_button) - assert(random_button.get_node(random_button.focus_neighbor_right) == custom_button) - assert(start_button.get_node(start_button.focus_neighbor_right) == back_button) - page.call("_choose_custom_seed") - assert(seed_edit.editable) - assert(custom_button.get_node(custom_button.focus_neighbor_bottom) == seed_edit) - assert(seed_edit.get_node(seed_edit.focus_neighbor_bottom) == start_button) - page.call("_set_world_layout", WorldLayout.STARTER_ISLAND) - assert(not (page.get_node("%SeedSection") as Control).visible) - var request: Dictionary = {} - page.start_requested.connect(func(layout: StringName, seed: int) -> void: - request["layout"] = layout - request["seed"] = seed - ) - page.call("_request_start") - assert(request.get("layout") == WorldLayout.STARTER_ISLAND) - assert(int(request.get("seed", 0)) > 0) - page.queue_free() - await process_frame - - -func _validate_world_switching() -> void: - var world := TestWorldScene.instantiate() as TestWorld - root.add_child(world) - await process_frame - assert(world.get_world_layout() == WorldLayout.GENERATED) - assert(world.get_fishing_shop() != null) - assert(world.get_player_storage() != null) - assert(world.activate_world(WorldLayout.STARTER_ISLAND, 13579)) - assert(world.get_world_layout() == WorldLayout.STARTER_ISLAND) - assert(world.get_generation_seed() == 13579) - assert(world.get_node_or_null("Regions/StarterIslandRegion") != null) - assert(world.get_node_or_null("Regions/GeneratedWorldRegion") == null) - assert(world.get_fishing_shop() != null) - assert(world.get_player_storage() != null) - assert(not world.get_fishable_water_regions().is_empty()) - assert(world.activate_world(WorldLayout.GENERATED, 24680)) - assert(world.get_world_layout() == WorldLayout.GENERATED) - assert(world.get_generation_seed() == 24680) - assert(world.get_node_or_null("Regions/GeneratedWorldRegion") != null) - assert(world.get_node_or_null("Regions/StarterIslandRegion") == null) - world.queue_free() - await process_frame - - -func _validate_network_metadata() -> void: - var hello: Dictionary = NetworkProtocol.make_server_hello( - true, - NetworkProtocol.RejectionCode.NONE, - "session", - 2, - 1, - 16, - "room", - 97531, - WorldLayout.STARTER_ISLAND, - ) - assert(str(hello.get("world_layout", "")) == String( - WorldLayout.STARTER_ISLAND - )) - assert(int(hello.get("world_seed", 0)) == 97531) - assert( - NetworkProtocol.WORLD_LAYOUT_CAPABILITY - in PackedStringArray(hello.get("capability_flags", [])) - ) diff --git a/tests/world_layout_validation.gd.uid b/tests/world_layout_validation.gd.uid deleted file mode 100644 index 9002c58..0000000 --- a/tests/world_layout_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bgxgyqq73sdpi diff --git a/tools/blender/export_terrain_chunks.py b/tools/blender/export_terrain_chunks.py index 02c7b95..27b775d 100644 --- a/tools/blender/export_terrain_chunks.py +++ b/tools/blender/export_terrain_chunks.py @@ -282,22 +282,10 @@ def _discover_chunks(tolerance: float) -> list[ChunkSource]: # Coastline meshes may intentionally stop before the positive-X edge, # leaving the surrounding ocean visible. Positive X is the canonical # authored ocean direction; Godot supplies the quarter-turn variants. - # Elevated inland cliff overlays also stop at their downhill edge, - # while second-tier sea pieces carry that edge down to ocean depth. - layered_cliff = "cliff_2" in label - allow_open_east_edge = ( - "ocean_edge" in label - or "sea_edge" in label - or "sea_corner" in label - or layered_cliff - ) + allow_open_east_edge = "ocean_edge" in label # Corner pieces additionally leave canonical negative Y open. Their - # two marked outer edges rotate together at runtime. - allow_open_south_edge = ( - "ocean_edge_corner" in label - or "sea_corner" in label - or (layered_cliff and "corner" in label) - ) + # two marked ocean edges rotate together at runtime. + allow_open_south_edge = "ocean_edge_corner" in label bound_problems = _validate_bounds( bounds, tolerance, diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index 0b26ffb..c79d80c 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -893,14 +893,12 @@ func _on_entry_text_submitted(_value: String) -> void: func _send() -> void: if _send_pending: return - var body := NetworkChatProtocol.sanitize_body(_entry.text) - if body.is_empty(): + var body := _entry.text + if body.strip_edges().is_empty(): close_chat() return - if _handle_editor_world_command(body): - return _send_pending = true - _pending_send_body = body + _pending_send_body = NetworkChatProtocol.sanitize_body(body) _entry.editable = false if not _service.send_local_message( body, @@ -914,82 +912,6 @@ func _send() -> void: _set_status("Sending…") -func _handle_editor_world_command(body: String) -> bool: - # These commands are deliberately limited to sessions launched by the - # Godot editor. Exported builds do not have the editor feature tag, and a - # joined editor client must not be able to mutate its host's world. - if not OS.has_feature("editor"): - return false - var command_text: String = body.strip_edges() - if not ( - command_text.begins_with("/time") - or command_text.begins_with("/weather") - ): - return false - var parts: PackedStringArray = command_text.split(" ", false) - var command: String = String(parts[0]).trim_prefix("/").to_lower() - var result: String = "" - if _session == null or not _session.is_host(): - result = "Editor world commands require the authoritative host." - elif command == "time": - result = _apply_editor_time_command(parts) - elif command == "weather": - result = _apply_editor_weather_command(parts) - else: - return false - _entry.clear() - _flush_draft() - _set_status(result) - close_chat(true) - return true - - -func _apply_editor_time_command(parts: PackedStringArray) -> String: - if parts.size() != 2 or _world_time == null: - return "Usage: /time [dawn, day, dusk, night]" - var phase_name: String = String(parts[1]).to_lower() - var target_hour: float = -1.0 - match phase_name: - "dawn": - target_hour = WorldTimeServiceType.DAWN_START_HOUR - "day": - target_hour = WorldTimeServiceType.DAWN_END_HOUR - "dusk": - target_hour = WorldTimeServiceType.DUSK_START_HOUR - "night": - target_hour = WorldTimeServiceType.DUSK_END_HOUR - _: - return "Usage: /time [dawn, day, dusk, night]" - if not _world_time.set_authoritative_time(target_hour): - return "Editor world time could not be changed." - return "Editor time: %s (%s)." % [ - phase_name, - _world_time.get_clock_text(), - ] - - -func _apply_editor_weather_command(parts: PackedStringArray) -> String: - if parts.size() != 2 or _world_weather == null: - return "Usage: /weather [sunny, cloudy, rainy, foggy]" - var weather_name: String = String(parts[1]).to_lower() - var target_weather: WorldWeatherServiceType.Weather - match weather_name: - "clear", "sunny": - weather_name = "sunny" - target_weather = WorldWeatherServiceType.Weather.SUNNY - "cloudy": - target_weather = WorldWeatherServiceType.Weather.CLOUDY - "rainy": - target_weather = WorldWeatherServiceType.Weather.RAINY - "foggy": - target_weather = WorldWeatherServiceType.Weather.FOGGY - _: - return "Usage: /weather [sunny, cloudy, rainy, foggy]" - if not _world_weather.set_authoritative_weather(target_weather): - return "Editor world weather could not be changed." - return "Editor weather: %s." % weather_name - - func _on_local_message_confirmed(message: Dictionary) -> void: if ( not _send_pending diff --git a/ui/components/general_inventory_slot.gd b/ui/components/general_inventory_slot.gd index 9204d0a..bdb132f 100644 --- a/ui/components/general_inventory_slot.gd +++ b/ui/components/general_inventory_slot.gd @@ -168,12 +168,6 @@ func _on_pressed() -> void: func _on_gui_input(event: InputEvent) -> void: var mouse_event := event as InputEventMouseButton - if mouse_event != null and mouse_event.pressed: - # Pointer clicks give Buttons keyboard focus after their GUI callback. - # Release that pointer-created focus on the next frame so a tooltip does - # not remain pinned after the pointer leaves. Controller navigation still - # keeps focus normally because it does not arrive as a mouse event. - call_deferred("_release_pointer_focus") if ( mouse_event != null and mouse_event.button_index == MOUSE_BUTTON_RIGHT @@ -185,11 +179,6 @@ func _on_gui_input(event: InputEvent) -> void: accept_event() -func _release_pointer_focus() -> void: - if has_focus(): - release_focus() - - func _set_context_hovered(active: bool) -> void: _context_hovered = active _update_context_presence() diff --git a/ui/fishing_shop.gd b/ui/fishing_shop.gd index f56b0ae..d76ec4a 100644 --- a/ui/fishing_shop.gd +++ b/ui/fishing_shop.gd @@ -627,10 +627,6 @@ func setup( _refresh_all() -func set_world_interaction(interaction: ShopInteractionType) -> void: - _interaction = interaction - - func open_shop() -> bool: if ( visible diff --git a/ui/game_ui.gd b/ui/game_ui.gd index c8d6f34..9d9ab32 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -214,7 +214,6 @@ var _controller_text_entry_is_open: Callable func _ready() -> void: - _prioritize_surface_drawing_pointer_input() _bite_prompt_button.pressed.connect(_on_bite_prompt_pressed) _apply_active_bait_indicator_style() _refresh_active_bait_indicator() @@ -488,37 +487,6 @@ func setup( ) -func set_world_interactions( - shop_interaction: ShopInteractionType, - storage_interaction: PlayerStorageInteractionType, -) -> void: - _shop_interaction = shop_interaction - _storage_interaction = storage_interaction - _fishing_shop.set_world_interaction(shop_interaction) - _player_storage.set_world_interaction(storage_interaction) - - -func _prioritize_surface_drawing_pointer_input() -> void: - # Control input follows sibling order rather than CanvasItem.z_index. Chat is - # a full-screen Control with interactive mobile children, so the toolbar must - # follow it in the tree to own their overlap while the art kit is active. - # Opening Chat deactivates surface drawing and hides the toolbar, returning - # the same area to Chat without any special-case pointer forwarding. - if ( - _surface_drawing_toolbar == null - or _chat_ui == null - or _surface_drawing_toolbar.get_parent() != _chat_ui.get_parent() - ): - return - var ui_root: Node = _surface_drawing_toolbar.get_parent() - var toolbar_index: int = _surface_drawing_toolbar.get_index() - var chat_index: int = _chat_ui.get_index() - if toolbar_index > chat_index: - return - # Removing the earlier toolbar shifts Chat one position toward the start. - ui_root.move_child(_surface_drawing_toolbar, chat_index) - - func _input(event: InputEvent) -> void: # The on-screen keyboard owns controller input while it is open. Its # overlay is processed before the UI beneath it and consumes the event. @@ -1296,7 +1264,6 @@ func _drawing_pointer_window_position() -> Vector2: func setup_data_and_identity( data_root: PlayerDataRoot, - progression_saves: PlayerSaveManager, identity_backups: IdentityBackupService, player_identity: PlayerIdentityStore, host_identity: HostIdentityStore, @@ -1308,7 +1275,6 @@ func setup_data_and_identity( ]: panel.setup_data_and_identity( data_root, - progression_saves, identity_backups, player_identity, host_identity, @@ -1637,14 +1603,9 @@ func _can_toggle_gameplay_hud() -> bool: func _refresh_gameplay_hud_visibility() -> void: - var fishing_override_active: bool = ( - _gameplay_hud_hidden - and _fishing_spot != null - and _fishing_spot.is_fishing_sequence_active() - ) var show_world_hud: bool = ( _gameplay_ui_enabled - and (not _gameplay_hud_hidden or fishing_override_active) + and not _gameplay_hud_hidden and not _system_menu_open and not _player_menu_open and not _shop_open @@ -1699,7 +1660,6 @@ func set_storage_prompt_visible( _storage_prompt.visible = ( requested_visible and _gameplay_ui_enabled - and not _gameplay_hud_hidden and not _system_menu_open and not _player_menu_open and not _shop_open @@ -1737,7 +1697,6 @@ func set_shop_prompt_visible( _shop_prompt.visible = ( requested_visible and _gameplay_ui_enabled - and not _gameplay_hud_hidden and not _system_menu_open and not _player_menu_open and not _shop_open @@ -1964,7 +1923,6 @@ func _refresh_fishing_panel_visibility() -> void: _gameplay_ui_enabled and has_content ) - _refresh_gameplay_hud_visibility() func _on_bite_prompt_changed(prompt_visible: bool) -> void: diff --git a/ui/new_game_setup_page.gd b/ui/new_game_setup_page.gd deleted file mode 100644 index 68e2aa8..0000000 --- a/ui/new_game_setup_page.gd +++ /dev/null @@ -1,309 +0,0 @@ -class_name NewGameSetupPage -extends Control - -signal start_requested(world_layout: StringName, world_seed: int) -signal back_requested - -const WorldLayoutType = preload("res://world/world_layout.gd") -const SaveManagerType = preload("res://save/player_save_manager.gd") -const ControllerFocusNavigationType = preload( - "res://ui/controller_focus_navigation.gd" -) - -enum SeedMode { - RANDOM, - CUSTOM, -} - -@onready var _paper: PanelContainer = %Paper -@onready var _generated_button: Button = %GeneratedButton -@onready var _starter_button: Button = %StarterButton -@onready var _world_description: Label = %WorldDescription -@onready var _seed_section: VBoxContainer = %SeedSection -@onready var _random_seed_button: Button = %RandomSeedButton -@onready var _custom_seed_button: Button = %CustomSeedButton -@onready var _seed_edit: LineEdit = %SeedEdit -@onready var _seed_help: Label = %SeedHelp -@onready var _overwrite_warning: Label = %OverwriteWarning -@onready var _status: Label = %Status -@onready var _start_button: Button = %StartButton -@onready var _back_button: Button = %BackButton - -var _world_layout: StringName = WorldLayoutType.GENERATED -var _seed_mode: SeedMode = SeedMode.RANDOM -var _random_seed: int = SaveManagerType.DEFAULT_WORLD_SEED - - -func _ready() -> void: - UtilityPageStyle.apply_page(self) - _paper.add_theme_stylebox_override( - "panel", UtilityPageStyle.panel_style() - ) - for button: Button in [ - _generated_button, - _starter_button, - _random_seed_button, - _custom_seed_button, - _start_button, - _back_button, - ]: - UtilityPageStyle.apply_ocean_button(button) - UtilityPageStyle.apply_ocean_line_edit(_seed_edit) - _generated_button.pressed.connect( - _set_world_layout.bind(WorldLayoutType.GENERATED) - ) - _starter_button.pressed.connect( - _set_world_layout.bind(WorldLayoutType.STARTER_ISLAND) - ) - _random_seed_button.pressed.connect(_choose_random_seed) - _custom_seed_button.pressed.connect(_choose_custom_seed) - _seed_edit.text_changed.connect(_on_seed_text_changed) - _seed_edit.text_submitted.connect(_on_seed_submitted) - _start_button.pressed.connect(_request_start) - _back_button.pressed.connect(back_requested.emit) - hide() - - -func open_page(has_existing_progression: bool) -> void: - _world_layout = WorldLayoutType.GENERATED - _seed_mode = SeedMode.RANDOM - _roll_random_seed() - _overwrite_warning.visible = has_existing_progression - _status.text = "" - _refresh_presentation() - show() - UtilityPageStyle.animate_in(self) - _generated_button.grab_focus.call_deferred() - - -func close_page() -> void: - var focus_owner: Control = get_viewport().gui_get_focus_owner() - if focus_owner != null and is_ancestor_of(focus_owner): - focus_owner.release_focus() - hide() - - -func request_back() -> void: - back_requested.emit() - - -func get_selected_world_layout() -> StringName: - return _world_layout - - -func get_selected_world_seed() -> int: - return ( - _random_seed - if _seed_mode == SeedMode.RANDOM - else parse_seed_text(_seed_edit.text) - ) - - -static func parse_seed_text(text: String) -> int: - var normalized: String = text.strip_edges() - if normalized.is_empty(): - return 0 - if normalized.is_valid_int(): - var numeric_seed: int = int(normalized) - if ( - numeric_seed <= 0 - or numeric_seed > SaveManagerType.MAX_WORLD_SEED - ): - return 0 - return numeric_seed - var digest: PackedByteArray = normalized.sha256_buffer() - var hashed_seed: int = ( - (int(digest[0]) << 24) - | (int(digest[1]) << 16) - | (int(digest[2]) << 8) - | int(digest[3]) - ) & 0x7fffffff - return (hashed_seed % SaveManagerType.MAX_WORLD_SEED) + 1 - - -func _set_world_layout(layout: StringName) -> void: - if not WorldLayoutType.is_valid(layout): - return - _world_layout = layout - _status.text = "" - _refresh_presentation() - if layout == WorldLayoutType.GENERATED: - _random_seed_button.grab_focus.call_deferred() - else: - _start_button.grab_focus.call_deferred() - - -func _choose_random_seed() -> void: - _seed_mode = SeedMode.RANDOM - _roll_random_seed() - _status.text = "" - _refresh_presentation() - - -func _choose_custom_seed() -> void: - _seed_mode = SeedMode.CUSTOM - if parse_seed_text(_seed_edit.text) == _random_seed: - _seed_edit.text = "" - _status.text = "" - _refresh_presentation() - _seed_edit.grab_focus.call_deferred() - _seed_edit.select_all.call_deferred() - - -func _roll_random_seed() -> void: - _random_seed = SaveManagerType.roll_world_seed() - _seed_edit.text = str(_random_seed) - - -func _on_seed_text_changed(_text: String) -> void: - if _seed_mode != SeedMode.CUSTOM: - return - _status.text = "" - _refresh_start_state() - - -func _on_seed_submitted(_text: String) -> void: - _request_start() - - -func _request_start() -> void: - var seed: int = get_selected_world_seed() - if _world_layout == WorldLayoutType.GENERATED and seed == 0: - _status.text = ( - "enter some text or a whole number from 1 to %d." - % SaveManagerType.MAX_WORLD_SEED - ) - _seed_edit.grab_focus() - _seed_edit.select_all() - return - if seed == 0: - seed = SaveManagerType.roll_world_seed() - _start_button.disabled = true - start_requested.emit(_world_layout, seed) - - -func _refresh_presentation() -> void: - var generated: bool = _world_layout == WorldLayoutType.GENERATED - _generated_button.set_pressed_no_signal(generated) - _starter_button.set_pressed_no_signal(not generated) - _world_description.text = ( - "build a new island from terrain chunks.\n" - + "the same seed always builds the same world." - if generated - else "play on the starter island." - ) - _seed_section.visible = generated - _random_seed_button.set_pressed_no_signal( - _seed_mode == SeedMode.RANDOM - ) - _custom_seed_button.set_pressed_no_signal( - _seed_mode == SeedMode.CUSTOM - ) - _seed_edit.editable = _seed_mode == SeedMode.CUSTOM - _seed_edit.focus_mode = ( - Control.FOCUS_ALL - if _seed_mode == SeedMode.CUSTOM - else Control.FOCUS_NONE - ) - _seed_help.text = ( - "press random seed again to roll another world." - if _seed_mode == SeedMode.RANDOM - else "enter text or a number from 1 to %d. the same entry makes the same world." - % SaveManagerType.MAX_WORLD_SEED - ) - _refresh_start_state() - _configure_controller_focus() - - -func _refresh_start_state() -> void: - _start_button.disabled = ( - _world_layout == WorldLayoutType.GENERATED - and _seed_mode == SeedMode.CUSTOM - and parse_seed_text(_seed_edit.text) == 0 - ) - - -func _configure_controller_focus() -> void: - var generated: bool = _world_layout == WorldLayoutType.GENERATED - var custom: bool = generated and _seed_mode == SeedMode.CUSTOM - _set_neighbors( - _generated_button, - _generated_button, - _starter_button, - _generated_button, - _random_seed_button if generated else _start_button, - ) - _set_neighbors( - _starter_button, - _generated_button, - _starter_button, - _starter_button, - _custom_seed_button if generated else _back_button, - ) - _set_neighbors( - _random_seed_button, - _random_seed_button, - _custom_seed_button, - _generated_button, - _seed_edit if custom else _start_button, - ) - _set_neighbors( - _custom_seed_button, - _random_seed_button, - _custom_seed_button, - _starter_button, - _seed_edit if custom else _back_button, - ) - _set_neighbors( - _seed_edit, - _seed_edit, - _seed_edit, - _custom_seed_button, - _start_button, - ) - var action_top: Control = ( - _seed_edit - if custom - else _random_seed_button - if generated - else _generated_button - ) - _set_neighbors( - _start_button, - _start_button, - _back_button, - action_top, - _start_button, - ) - _set_neighbors( - _back_button, - _start_button, - _back_button, - action_top, - _back_button, - ) - var traversal: Array[Control] = [ - _generated_button, - _starter_button, - ] - if generated: - traversal.append(_random_seed_button) - traversal.append(_custom_seed_button) - if custom: - traversal.append(_seed_edit) - traversal.append(_start_button) - traversal.append(_back_button) - ControllerFocusNavigationType.configure_traversal(traversal) - - -func _set_neighbors( - control: Control, - left: Control, - right: Control, - top: Control, - bottom: Control, -) -> void: - control.focus_neighbor_left = control.get_path_to(left) - control.focus_neighbor_right = control.get_path_to(right) - control.focus_neighbor_top = control.get_path_to(top) - control.focus_neighbor_bottom = control.get_path_to(bottom) diff --git a/ui/new_game_setup_page.gd.uid b/ui/new_game_setup_page.gd.uid deleted file mode 100644 index d0654b5..0000000 --- a/ui/new_game_setup_page.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://d3iueu4gj6bwo diff --git a/ui/new_game_setup_page.tscn b/ui/new_game_setup_page.tscn deleted file mode 100644 index d863735..0000000 --- a/ui/new_game_setup_page.tscn +++ /dev/null @@ -1,170 +0,0 @@ -[gd_scene load_steps=3 format=3] - -[ext_resource type="Script" path="res://ui/new_game_setup_page.gd" id="1_script"] -[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] - -[node name="NewGameSetupPage" type="Control"] -visible = false -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -theme = ExtResource("2_theme") -script = ExtResource("1_script") - -[node name="Paper" type="PanelContainer" parent="."] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -360.0 -offset_top = -280.0 -offset_right = 360.0 -offset_bottom = 280.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="Margin" type="MarginContainer" parent="Paper"] -layout_mode = 2 -theme_override_constants/margin_left = 46 -theme_override_constants/margin_top = 30 -theme_override_constants/margin_right = 46 -theme_override_constants/margin_bottom = 30 - -[node name="Layout" type="VBoxContainer" parent="Paper/Margin"] -layout_mode = 2 -theme_override_constants/separation = 10 - -[node name="Title" type="Label" parent="Paper/Margin/Layout"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1) -theme_override_font_sizes/font_size = 32 -text = "new game" -horizontal_alignment = 1 - -[node name="Prompt" type="Label" parent="Paper/Margin/Layout"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1) -theme_override_font_sizes/font_size = 17 -text = "choose where you want to begin" -horizontal_alignment = 1 - -[node name="WorldButtons" type="HBoxContainer" parent="Paper/Margin/Layout"] -layout_mode = 2 -theme_override_constants/separation = 14 -alignment = 1 - -[node name="GeneratedButton" type="Button" parent="Paper/Margin/Layout/WorldButtons"] -unique_name_in_owner = true -custom_minimum_size = Vector2(230, 58) -layout_mode = 2 -toggle_mode = true -text = "generate a world" - -[node name="StarterButton" type="Button" parent="Paper/Margin/Layout/WorldButtons"] -unique_name_in_owner = true -custom_minimum_size = Vector2(230, 58) -layout_mode = 2 -toggle_mode = true -text = "starter island" - -[node name="WorldDescription" type="Label" parent="Paper/Margin/Layout"] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 46) -layout_mode = 2 -theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1) -theme_override_font_sizes/font_size = 16 -horizontal_alignment = 1 -vertical_alignment = 1 -autowrap_mode = 2 - -[node name="SeedSection" type="VBoxContainer" parent="Paper/Margin/Layout"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/separation = 8 - -[node name="SeedLabel" type="Label" parent="Paper/Margin/Layout/SeedSection"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1) -theme_override_font_sizes/font_size = 20 -text = "world seed" -horizontal_alignment = 1 - -[node name="SeedModes" type="HBoxContainer" parent="Paper/Margin/Layout/SeedSection"] -layout_mode = 2 -theme_override_constants/separation = 14 -alignment = 1 - -[node name="RandomSeedButton" type="Button" parent="Paper/Margin/Layout/SeedSection/SeedModes"] -unique_name_in_owner = true -custom_minimum_size = Vector2(190, 48) -layout_mode = 2 -toggle_mode = true -text = "random seed" - -[node name="CustomSeedButton" type="Button" parent="Paper/Margin/Layout/SeedSection/SeedModes"] -unique_name_in_owner = true -custom_minimum_size = Vector2(190, 48) -layout_mode = 2 -toggle_mode = true -text = "enter a seed" - -[node name="SeedEdit" type="LineEdit" parent="Paper/Margin/Layout/SeedSection"] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 46) -layout_mode = 2 -theme_override_font_sizes/font_size = 20 -placeholder_text = "number or words" -alignment = 1 -max_length = 64 - -[node name="SeedHelp" type="Label" parent="Paper/Margin/Layout/SeedSection"] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 28) -layout_mode = 2 -theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1) -theme_override_font_sizes/font_size = 14 -horizontal_alignment = 1 - -[node name="OverwriteWarning" type="Label" parent="Paper/Margin/Layout"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.78, 0.62, 1) -theme_override_font_sizes/font_size = 15 -text = "starting a new game replaces your existing progression." -horizontal_alignment = 1 - -[node name="Status" type="Label" parent="Paper/Margin/Layout"] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 28) -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.7, 0.65, 1) -theme_override_font_sizes/font_size = 14 -horizontal_alignment = 1 - -[node name="Spacer" type="Control" parent="Paper/Margin/Layout"] -custom_minimum_size = Vector2(0, 4) -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Actions" type="HBoxContainer" parent="Paper/Margin/Layout"] -layout_mode = 2 -theme_override_constants/separation = 14 -alignment = 1 - -[node name="StartButton" type="Button" parent="Paper/Margin/Layout/Actions"] -unique_name_in_owner = true -custom_minimum_size = Vector2(190, 54) -layout_mode = 2 -text = "start new game" - -[node name="BackButton" type="Button" parent="Paper/Margin/Layout/Actions"] -unique_name_in_owner = true -custom_minimum_size = Vector2(150, 54) -layout_mode = 2 -text = "back" diff --git a/ui/on_screen_keyboard.gd b/ui/on_screen_keyboard.gd index ee7475c..a6ed070 100644 --- a/ui/on_screen_keyboard.gd +++ b/ui/on_screen_keyboard.gd @@ -430,8 +430,7 @@ func _show_native_keyboard_for(control: Control) -> void: func _hide_native_keyboard() -> void: - if DisplayServer.has_feature(DisplayServer.FEATURE_VIRTUAL_KEYBOARD): - DisplayServer.virtual_keyboard_hide() + DisplayServer.virtual_keyboard_hide() static func _line_edit_keyboard_type( diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 3bb428e..263c5e4 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -1693,7 +1693,6 @@ func close_menu( _release_controller_ownership(false, true) get_viewport().gui_cancel_drag() _close_sale_confirmation() - _hide_inventory_context_tooltip() if reason in [ CloseReason.BITE_STARTED, CloseReason.WATER_RECOVERY, @@ -2498,8 +2497,6 @@ func _populate_tackle_column( owned.quantity, item.max_stack, &"QuantityBadge", - UtilityPageStyle.SUPPLY_BADGE_EDGE_MARGIN, - 0.0, ) button.pressed.connect(_select_tackle_item.bind(owned.item_id)) button.gui_input.connect( @@ -2543,8 +2540,6 @@ func _on_tackle_button_gui_input( button: Button, ) -> void: var mouse_event := event as InputEventMouseButton - if mouse_event != null and mouse_event.pressed: - call_deferred("_release_pointer_focus", button) if ( mouse_event == null or mouse_event.button_index != MOUSE_BUTTON_RIGHT @@ -2560,11 +2555,6 @@ func _on_tackle_button_gui_input( button.accept_event() -func _release_pointer_focus(control: Control) -> void: - if control != null and is_instance_valid(control) and control.has_focus(): - control.release_focus() - - func _configure_tackle_item_focus() -> void: if not is_node_ready(): return diff --git a/ui/player_storage.gd b/ui/player_storage.gd index 25eb0d4..fd885d7 100644 --- a/ui/player_storage.gd +++ b/ui/player_storage.gd @@ -70,10 +70,6 @@ func setup( _refresh() -func set_world_interaction(interaction: PlayerStorageInteraction) -> void: - _interaction = interaction - - func open_storage() -> bool: if ( visible diff --git a/ui/settings_panel.gd b/ui/settings_panel.gd index e93f0a2..b49db11 100644 --- a/ui/settings_panel.gd +++ b/ui/settings_panel.gd @@ -125,7 +125,6 @@ var _environment_volume: float = 1.0 var _network_profile: NetworkProfilePreferences var _network_session: NetworkSession var _data_root: PlayerDataRoot -var _progression_saves: PlayerSaveManager var _identity_backups: IdentityBackupService var _player_identity: PlayerIdentityStore var _host_identity: HostIdentityStore @@ -135,8 +134,6 @@ var _controller_mapping_panel: ControllerMappingPanelType var _keyboard_mouse_mapping_manager: KeyboardMouseMappingManagerType var _keyboard_mouse_mapping_panel: KeyboardMouseMappingPanelType var _data_folder_dialog: FileDialog -var _progression_import_dialog: FileDialog -var _progression_export_dialog: FileDialog var _backup_file_dialog: FileDialog var _export_file_dialog: FileDialog var _passphrase_dialog: ConfirmationDialog @@ -146,7 +143,6 @@ var _pending_identity_operation := "" var _pending_identity_type := "" var _pending_identity_path := "" var _pending_import_data: Dictionary = {} -var _pending_progression_path := "" func _notification(what: int) -> void: @@ -248,8 +244,6 @@ func _connect_controls() -> void: %KeyboardMapping.pressed.connect(_open_keyboard_mouse_mapping) %OpenDataFolder.pressed.connect(_open_data_folder) %ChangeDataFolder.pressed.connect(_choose_data_folder) - %ExportProgression.pressed.connect(_choose_progression_export) - %ImportProgression.pressed.connect(_choose_progression_import) %ExportPlayerIdentity.pressed.connect( _choose_identity_export.bind("player") ) @@ -343,7 +337,6 @@ func setup_keyboard_mouse_mapping( func setup_data_and_identity( data_root: PlayerDataRoot, - progression_saves: PlayerSaveManager, identity_backups: IdentityBackupService, player_identity: PlayerIdentityStore, host_identity: HostIdentityStore, @@ -351,7 +344,6 @@ func setup_data_and_identity( interface_fonts: InterfaceFontController, ) -> void: _data_root = data_root - _progression_saves = progression_saves _identity_backups = identity_backups _player_identity = player_identity _host_identity = host_identity @@ -619,11 +611,6 @@ func _refresh_data_page() -> void: _data_root.override_active or (_network_session != null and _network_session.is_session_active()) ) - %ExportProgression.disabled = _progression_saves == null - %ImportProgression.disabled = ( - _progression_saves == null - or (_network_session != null and _network_session.is_session_active()) - ) var fingerprint: String = ( _player_identity.fingerprint if _player_identity != null else "" ) @@ -674,122 +661,6 @@ func _copy_player_fingerprint() -> void: _feedback.text = "full player fingerprint copied for server operator setup." -func _choose_progression_export() -> void: - if _progression_saves == null or _data_root == null: - _feedback.text = "progression export is unavailable." - return - var timestamp: String = Time.get_datetime_string_from_system().replace( - ":", "-" - ) - var suggested: String = _data_root.progression_backup_directory().path_join( - "NETfishing-progression-%s%s" - % [timestamp, PlayerSaveManager.ARCHIVE_EXTENSION] - ) - if _progression_export_dialog == null: - _progression_export_dialog = FileDialog.new() - _progression_export_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE - _progression_export_dialog.access = FileDialog.ACCESS_FILESYSTEM - _progression_export_dialog.use_native_dialog = false - _progression_export_dialog.filters = PackedStringArray([ - "*.nfsave ; NETfishing progression archive", - ]) - _progression_export_dialog.file_selected.connect( - _progression_export_file_selected - ) - _interface_fonts.apply_utility_theme(_progression_export_dialog) - add_child(_progression_export_dialog) - _progression_export_dialog.current_dir = suggested.get_base_dir() - _progression_export_dialog.current_file = suggested.get_file() - _interface_fonts.popup_file_dialog(_progression_export_dialog) - - -func _progression_export_file_selected(path: String) -> void: - var destination: String = ( - path - if path.ends_with(PlayerSaveManager.ARCHIVE_EXTENSION) - else path + PlayerSaveManager.ARCHIVE_EXTENSION - ) - var result: Dictionary = _progression_saves.export_progression_archive( - destination - ) - _feedback.text = str( - result.get("message", "progression export failed.") - ) - - -func _choose_progression_import() -> void: - if _progression_saves == null or _data_root == null: - _feedback.text = "progression import is unavailable." - return - if _network_session != null and _network_session.is_session_active(): - _feedback.text = "return to title before importing progression." - return - if _progression_import_dialog == null: - _progression_import_dialog = FileDialog.new() - _progression_import_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE - _progression_import_dialog.access = FileDialog.ACCESS_FILESYSTEM - _progression_import_dialog.use_native_dialog = false - _progression_import_dialog.filters = PackedStringArray([ - "*.nfsave ; NETfishing progression archive", - ]) - _progression_import_dialog.file_selected.connect( - _progression_import_file_selected - ) - _interface_fonts.apply_utility_theme(_progression_import_dialog) - add_child(_progression_import_dialog) - _progression_import_dialog.current_dir = ( - _data_root.progression_backup_directory() - ) - _interface_fonts.popup_file_dialog(_progression_import_dialog) - - -func _progression_import_file_selected(path: String) -> void: - var inspected: Dictionary = ( - _progression_saves.inspect_progression_archive(path) - ) - if not bool(inspected.get("ok", false)): - _feedback.text = str( - inspected.get("message", "progression archive could not be opened.") - ) - return - _pending_progression_path = path - var dialog := ConfirmationDialog.new() - dialog.title = "replace saved progression?" - dialog.ok_button_text = "import progression" - dialog.dialog_text = ( - "this will replace the current progression after making a backup.\n\n" - + "fish: %d\ndiscovered: %d\nworld: %s\nworld seed: %d\n\n" - + "identities, settings, friends, bans, and trusted servers are unchanged." - ) % [ - int(inspected.get("catch_count", 0)), - int(inspected.get("discovered_species_count", 0)), - WorldLayout.display_name(inspected.get( - "world_layout", - String(WorldLayout.GENERATED), - )), - int(inspected.get("world_seed", 0)), - ] - dialog.confirmed.connect(_confirm_progression_import.bind(dialog)) - dialog.canceled.connect(dialog.queue_free) - _interface_fonts.apply_utility_theme(dialog) - add_child(dialog) - dialog.popup_centered(Vector2i(640, 390)) - _configure_confirmation_dialog.call_deferred( - dialog, dialog.get_cancel_button() - ) - - -func _confirm_progression_import(dialog: ConfirmationDialog) -> void: - var result: Dictionary = _progression_saves.import_progression_archive( - _pending_progression_path - ) - _feedback.text = str( - result.get("message", "progression import failed.") - ) - _pending_progression_path = "" - dialog.queue_free() - - func _choose_data_folder() -> void: if _data_root == null or _data_root.override_active: _feedback.text = "the data folder is externally managed." diff --git a/ui/settings_panel.tscn b/ui/settings_panel.tscn index 94f627c..2af0256 100644 --- a/ui/settings_panel.tscn +++ b/ui/settings_panel.tscn @@ -567,7 +567,7 @@ size_flags_horizontal = 3 focus_mode = 2 focus_neighbor_right = NodePath("../ChangeDataFolder") focus_neighbor_top = NodePath("../../../../../../../../TabBar/DataTab") -focus_neighbor_bottom = NodePath("../../ProgressionRow/ExportProgression") +focus_neighbor_bottom = NodePath("../../CopyPlayerFingerprint") text = "open data folder" [node name="ChangeDataFolder" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/FolderRow"] @@ -578,41 +578,15 @@ size_flags_horizontal = 3 focus_mode = 2 focus_neighbor_left = NodePath("../OpenDataFolder") focus_neighbor_top = NodePath("../../../../../../../../TabBar/DataTab") -focus_neighbor_bottom = NodePath("../../ProgressionRow/ImportProgression") +focus_neighbor_bottom = NodePath("../../CopyPlayerFingerprint") text = "change data folder" -[node name="ProgressionRow" type="HBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons"] -layout_mode = 2 -theme_override_constants/separation = 12 - -[node name="ExportProgression" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/ProgressionRow"] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 44) -layout_mode = 2 -size_flags_horizontal = 3 -focus_mode = 2 -focus_neighbor_right = NodePath("../ImportProgression") -focus_neighbor_top = NodePath("../../FolderRow/OpenDataFolder") -focus_neighbor_bottom = NodePath("../../CopyPlayerFingerprint") -text = "export progression" - -[node name="ImportProgression" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/ProgressionRow"] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 44) -layout_mode = 2 -size_flags_horizontal = 3 -focus_mode = 2 -focus_neighbor_left = NodePath("../ExportProgression") -focus_neighbor_top = NodePath("../../FolderRow/ChangeDataFolder") -focus_neighbor_bottom = NodePath("../../CopyPlayerFingerprint") -text = "import progression" - [node name="CopyPlayerFingerprint" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons"] unique_name_in_owner = true custom_minimum_size = Vector2(0, 44) layout_mode = 2 focus_mode = 2 -focus_neighbor_top = NodePath("../ProgressionRow/ExportProgression") +focus_neighbor_top = NodePath("../FolderRow/OpenDataFolder") focus_neighbor_bottom = NodePath("../PlayerIdentityRow/ExportPlayerIdentity") text = "copy player fingerprint" diff --git a/ui/title_screen.gd b/ui/title_screen.gd index bff04d8..757f005 100644 --- a/ui/title_screen.gd +++ b/ui/title_screen.gd @@ -26,7 +26,6 @@ const TitleCreditsPageType = preload("res://ui/title_credits_page.gd") const NetworkSessionType = preload("res://network/network_session.gd") const SavedServerStoreType = preload("res://network/saved_server_store.gd") const JoinGamePageType = preload("res://ui/network/join_game_page.gd") -const NewGameSetupPageType = preload("res://ui/new_game_setup_page.gd") const CurrencyPresentationType = preload( "res://ui/currency_presentation.gd" ) @@ -95,13 +94,14 @@ const INTRO_BRANDING_TRAVEL_DURATION: float = 2.0 const INTRO_BRANDING_START_DELAY: float = 0.35 const HOST_CLUSTER_SAFE_MARGIN: float = 24.0 -signal new_game_requested(world_layout: StringName, world_seed: int) +signal new_game_requested(world_seed: int) signal continue_game_requested signal quit_requested signal join_game_requested(endpoint: String) enum ConfirmationAction { NONE, + NEW_GAME, DELETE_SAVE, } @@ -142,12 +142,12 @@ enum ConfirmationAction { @onready var _start_prompt_center: CenterContainer = %StartPromptCenter @onready var _start_prompt_label: Label = %StartPromptLabel @onready var _join_game_page: JoinGamePageType = %JoinGamePage -@onready var _new_game_setup_page: NewGameSetupPageType = %NewGameSetupPage @onready var _credits_page: TitleCreditsPageType = %CreditsPage var _save_manager: SaveManagerType var _settings_manager: SettingsManagerType var _inspection: SaveInspectionType +var _pending_new_game_seed: int = PlayerSaveManager.DEFAULT_WORLD_SEED var _confirmation_action: ConfirmationAction = ConfirmationAction.NONE var _action_in_progress: bool = false var _decorative_rng := RandomNumberGenerator.new() @@ -210,8 +210,6 @@ func _ready() -> void: ) _new_game_button.pressed.connect(_on_new_game_pressed) _join_game_button.pressed.connect(_open_join_game) - _new_game_setup_page.start_requested.connect(_start_configured_new_game) - _new_game_setup_page.back_requested.connect(_close_new_game_setup) _settings_button.pressed.connect(_open_settings) _credits_button.pressed.connect(_open_credits) _delete_button.pressed.connect(_on_delete_pressed) @@ -285,7 +283,6 @@ func reopen() -> void: _reset_confirmation() _settings_panel.hide() _join_game_page.close_page() - _new_game_setup_page.close_page() _credits_page.close_page() _refresh_save_inspection() show() @@ -314,7 +311,6 @@ func open_join_game_page(endpoint: String = "") -> void: _settings_panel.hide() _confirmation_page.hide_page() _credits_page.close_page() - _new_game_setup_page.close_page() _join_game_page.open_page(endpoint) @@ -330,7 +326,6 @@ func _open_join_game() -> void: if ( _action_in_progress or _is_confirmation_active() - or _new_game_setup_page.visible or _credits_page.visible ): return @@ -351,7 +346,6 @@ func _open_credits() -> void: or _is_confirmation_active() or _settings_panel.visible or _join_game_page.visible - or _new_game_setup_page.visible or _credits_page.visible ): return @@ -589,11 +583,6 @@ func _input(event: InputEvent) -> void: _join_game_page.request_back() get_viewport().set_input_as_handled() return - if _new_game_setup_page.visible: - if event.is_action_pressed("ui_cancel"): - _new_game_setup_page.request_back() - get_viewport().set_input_as_handled() - return if ( _title_settings_transition_active or _title_entry_transition_active @@ -640,7 +629,6 @@ func _handle_primary_menu_focus_input(event: InputEvent) -> bool: not _button_center.visible or _is_confirmation_active() or _settings_panel.visible - or _new_game_setup_page.visible or _credits_page.visible ): return false @@ -1043,59 +1031,19 @@ func _on_new_game_pressed() -> void: "the existing save cannot be accessed safely." ) return - _open_new_game_setup( - _inspection.status != SaveInspectionType.Status.MISSING + _pending_new_game_seed = PlayerSaveManager.roll_world_seed() + var warning := "start a new world?" + if _inspection.status != SaveInspectionType.Status.MISSING: + warning += " existing progression will be deleted." + warning += "\nworld seed: %d" % _pending_new_game_seed + _open_confirmation( + ConfirmationAction.NEW_GAME, + warning, + "start\nnew game", + true ) -func _open_new_game_setup(has_existing_progression: bool) -> void: - _modal_restore_navigation_focus = _navigation_focus_active - _set_title_bubbles_interactive(false) - _release_primary_menu_focus() - _presentation_center.hide() - _start_prompt_center.hide() - _join_game_page.close_page() - _credits_page.close_page() - _new_game_setup_page.open_page(has_existing_progression) - - -func _close_new_game_setup() -> void: - _new_game_setup_page.close_page() - _presentation_center.show() - _button_center.show() - _start_prompt_center.hide() - _set_title_bubbles_interactive(true) - var restore_navigation_focus: bool = _modal_restore_navigation_focus - _modal_restore_navigation_focus = false - if restore_navigation_focus: - _navigation_focus_active = true - _new_game_button.grab_focus() - else: - _navigation_focus_active = false - _release_title_focus() - _update_continue_stats_visibility() - - -func _start_configured_new_game( - world_layout: StringName, - world_seed: int, -) -> void: - if _action_in_progress or not _new_game_setup_page.visible: - return - _new_game_setup_page.close_page() - _action_in_progress = true - new_game_requested.emit(world_layout, world_seed) - _action_in_progress = false - # Signal delivery is synchronous. A successful start hides this title screen; - # if it remains visible, restore the menu so a generation error is recoverable. - if visible: - _presentation_center.show() - _button_center.show() - _set_title_bubbles_interactive(true) - _navigation_focus_active = true - _new_game_button.grab_focus() - - func _on_delete_pressed() -> void: if ( _action_in_progress @@ -1123,6 +1071,14 @@ func _on_confirmation_accepted() -> void: var action: ConfirmationAction = _confirmation_action _confirmation_page.lock_interaction() _action_in_progress = true + if action == ConfirmationAction.NEW_GAME: + _confirmation_action = ConfirmationAction.NONE + _confirmation_transition_generation += 1 + _cancel_confirmation_transition() + _confirmation_page.hide_page() + new_game_requested.emit(_pending_new_game_seed) + _action_in_progress = false + return if not _save_manager.delete_progression_save(): _feedback_label.text = _center_feedback_text( "failed to delete saved progression." @@ -1339,7 +1295,7 @@ func _on_settings_applied() -> void: func _on_settings_closed() -> void: - _refresh_save_inspection() + pass func _on_settings_closing() -> void: diff --git a/ui/title_screen.tscn b/ui/title_screen.tscn index 90074a6..323e689 100644 --- a/ui/title_screen.tscn +++ b/ui/title_screen.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=24 format=3] +[gd_scene load_steps=23 format=3] [ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] @@ -19,7 +19,6 @@ [ext_resource type="Texture2D" path="res://ui/icons/main_menu/new_game.png" id="17_new_game"] [ext_resource type="Texture2D" path="res://ui/icons/main_menu/credits.png" id="18_credits"] [ext_resource type="Texture2D" path="res://ui/icons/main_menu/delete_save.png" id="19_delete_save"] -[ext_resource type="PackedScene" path="res://ui/new_game_setup_page.tscn" id="20_new_game_setup"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"] shader = ExtResource("4_water_shader") @@ -226,7 +225,7 @@ unique_name_in_owner = true layout_mode = 2 theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1) theme_override_font_sizes/font_size = 22 -text = "v0.14.0-alpha" +text = "v0.13.1-alpha" horizontal_alignment = 1 [node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"] @@ -487,13 +486,3 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 - -[node name="NewGameSetupPage" parent="ResponsiveTitleStage/TitlePresentationScaleRoot" instance=ExtResource("20_new_game_setup")] -unique_name_in_owner = true -z_index = 220 -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 diff --git a/ui/utility_page_style.gd b/ui/utility_page_style.gd index e31f6ac..edaa837 100644 --- a/ui/utility_page_style.gd +++ b/ui/utility_page_style.gd @@ -53,7 +53,6 @@ static func add_supply_quantity_badge( total: int, badge_name: StringName = &"SupplyQuantityBadge", top_margin: float = SUPPLY_BADGE_EDGE_MARGIN, - x_offset: float = SUPPLY_BADGE_X_OFFSET, ) -> Panel: var badge := Panel.new() badge.name = badge_name @@ -69,7 +68,7 @@ static func add_supply_quantity_badge( quantity_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER badge.add_child(quantity_label) configure_supply_quantity_badge( - badge, quantity_label, current, total, top_margin, x_offset + badge, quantity_label, current, total, top_margin ) return badge @@ -80,7 +79,6 @@ static func configure_supply_quantity_badge( current: int, total: int, top_margin: float = SUPPLY_BADGE_EDGE_MARGIN, - x_offset: float = SUPPLY_BADGE_X_OFFSET, ) -> void: var supply_text := supply_quantity_text(current, total) var text_width: float = TuffyFont.get_string_size( @@ -94,10 +92,10 @@ static func configure_supply_quantity_badge( ceilf(text_width + SUPPLY_BADGE_HORIZONTAL_PADDING), ) badge.offset_left = ( - -badge_width - SUPPLY_BADGE_EDGE_MARGIN + x_offset + -badge_width - SUPPLY_BADGE_EDGE_MARGIN + SUPPLY_BADGE_X_OFFSET ) badge.offset_top = top_margin - badge.offset_right = -SUPPLY_BADGE_EDGE_MARGIN + x_offset + badge.offset_right = -SUPPLY_BADGE_EDGE_MARGIN + SUPPLY_BADGE_X_OFFSET badge.offset_bottom = top_margin + SUPPLY_BADGE_HEIGHT badge.pivot_offset = Vector2( badge_width * 0.5, diff --git a/world/generation/biomes/definitions/biome_coast.tres b/world/generation/biomes/definitions/biome_coast.tres index 635181b..d1864d3 100644 --- a/world/generation/biomes/definitions/biome_coast.tres +++ b/world/generation/biomes/definitions/biome_coast.tres @@ -17,5 +17,4 @@ script = ExtResource("1_definition") stable_id = &"biome_coast" label = "coast" required_chunk_tags = PackedStringArray("sand") -forbidden_chunk_tags = PackedStringArray("mixed_surface") prop_rules = Array[ExtResource("2_rule")]([SubResource("Rule_sand_tree")]) diff --git a/world/generation/biomes/definitions/biome_forest.tres b/world/generation/biomes/definitions/biome_forest.tres index 21cd724..0d11be2 100644 --- a/world/generation/biomes/definitions/biome_forest.tres +++ b/world/generation/biomes/definitions/biome_forest.tres @@ -6,11 +6,10 @@ [sub_resource type="Resource" id="Rule_grass_tree"] script = ExtResource("2_rule") procedural_group = &"grass_tree" -placement_chance = 8200 -adjacency_bonus = 1200 -maximum_density = 8500 +placement_chance = 9000 +adjacency_bonus = 1000 +maximum_density = 10000 minimum_placements = 2 -placement_attempts_per_chunk = 3 allowed_prop_ids = PackedStringArray("prop_tree_1", "prop_tree_2", "prop_tree_3") [sub_resource type="Resource" id="Rule_grass_detail"] @@ -27,6 +26,6 @@ script = ExtResource("1_definition") stable_id = &"biome_forest" label = "forest" required_chunk_tags = PackedStringArray("grass") -forbidden_chunk_tags = PackedStringArray("spawn", "mixed_surface") +forbidden_chunk_tags = PackedStringArray("spawn") region_weight = 1.4 prop_rules = Array[ExtResource("2_rule")]([SubResource("Rule_grass_tree"), SubResource("Rule_grass_detail")]) diff --git a/world/generation/biomes/definitions/biome_pine_forest.tres b/world/generation/biomes/definitions/biome_pine_forest.tres index eec39a4..293acbc 100644 --- a/world/generation/biomes/definitions/biome_pine_forest.tres +++ b/world/generation/biomes/definitions/biome_pine_forest.tres @@ -6,11 +6,10 @@ [sub_resource type="Resource" id="Rule_grass_tree"] script = ExtResource("2_rule") procedural_group = &"grass_tree" -placement_chance = 8200 -adjacency_bonus = 1200 -maximum_density = 8500 +placement_chance = 9000 +adjacency_bonus = 1000 +maximum_density = 10000 minimum_placements = 2 -placement_attempts_per_chunk = 3 allowed_prop_ids = PackedStringArray("prop_pine") [sub_resource type="Resource" id="Rule_grass_detail"] @@ -27,6 +26,6 @@ script = ExtResource("1_definition") stable_id = &"biome_pine_forest" label = "pine forest" required_chunk_tags = PackedStringArray("grass") -forbidden_chunk_tags = PackedStringArray("spawn", "mixed_surface") +forbidden_chunk_tags = PackedStringArray("spawn") region_weight = 1.2 prop_rules = Array[ExtResource("2_rule")]([SubResource("Rule_grass_tree"), SubResource("Rule_grass_detail")]) diff --git a/world/generation/biomes/definitions/biome_plains.tres b/world/generation/biomes/definitions/biome_plains.tres index 46b8c71..c979cb8 100644 --- a/world/generation/biomes/definitions/biome_plains.tres +++ b/world/generation/biomes/definitions/biome_plains.tres @@ -24,6 +24,5 @@ script = ExtResource("1_definition") stable_id = &"biome_plains" label = "plains" required_chunk_tags = PackedStringArray("grass") -forbidden_chunk_tags = PackedStringArray("mixed_surface") region_weight = 1.0 prop_rules = Array[ExtResource("2_rule")]([SubResource("Rule_grass_tree"), SubResource("Rule_grass_detail")]) diff --git a/world/generation/chunks/assets/chunk_0001.glb b/world/generation/chunks/assets/chunk_0001.glb index 6014230..0b9bc50 100644 Binary files a/world/generation/chunks/assets/chunk_0001.glb and b/world/generation/chunks/assets/chunk_0001.glb differ diff --git a/world/generation/chunks/assets/chunk_0002.glb b/world/generation/chunks/assets/chunk_0002.glb index 2a36b6e..db18d8c 100644 Binary files a/world/generation/chunks/assets/chunk_0002.glb and b/world/generation/chunks/assets/chunk_0002.glb differ diff --git a/world/generation/chunks/assets/chunk_0003.glb b/world/generation/chunks/assets/chunk_0003.glb index a1996eb..45708a3 100644 Binary files a/world/generation/chunks/assets/chunk_0003.glb and b/world/generation/chunks/assets/chunk_0003.glb differ diff --git a/world/generation/chunks/assets/chunk_0004.glb b/world/generation/chunks/assets/chunk_0004.glb index 2839d03..62219b9 100644 Binary files a/world/generation/chunks/assets/chunk_0004.glb and b/world/generation/chunks/assets/chunk_0004.glb differ diff --git a/world/generation/chunks/assets/chunk_0005_cliff_wall.png.import b/world/generation/chunks/assets/chunk_0005_cliff_wall.png.import index 51d8e11..0796279 100644 --- a/world/generation/chunks/assets/chunk_0005_cliff_wall.png.import +++ b/world/generation/chunks/assets/chunk_0005_cliff_wall.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0005_grass_lite.png.import b/world/generation/chunks/assets/chunk_0005_grass_lite.png.import index 01e84d4..c1b1dd6 100644 --- a/world/generation/chunks/assets/chunk_0005_grass_lite.png.import +++ b/world/generation/chunks/assets/chunk_0005_grass_lite.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0006_cliff_wall.png.import b/world/generation/chunks/assets/chunk_0006_cliff_wall.png.import index 411a5b1..238e929 100644 --- a/world/generation/chunks/assets/chunk_0006_cliff_wall.png.import +++ b/world/generation/chunks/assets/chunk_0006_cliff_wall.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0006_grass_lite.png.import b/world/generation/chunks/assets/chunk_0006_grass_lite.png.import index 2d01a58..fb37b89 100644 --- a/world/generation/chunks/assets/chunk_0006_grass_lite.png.import +++ b/world/generation/chunks/assets/chunk_0006_grass_lite.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0006_sand.png.import b/world/generation/chunks/assets/chunk_0006_sand.png.import index d829b05..bc9f61a 100644 --- a/world/generation/chunks/assets/chunk_0006_sand.png.import +++ b/world/generation/chunks/assets/chunk_0006_sand.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0007_cliff_wall.png.import b/world/generation/chunks/assets/chunk_0007_cliff_wall.png.import index d78acc6..007f60d 100644 --- a/world/generation/chunks/assets/chunk_0007_cliff_wall.png.import +++ b/world/generation/chunks/assets/chunk_0007_cliff_wall.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0007_grass_lite.png.import b/world/generation/chunks/assets/chunk_0007_grass_lite.png.import index 03e4dc5..abe6b52 100644 --- a/world/generation/chunks/assets/chunk_0007_grass_lite.png.import +++ b/world/generation/chunks/assets/chunk_0007_grass_lite.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0008.glb b/world/generation/chunks/assets/chunk_0008.glb deleted file mode 100644 index d785e65..0000000 Binary files a/world/generation/chunks/assets/chunk_0008.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0008.glb.import b/world/generation/chunks/assets/chunk_0008.glb.import deleted file mode 100644 index a090711..0000000 --- a/world/generation/chunks/assets/chunk_0008.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://bm1teitx5ijxl" -path="res://.godot/imported/chunk_0008.glb-0b0a6712e5384c3f72c65e6ef1fc12c4.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0008.glb" -dest_files=["res://.godot/imported/chunk_0008.glb-0b0a6712e5384c3f72c65e6ef1fc12c4.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0008_sand.png b/world/generation/chunks/assets/chunk_0008_sand.png deleted file mode 100644 index 06f807a..0000000 Binary files a/world/generation/chunks/assets/chunk_0008_sand.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0008_sand.png.import b/world/generation/chunks/assets/chunk_0008_sand.png.import deleted file mode 100644 index 9c135d8..0000000 --- a/world/generation/chunks/assets/chunk_0008_sand.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dj24j2d1u8712" -path.s3tc="res://.godot/imported/chunk_0008_sand.png-55d7cf4f540d9cfb7d4ba59e9f9f38da.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0008_sand.png-55d7cf4f540d9cfb7d4ba59e9f9f38da.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "adfa66b8fe9da7f3450d3ddfd432b86a" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0008_sand.png" -dest_files=["res://.godot/imported/chunk_0008_sand.png-55d7cf4f540d9cfb7d4ba59e9f9f38da.s3tc.ctex", "res://.godot/imported/chunk_0008_sand.png-55d7cf4f540d9cfb7d4ba59e9f9f38da.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0009.glb b/world/generation/chunks/assets/chunk_0009.glb deleted file mode 100644 index 8c09d3a..0000000 Binary files a/world/generation/chunks/assets/chunk_0009.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0009.glb.import b/world/generation/chunks/assets/chunk_0009.glb.import deleted file mode 100644 index 72c9778..0000000 --- a/world/generation/chunks/assets/chunk_0009.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://hasempwso5gi" -path="res://.godot/imported/chunk_0009.glb-397f642bee3da14146c56fddf8e21369.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0009.glb" -dest_files=["res://.godot/imported/chunk_0009.glb-397f642bee3da14146c56fddf8e21369.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0009_grass_lite.png b/world/generation/chunks/assets/chunk_0009_grass_lite.png deleted file mode 100644 index 52c7ccb..0000000 Binary files a/world/generation/chunks/assets/chunk_0009_grass_lite.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0009_grass_lite.png.import b/world/generation/chunks/assets/chunk_0009_grass_lite.png.import deleted file mode 100644 index be93d5e..0000000 --- a/world/generation/chunks/assets/chunk_0009_grass_lite.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://djjyaoih5vcnd" -path.s3tc="res://.godot/imported/chunk_0009_grass_lite.png-5d49eb465b8a8f8739796ce13c646f7d.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0009_grass_lite.png-5d49eb465b8a8f8739796ce13c646f7d.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0009_grass_lite.png" -dest_files=["res://.godot/imported/chunk_0009_grass_lite.png-5d49eb465b8a8f8739796ce13c646f7d.s3tc.ctex", "res://.godot/imported/chunk_0009_grass_lite.png-5d49eb465b8a8f8739796ce13c646f7d.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0010.glb b/world/generation/chunks/assets/chunk_0010.glb deleted file mode 100644 index 302fb11..0000000 Binary files a/world/generation/chunks/assets/chunk_0010.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0010.glb.import b/world/generation/chunks/assets/chunk_0010.glb.import deleted file mode 100644 index a8039a0..0000000 --- a/world/generation/chunks/assets/chunk_0010.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://b5tf5e1g5auu7" -path="res://.godot/imported/chunk_0010.glb-6ffffa0064f5930acc433afe5e4334e4.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0010.glb" -dest_files=["res://.godot/imported/chunk_0010.glb-6ffffa0064f5930acc433afe5e4334e4.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0010_cliff_wall.png b/world/generation/chunks/assets/chunk_0010_cliff_wall.png deleted file mode 100644 index 3fe157f..0000000 Binary files a/world/generation/chunks/assets/chunk_0010_cliff_wall.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0010_cliff_wall.png.import b/world/generation/chunks/assets/chunk_0010_cliff_wall.png.import deleted file mode 100644 index 5899d3a..0000000 --- a/world/generation/chunks/assets/chunk_0010_cliff_wall.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cwwraho3pj5aa" -path.s3tc="res://.godot/imported/chunk_0010_cliff_wall.png-78830d69b22e3c32cc295e574d596c3a.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0010_cliff_wall.png-78830d69b22e3c32cc295e574d596c3a.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "fc97184f537cc23f83a731327e10c836" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0010_cliff_wall.png" -dest_files=["res://.godot/imported/chunk_0010_cliff_wall.png-78830d69b22e3c32cc295e574d596c3a.s3tc.ctex", "res://.godot/imported/chunk_0010_cliff_wall.png-78830d69b22e3c32cc295e574d596c3a.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0010_grass_lite.png b/world/generation/chunks/assets/chunk_0010_grass_lite.png deleted file mode 100644 index 52c7ccb..0000000 Binary files a/world/generation/chunks/assets/chunk_0010_grass_lite.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0010_grass_lite.png.import b/world/generation/chunks/assets/chunk_0010_grass_lite.png.import deleted file mode 100644 index 7cd6b3d..0000000 --- a/world/generation/chunks/assets/chunk_0010_grass_lite.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://q7k2r4mb3bsr" -path.s3tc="res://.godot/imported/chunk_0010_grass_lite.png-694b30e6e36cc16c1cf98b2f3823a0c2.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0010_grass_lite.png-694b30e6e36cc16c1cf98b2f3823a0c2.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0010_grass_lite.png" -dest_files=["res://.godot/imported/chunk_0010_grass_lite.png-694b30e6e36cc16c1cf98b2f3823a0c2.s3tc.ctex", "res://.godot/imported/chunk_0010_grass_lite.png-694b30e6e36cc16c1cf98b2f3823a0c2.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0011.glb b/world/generation/chunks/assets/chunk_0011.glb deleted file mode 100644 index 85f73ed..0000000 Binary files a/world/generation/chunks/assets/chunk_0011.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0011.glb.import b/world/generation/chunks/assets/chunk_0011.glb.import deleted file mode 100644 index 86ee993..0000000 --- a/world/generation/chunks/assets/chunk_0011.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://cym6ilrdikjcj" -path="res://.godot/imported/chunk_0011.glb-cda3ab701cf5e469b53437c043314d05.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0011.glb" -dest_files=["res://.godot/imported/chunk_0011.glb-cda3ab701cf5e469b53437c043314d05.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0011_cliff_wall.png b/world/generation/chunks/assets/chunk_0011_cliff_wall.png deleted file mode 100644 index 3fe157f..0000000 Binary files a/world/generation/chunks/assets/chunk_0011_cliff_wall.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0011_cliff_wall.png.import b/world/generation/chunks/assets/chunk_0011_cliff_wall.png.import deleted file mode 100644 index 423ad9c..0000000 --- a/world/generation/chunks/assets/chunk_0011_cliff_wall.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dggc1jbgenl11" -path.s3tc="res://.godot/imported/chunk_0011_cliff_wall.png-9a4d64a03d75f23c2ef3614386484a4e.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0011_cliff_wall.png-9a4d64a03d75f23c2ef3614386484a4e.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "fc97184f537cc23f83a731327e10c836" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0011_cliff_wall.png" -dest_files=["res://.godot/imported/chunk_0011_cliff_wall.png-9a4d64a03d75f23c2ef3614386484a4e.s3tc.ctex", "res://.godot/imported/chunk_0011_cliff_wall.png-9a4d64a03d75f23c2ef3614386484a4e.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0011_grass_lite.png b/world/generation/chunks/assets/chunk_0011_grass_lite.png deleted file mode 100644 index 52c7ccb..0000000 Binary files a/world/generation/chunks/assets/chunk_0011_grass_lite.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0011_grass_lite.png.import b/world/generation/chunks/assets/chunk_0011_grass_lite.png.import deleted file mode 100644 index 7a3b48d..0000000 --- a/world/generation/chunks/assets/chunk_0011_grass_lite.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bhuhcaowvcyrk" -path.s3tc="res://.godot/imported/chunk_0011_grass_lite.png-3a0a1a7ad2d55bd853b238730af91a7c.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0011_grass_lite.png-3a0a1a7ad2d55bd853b238730af91a7c.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0011_grass_lite.png" -dest_files=["res://.godot/imported/chunk_0011_grass_lite.png-3a0a1a7ad2d55bd853b238730af91a7c.s3tc.ctex", "res://.godot/imported/chunk_0011_grass_lite.png-3a0a1a7ad2d55bd853b238730af91a7c.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0012.glb b/world/generation/chunks/assets/chunk_0012.glb deleted file mode 100644 index bbde6a3..0000000 Binary files a/world/generation/chunks/assets/chunk_0012.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0012.glb.import b/world/generation/chunks/assets/chunk_0012.glb.import deleted file mode 100644 index 65fad11..0000000 --- a/world/generation/chunks/assets/chunk_0012.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://c4i32u0bdtix5" -path="res://.godot/imported/chunk_0012.glb-1c818ae539bef19d5ca4fbe3ace96887.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0012.glb" -dest_files=["res://.godot/imported/chunk_0012.glb-1c818ae539bef19d5ca4fbe3ace96887.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0012_cliff_wall.png b/world/generation/chunks/assets/chunk_0012_cliff_wall.png deleted file mode 100644 index 3fe157f..0000000 Binary files a/world/generation/chunks/assets/chunk_0012_cliff_wall.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0012_cliff_wall.png.import b/world/generation/chunks/assets/chunk_0012_cliff_wall.png.import deleted file mode 100644 index 69a239c..0000000 --- a/world/generation/chunks/assets/chunk_0012_cliff_wall.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cf27tcolkmfog" -path.s3tc="res://.godot/imported/chunk_0012_cliff_wall.png-3d27d4041790b1c3dbc838d6a0c94d40.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0012_cliff_wall.png-3d27d4041790b1c3dbc838d6a0c94d40.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "fc97184f537cc23f83a731327e10c836" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0012_cliff_wall.png" -dest_files=["res://.godot/imported/chunk_0012_cliff_wall.png-3d27d4041790b1c3dbc838d6a0c94d40.s3tc.ctex", "res://.godot/imported/chunk_0012_cliff_wall.png-3d27d4041790b1c3dbc838d6a0c94d40.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0012_dirt.png b/world/generation/chunks/assets/chunk_0012_dirt.png deleted file mode 100644 index 588c8c6..0000000 Binary files a/world/generation/chunks/assets/chunk_0012_dirt.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0012_dirt.png.import b/world/generation/chunks/assets/chunk_0012_dirt.png.import deleted file mode 100644 index c81e741..0000000 --- a/world/generation/chunks/assets/chunk_0012_dirt.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://c3f1w276lmg7i" -path.s3tc="res://.godot/imported/chunk_0012_dirt.png-5c3a2a10642f9d078ad1d1c6333d8264.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0012_dirt.png-5c3a2a10642f9d078ad1d1c6333d8264.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "07d90b3c46a4c3d0c172bed143dc9436" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0012_dirt.png" -dest_files=["res://.godot/imported/chunk_0012_dirt.png-5c3a2a10642f9d078ad1d1c6333d8264.s3tc.ctex", "res://.godot/imported/chunk_0012_dirt.png-5c3a2a10642f9d078ad1d1c6333d8264.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0012_grass_lite.png b/world/generation/chunks/assets/chunk_0012_grass_lite.png deleted file mode 100644 index 52c7ccb..0000000 Binary files a/world/generation/chunks/assets/chunk_0012_grass_lite.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0012_grass_lite.png.import b/world/generation/chunks/assets/chunk_0012_grass_lite.png.import deleted file mode 100644 index 971d350..0000000 --- a/world/generation/chunks/assets/chunk_0012_grass_lite.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://lv8xn2moas2s" -path.s3tc="res://.godot/imported/chunk_0012_grass_lite.png-ccfdc53acb9a967eb5d00e669258fc3c.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0012_grass_lite.png-ccfdc53acb9a967eb5d00e669258fc3c.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0012_grass_lite.png" -dest_files=["res://.godot/imported/chunk_0012_grass_lite.png-ccfdc53acb9a967eb5d00e669258fc3c.s3tc.ctex", "res://.godot/imported/chunk_0012_grass_lite.png-ccfdc53acb9a967eb5d00e669258fc3c.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0013.glb b/world/generation/chunks/assets/chunk_0013.glb deleted file mode 100644 index b5aa63c..0000000 Binary files a/world/generation/chunks/assets/chunk_0013.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0013.glb.import b/world/generation/chunks/assets/chunk_0013.glb.import deleted file mode 100644 index b63b3e4..0000000 --- a/world/generation/chunks/assets/chunk_0013.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://dom2fvsn0s1wa" -path="res://.godot/imported/chunk_0013.glb-64cf2d2bb9c9020fa3d06004b1963f09.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0013.glb" -dest_files=["res://.godot/imported/chunk_0013.glb-64cf2d2bb9c9020fa3d06004b1963f09.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0013_grass_lite.png b/world/generation/chunks/assets/chunk_0013_grass_lite.png deleted file mode 100644 index 52c7ccb..0000000 Binary files a/world/generation/chunks/assets/chunk_0013_grass_lite.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0013_grass_lite.png.import b/world/generation/chunks/assets/chunk_0013_grass_lite.png.import deleted file mode 100644 index 95218ff..0000000 --- a/world/generation/chunks/assets/chunk_0013_grass_lite.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dct65l02vr54q" -path.s3tc="res://.godot/imported/chunk_0013_grass_lite.png-0cf332d69bdd6f5da588e0ddda3bd437.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0013_grass_lite.png-0cf332d69bdd6f5da588e0ddda3bd437.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0013_grass_lite.png" -dest_files=["res://.godot/imported/chunk_0013_grass_lite.png-0cf332d69bdd6f5da588e0ddda3bd437.s3tc.ctex", "res://.godot/imported/chunk_0013_grass_lite.png-0cf332d69bdd6f5da588e0ddda3bd437.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0013_sand.png b/world/generation/chunks/assets/chunk_0013_sand.png deleted file mode 100644 index 06f807a..0000000 Binary files a/world/generation/chunks/assets/chunk_0013_sand.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0013_sand.png.import b/world/generation/chunks/assets/chunk_0013_sand.png.import deleted file mode 100644 index cd4178a..0000000 --- a/world/generation/chunks/assets/chunk_0013_sand.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://btjhnb6owbjj2" -path.s3tc="res://.godot/imported/chunk_0013_sand.png-5fd756543742405c3864f1fdfb36632c.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0013_sand.png-5fd756543742405c3864f1fdfb36632c.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "adfa66b8fe9da7f3450d3ddfd432b86a" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0013_sand.png" -dest_files=["res://.godot/imported/chunk_0013_sand.png-5fd756543742405c3864f1fdfb36632c.s3tc.ctex", "res://.godot/imported/chunk_0013_sand.png-5fd756543742405c3864f1fdfb36632c.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0014.glb b/world/generation/chunks/assets/chunk_0014.glb deleted file mode 100644 index 13eef5a..0000000 Binary files a/world/generation/chunks/assets/chunk_0014.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0014.glb.import b/world/generation/chunks/assets/chunk_0014.glb.import deleted file mode 100644 index 3d4bcb8..0000000 --- a/world/generation/chunks/assets/chunk_0014.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://50aolv5pg8d2" -path="res://.godot/imported/chunk_0014.glb-636ccc776ff9575e58a9db68c3f273af.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0014.glb" -dest_files=["res://.godot/imported/chunk_0014.glb-636ccc776ff9575e58a9db68c3f273af.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0014_cliff_wall.png b/world/generation/chunks/assets/chunk_0014_cliff_wall.png deleted file mode 100644 index 3fe157f..0000000 Binary files a/world/generation/chunks/assets/chunk_0014_cliff_wall.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0014_cliff_wall.png.import b/world/generation/chunks/assets/chunk_0014_cliff_wall.png.import deleted file mode 100644 index 45e466a..0000000 --- a/world/generation/chunks/assets/chunk_0014_cliff_wall.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://6sd1uj06ti8f" -path.s3tc="res://.godot/imported/chunk_0014_cliff_wall.png-9ea694f83659f89e6c17ae1a39cf97cb.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0014_cliff_wall.png-9ea694f83659f89e6c17ae1a39cf97cb.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "fc97184f537cc23f83a731327e10c836" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0014_cliff_wall.png" -dest_files=["res://.godot/imported/chunk_0014_cliff_wall.png-9ea694f83659f89e6c17ae1a39cf97cb.s3tc.ctex", "res://.godot/imported/chunk_0014_cliff_wall.png-9ea694f83659f89e6c17ae1a39cf97cb.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0014_grass_lite.png b/world/generation/chunks/assets/chunk_0014_grass_lite.png deleted file mode 100644 index 52c7ccb..0000000 Binary files a/world/generation/chunks/assets/chunk_0014_grass_lite.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0014_grass_lite.png.import b/world/generation/chunks/assets/chunk_0014_grass_lite.png.import deleted file mode 100644 index d4c09fe..0000000 --- a/world/generation/chunks/assets/chunk_0014_grass_lite.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://b6fftn8x2o1dd" -path.s3tc="res://.godot/imported/chunk_0014_grass_lite.png-90b3bea1cb09cf8dbae98afc39c1f5cd.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0014_grass_lite.png-90b3bea1cb09cf8dbae98afc39c1f5cd.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0014_grass_lite.png" -dest_files=["res://.godot/imported/chunk_0014_grass_lite.png-90b3bea1cb09cf8dbae98afc39c1f5cd.s3tc.ctex", "res://.godot/imported/chunk_0014_grass_lite.png-90b3bea1cb09cf8dbae98afc39c1f5cd.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0015.glb b/world/generation/chunks/assets/chunk_0015.glb deleted file mode 100644 index 2c42918..0000000 Binary files a/world/generation/chunks/assets/chunk_0015.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0015.glb.import b/world/generation/chunks/assets/chunk_0015.glb.import deleted file mode 100644 index 7078d3b..0000000 --- a/world/generation/chunks/assets/chunk_0015.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://d26fd6sjsh50x" -path="res://.godot/imported/chunk_0015.glb-0a28f27956d19c7965f7bc9c838f1b29.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0015.glb" -dest_files=["res://.godot/imported/chunk_0015.glb-0a28f27956d19c7965f7bc9c838f1b29.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0015_cliff_wall.png b/world/generation/chunks/assets/chunk_0015_cliff_wall.png deleted file mode 100644 index 3fe157f..0000000 Binary files a/world/generation/chunks/assets/chunk_0015_cliff_wall.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0015_cliff_wall.png.import b/world/generation/chunks/assets/chunk_0015_cliff_wall.png.import deleted file mode 100644 index 560f5b4..0000000 --- a/world/generation/chunks/assets/chunk_0015_cliff_wall.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cpce75ti5cwnw" -path.s3tc="res://.godot/imported/chunk_0015_cliff_wall.png-08e30f267a5816ed94aa39dc21ccf05c.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0015_cliff_wall.png-08e30f267a5816ed94aa39dc21ccf05c.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "fc97184f537cc23f83a731327e10c836" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0015_cliff_wall.png" -dest_files=["res://.godot/imported/chunk_0015_cliff_wall.png-08e30f267a5816ed94aa39dc21ccf05c.s3tc.ctex", "res://.godot/imported/chunk_0015_cliff_wall.png-08e30f267a5816ed94aa39dc21ccf05c.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0015_grass_lite.png b/world/generation/chunks/assets/chunk_0015_grass_lite.png deleted file mode 100644 index 52c7ccb..0000000 Binary files a/world/generation/chunks/assets/chunk_0015_grass_lite.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0015_grass_lite.png.import b/world/generation/chunks/assets/chunk_0015_grass_lite.png.import deleted file mode 100644 index 1ad5aa3..0000000 --- a/world/generation/chunks/assets/chunk_0015_grass_lite.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://pmaf2oetdqln" -path.s3tc="res://.godot/imported/chunk_0015_grass_lite.png-0ae072e9d3b6d68d7fdb493a6acf3bad.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0015_grass_lite.png-0ae072e9d3b6d68d7fdb493a6acf3bad.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0015_grass_lite.png" -dest_files=["res://.godot/imported/chunk_0015_grass_lite.png-0ae072e9d3b6d68d7fdb493a6acf3bad.s3tc.ctex", "res://.godot/imported/chunk_0015_grass_lite.png-0ae072e9d3b6d68d7fdb493a6acf3bad.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0016.glb b/world/generation/chunks/assets/chunk_0016.glb deleted file mode 100644 index f4428f1..0000000 Binary files a/world/generation/chunks/assets/chunk_0016.glb and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0016.glb.import b/world/generation/chunks/assets/chunk_0016.glb.import deleted file mode 100644 index bc947f3..0000000 --- a/world/generation/chunks/assets/chunk_0016.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://te1yrm2uhin1" -path="res://.godot/imported/chunk_0016.glb-6904d8b3583dd301bda1835c501225f3.scn" - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0016.glb" -dest_files=["res://.godot/imported/chunk_0016.glb-6904d8b3583dd301bda1835c501225f3.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/world/generation/chunks/assets/chunk_0016_dirt.png b/world/generation/chunks/assets/chunk_0016_dirt.png deleted file mode 100644 index 588c8c6..0000000 Binary files a/world/generation/chunks/assets/chunk_0016_dirt.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0016_dirt.png.import b/world/generation/chunks/assets/chunk_0016_dirt.png.import deleted file mode 100644 index 00b774f..0000000 --- a/world/generation/chunks/assets/chunk_0016_dirt.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dxy8u3vyyu2on" -path.s3tc="res://.godot/imported/chunk_0016_dirt.png-5f2ccbadabdb1e395518abce5c09d66f.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0016_dirt.png-5f2ccbadabdb1e395518abce5c09d66f.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "07d90b3c46a4c3d0c172bed143dc9436" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0016_dirt.png" -dest_files=["res://.godot/imported/chunk_0016_dirt.png-5f2ccbadabdb1e395518abce5c09d66f.s3tc.ctex", "res://.godot/imported/chunk_0016_dirt.png-5f2ccbadabdb1e395518abce5c09d66f.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0016_dirt_wall.png b/world/generation/chunks/assets/chunk_0016_dirt_wall.png deleted file mode 100644 index dcc2364..0000000 Binary files a/world/generation/chunks/assets/chunk_0016_dirt_wall.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0016_dirt_wall.png.import b/world/generation/chunks/assets/chunk_0016_dirt_wall.png.import deleted file mode 100644 index 373e8cf..0000000 --- a/world/generation/chunks/assets/chunk_0016_dirt_wall.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cn8ljtpbsg253" -path.s3tc="res://.godot/imported/chunk_0016_dirt_wall.png-536115f36dddb3063c7b5162468a4876.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0016_dirt_wall.png-536115f36dddb3063c7b5162468a4876.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "b5430dba0b52db87d30bae85ae640b09" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0016_dirt_wall.png" -dest_files=["res://.godot/imported/chunk_0016_dirt_wall.png-536115f36dddb3063c7b5162468a4876.s3tc.ctex", "res://.godot/imported/chunk_0016_dirt_wall.png-536115f36dddb3063c7b5162468a4876.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0016_grass_lite.png b/world/generation/chunks/assets/chunk_0016_grass_lite.png deleted file mode 100644 index 52c7ccb..0000000 Binary files a/world/generation/chunks/assets/chunk_0016_grass_lite.png and /dev/null differ diff --git a/world/generation/chunks/assets/chunk_0016_grass_lite.png.import b/world/generation/chunks/assets/chunk_0016_grass_lite.png.import deleted file mode 100644 index 6311cde..0000000 --- a/world/generation/chunks/assets/chunk_0016_grass_lite.png.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dn84haedw08rx" -path.s3tc="res://.godot/imported/chunk_0016_grass_lite.png-df65a0c79eb0a4e70a36096ead0f76f3.s3tc.ctex" -path.etc2="res://.godot/imported/chunk_0016_grass_lite.png-df65a0c79eb0a4e70a36096ead0f76f3.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} -generator_parameters={ -"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4" -} - -[deps] - -source_file="res://world/generation/chunks/assets/chunk_0016_grass_lite.png" -dest_files=["res://.godot/imported/chunk_0016_grass_lite.png-df65a0c79eb0a4e70a36096ead0f76f3.s3tc.ctex", "res://.godot/imported/chunk_0016_grass_lite.png-df65a0c79eb0a4e70a36096ead0f76f3.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/world/generation/chunks/definitions/chunk_0002.tres b/world/generation/chunks/definitions/chunk_0002.tres index 77545a7..1e51d12 100644 --- a/world/generation/chunks/definitions/chunk_0002.tres +++ b/world/generation/chunks/definitions/chunk_0002.tres @@ -11,7 +11,7 @@ packed_scene = ExtResource("2_scene") primary_mesh_name = &"chunk_0002" selection_weight = 0.75 maximum_placements = 20 -tags = PackedStringArray("land", "walkable", "sand", "coast", "slope", "beach_ocean_edge") +tags = PackedStringArray("land", "walkable", "sand", "coast", "slope") allowed_neighbor_tags = PackedStringArray("sand", "grass") required_neighbor_tags = PackedStringArray("sand") minimum_required_neighbors = 1 diff --git a/world/generation/chunks/definitions/chunk_0003.tres b/world/generation/chunks/definitions/chunk_0003.tres index 4218357..1a1b3dc 100644 --- a/world/generation/chunks/definitions/chunk_0003.tres +++ b/world/generation/chunks/definitions/chunk_0003.tres @@ -9,7 +9,7 @@ stable_id = &"chunk_0003" label = "stream" packed_scene = ExtResource("2_scene") primary_mesh_name = &"chunk_0003" -selection_weight = 0.3 +selection_weight = 0.6 tags = PackedStringArray("land", "walkable", "fresh_water", "river", "flowing") allowed_neighbor_tags = PackedStringArray("grass") preferred_neighbor_tags = PackedStringArray("grass") diff --git a/world/generation/chunks/definitions/chunk_0008.tres b/world/generation/chunks/definitions/chunk_0008.tres deleted file mode 100644 index 29854f7..0000000 --- a/world/generation/chunks/definitions/chunk_0008.tres +++ /dev/null @@ -1,22 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=3 format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0008.glb" id="2_scene"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0008" -label = "beach corner" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0008" -selection_weight = 0.2 -maximum_placements = 4 -tags = PackedStringArray("land", "walkable", "sand", "coast", "slope", "corner") -allowed_neighbor_tags = PackedStringArray("sand", "coast") -north_allowed_neighbor_tags = PackedStringArray("beach_ocean_edge") -west_allowed_neighbor_tags = PackedStringArray("beach_ocean_edge") -required_neighbor_tags = PackedStringArray("beach_ocean_edge") -minimum_required_neighbors = 2 -preferred_neighbor_tags = PackedStringArray("coast") -prefers_map_boundary = true -ocean_facing_edges = 6 diff --git a/world/generation/chunks/definitions/chunk_0009.tres b/world/generation/chunks/definitions/chunk_0009.tres deleted file mode 100644 index 4be6d07..0000000 --- a/world/generation/chunks/definitions/chunk_0009.tres +++ /dev/null @@ -1,23 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0009.glb" id="2_scene"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0000.glb" id="3_base_grass"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0009" -label = "second-tier grass top" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0009" -base_layer_scene = ExtResource("3_base_grass") -base_layer_mesh_name = &"chunk_0000" -overlay_only = true -selection_weight = 0.22 -maximum_placements = 8 -tags = PackedStringArray("land", "walkable", "grass", "elevation_2", "elevation_2_top") -allowed_neighbor_tags = PackedStringArray("elevation_2") -required_neighbor_tags = PackedStringArray("elevation_2") -minimum_required_neighbors = 4 -preferred_neighbor_tags = PackedStringArray("elevation_2_top") -must_be_interior = true diff --git a/world/generation/chunks/definitions/chunk_0010.tres b/world/generation/chunks/definitions/chunk_0010.tres deleted file mode 100644 index d9120b1..0000000 --- a/world/generation/chunks/definitions/chunk_0010.tres +++ /dev/null @@ -1,27 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0010.glb" id="2_scene"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0000.glb" id="3_base_grass"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0010" -label = "second-tier grass corner" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0010" -base_layer_scene = ExtResource("3_base_grass") -base_layer_mesh_name = &"chunk_0000" -overlay_only = true -selection_weight = 0.12 -maximum_placements = 16 -tags = PackedStringArray("land", "walkable", "grass", "cliff", "corner", "elevation_2", "elevation_2_border") -allowed_neighbor_tags = PackedStringArray("grass", "elevation_2") -north_allowed_neighbor_tags = PackedStringArray("elevation_2_border") -east_allowed_neighbor_tags = PackedStringArray("grass") -south_allowed_neighbor_tags = PackedStringArray("grass") -west_allowed_neighbor_tags = PackedStringArray("elevation_2_border") -required_neighbor_tags = PackedStringArray("elevation_2_border") -minimum_required_neighbors = 2 -preferred_neighbor_tags = PackedStringArray("elevation_2") -must_be_interior = true diff --git a/world/generation/chunks/definitions/chunk_0011.tres b/world/generation/chunks/definitions/chunk_0011.tres deleted file mode 100644 index e7ce943..0000000 --- a/world/generation/chunks/definitions/chunk_0011.tres +++ /dev/null @@ -1,27 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0011.glb" id="2_scene"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0000.glb" id="3_base_grass"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0011" -label = "second-tier grass edge" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0011" -base_layer_scene = ExtResource("3_base_grass") -base_layer_mesh_name = &"chunk_0000" -overlay_only = true -selection_weight = 0.28 -maximum_placements = 32 -tags = PackedStringArray("land", "walkable", "grass", "cliff", "edge", "elevation_2", "elevation_2_border") -allowed_neighbor_tags = PackedStringArray("grass", "elevation_2") -north_allowed_neighbor_tags = PackedStringArray("elevation_2_border") -east_allowed_neighbor_tags = PackedStringArray("grass") -south_allowed_neighbor_tags = PackedStringArray("elevation_2_border") -west_allowed_neighbor_tags = PackedStringArray("elevation_2_top") -required_neighbor_tags = PackedStringArray("elevation_2_top") -minimum_required_neighbors = 1 -preferred_neighbor_tags = PackedStringArray("elevation_2") -must_be_interior = true diff --git a/world/generation/chunks/definitions/chunk_0012.tres b/world/generation/chunks/definitions/chunk_0012.tres deleted file mode 100644 index 06caccb..0000000 --- a/world/generation/chunks/definitions/chunk_0012.tres +++ /dev/null @@ -1,27 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0012.glb" id="2_scene"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0000.glb" id="3_base_grass"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0012" -label = "second-tier grass ramp" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0012" -base_layer_scene = ExtResource("3_base_grass") -base_layer_mesh_name = &"chunk_0000" -overlay_only = true -selection_weight = 0.12 -maximum_placements = 8 -tags = PackedStringArray("land", "walkable", "grass", "cliff", "edge", "ramp", "elevation_2", "elevation_2_border") -allowed_neighbor_tags = PackedStringArray("grass", "elevation_2") -north_allowed_neighbor_tags = PackedStringArray("elevation_2_border") -east_allowed_neighbor_tags = PackedStringArray("grass") -south_allowed_neighbor_tags = PackedStringArray("elevation_2_border") -west_allowed_neighbor_tags = PackedStringArray("elevation_2_top") -required_neighbor_tags = PackedStringArray("elevation_2_top") -minimum_required_neighbors = 1 -preferred_neighbor_tags = PackedStringArray("elevation_2") -must_be_interior = true diff --git a/world/generation/chunks/definitions/chunk_0013.tres b/world/generation/chunks/definitions/chunk_0013.tres deleted file mode 100644 index 22b6061..0000000 --- a/world/generation/chunks/definitions/chunk_0013.tres +++ /dev/null @@ -1,28 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=3 format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0013.glb" id="2_scene"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0013" -label = "grass-sand diagonal" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0013" -participates_in_base_solver = false -allowed_rotation_mask = 15 -selection_weight = 0.8 -maximum_placements = 16 -tags = PackedStringArray("land", "walkable", "grass", "sand", "flat", "transition", "mixed_surface") -allowed_neighbor_tags = PackedStringArray("grass", "sand") -forbidden_neighbor_tags = PackedStringArray("fresh_water", "spawn") -north_allowed_neighbor_tags = PackedStringArray("sand") -east_allowed_neighbor_tags = PackedStringArray("grass") -south_allowed_neighbor_tags = PackedStringArray("grass") -west_allowed_neighbor_tags = PackedStringArray("sand") -north_surface_tags = PackedStringArray("sand") -east_surface_tags = PackedStringArray("grass") -south_surface_tags = PackedStringArray("grass") -west_surface_tags = PackedStringArray("sand") -preferred_neighbor_tags = PackedStringArray("grass", "sand", "transition") -must_be_interior = true diff --git a/world/generation/chunks/definitions/chunk_0014.tres b/world/generation/chunks/definitions/chunk_0014.tres deleted file mode 100644 index c3a0d87..0000000 --- a/world/generation/chunks/definitions/chunk_0014.tres +++ /dev/null @@ -1,24 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=3 format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0014.glb" id="2_scene"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0014" -label = "second-tier grass sea edge" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0014" -overlay_only = true -selection_weight = 0.28 -maximum_placements = 32 -tags = PackedStringArray("land", "walkable", "grass", "coast", "cliff", "edge", "elevation_2", "elevation_2_border", "elevation_2_sea_edge") -allowed_neighbor_tags = PackedStringArray("elevation_2") -north_allowed_neighbor_tags = PackedStringArray("elevation_2_sea_edge") -south_allowed_neighbor_tags = PackedStringArray("elevation_2_sea_edge") -west_allowed_neighbor_tags = PackedStringArray("elevation_2_top") -required_neighbor_tags = PackedStringArray("elevation_2_top") -minimum_required_neighbors = 1 -preferred_neighbor_tags = PackedStringArray("elevation_2", "coast") -prefers_map_boundary = true -ocean_facing_edges = 2 diff --git a/world/generation/chunks/definitions/chunk_0015.tres b/world/generation/chunks/definitions/chunk_0015.tres deleted file mode 100644 index d7f0a5a..0000000 --- a/world/generation/chunks/definitions/chunk_0015.tres +++ /dev/null @@ -1,23 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=3 format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0015.glb" id="2_scene"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0015" -label = "second-tier grass sea corner" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0015" -overlay_only = true -selection_weight = 0.12 -maximum_placements = 16 -tags = PackedStringArray("land", "walkable", "grass", "coast", "cliff", "corner", "elevation_2", "elevation_2_border", "elevation_2_sea_edge") -allowed_neighbor_tags = PackedStringArray("elevation_2") -north_allowed_neighbor_tags = PackedStringArray("elevation_2_sea_edge") -west_allowed_neighbor_tags = PackedStringArray("elevation_2_sea_edge") -required_neighbor_tags = PackedStringArray("elevation_2_sea_edge") -minimum_required_neighbors = 2 -preferred_neighbor_tags = PackedStringArray("elevation_2", "coast") -prefers_map_boundary = true -ocean_facing_edges = 6 diff --git a/world/generation/chunks/definitions/chunk_0016.tres b/world/generation/chunks/definitions/chunk_0016.tres deleted file mode 100644 index 804bb34..0000000 --- a/world/generation/chunks/definitions/chunk_0016.tres +++ /dev/null @@ -1,19 +0,0 @@ -[gd_resource type="Resource" script_class="TerrainChunkDefinition" format=3] - -[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0016.glb" id="2_scene"] - -[resource] -script = ExtResource("1_definition") -stable_id = &"chunk_0016" -label = "stream variant" -packed_scene = ExtResource("2_scene") -primary_mesh_name = &"chunk_0016" -selection_weight = 0.3 -tags = PackedStringArray("land", "walkable", "fresh_water", "river", "flowing") -allowed_neighbor_tags = PackedStringArray("grass") -preferred_neighbor_tags = PackedStringArray("grass") -water_inlet_edges = 1 -water_outlet_edges = 4 -water_surface_size = Vector2(2.1, 10) -water_surface_offset = Vector2(-0.315, 0) diff --git a/world/generation/chunks/terrain_chunk_catalog.tres b/world/generation/chunks/terrain_chunk_catalog.tres index 7d577a3..6240098 100644 --- a/world/generation/chunks/terrain_chunk_catalog.tres +++ b/world/generation/chunks/terrain_chunk_catalog.tres @@ -1,4 +1,4 @@ -[gd_resource type="Resource" script_class="TerrainChunkCatalog" load_steps=21 format=3] +[gd_resource type="Resource" script_class="TerrainChunkCatalog" load_steps=12 format=3] [ext_resource type="Script" path="res://world/generation/terrain_chunk_catalog.gd" id="1_catalog"] [ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0000.tres" id="2_grass"] @@ -11,17 +11,8 @@ [ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0005.tres" id="9_grass_ocean_edge"] [ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0006.tres" id="10_grass_beach_transition"] [ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0007.tres" id="11_grass_ocean_corner"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0008.tres" id="12_beach_ocean_corner"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0009.tres" id="13_cliff_top"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0010.tres" id="14_cliff_corner"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0011.tres" id="15_cliff_edge"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0012.tres" id="16_cliff_ramp"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0013.tres" id="17_grass_sand_diagonal"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0014.tres" id="18_cliff_sea_edge"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0015.tres" id="19_cliff_sea_corner"] -[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0016.tres" id="20_stream_variant"] [resource] script = ExtResource("1_catalog") chunk_size = 10.0 -definitions = Array[ExtResource("7_definition_script")]([ExtResource("2_grass"), ExtResource("3_sand"), ExtResource("4_beach"), ExtResource("5_stream"), ExtResource("20_stream_variant"), ExtResource("6_pond"), ExtResource("9_grass_ocean_edge"), ExtResource("10_grass_beach_transition"), ExtResource("11_grass_ocean_corner"), ExtResource("12_beach_ocean_corner"), ExtResource("17_grass_sand_diagonal"), ExtResource("13_cliff_top"), ExtResource("14_cliff_corner"), ExtResource("15_cliff_edge"), ExtResource("16_cliff_ramp"), ExtResource("18_cliff_sea_edge"), ExtResource("19_cliff_sea_corner"), ExtResource("8_spawn")]) +definitions = Array[ExtResource("7_definition_script")]([ExtResource("2_grass"), ExtResource("3_sand"), ExtResource("4_beach"), ExtResource("5_stream"), ExtResource("6_pond"), ExtResource("9_grass_ocean_edge"), ExtResource("10_grass_beach_transition"), ExtResource("11_grass_ocean_corner"), ExtResource("8_spawn")]) diff --git a/world/generation/generated_world_region.gd b/world/generation/generated_world_region.gd index 59876b3..f313b51 100644 --- a/world/generation/generated_world_region.gd +++ b/world/generation/generated_world_region.gd @@ -78,11 +78,6 @@ func _ready() -> void: func generate_world(seed: int) -> bool: if seed <= 0 or seed > PlayerSaveManager.MAX_WORLD_SEED: return false - if ( - seed == _current_seed - and _generator.get_generated_chunks_root() != null - ): - return true _current_seed = seed _generator.generation_seed = seed return _generator.generate() @@ -242,34 +237,29 @@ func _on_generation_completed(summary: Dictionary) -> void: _prop_definitions_for(rule, tags), coordinate, ) - for _placement_attempt: int in ( - rule.placement_attempts_per_chunk - ): - if group_counts.get(group_key, 0) >= maximum: - break - if ( - definitions.is_empty() - or not _prop_group_roll_succeeds( - rule, - group_key, - coordinate, - random, - ) - ): - continue - if _maybe_add_prop( - definitions, - record.get("position", Vector3.ZERO), + if ( + definitions.is_empty() + or not _prop_group_roll_succeeds( + rule, + group_key, coordinate, - biome.stable_id, random, - terrain_triangles, - ): - group_counts[group_key] += 1 - _record_prop_group_coordinate( - group_key, - coordinate, - ) + ) + ): + continue + if _maybe_add_prop( + definitions, + record.get("position", Vector3.ZERO), + coordinate, + biome.stable_id, + random, + terrain_triangles, + ): + group_counts[group_key] += 1 + _record_prop_group_coordinate( + group_key, + coordinate, + ) _ensure_minimum_props( eligible_records, group_counts, @@ -332,7 +322,7 @@ func _configure_static_water() -> void: _ocean.fish_pool = OCEAN_POOL _ocean.location_tags = [&"coast", &"ocean", &"generated_ocean"] _ocean.surface_size = Vector2(10000.0, 10000.0) - _ocean.position.y = WATER_HEIGHT + _ocean.position.y = WATER_HEIGHT - 0.025 _apply_water_materials() @@ -461,11 +451,6 @@ func _add_prop( ) -> bool: if definition == null or definition.packed_scene == null: return false - var visual_scale := random.randf_range( - definition.minimum_visual_scale, - definition.maximum_visual_scale, - ) - var scaled_clearance := definition.clearance_radius * visual_scale var placement := Vector3.ZERO var found_surface := false for attempt: int in PROP_PLACEMENT_ATTEMPTS: @@ -485,7 +470,7 @@ func _add_prop( > WATER_HEIGHT + PROP_MINIMUM_GROUND_CLEARANCE and _has_prop_clearance( placement, - scaled_clearance, + definition.clearance_radius, ) ): placement.y = surface_height @@ -507,26 +492,22 @@ func _add_prop( prop.set_meta(&"terrain_prop_group", definition.procedural_group) prop.set_meta(&"terrain_chunk_coordinate", chunk_coordinate) prop.set_meta(&"terrain_biome_id", biome_id) - prop.set_meta(&"terrain_prop_visual_scale", visual_scale) prop.position = placement prop.rotation.y = random.randf_range(-PI, PI) _decorations.add_child(prop) visual_root.name = "Visual" prop.add_child(visual_root) - visual_root.position = definition.visual_offset - visual_root.scale = Vector3.ONE * visual_scale _configure_prop_visuals(visual_root) - _apply_prop_material_variant(visual_root, definition, random) - _add_prop_collision(prop, definition, visual_scale) + _add_prop_collision(prop, definition) _placed_prop_positions.append(placement) - _placed_prop_clearance_radii.append(scaled_clearance) + _placed_prop_clearance_radii.append(definition.clearance_radius) _placed_prop_groups.append(definition.procedural_group) if definition.gatherable_anchor_height > 0.0: var anchor := Marker3D.new() anchor.name = "TreeAnchor_%d" % _tree_anchors.get_child_count() anchor.position = prop.position + Vector3( 0.0, - definition.gatherable_anchor_height * visual_scale, + definition.gatherable_anchor_height, 0.0, ) _tree_anchors.add_child(anchor) @@ -643,11 +624,7 @@ func _prop_group_maximum( if eligible_count <= 0: return 0 var maximum := ceili( - float( - eligible_count - * rule.placement_attempts_per_chunk - * rule.maximum_density - ) + float(eligible_count * rule.maximum_density) / float(PROP_CHANCE_SCALE) ) return maxi(maximum, rule.minimum_placements) @@ -800,51 +777,6 @@ func _configure_prop_visuals(root_node: Node) -> void: _configure_prop_visuals(child) -func _apply_prop_material_variant( - root_node: Node, - definition: TerrainPropDefinition, - random: RandomNumberGenerator, -) -> void: - if ( - definition.material_variants.is_empty() - or definition.variant_material_slot_names.is_empty() - ): - return - var variant := definition.material_variants[ - random.randi_range(0, definition.material_variants.size() - 1) - ] - _apply_material_variant_to_meshes( - root_node, - definition.variant_material_slot_names, - variant, - ) - - -func _apply_material_variant_to_meshes( - root_node: Node, - target_material_names: PackedStringArray, - variant: Material, -) -> void: - var mesh_instance := root_node as MeshInstance3D - if mesh_instance != null and mesh_instance.mesh != null: - for surface_index: int in mesh_instance.mesh.get_surface_count(): - var active_material := mesh_instance.get_active_material(surface_index) - if ( - active_material != null - and active_material.resource_name in target_material_names - ): - mesh_instance.set_surface_override_material( - surface_index, - variant, - ) - for child: Node in root_node.get_children(): - _apply_material_variant_to_meshes( - child, - target_material_names, - variant, - ) - - func _terrain_surface_triangles() -> Array[PackedVector3Array]: var triangles: Array[PackedVector3Array] = [] for mesh_instance: MeshInstance3D in _generator.get_primary_terrain_meshes(): @@ -886,7 +818,6 @@ func _surface_height_at( func _add_prop_collision( prop: Node3D, definition: TerrainPropDefinition, - visual_scale: float, ) -> void: if not definition.has_collision(): return @@ -899,16 +830,16 @@ func _add_prop_collision( collision.name = "CollisionShape" if definition.has_box_collision(): var box := BoxShape3D.new() - box.size = definition.collision_box_size * visual_scale + box.size = definition.collision_box_size collision.shape = box - collision.position = definition.collision_offset * visual_scale + collision.position = definition.collision_offset else: var cylinder := CylinderShape3D.new() - cylinder.radius = definition.collision_radius * visual_scale - cylinder.height = definition.collision_height * visual_scale + cylinder.radius = definition.collision_radius + cylinder.height = definition.collision_height collision.shape = cylinder collision.position = ( - definition.collision_offset * visual_scale + definition.collision_offset + Vector3.UP * cylinder.height * 0.5 ) body.add_child(collision) diff --git a/world/generation/generated_world_region.tscn b/world/generation/generated_world_region.tscn index 7987172..e46796f 100644 --- a/world/generation/generated_world_region.tscn +++ b/world/generation/generated_world_region.tscn @@ -31,14 +31,12 @@ gatherable_anchor_root = NodePath("GatherableAnchors") unique_name_in_owner = true script = ExtResource("2_generator") catalog = ExtResource("3_catalog") -grid_size = Vector2i(15, 13) +grid_size = Vector2i(7, 7) generation_seed = 13001 generate_on_ready = false build_collision = true force_center_chunk_id = &"chunk_spawn" -required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007", "chunk_0008") -grass_sand_smoothing_enabled = true -elevated_cliff_feature_enabled = true +required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007") required_chunk_weight_multiplier = 64.0 maximum_backtracks = 100000 @@ -62,7 +60,7 @@ script = ExtResource("8_diggable") area_id = &"starter_beach" terrain_source = NodePath("../../Terrain/TerrainChunkGenerator") surface_materials = Array[StringName]([&"sand"]) -generation_bounds = Rect2(-55, -45, 110, 90) +generation_bounds = Rect2(-35, -35, 70, 70) minimum_global_y = -0.24 maximum_global_y = 0.5 minimum_up_dot = 0.72 diff --git a/world/generation/props/definitions/prop_mushroom.tres b/world/generation/props/definitions/prop_mushroom.tres index e4bff54..cf63eff 100644 --- a/world/generation/props/definitions/prop_mushroom.tres +++ b/world/generation/props/definitions/prop_mushroom.tres @@ -16,7 +16,3 @@ preferred_nearby_prop_groups = PackedStringArray("grass_tree") preferred_nearby_radius = 12.0 nearby_preference_weight_multiplier = 3.0 clearance_radius = 0.8 -visual_offset = Vector3(0, -0.08, 0) -collision_radius = 0.65 -collision_height = 1.0 -collision_offset = Vector3(0, -0.08, 0) diff --git a/world/generation/props/definitions/prop_pine.tres b/world/generation/props/definitions/prop_pine.tres index 0a569cf..19361c8 100644 --- a/world/generation/props/definitions/prop_pine.tres +++ b/world/generation/props/definitions/prop_pine.tres @@ -13,8 +13,6 @@ required_chunk_tags = PackedStringArray("grass") selection_weight = 0.7 minimum_spawn_chunk_distance = 2 clearance_radius = 1.55 -minimum_visual_scale = 0.75 -maximum_visual_scale = 1.22 collision_radius = 0.5 collision_height = 4.0 gatherable_anchor_height = 2.15 diff --git a/world/generation/props/definitions/prop_tree_1.tres b/world/generation/props/definitions/prop_tree_1.tres index 41ab406..4b942db 100644 --- a/world/generation/props/definitions/prop_tree_1.tres +++ b/world/generation/props/definitions/prop_tree_1.tres @@ -1,10 +1,7 @@ -[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=6 format=3] +[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"] [ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_1.glb" id="2_scene"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_light.tres" id="3_leaf_light"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_mid.tres" id="4_leaf_mid"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_dark.tres" id="5_leaf_dark"] [resource] script = ExtResource("1_definition") @@ -15,10 +12,6 @@ procedural_group = &"grass_tree" required_chunk_tags = PackedStringArray("grass") minimum_spawn_chunk_distance = 2 clearance_radius = 1.3 -minimum_visual_scale = 0.75 -maximum_visual_scale = 1.15 -variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark") -material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")]) collision_radius = 0.4 collision_height = 3.2 gatherable_anchor_height = 2.15 diff --git a/world/generation/props/definitions/prop_tree_2.tres b/world/generation/props/definitions/prop_tree_2.tres index c4d09fa..3d1ab62 100644 --- a/world/generation/props/definitions/prop_tree_2.tres +++ b/world/generation/props/definitions/prop_tree_2.tres @@ -1,10 +1,7 @@ -[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=6 format=3] +[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"] [ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_2.glb" id="2_scene"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_light.tres" id="3_leaf_light"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_mid.tres" id="4_leaf_mid"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_dark.tres" id="5_leaf_dark"] [resource] script = ExtResource("1_definition") @@ -15,10 +12,6 @@ procedural_group = &"grass_tree" required_chunk_tags = PackedStringArray("grass") minimum_spawn_chunk_distance = 2 clearance_radius = 1.5 -minimum_visual_scale = 0.8 -maximum_visual_scale = 1.18 -variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark") -material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")]) collision_radius = 0.5 collision_height = 3.8 gatherable_anchor_height = 2.15 diff --git a/world/generation/props/definitions/prop_tree_3.tres b/world/generation/props/definitions/prop_tree_3.tres index 642a8de..637a281 100644 --- a/world/generation/props/definitions/prop_tree_3.tres +++ b/world/generation/props/definitions/prop_tree_3.tres @@ -1,10 +1,7 @@ -[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=6 format=3] +[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"] [ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_3.glb" id="2_scene"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_light.tres" id="3_leaf_light"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_mid.tres" id="4_leaf_mid"] -[ext_resource type="Material" path="res://world/generation/props/materials/leaf_dark.tres" id="5_leaf_dark"] [resource] script = ExtResource("1_definition") @@ -15,10 +12,6 @@ procedural_group = &"grass_tree" required_chunk_tags = PackedStringArray("grass") minimum_spawn_chunk_distance = 2 clearance_radius = 1.8 -minimum_visual_scale = 0.85 -maximum_visual_scale = 1.15 -variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark") -material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")]) collision_radius = 0.55 collision_height = 4.2 gatherable_anchor_height = 2.15 diff --git a/world/generation/props/materials/leaf_dark.tres b/world/generation/props/materials/leaf_dark.tres deleted file mode 100644 index d75f991..0000000 --- a/world/generation/props/materials/leaf_dark.tres +++ /dev/null @@ -1,9 +0,0 @@ -[gd_resource type="StandardMaterial3D" format=3] - -[resource] -resource_name = "leaf_variant_dark" -albedo_color = Color(0.1712, 0.252, 0, 1) -metallic = 0.0 -roughness = 1.0 -shading_mode = 1 -texture_filter = 0 diff --git a/world/generation/props/materials/leaf_light.tres b/world/generation/props/materials/leaf_light.tres deleted file mode 100644 index d49f786..0000000 --- a/world/generation/props/materials/leaf_light.tres +++ /dev/null @@ -1,9 +0,0 @@ -[gd_resource type="StandardMaterial3D" format=3] - -[resource] -resource_name = "leaf_variant_light" -albedo_color = Color(0.3388, 0.7187, 0, 1) -metallic = 0.0 -roughness = 1.0 -shading_mode = 1 -texture_filter = 0 diff --git a/world/generation/props/materials/leaf_mid.tres b/world/generation/props/materials/leaf_mid.tres deleted file mode 100644 index d0ee646..0000000 --- a/world/generation/props/materials/leaf_mid.tres +++ /dev/null @@ -1,9 +0,0 @@ -[gd_resource type="StandardMaterial3D" format=3] - -[resource] -resource_name = "leaf_variant_mid" -albedo_color = Color(0.3147, 0.4631, 0, 1) -metallic = 0.0 -roughness = 1.0 -shading_mode = 1 -texture_filter = 0 diff --git a/world/generation/terrain_biome_prop_rule.gd b/world/generation/terrain_biome_prop_rule.gd index 7b02e98..96da0db 100644 --- a/world/generation/terrain_biome_prop_rule.gd +++ b/world/generation/terrain_biome_prop_rule.gd @@ -6,9 +6,6 @@ extends Resource @export_range(0, 10000, 1) var adjacency_bonus := 0 @export_range(0, 10000, 1) var maximum_density := 10000 @export_range(0, 64, 1) var minimum_placements := 0 -## More than one attempt permits intentionally dense biomes without changing -## the density of every biome that uses the same procedural prop group. -@export_range(1, 8, 1) var placement_attempts_per_chunk := 1 ## Empty permits every compatible prop in the procedural group. @export var allowed_prop_ids := PackedStringArray() diff --git a/world/generation/terrain_chunk_analyzer.gd b/world/generation/terrain_chunk_analyzer.gd index 84895b1..9f1557c 100644 --- a/world/generation/terrain_chunk_analyzer.gd +++ b/world/generation/terrain_chunk_analyzer.gd @@ -34,29 +34,6 @@ static func create_variants( chunk_size, edge_epsilon, ) - if definition.base_layer_scene != null: - var base_layer_root := definition.base_layer_scene.instantiate() - var base_layer_mesh := find_primary_mesh( - base_layer_root, - definition.base_layer_mesh_name, - ) - if base_layer_mesh == null or base_layer_mesh.mesh == null: - push_error( - "Terrain chunk %s has no base-layer MeshInstance3D named %s." - % [definition.stable_id, definition.base_layer_mesh_name] - ) - base_layer_root.free() - chunk_root.free() - return variants - boundary_points.append_array( - _collect_boundary_points( - base_layer_mesh.mesh, - _transform_relative_to(base_layer_mesh, base_layer_root), - chunk_size, - edge_epsilon, - ) - ) - base_layer_root.free() for quarter_turns: int in 4: if not definition.allows_quarter_turn(quarter_turns): continue diff --git a/world/generation/terrain_chunk_catalog.gd b/world/generation/terrain_chunk_catalog.gd index 6271e04..3c73822 100644 --- a/world/generation/terrain_chunk_catalog.gd +++ b/world/generation/terrain_chunk_catalog.gd @@ -30,25 +30,6 @@ func validation_errors() -> PackedStringArray: errors.append("%s has no PackedScene." % definition.stable_id) if definition.primary_mesh_name == &"": errors.append("%s has no primary mesh name." % definition.stable_id) - if ( - (definition.base_layer_scene == null) - != (definition.base_layer_mesh_name == &"") - ): - errors.append( - "%s must define both base-layer scene and mesh name." - % definition.stable_id - ) - if ( - definition.overlay_only - and definition.base_layer_scene == null - and definition.ocean_facing_edges == 0 - ): - errors.append( - ( - "%s is an inland overlay but has no base-layer scene." - % definition.stable_id - ) - ) if definition.allowed_rotation_mask == 0: errors.append("%s allows no rotations." % definition.stable_id) var has_coast := "coast" in definition.tags @@ -63,11 +44,6 @@ func validation_errors() -> PackedStringArray: "%s has an ocean-facing edge without the coast tag." % definition.stable_id ) - if definition.must_be_interior and definition.prefers_map_boundary: - errors.append( - "%s cannot require the interior and prefer the boundary." - % definition.stable_id - ) var has_fresh_water := "fresh_water" in definition.tags var has_water_footprint := ( definition.water_surface_size.x > 0.0 diff --git a/world/generation/terrain_chunk_definition.gd b/world/generation/terrain_chunk_definition.gd index ca38dc3..54f27cb 100644 --- a/world/generation/terrain_chunk_definition.gd +++ b/world/generation/terrain_chunk_definition.gd @@ -5,22 +5,6 @@ extends Resource @export var label := "" @export var packed_scene: PackedScene @export var primary_mesh_name: StringName -## Optional existing level-one chunk placed beneath an authored elevated -## overlay. This lets cliff edges leave their downhill side open while the -## ordinary base terrain remains present in the same generated cell. -@export_group("Layered Terrain") -@export var base_layer_scene: PackedScene -@export var base_layer_mesh_name: StringName -## Overlay-only chunks replace a reserved base-terrain placement after the -## ordinary terrain solve. Inland overlays normally provide base_layer_scene; -## complete coastal overlays can instead carry their own descent to sea level. -## They remain in placement manifests and runtime output, but do not inflate -## every cell's global solver domain. -@export var overlay_only := false -## Post-process pieces remain available as authored variants and in placement -## manifests, but do not participate in the ordinary base-terrain solve. -@export var participates_in_base_solver := true -@export_group("") @export_flags("0 degrees", "90 degrees", "180 degrees", "270 degrees") var allowed_rotation_mask := 15 @export_range(0.01, 100.0, 0.01) var selection_weight := 1.0 @@ -43,16 +27,6 @@ var allowed_rotation_mask := 15 @export var south_allowed_neighbor_tags := PackedStringArray() @export var west_allowed_neighbor_tags := PackedStringArray() @export_group("") -## Optional canonical descriptions of the visible terrain surface at each -## edge. Empty edges inherit the definition's ordinary tags. Mixed-surface -## chunks use these to prevent a visually grass edge from joining sand (or the -## reverse) even when the chunk itself carries both biome tags. -@export_group("Directional Surface Rules") -@export var north_surface_tags := PackedStringArray() -@export var east_surface_tags := PackedStringArray() -@export var south_surface_tags := PackedStringArray() -@export var west_surface_tags := PackedStringArray() -@export_group("") ## When non-empty, at least this many in-grid cardinal neighbors must carry one ## of the listed tags. This supports statements such as "three sides around the ## spawn must be safe grass" without hard-coding a particular chunk ID. @@ -62,9 +36,6 @@ var allowed_rotation_mask := 15 ## weight. This shapes regions without turning a visual preference into a hard ## generation constraint. @export var preferred_neighbor_tags := PackedStringArray() -## Interior-only pieces may not occupy any outer grid cell. Coastal elevated -## pieces use separate authored definitions rather than weakening this rule. -@export var must_be_interior := false ## Coastal transition pieces should normally migrate toward the generated ## region's perimeter while remaining legal in the interior. @export var prefers_map_boundary := false @@ -121,28 +92,6 @@ func directional_allowed_neighbor_tags( return PackedStringArray() -func surface_tags_for_edge( - edge: TerrainChunkTopology.Edge, -) -> PackedStringArray: - var edge_tags := directional_surface_tags(edge) - return tags if edge_tags.is_empty() else edge_tags - - -func directional_surface_tags( - edge: TerrainChunkTopology.Edge, -) -> PackedStringArray: - match edge: - TerrainChunkTopology.Edge.NORTH: - return north_surface_tags - TerrainChunkTopology.Edge.EAST: - return east_surface_tags - TerrainChunkTopology.Edge.SOUTH: - return south_surface_tags - TerrainChunkTopology.Edge.WEST: - return west_surface_tags - return PackedStringArray() - - func _allows_neighbor_with_tags( neighbor: TerrainChunkDefinition, allowed_tags: PackedStringArray, diff --git a/world/generation/terrain_chunk_generator.gd b/world/generation/terrain_chunk_generator.gd index 8f6235d..df3afc6 100644 --- a/world/generation/terrain_chunk_generator.gd +++ b/world/generation/terrain_chunk_generator.gd @@ -5,11 +5,6 @@ signal generation_completed(summary: Dictionary) const CONSTRAINT_PROFILE_QUANTIZATION := 0.001 const CANDIDATE_WEIGHT_SCALE := 10000.0 -const LARGE_GRID_LIGHTWEIGHT_THRESHOLD := 128 -const LARGE_GRID_PROPAGATION_INTERVAL := 8 -# Keep packed domain bits below the signed 64-bit sign bit. Catalogs larger -# than this remain correct through the unpacked solver path. -const MAX_PACKED_SOLVER_VARIANTS := 62 @export var catalog: TerrainChunkCatalog @export var grid_size := Vector2i(7, 7) @@ -19,22 +14,6 @@ const MAX_PACKED_SOLVER_VARIANTS := 62 @export var show_chunk_labels := false @export var force_center_chunk_id: StringName = &"chunk_0000" @export var required_chunk_ids := PackedStringArray() -@export_group("Grass-Sand Smoothing") -@export var grass_sand_smoothing_enabled := false -@export var smoothing_grass_chunk_id: StringName = &"chunk_0000" -@export var smoothing_sand_chunk_id: StringName = &"chunk_0001" -@export var smoothing_diagonal_chunk_id: StringName = &"chunk_0013" -@export_range(1, 64, 1) var maximum_smoothing_placements := 16 -@export_group("") -@export_group("Elevated Inland Feature") -@export var elevated_cliff_feature_enabled := false -@export var elevated_cliff_base_chunk_id: StringName = &"chunk_0000" -@export var elevated_cliff_top_chunk_id: StringName = &"chunk_0009" -@export var elevated_cliff_corner_chunk_id: StringName = &"chunk_0010" -@export var elevated_cliff_edge_chunk_id: StringName = &"chunk_0011" -@export var elevated_cliff_ramp_chunk_id: StringName = &"chunk_0012" -@export_range(0.0, 1.0, 0.05) var elevated_cliff_ramp_chance := 0.75 -@export_group("") @export_range(1.0, 100.0, 1.0) var required_chunk_weight_multiplier := 64.0 @export_range(1.0, 4.0, 0.05) var preferred_neighbor_weight_multiplier := 1.6 @export_range(0.01, 1.0, 0.01) var long_repeat_weight_multiplier := 0.3 @@ -47,27 +26,13 @@ var _variants: Array[TerrainChunkVariant] = [] # Rotations with identical terrain edges and connector directions share one # solver candidate, then resolve back to an authored rotation after solving. var _solver_variants: Array[TerrainChunkVariant] = [] -var _solver_definitions: Array[TerrainChunkDefinition] = [] -var _solver_variants_by_definition: Dictionary[StringName, Array] = {} -var _definition_variant_masks: Dictionary[StringName, int] = {} -var _required_neighbor_variant_masks: Dictionary[StringName, int] = {} var _equivalent_rotations: Dictionary[String, PackedInt32Array] = {} var _solver_variant_indices: Dictionary = {} var _edge_compatibility := PackedByteArray() -var _edge_compatibility_masks := PackedInt64Array() -var _static_cell_candidate_masks := PackedInt64Array() -var _neighbor_indices: Array[PackedInt32Array] = [] var _placements: Array[TerrainChunkVariant] = [] -var _placement_counts: Dictionary[StringName, int] = {} -var _unfilled_cells := 0 -var _required_stable_ids: Array[StringName] = [] -var _selection_missing_required_count := 0 -var _selection_missing_required_mask := 0 var _random := RandomNumberGenerator.new() var _generated_chunks: Node3D var _backtrack_count := 0 -var _elevated_feature_center := Vector2i(-1, -1) -var _elevated_feature_reserved_grass_indices: Dictionary[int, bool] = {} func _ready() -> void: @@ -82,27 +47,19 @@ func generate() -> bool: previous_placements.assign(_placements) _random.seed = generation_seed _backtrack_count = 0 - _reset_placements(grid_size.x * grid_size.y) - if not _prepare_elevated_feature_region(): - _assign_placements(previous_placements) - return false + _placements.clear() + _placements.resize(grid_size.x * grid_size.y) if not _solve_cell(0): - _assign_placements(previous_placements) + _placements.assign(previous_placements) push_error( "Terrain generation could not solve a %dx%d grid with seed %d." % [grid_size.x, grid_size.y, generation_seed] ) return false _resolve_equivalent_rotations() - if not _apply_grass_sand_smoothing(): - _assign_placements(previous_placements) - return false - if not _apply_elevated_feature(): - _assign_placements(previous_placements) - return false var solution_root := _build_solution_root() if solution_root == null: - _assign_placements(previous_placements) + _placements.assign(previous_placements) return false _replace_generated_chunks(solution_root) var summary := _build_summary() @@ -136,11 +93,11 @@ func generate_from_placement_keys(keys: PackedStringArray) -> bool: var previous_placements: Array[TerrainChunkVariant] = [] previous_placements.assign(_placements) - _assign_placements(resolved) + _placements.assign(resolved) _backtrack_count = 0 var solution_root := _build_solution_root() if solution_root == null: - _assign_placements(previous_placements) + _placements.assign(previous_placements) return false _replace_generated_chunks(solution_root) var summary := _build_summary() @@ -223,10 +180,6 @@ func _prepare_catalog() -> bool: _variants.clear() _solver_variants.clear() - _solver_definitions.clear() - _solver_variants_by_definition.clear() - _definition_variant_masks.clear() - _required_neighbor_variant_masks.clear() _equivalent_rotations.clear() _solver_variant_indices.clear() for definition: TerrainChunkDefinition in catalog.definitions: @@ -242,11 +195,6 @@ func _prepare_catalog() -> bool: return false for variant: TerrainChunkVariant in analyzed: _variants.append(variant) - if ( - definition.overlay_only - or not definition.participates_in_base_solver - ): - continue var constraint_key := _constraint_key(variant) var rotations: PackedInt32Array = _equivalent_rotations.get( constraint_key, @@ -259,81 +207,12 @@ func _prepare_catalog() -> bool: if _variants.is_empty(): push_error("Terrain chunk catalog produced no usable variants.") return false - if _solver_variants.is_empty(): - push_error("Terrain chunk catalog produced no base-terrain solver variants.") - return false for index: int in _solver_variants.size(): - var variant := _solver_variants[index] - _solver_variant_indices[variant] = index - var definition_variants: Array = _solver_variants_by_definition.get( - variant.definition.stable_id, - [], - ) - definition_variants.append(variant) - if definition_variants.size() == 1: - _solver_definitions.append(variant.definition) - _solver_variants_by_definition[ - variant.definition.stable_id - ] = definition_variants - if index < MAX_PACKED_SOLVER_VARIANTS: - _definition_variant_masks[variant.definition.stable_id] = ( - int( - _definition_variant_masks.get( - variant.definition.stable_id, - 0, - ) - ) - | (1 << index) - ) + _solver_variant_indices[_solver_variants[index]] = index _build_edge_compatibility_cache() return true -func _reset_placements(cell_count: int) -> void: - _placements.clear() - _placements.resize(cell_count) - _placement_counts.clear() - _unfilled_cells = cell_count - - -func _assign_placements(layout: Array[TerrainChunkVariant]) -> void: - _placements.assign(layout) - _rebuild_placement_tracking() - - -func _rebuild_placement_tracking() -> void: - _placement_counts.clear() - _unfilled_cells = 0 - for placement: TerrainChunkVariant in _placements: - if placement == null: - _unfilled_cells += 1 - continue - _adjust_placement_count(placement.definition.stable_id, 1) - - -func _set_placement(index: int, placement: TerrainChunkVariant) -> void: - var previous := _placements[index] - if previous == placement: - return - if previous == null: - _unfilled_cells -= 1 - else: - _adjust_placement_count(previous.definition.stable_id, -1) - _placements[index] = placement - if placement == null: - _unfilled_cells += 1 - else: - _adjust_placement_count(placement.definition.stable_id, 1) - - -func _adjust_placement_count(stable_id: StringName, difference: int) -> void: - var updated := int(_placement_counts.get(stable_id, 0)) + difference - if updated <= 0: - _placement_counts.erase(stable_id) - return - _placement_counts[stable_id] = updated - - func _constraint_key(variant: TerrainChunkVariant) -> String: return "%s|%s" % [ variant.definition.stable_id, @@ -345,10 +224,6 @@ func _build_edge_compatibility_cache() -> void: var variant_count := _solver_variants.size() _edge_compatibility.resize(variant_count * 4 * variant_count) _edge_compatibility.fill(0) - _edge_compatibility_masks.resize( - variant_count * 4 if _packed_solver_domains_are_available() else 0 - ) - _edge_compatibility_masks.fill(0) for first_index: int in variant_count: var first := _solver_variants[first_index] for first_edge_value: int in TerrainChunkTopology.Edge.values(): @@ -361,19 +236,14 @@ func _build_edge_compatibility_cache() -> void: first_edge, second_index, ) - var compatible := _calculate_edge_compatibility( + _edge_compatibility[cache_index] = int( + _calculate_edge_compatibility( first, first_edge, second, second_edge, - ) - _edge_compatibility[cache_index] = int(compatible) - if compatible and _packed_solver_domains_are_available(): - var mask_index := first_index * 4 + int(first_edge) - _edge_compatibility_masks[mask_index] = ( - _edge_compatibility_masks[mask_index] - | (1 << second_index) ) + ) func _edge_compatibility_index( @@ -390,13 +260,11 @@ func _edge_compatibility_index( func _validate_generation_requirements() -> bool: var seen_required: Dictionary[StringName, bool] = {} - _required_stable_ids.clear() for required_id_value: String in required_chunk_ids: var required_id := StringName(required_id_value) if seen_required.has(required_id): continue seen_required[required_id] = true - _required_stable_ids.append(required_id) var definition := catalog.definition_for_id(required_id) if definition == null: push_error("Required terrain chunk %s is not in the catalog." % required_id) @@ -404,15 +272,6 @@ func _validate_generation_requirements() -> bool: if definition.maximum_placements == 0: push_error("Required terrain chunk %s permits no placements." % required_id) return false - if ( - definition.overlay_only - or not definition.participates_in_base_solver - ): - push_error( - "Required terrain chunk %s does not participate in the base solver." - % required_id - ) - return false if seen_required.size() > grid_size.x * grid_size.y: push_error("The terrain grid has fewer cells than required chunk IDs.") return false @@ -442,52 +301,31 @@ func _solve_cell(placed_count: int) -> bool: ) if _backtrack_count >= maximum_backtracks: return false - _refresh_selection_required_cache() if not _requirements_can_still_be_satisfied(): return false - var next_cell := _select_next_cell(placed_count) + var next_cell := _select_next_cell() var index := int(next_cell.get("index", -1)) if index < 0: return false - var candidates: Array[TerrainChunkVariant] = [] - candidates.assign(next_cell.get("candidates", [])) + var candidates := next_cell.get( + "candidates", + [], + ) as Array[TerrainChunkVariant] for candidate: TerrainChunkVariant in _weighted_candidate_order( candidates, index, ): - _set_placement(index, candidate) + _placements[index] = candidate if _solve_cell(placed_count + 1): return true - _set_placement(index, null) + _placements[index] = null _backtrack_count += 1 if _backtrack_count >= maximum_backtracks: break return false -func _refresh_selection_required_cache() -> void: - _selection_missing_required_count = 0 - _selection_missing_required_mask = 0 - for stable_id: StringName in _required_stable_ids: - if int(_placement_counts.get(stable_id, 0)) > 0: - continue - _selection_missing_required_count += 1 - _selection_missing_required_mask |= int( - _definition_variant_masks.get(stable_id, 0) - ) - - -func _select_next_cell(placed_count: int) -> Dictionary: - if _packed_solver_domains_are_available(): - if ( - _placements.size() >= LARGE_GRID_LIGHTWEIGHT_THRESHOLD - and placed_count % LARGE_GRID_PROPAGATION_INTERVAL != 0 - ): - return _select_next_large_grid_cell() - return _select_next_large_grid_propagated_cell() - if _placements.size() >= LARGE_GRID_LIGHTWEIGHT_THRESHOLD: - if placed_count % LARGE_GRID_PROPAGATION_INTERVAL != 0: - return _select_next_large_grid_cell_unpacked() +func _select_next_cell() -> Dictionary: # Rebuild and propagate the small domain table on every branch. This catches # unsupported connector chains before they turn into a deep recursive dead # end, while keeping placement state simple and deterministic. @@ -518,285 +356,6 @@ func _select_next_cell(placed_count: int) -> Dictionary: return {"index": best_index, "candidates": best_candidates} -func _select_next_large_grid_propagated_cell() -> Dictionary: - var domains: Dictionary[int, int] = {} - for index: int in _placements.size(): - if _placements[index] != null: - continue - var candidate_mask := _compatible_candidate_mask(index) - if candidate_mask == 0: - return {"index": index, "candidates": []} - domains[index] = candidate_mask - if not _propagate_domain_masks(domains): - return {"index": -1, "candidates": []} - - var best_index := -1 - var best_mask := 0 - var best_size := _solver_variants.size() + 1 - for index: int in domains: - var candidate_mask := domains[index] - var candidate_count := _mask_bit_count(candidate_mask) - if best_index < 0 or candidate_count < best_size: - best_index = index - best_mask = candidate_mask - best_size = candidate_count - if candidate_count == 1: - break - return { - "index": best_index, - "candidates": _candidates_from_mask(best_mask), - } - - -func _candidates_from_mask(candidate_mask: int) -> Array[TerrainChunkVariant]: - var result: Array[TerrainChunkVariant] = [] - for candidate_index: int in _solver_variants.size(): - if (candidate_mask & (1 << candidate_index)) != 0: - result.append(_solver_variants[candidate_index]) - return result - - -func _mask_bit_count(candidate_mask: int) -> int: - var count := 0 - var remaining := candidate_mask - while remaining != 0: - remaining &= remaining - 1 - count += 1 - return count - - -func _propagate_domain_masks(domains: Dictionary[int, int]) -> bool: - var changed := true - while changed: - changed = false - for index: int in domains.keys(): - var candidates := domains[index] - var supported := 0 - for candidate_index: int in _solver_variants.size(): - var candidate_bit := 1 << candidate_index - if ( - (candidates & candidate_bit) != 0 - and _domain_candidate_has_mask_support( - candidate_index, - index, - domains, - ) - ): - supported |= candidate_bit - if supported == 0: - return false - if supported != candidates: - domains[index] = supported - changed = true - var required_result := _constrain_required_domain_masks(domains) - if required_result < 0: - return false - if required_result > 0: - changed = true - if not _placed_neighbor_requirements_have_domain_mask_support(domains): - return false - return true - - -func _domain_candidate_has_mask_support( - candidate_index: int, - index: int, - domains: Dictionary[int, int], -) -> bool: - var candidate := _solver_variants[candidate_index] - var possible_required_neighbors := 0 - var required_neighbor_mask := _required_neighbor_variant_mask( - candidate.definition - ) - for edge_value: int in TerrainChunkTopology.Edge.values(): - var edge := edge_value as TerrainChunkTopology.Edge - var neighbor_index := _neighbor_indices[index][edge_value] - if neighbor_index < 0: - continue - var placed_neighbor := _placements[neighbor_index] - if placed_neighbor != null: - if _definition_matches_required_neighbor_tags( - candidate.definition, - placed_neighbor.definition, - ): - possible_required_neighbors += 1 - continue - var support_mask := ( - int(domains.get(neighbor_index, 0)) - & _edge_compatibility_masks[candidate_index * 4 + int(edge)] - ) - if ( - candidate.definition.maximum_placements >= 0 - and _definition_placement_count(candidate.definition) + 2 - > candidate.definition.maximum_placements - ): - support_mask &= ~int( - _definition_variant_masks.get( - candidate.definition.stable_id, - 0, - ) - ) - if support_mask == 0: - return false - if (support_mask & required_neighbor_mask) != 0: - possible_required_neighbors += 1 - return ( - possible_required_neighbors - >= candidate.definition.minimum_required_neighbors - ) - - -func _required_neighbor_variant_mask( - definition: TerrainChunkDefinition, -) -> int: - if _required_neighbor_variant_masks.has(definition.stable_id): - return _required_neighbor_variant_masks[definition.stable_id] - var result := 0 - for candidate_index: int in _solver_variants.size(): - if _definition_matches_required_neighbor_tags( - definition, - _solver_variants[candidate_index].definition, - ): - result |= 1 << candidate_index - _required_neighbor_variant_masks[definition.stable_id] = result - return result - - -func _constrain_required_domain_masks( - domains: Dictionary[int, int], -) -> int: - var changed := false - for stable_id: StringName in _missing_required_ids(): - var definition_mask := int(_definition_variant_masks.get(stable_id, 0)) - var supporting_cell := -1 - var supporting_cell_count := 0 - for index: int in domains: - if (domains[index] & definition_mask) == 0: - continue - supporting_cell = index - supporting_cell_count += 1 - if supporting_cell_count > 1: - break - if supporting_cell_count == 0: - return -1 - if supporting_cell_count != 1: - continue - var forced_mask := domains[supporting_cell] & definition_mask - if forced_mask == 0: - return -1 - if forced_mask != domains[supporting_cell]: - domains[supporting_cell] = forced_mask - changed = true - return 1 if changed else 0 - - -func _placed_neighbor_requirements_have_domain_mask_support( - domains: Dictionary[int, int], -) -> bool: - for index: int in _placements.size(): - var placement := _placements[index] - if placement == null or placement.definition.minimum_required_neighbors <= 0: - continue - var placement_variant_index := int( - _solver_variant_indices.get(placement, -1) - ) - if placement_variant_index < 0: - return false - var required_neighbor_mask := _required_neighbor_variant_mask( - placement.definition - ) - var possible_neighbors := 0 - for edge_value: int in TerrainChunkTopology.Edge.values(): - var edge := edge_value as TerrainChunkTopology.Edge - var neighbor_index := _neighbor_indices[index][edge_value] - if neighbor_index < 0: - continue - var neighbor := _placements[neighbor_index] - if neighbor != null: - if _definition_matches_required_neighbor_tags( - placement.definition, - neighbor.definition, - ): - possible_neighbors += 1 - continue - var compatible_required_mask := ( - int(domains.get(neighbor_index, 0)) - & required_neighbor_mask - & _edge_compatibility_masks[ - placement_variant_index * 4 + int(edge) - ] - ) - if compatible_required_mask != 0: - possible_neighbors += 1 - if possible_neighbors < placement.definition.minimum_required_neighbors: - return false - return true - - -func _select_next_large_grid_cell() -> Dictionary: - # Full all-cell arc propagation scales cubically as the map grows. Large - # worlds instead use the same compatibility checks with a frontier-aware MRV - # pass. Exact placement validation and backtracking remain unchanged. - var best_index := -1 - var best_candidates: Array[TerrainChunkVariant] = [] - var best_placed_neighbors := -1 - for index: int in _placements.size(): - if _placements[index] != null: - continue - var candidate_mask := _compatible_candidate_mask(index) - if candidate_mask == 0: - return {"index": index, "candidates": []} - var candidates := _candidates_from_mask(candidate_mask) - var placed_neighbors := _placed_neighbor_count(index) - if ( - best_index < 0 - or candidates.size() < best_candidates.size() - or ( - candidates.size() == best_candidates.size() - and placed_neighbors > best_placed_neighbors - ) - ): - best_index = index - best_candidates = candidates - best_placed_neighbors = placed_neighbors - return {"index": best_index, "candidates": best_candidates} - - -func _select_next_large_grid_cell_unpacked() -> Dictionary: - var best_index := -1 - var best_candidates: Array[TerrainChunkVariant] = [] - var best_placed_neighbors := -1 - for index: int in _placements.size(): - if _placements[index] != null: - continue - var candidates := _compatible_candidates_unpacked(index) - if candidates.is_empty(): - return {"index": index, "candidates": candidates} - var placed_neighbors := _placed_neighbor_count(index) - if ( - best_index < 0 - or candidates.size() < best_candidates.size() - or ( - candidates.size() == best_candidates.size() - and placed_neighbors > best_placed_neighbors - ) - ): - best_index = index - best_candidates = candidates - best_placed_neighbors = placed_neighbors - return {"index": best_index, "candidates": best_candidates} - - -func _placed_neighbor_count(index: int) -> int: - var count := 0 - for neighbor_index: int in _neighbor_indices[index]: - if neighbor_index < 0: - continue - if _placements[neighbor_index] != null: - count += 1 - return count - - func _propagate_domains(domains: Dictionary) -> bool: var changed := true while changed: @@ -986,99 +545,37 @@ func _constrain_required_domains(domains: Dictionary) -> int: func _compatible_candidates(index: int) -> Array[TerrainChunkVariant]: - if not _packed_solver_domains_are_available(): - return _compatible_candidates_unpacked(index) - return _candidates_from_mask(_compatible_candidate_mask(index)) - - -func _compatible_candidates_unpacked( - index: int, -) -> Array[TerrainChunkVariant]: var result: Array[TerrainChunkVariant] = [] var missing_required := _missing_required_ids() - var must_place_missing_required := ( - missing_required.size() >= _unfilled_cells - ) + var remaining_cells := _unfilled_cell_count() + var must_place_missing_required := missing_required.size() >= remaining_cells for candidate: TerrainChunkVariant in _solver_variants: if ( must_place_missing_required and not missing_required.has(candidate.definition.stable_id) ): continue - if _candidate_can_occupy_cell(candidate, index): - result.append(candidate) + if not _candidate_can_occupy_cell(candidate, index): + continue + result.append(candidate) return result -func _packed_solver_domains_are_available() -> bool: - return _solver_variants.size() <= MAX_PACKED_SOLVER_VARIANTS - - -func _compatible_candidate_mask(index: int) -> int: - if index < 0 or index >= _static_cell_candidate_masks.size(): - return 0 - var result := int(_static_cell_candidate_masks[index]) - if _selection_missing_required_count >= _unfilled_cells: - result &= _selection_missing_required_mask - if result == 0: - return 0 - result = _mask_matching_placed_neighbors(result, index) - if result == 0: - return 0 - - for definition: TerrainChunkDefinition in _solver_definitions: - var definition_mask := int( - _definition_variant_masks.get(definition.stable_id, 0) - ) - if (result & definition_mask) == 0: - continue - var definition_variants: Array = _solver_variants_by_definition.get( - definition.stable_id, - [], - ) - if ( - definition_variants.is_empty() - or not _candidate_dynamic_cell_rules_are_satisfied( - definition_variants[0] as TerrainChunkVariant, - index, - ) - ): - result &= ~definition_mask - return result - - -func _mask_matching_placed_neighbors(candidate_mask: int, index: int) -> int: - var result := candidate_mask - for edge_value: int in TerrainChunkTopology.Edge.values(): - var neighbor_index := _neighbor_indices[index][edge_value] - if neighbor_index < 0: - continue - var neighbor := _placements[neighbor_index] - if neighbor == null: - continue - var neighbor_variant_index := int( - _solver_variant_indices.get(neighbor, -1) - ) - if neighbor_variant_index < 0: - return 0 - var neighbor_edge := TerrainChunkTopology.opposite_edge( - edge_value as TerrainChunkTopology.Edge - ) - result &= _edge_compatibility_masks[ - neighbor_variant_index * 4 + int(neighbor_edge) - ] - if result == 0: - return 0 - return result - - -func _candidate_dynamic_cell_rules_are_satisfied( +func _candidate_can_occupy_cell( candidate: TerrainChunkVariant, index: int, ) -> bool: if not _definition_has_capacity(candidate.definition): return false - var center_index := (grid_size.y / 2) * grid_size.x + grid_size.x / 2 + var coordinate := Vector2i(index % grid_size.x, index / grid_size.x) + var center := Vector2i(grid_size.x / 2, grid_size.y / 2) + var center_index := center.y * grid_size.x + center.x + if ( + coordinate == center + and force_center_chunk_id != &"" + and candidate.definition.stable_id != force_center_chunk_id + ): + return false if ( index != center_index and _placements[center_index] == null @@ -1088,366 +585,24 @@ func _candidate_dynamic_cell_rules_are_satisfied( >= candidate.definition.maximum_placements - 1 ): return false - return ( - _candidate_neighbor_requirement_can_still_be_met_at_index( - candidate.definition, - index, - ) - and _candidate_preserves_placed_neighbor_requirements_at_index( - candidate.definition, - index, - ) - ) - - -func _candidate_neighbor_requirement_can_still_be_met_at_index( - definition: TerrainChunkDefinition, - index: int, -) -> bool: - if definition.minimum_required_neighbors <= 0: - return true - var possible_neighbors := 0 - for neighbor_index: int in _neighbor_indices[index]: - if neighbor_index < 0: - continue - var neighbor := _placements[neighbor_index] - if ( - neighbor == null - or _definition_matches_required_neighbor_tags( - definition, - neighbor.definition, - ) - ): - possible_neighbors += 1 - return possible_neighbors >= definition.minimum_required_neighbors - - -func _candidate_preserves_placed_neighbor_requirements_at_index( - candidate_definition: TerrainChunkDefinition, - candidate_index: int, -) -> bool: - for neighbor_index: int in _neighbor_indices[candidate_index]: - if neighbor_index < 0: - continue - var neighbor := _placements[neighbor_index] - if neighbor == null or neighbor.definition.minimum_required_neighbors <= 0: - continue - var possible_neighbors := 0 - for requirement_neighbor_index: int in _neighbor_indices[neighbor_index]: - if requirement_neighbor_index < 0: - continue - if requirement_neighbor_index == candidate_index: - if _definition_matches_required_neighbor_tags( - neighbor.definition, - candidate_definition, - ): - possible_neighbors += 1 - continue - var requirement_neighbor := _placements[requirement_neighbor_index] - if ( - requirement_neighbor == null - or _definition_matches_required_neighbor_tags( - neighbor.definition, - requirement_neighbor.definition, - ) - ): - possible_neighbors += 1 - if possible_neighbors < neighbor.definition.minimum_required_neighbors: - return false - return true - - -func _candidate_can_occupy_cell( - candidate: TerrainChunkVariant, - index: int, -) -> bool: - var coordinate := Vector2i(index % grid_size.x, index / grid_size.x) - if ( - _elevated_feature_reserved_grass_indices.has(index) - and candidate.definition.stable_id != elevated_cliff_base_chunk_id - ): - return false - var center := Vector2i(grid_size.x / 2, grid_size.y / 2) - if ( - coordinate == center - and force_center_chunk_id != &"" - and candidate.definition.stable_id != force_center_chunk_id - ): - return false return ( _variant_respects_ocean_boundary(candidate, coordinate) and _candidate_matches_placed_neighbors(candidate, coordinate) - and _candidate_dynamic_cell_rules_are_satisfied(candidate, index) + and _candidate_neighbor_requirement_can_still_be_met( + candidate.definition, + coordinate, + ) + and _candidate_preserves_placed_neighbor_requirements( + candidate.definition, + coordinate, + ) ) -func _prepare_elevated_feature_region() -> bool: - _elevated_feature_center = Vector2i(-1, -1) - _elevated_feature_reserved_grass_indices.clear() - if not elevated_cliff_feature_enabled: - _build_grid_solver_caches() - return true - for stable_id: StringName in [ - elevated_cliff_base_chunk_id, - elevated_cliff_top_chunk_id, - elevated_cliff_corner_chunk_id, - elevated_cliff_edge_chunk_id, - elevated_cliff_ramp_chunk_id, - ]: - if catalog.definition_for_id(stable_id) == null: - push_error("Elevated cliff feature references missing chunk %s." % stable_id) - return false - if grid_size.x < 11 or grid_size.y < 9: - push_error( - "Elevated cliff feature requires at least an 11x9 terrain grid." - ) - return false - var forced_center := Vector2i(grid_size.x / 2, grid_size.y / 2) - var candidates: Array[Vector2i] = [] - for row: int in range(2, grid_size.y - 2): - for column: int in range(2, grid_size.x - 2): - var candidate := Vector2i(column, row) - if ( - absi(candidate.x - forced_center.x) <= 2 - and absi(candidate.y - forced_center.y) <= 2 - ): - continue - candidates.append(candidate) - if candidates.is_empty(): - push_error("Terrain grid has no inland 3x3 cliff feature location.") - return false - var feature_random := RandomNumberGenerator.new() - feature_random.seed = generation_seed ^ 0x2E1E7A7ED - _elevated_feature_center = candidates[ - feature_random.randi_range(0, candidates.size() - 1) - ] - # Solve the authored 3x3 cliff assembly into a one-cell ring of ordinary - # level-one flat grass. Every raised piece still occupies one of the inner - # nine grass cells; the outer ring prevents a stream, pond, or coast profile - # from being exposed directly against its downhill edge. - for row_offset: int in range(-2, 3): - for column_offset: int in range(-2, 3): - var coordinate := ( - _elevated_feature_center - + Vector2i(column_offset, row_offset) - ) - _elevated_feature_reserved_grass_indices[ - coordinate.y * grid_size.x + coordinate.x - ] = true - _build_grid_solver_caches() - return true - - -func _build_grid_solver_caches() -> void: - _neighbor_indices.clear() - _neighbor_indices.resize(_placements.size()) - _static_cell_candidate_masks.resize(_placements.size()) - _static_cell_candidate_masks.fill(0) - var center := Vector2i(grid_size.x / 2, grid_size.y / 2) - for index: int in _placements.size(): - var coordinate := Vector2i(index % grid_size.x, index / grid_size.x) - var neighbors := PackedInt32Array([-1, -1, -1, -1]) - for edge_value: int in TerrainChunkTopology.Edge.values(): - var neighbor_coordinate := ( - coordinate - + TerrainChunkTopology.grid_offset( - edge_value as TerrainChunkTopology.Edge - ) - ) - if _coordinate_is_inside_grid(neighbor_coordinate): - neighbors[edge_value] = ( - neighbor_coordinate.y * grid_size.x - + neighbor_coordinate.x - ) - _neighbor_indices[index] = neighbors - - if not _packed_solver_domains_are_available(): - continue - var candidate_mask := 0 - for candidate_index: int in _solver_variants.size(): - var candidate := _solver_variants[candidate_index] - if ( - _elevated_feature_reserved_grass_indices.has(index) - and candidate.definition.stable_id - != elevated_cliff_base_chunk_id - ): - continue - if ( - coordinate == center - and force_center_chunk_id != &"" - and candidate.definition.stable_id != force_center_chunk_id - ): - continue - if not _variant_respects_ocean_boundary(candidate, coordinate): - continue - candidate_mask |= 1 << candidate_index - _static_cell_candidate_masks[index] = candidate_mask - - -func _apply_grass_sand_smoothing() -> bool: - if not grass_sand_smoothing_enabled: - return true - var diagonal_definition := catalog.definition_for_id( - smoothing_diagonal_chunk_id - ) - if diagonal_definition == null: - push_error( - "Grass-sand smoothing references missing chunk %s." - % smoothing_diagonal_chunk_id - ) - return false - if diagonal_definition.participates_in_base_solver: - push_error( - "Grass-sand smoothing chunk %s must be excluded from the base solver." - % smoothing_diagonal_chunk_id - ) - return false - - var candidates: Array[Dictionary] = [] - var center := Vector2i(grid_size.x / 2, grid_size.y / 2) - for index: int in _placements.size(): - var placement := _placements[index] - if placement == null or placement.definition.stable_id not in [ - smoothing_grass_chunk_id, - smoothing_sand_chunk_id, - ]: - continue - var coordinate := Vector2i(index % grid_size.x, index / grid_size.x) - if ( - _coordinate_is_on_boundary(coordinate) - or _elevated_feature_reserved_grass_indices.has(index) - or ( - absi(coordinate.x - center.x) - + absi(coordinate.y - center.y) - <= 1 - ) - ): - continue - for quarter_turns: int in 4: - var diagonal := _authored_variant( - diagonal_definition, - quarter_turns, - ) - if ( - diagonal != null - and _candidate_matches_placed_neighbors(diagonal, coordinate) - ): - candidates.append({"index": index, "variant": diagonal}) - break - - var smoothing_random := RandomNumberGenerator.new() - smoothing_random.seed = generation_seed ^ 0xD1A60A1 - for candidate_index: int in range(candidates.size() - 1, 0, -1): - var swap_index := smoothing_random.randi_range(0, candidate_index) - var temporary := candidates[candidate_index] - candidates[candidate_index] = candidates[swap_index] - candidates[swap_index] = temporary - - var previous_placements: Array[TerrainChunkVariant] = [] - previous_placements.assign(_placements) - var replacement_count := 0 - for candidate: Dictionary in candidates: - if replacement_count >= maximum_smoothing_placements: - break - var index := int(candidate["index"]) - var coordinate := Vector2i(index % grid_size.x, index / grid_size.x) - var diagonal := candidate["variant"] as TerrainChunkVariant - if ( - diagonal == null - or not _candidate_matches_placed_neighbors(diagonal, coordinate) - or not _replacement_preserves_required_chunk(_placements[index]) - ): - continue - _set_placement(index, diagonal) - replacement_count += 1 - - var validation_error := _resolved_layout_validation_error(_placements) - if not validation_error.is_empty(): - _assign_placements(previous_placements) - push_error( - "Grass-sand smoothing produced an invalid layout: " - + validation_error - ) - return false - return true - - -func _replacement_preserves_required_chunk( - original: TerrainChunkVariant, -) -> bool: - if original == null: - return false - var stable_id := original.definition.stable_id - if String(stable_id) not in required_chunk_ids: - return true - return _definition_placement_count(original.definition) > 1 - - -func _apply_elevated_feature() -> bool: - if not elevated_cliff_feature_enabled: - return true - if _elevated_feature_center.x < 0 or _elevated_feature_center.y < 0: - push_error("Elevated cliff feature has no reserved base region.") - return false - var edge_specs: Array[Dictionary] = [ - {"offset": Vector2i(0, -1), "turns": 1}, - {"offset": Vector2i(1, 0), "turns": 0}, - {"offset": Vector2i(0, 1), "turns": 3}, - {"offset": Vector2i(-1, 0), "turns": 2}, - ] - var feature_random := RandomNumberGenerator.new() - feature_random.seed = generation_seed ^ 0x51A7C11FF - var ramp_edge := -1 - if feature_random.randf() < elevated_cliff_ramp_chance: - ramp_edge = feature_random.randi_range(0, edge_specs.size() - 1) - var placements: Array[Dictionary] = [ - {"offset": Vector2i(-1, -1), "id": elevated_cliff_corner_chunk_id, "turns": 2}, - {"offset": Vector2i(1, -1), "id": elevated_cliff_corner_chunk_id, "turns": 1}, - {"offset": Vector2i(0, 0), "id": elevated_cliff_top_chunk_id, "turns": 0}, - {"offset": Vector2i(-1, 1), "id": elevated_cliff_corner_chunk_id, "turns": 3}, - {"offset": Vector2i(1, 1), "id": elevated_cliff_corner_chunk_id, "turns": 0}, - ] - for edge_index: int in edge_specs.size(): - var edge_spec := edge_specs[edge_index] - placements.append({ - "offset": edge_spec["offset"], - "id": ( - elevated_cliff_ramp_chunk_id - if edge_index == ramp_edge - else elevated_cliff_edge_chunk_id - ), - "turns": edge_spec["turns"], - }) - for spec: Dictionary in placements: - var definition := catalog.definition_for_id(spec["id"] as StringName) - var variant := _authored_variant(definition, int(spec["turns"])) - if variant == null: - push_error( - "Elevated cliff feature cannot resolve %s rotation %d." - % [spec["id"], spec["turns"]] - ) - return false - var coordinate := _elevated_feature_center + (spec["offset"] as Vector2i) - _set_placement( - coordinate.y * grid_size.x + coordinate.x, - variant, - ) - var validation_error := _resolved_layout_validation_error(_placements) - if not validation_error.is_empty(): - push_error("Elevated cliff feature is invalid: " + validation_error) - return false - return true - - func _variant_respects_ocean_boundary( variant: TerrainChunkVariant, coordinate: Vector2i, ) -> bool: - if ( - variant.definition.must_be_interior - and _coordinate_is_on_boundary(coordinate) - ): - return false var ocean_edges := variant.rotated_edge_mask( variant.definition.ocean_facing_edges ) @@ -1501,6 +656,101 @@ func _coordinate_is_inside_grid(coordinate: Vector2i) -> bool: ) +func _candidate_neighbor_requirement_can_still_be_met( + definition: TerrainChunkDefinition, + coordinate: Vector2i, +) -> bool: + if definition.minimum_required_neighbors <= 0: + return true + var possible_neighbors := 0 + for edge_value: int in TerrainChunkTopology.Edge.values(): + var neighbor_coordinate := ( + coordinate + + TerrainChunkTopology.grid_offset( + edge_value as TerrainChunkTopology.Edge + ) + ) + if not _coordinate_is_inside_grid(neighbor_coordinate): + continue + var neighbor := _placements[ + neighbor_coordinate.y * grid_size.x + neighbor_coordinate.x + ] + if ( + neighbor == null + or _definition_matches_required_neighbor_tags( + definition, + neighbor.definition, + ) + ): + possible_neighbors += 1 + return possible_neighbors >= definition.minimum_required_neighbors + + +func _candidate_preserves_placed_neighbor_requirements( + definition: TerrainChunkDefinition, + coordinate: Vector2i, +) -> bool: + for edge_value: int in TerrainChunkTopology.Edge.values(): + var neighbor_coordinate := ( + coordinate + + TerrainChunkTopology.grid_offset( + edge_value as TerrainChunkTopology.Edge + ) + ) + if not _coordinate_is_inside_grid(neighbor_coordinate): + continue + var neighbor := _placements[ + neighbor_coordinate.y * grid_size.x + neighbor_coordinate.x + ] + if neighbor == null or neighbor.definition.minimum_required_neighbors <= 0: + continue + if not _placed_requirement_can_still_be_met_with_candidate( + neighbor.definition, + neighbor_coordinate, + definition, + coordinate, + ): + return false + return true + + +func _placed_requirement_can_still_be_met_with_candidate( + definition: TerrainChunkDefinition, + coordinate: Vector2i, + candidate_definition: TerrainChunkDefinition, + candidate_coordinate: Vector2i, +) -> bool: + var possible_neighbors := 0 + for edge_value: int in TerrainChunkTopology.Edge.values(): + var neighbor_coordinate := ( + coordinate + + TerrainChunkTopology.grid_offset( + edge_value as TerrainChunkTopology.Edge + ) + ) + if not _coordinate_is_inside_grid(neighbor_coordinate): + continue + if neighbor_coordinate == candidate_coordinate: + if _definition_matches_required_neighbor_tags( + definition, + candidate_definition, + ): + possible_neighbors += 1 + continue + var neighbor := _placements[ + neighbor_coordinate.y * grid_size.x + neighbor_coordinate.x + ] + if ( + neighbor == null + or _definition_matches_required_neighbor_tags( + definition, + neighbor.definition, + ) + ): + possible_neighbors += 1 + return possible_neighbors >= definition.minimum_required_neighbors + + func _definition_has_capacity(definition: TerrainChunkDefinition) -> bool: if definition.maximum_placements < 0: return true @@ -1508,9 +758,11 @@ func _definition_has_capacity(definition: TerrainChunkDefinition) -> bool: func _definition_placement_count(definition: TerrainChunkDefinition) -> int: - if definition == null: - return 0 - return int(_placement_counts.get(definition.stable_id, 0)) + var count := 0 + for placement: TerrainChunkVariant in _placements: + if placement != null and placement.definition == definition: + count += 1 + return count func _edges_are_compatible( @@ -1577,30 +829,11 @@ func _calculate_edge_compatibility( or (first_inlet and second_outlet) ) return ( - _edge_surfaces_are_compatible( - first, - first_edge, - second, - second_edge, - ) - and first.allows_non_water_neighbor_on_edge(second, first_edge) + first.allows_non_water_neighbor_on_edge(second, first_edge) and second.allows_non_water_neighbor_on_edge(first, second_edge) ) -func _edge_surfaces_are_compatible( - first: TerrainChunkVariant, - first_edge: TerrainChunkTopology.Edge, - second: TerrainChunkVariant, - second_edge: TerrainChunkTopology.Edge, -) -> bool: - var first_tags := first.surface_tags(first_edge) - var second_tags := second.surface_tags(second_edge) - if first_tags.is_empty() or second_tags.is_empty(): - return true - return TerrainChunkDefinition.has_any_tag(first_tags, second_tags) - - func _variant_edge_has_connector( variant: TerrainChunkVariant, source_mask: int, @@ -1629,57 +862,35 @@ func _requirements_can_still_be_satisfied() -> bool: func _definition_has_available_cell( definition: TerrainChunkDefinition, ) -> bool: - var definition_variants: Array = _solver_variants_by_definition.get( - definition.stable_id, - [], - ) - if ( - _packed_solver_domains_are_available() - and not definition_variants.is_empty() - ): - var definition_mask := int( - _definition_variant_masks.get(definition.stable_id, 0) - ) - for index: int in _placements.size(): - if _placements[index] != null: - continue - var candidate_mask := ( - int(_static_cell_candidate_masks[index]) & definition_mask - ) - if candidate_mask == 0: - continue - candidate_mask = _mask_matching_placed_neighbors( - candidate_mask, - index, - ) - if ( - candidate_mask != 0 - and _candidate_dynamic_cell_rules_are_satisfied( - definition_variants[0] as TerrainChunkVariant, - index, - ) - ): - return true - return false for index: int in _placements.size(): if _placements[index] != null: continue - for candidate: TerrainChunkVariant in definition_variants: - if _candidate_can_occupy_cell(candidate, index): + for candidate: TerrainChunkVariant in _solver_variants: + if ( + candidate.definition == definition + and _candidate_can_occupy_cell(candidate, index) + ): return true return false func _unfilled_cell_count() -> int: - return _unfilled_cells + var count := 0 + for placement: TerrainChunkVariant in _placements: + if placement == null: + count += 1 + return count func _missing_required_ids() -> Array[StringName]: + var found: Dictionary[StringName, bool] = {} + for placement: TerrainChunkVariant in _placements: + if placement != null: + found[placement.definition.stable_id] = true var result: Array[StringName] = [] - for required_id: StringName in _required_stable_ids: - if ( - int(_placement_counts.get(required_id, 0)) <= 0 - ): + for required_id_value: String in required_chunk_ids: + var required_id := StringName(required_id_value) + if not found.has(required_id) and not result.has(required_id): result.append(required_id) return result @@ -1929,7 +1140,13 @@ func _walkable_connectivity_validation_error( func _required_chunk_is_missing(stable_id: StringName) -> bool: if not required_chunk_ids.has(String(stable_id)): return false - return int(_placement_counts.get(stable_id, 0)) <= 0 + for placement: TerrainChunkVariant in _placements: + if ( + placement != null + and placement.definition.stable_id == stable_id + ): + return false + return true func _resolve_equivalent_rotations() -> void: @@ -2014,7 +1231,7 @@ func _build_solution_root() -> Node3D: if variant == null: continue var coordinate := Vector2i(index % grid_size.x, index / grid_size.x) - var chunk_root := _instantiate_chunk_root(variant.definition) + var chunk_root := variant.definition.packed_scene.instantiate() as Node3D if chunk_root == null: push_error("%s does not instantiate as Node3D." % variant.stable_key()) solution_root.free() @@ -2043,27 +1260,6 @@ func _build_solution_root() -> Node3D: return solution_root -func _instantiate_chunk_root( - definition: TerrainChunkDefinition, -) -> Node3D: - var terrain_visual := definition.packed_scene.instantiate() as Node3D - if terrain_visual == null: - return null - if definition.base_layer_scene == null: - return terrain_visual - var base_layer_visual := definition.base_layer_scene.instantiate() as Node3D - if base_layer_visual == null: - terrain_visual.free() - return null - var layered_root := Node3D.new() - layered_root.name = "LayeredTerrainChunk" - base_layer_visual.name = "TerrainBaseLayer" - terrain_visual.name = "TerrainOverlay" - layered_root.add_child(base_layer_visual) - layered_root.add_child(terrain_visual) - return layered_root - - func _replace_generated_chunks(solution_root: Node3D) -> void: _clear_generated_chunks() _generated_chunks = solution_root @@ -2078,34 +1274,11 @@ func _add_collision( chunk_root, definition.primary_mesh_name, ) - _add_collision_to_mesh( - primary_mesh, - definition.stable_id, - "TerrainCollision", - ) - if definition.base_layer_scene == null: - return - var base_layer_mesh := TerrainChunkAnalyzer.find_primary_mesh( - chunk_root, - definition.base_layer_mesh_name, - ) - _add_collision_to_mesh( - base_layer_mesh, - definition.stable_id, - "TerrainBaseLayerCollision", - ) - - -func _add_collision_to_mesh( - primary_mesh: MeshInstance3D, - stable_id: StringName, - collision_name: String, -) -> void: if primary_mesh == null or primary_mesh.mesh == null: return var terrain_shape := primary_mesh.mesh.create_trimesh_shape() if terrain_shape == null or terrain_shape.get_faces().is_empty(): - push_warning("%s produced no terrain collision." % stable_id) + push_warning("%s produced no terrain collision." % definition.stable_id) return var concave_shape := terrain_shape as ConcavePolygonShape3D if concave_shape != null: @@ -2114,7 +1287,7 @@ func _add_collision_to_mesh( # can never become an invisible collision gap. concave_shape.backface_collision = true var collision_body := StaticBody3D.new() - collision_body.name = collision_name + collision_body.name = "TerrainCollision" collision_body.collision_layer = 1 collision_body.collision_mask = 0 primary_mesh.add_child(collision_body) diff --git a/world/generation/terrain_chunk_variant.gd b/world/generation/terrain_chunk_variant.gd index ca45812..efb590a 100644 --- a/world/generation/terrain_chunk_variant.gd +++ b/world/generation/terrain_chunk_variant.gd @@ -35,10 +35,6 @@ func allows_non_water_neighbor_on_edge( ) -func surface_tags(edge: TerrainChunkTopology.Edge) -> PackedStringArray: - return definition.surface_tags_for_edge(source_edge(edge)) - - func topology_signature(quantization: float) -> String: var tokens := PackedStringArray() for edge_value: int in TerrainChunkTopology.Edge.values(): @@ -48,10 +44,9 @@ func topology_signature(quantization: float) -> String: func constraint_signature(quantization: float) -> String: - return "%s|neighbors:%s|surfaces:%s|ocean:%d|in:%d|out:%d" % [ + return "%s|neighbors:%s|ocean:%d|in:%d|out:%d" % [ topology_signature(quantization), _neighbor_rule_signature(), - _surface_rule_signature(), rotated_edge_mask(definition.ocean_facing_edges), rotated_edge_mask(definition.water_inlet_edges), rotated_edge_mask(definition.water_outlet_edges), @@ -70,16 +65,6 @@ func _neighbor_rule_signature() -> String: return "/".join(edge_tokens) -func _surface_rule_signature() -> String: - var edge_tokens := PackedStringArray() - for edge_value: int in TerrainChunkTopology.Edge.values(): - var edge := edge_value as TerrainChunkTopology.Edge - var edge_tags: PackedStringArray = surface_tags(edge).duplicate() - edge_tags.sort() - edge_tokens.append(",".join(edge_tags)) - return "/".join(edge_tokens) - - func rotated_edge_mask(source_mask: int) -> int: var result := 0 for edge_value: int in TerrainChunkTopology.Edge.values(): diff --git a/world/generation/terrain_prop_catalog.gd b/world/generation/terrain_prop_catalog.gd index e73fbe0..f3375b1 100644 --- a/world/generation/terrain_prop_catalog.gd +++ b/world/generation/terrain_prop_catalog.gd @@ -82,23 +82,4 @@ func validation_errors() -> PackedStringArray: "%s has a negative box collision dimension." % definition.stable_id ) - if definition.minimum_visual_scale > definition.maximum_visual_scale: - errors.append( - "%s has an inverted visual scale range." - % definition.stable_id - ) - if ( - not definition.material_variants.is_empty() - and definition.variant_material_slot_names.is_empty() - ): - errors.append( - "%s has material variants but no target material slots." - % definition.stable_id - ) - for material: Material in definition.material_variants: - if material == null: - errors.append( - "%s contains an empty material variant." - % definition.stable_id - ) return errors diff --git a/world/generation/terrain_prop_definition.gd b/world/generation/terrain_prop_definition.gd index f6e73ae..ca04b21 100644 --- a/world/generation/terrain_prop_definition.gd +++ b/world/generation/terrain_prop_definition.gd @@ -18,15 +18,6 @@ extends Resource @export_range(0.0, 100.0, 0.5) var preferred_nearby_radius := 0.0 @export_range(1.0, 10.0, 0.1) var nearby_preference_weight_multiplier := 1.0 @export_range(0.0, 10.0, 0.05) var clearance_radius := 0.5 -## Procedural instances use a deterministic uniform scale in this range. -@export_range(0.1, 4.0, 0.05) var minimum_visual_scale := 1.0 -@export_range(0.1, 4.0, 0.05) var maximum_visual_scale := 1.0 -## Presentation-only offset. The prop root remains exactly terrain-aligned. -@export var visual_offset := Vector3.ZERO -## A single material is chosen per prop and applied only to matching imported -## material slots. This keeps trunks untouched while varying foliage. -@export var variant_material_slot_names := PackedStringArray() -@export var material_variants: Array[Material] = [] @export_range(0.0, 5.0, 0.05) var collision_radius := 0.0 @export_range(0.0, 20.0, 0.05) var collision_height := 0.0 @export var collision_box_size := Vector3.ZERO diff --git a/world/test_world.gd b/world/test_world.gd index 0d99e45..66c4e92 100644 --- a/world/test_world.gd +++ b/world/test_world.gd @@ -7,13 +7,6 @@ const FishingShopInteractionType = preload( const PlayerStorageInteractionType = preload( "res://world/player_storage_interaction.gd" ) -const WorldLayoutType = preload("res://world/world_layout.gd") -const GENERATED_REGION_SCENE: PackedScene = preload( - "res://world/generation/generated_world_region.tscn" -) -const STARTER_ISLAND_REGION_SCENE: PackedScene = preload( - "res://world/regions/starter_island_region.tscn" -) @onready var _regions_root: Node3D = $Regions @onready var _active_region: WorldRegion = _find_active_region() @@ -23,25 +16,20 @@ const STARTER_ISLAND_REGION_SCENE: PackedScene = preload( @onready var _world_environment: WorldEnvironment = $Environment/WorldEnvironment @onready var _sun: DirectionalLight3D = $Environment/Sun -var _world_layout: StringName = WorldLayoutType.GENERATED -var _world_seed: int = PlayerSaveManager.DEFAULT_WORLD_SEED -var _light_performance_profile: bool = false - func get_player_water_triggers() -> Array[PlayerWaterTrigger]: var triggers: Array[PlayerWaterTrigger] = [] - if _active_region != null: - triggers.append_array(_active_region.get_water_recovery_triggers()) + for region: WorldRegion in _get_regions(): + triggers.append_array(region.get_water_recovery_triggers()) triggers.append(_below_world_failsafe) return triggers func get_safe_respawn_points() -> Array[SafeRespawnPoint]: - return ( - _active_region.get_safe_respawn_points() - if _active_region != null - else [] - ) + var points: Array[SafeRespawnPoint] = [] + for region: WorldRegion in _get_regions(): + points.append_array(region.get_safe_respawn_points()) + return points func get_fishing_shop() -> FishingShopInteractionType: @@ -65,17 +53,14 @@ func get_sun() -> DirectionalLight3D: func set_light_performance_profile(enabled: bool) -> void: - _light_performance_profile = enabled - if _active_region != null: - _active_region.set_light_performance_profile(enabled) + _active_region.set_light_performance_profile(enabled) func get_fishable_water_regions() -> Array[FishableWaterRegion]: - return ( - _active_region.get_fishable_water_regions() - if _active_region != null - else [] - ) + var waters: Array[FishableWaterRegion] = [] + for region: WorldRegion in _get_regions(): + waters.append_array(region.get_fishable_water_regions()) + return waters func get_saltwater_shoreline_mesh() -> MeshInstance3D: @@ -92,44 +77,26 @@ func get_spawn_surface_triangles( ) -func activate_world(layout: StringName, seed: int) -> bool: - if ( - not WorldLayoutType.is_valid(layout) - or seed <= 0 - or seed > PlayerSaveManager.MAX_WORLD_SEED - ): - return false - if _active_region == null or _active_region.region_id != layout: - if not _replace_active_region(layout, seed): - return false - _world_layout = layout - _world_seed = seed - if layout == WorldLayoutType.GENERATED: - if not _active_region.has_method("generate_world"): - return false - if not bool(_active_region.call("generate_world", seed)): - return false - _configure_world_coverage() - return true - - func generate_world(seed: int) -> bool: - return activate_world(WorldLayoutType.GENERATED, seed) - - -func get_world_layout() -> StringName: - return _world_layout + if _active_region == null or not _active_region.has_method("generate_world"): + return false + var generated: bool = bool(_active_region.call("generate_world", seed)) + if generated: + _configure_world_coverage() + return generated func get_generation_seed() -> int: - return _world_seed + if _active_region != null and _active_region.has_method("get_generation_seed"): + return int(_active_region.call("get_generation_seed")) + return PlayerSaveManager.DEFAULT_WORLD_SEED func get_diggable_area_triangles( area_id: StringName, ) -> Array[PackedVector3Array]: - if _active_region != null: - var area: DiggableArea3D = _active_region.get_diggable_area(area_id) + for region: WorldRegion in _get_regions(): + var area: DiggableArea3D = region.get_diggable_area(area_id) if area != null: return area.get_surface_triangles() return [] @@ -138,18 +105,25 @@ func get_diggable_area_triangles( func get_gatherable_spawn_positions( anchor_set_id: StringName, ) -> PackedVector3Array: - if _active_region != null: + for region: WorldRegion in _get_regions(): var anchor_set: GatherableAnchorSet3D = ( - _active_region.get_gatherable_anchor_set(anchor_set_id) + region.get_gatherable_anchor_set(anchor_set_id) ) if anchor_set != null: return anchor_set.get_spawn_positions() return PackedVector3Array() +func _get_regions() -> Array[WorldRegion]: + var regions: Array[WorldRegion] = [] + for child: Node in _regions_root.get_children(): + var region: WorldRegion = child as WorldRegion + if region != null: + regions.append(region) + return regions + + func _ready() -> void: - if _active_region != null: - _world_layout = _active_region.region_id _configure_world_coverage() @@ -161,26 +135,6 @@ func _find_active_region() -> WorldRegion: return null -func _replace_active_region(layout: StringName, seed: int) -> bool: - var region_scene: PackedScene = ( - GENERATED_REGION_SCENE - if layout == WorldLayoutType.GENERATED - else STARTER_ISLAND_REGION_SCENE - ) - var replacement := region_scene.instantiate() as WorldRegion - if replacement == null: - return false - if layout == WorldLayoutType.GENERATED: - replacement.set("initial_seed", seed) - if _active_region != null: - _regions_root.remove_child(_active_region) - _active_region.free() - _active_region = replacement - _regions_root.add_child(_active_region) - _active_region.set_light_performance_profile(_light_performance_profile) - return true - - func _configure_world_coverage() -> void: if _active_region == null: return diff --git a/world/world_layout.gd b/world/world_layout.gd deleted file mode 100644 index 6d27333..0000000 --- a/world/world_layout.gd +++ /dev/null @@ -1,29 +0,0 @@ -class_name WorldLayout -extends RefCounted - -const GENERATED: StringName = &"generated_world" -const STARTER_ISLAND: StringName = &"starter_island" - - -static func is_valid(value: Variant) -> bool: - if typeof(value) not in [TYPE_STRING, TYPE_STRING_NAME]: - return false - var layout := StringName(str(value).strip_edges()) - return layout in [GENERATED, STARTER_ISLAND] - - -static func normalized( - value: Variant, - fallback: StringName = GENERATED, -) -> StringName: - if not is_valid(value): - return fallback - return StringName(str(value).strip_edges()) - - -static func display_name(value: Variant) -> String: - match normalized(value): - STARTER_ISLAND: - return "starter island" - _: - return "generated world" diff --git a/world/world_layout.gd.uid b/world/world_layout.gd.uid deleted file mode 100644 index a9bc549..0000000 --- a/world/world_layout.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bi2fydywih4lv