Compare commits
1 commit
ea99f2b7d7
...
c64f42ad7d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c64f42ad7d |
100
ASSET-LICENSE.md
|
|
@ -1,4 +1,4 @@
|
|||
# NETfishing Asset License 1.1
|
||||
# NETfishing Asset License 1.0
|
||||
|
||||
Copyright © 2026 Woofmeow. All rights reserved except for the permissions
|
||||
expressly granted below.
|
||||
|
|
@ -7,9 +7,8 @@ expressly granted below.
|
|||
|
||||
This license applies to original NETfishing artwork, graphics, textures,
|
||||
models, animations, music, sound recordings, narrative content, visual
|
||||
designs, characters, and other creative assets owned by the NETfishing owners
|
||||
or licensed to them with authority to sublicense, publishing as Woofmeow (the
|
||||
**“NETfishing Assets”**).
|
||||
designs, characters, and other creative assets owned by the NETfishing owners,
|
||||
publishing as Woofmeow (the **“NETfishing Assets”**).
|
||||
|
||||
It does not apply to:
|
||||
|
||||
|
|
@ -36,13 +35,6 @@ NETfishing Software. A Mod may add, replace, or alter content loaded by the
|
|||
Official NETfishing Software, but it may not redistribute a replacement copy
|
||||
of the game or represent a fork, clone, sequel, or separate game.
|
||||
|
||||
**Contribution Fork** means a source-control fork or clone of an official
|
||||
NETfishing source repository that is created and maintained in good faith
|
||||
primarily to prepare, test, review, or submit changes for possible inclusion
|
||||
in Official NETfishing Software. It does not include a fork presented or
|
||||
developed as an independent game, community edition, replacement distribution,
|
||||
or ongoing product separate from the official NETfishing project.
|
||||
|
||||
## 3. Permission to use the official game
|
||||
|
||||
You may reproduce NETfishing Assets as technically necessary to install, run,
|
||||
|
|
@ -72,7 +64,7 @@ A distributed Mod must:
|
|||
3. include the following notice in its documentation or distribution page:
|
||||
|
||||
> Contains or adapts NETfishing assets © 2026 Woofmeow, used under the
|
||||
> NETfishing Asset License 1.1. This Mod is unofficial and is not endorsed
|
||||
> NETfishing Asset License 1.0. This Mod is unofficial and is not endorsed
|
||||
> by Woofmeow.
|
||||
|
||||
4. identify material created by the Mod author separately from NETfishing
|
||||
|
|
@ -85,57 +77,14 @@ charging for access to it, licensing it commercially, or using NETfishing
|
|||
Assets in paid products requires separate written permission from both
|
||||
NETfishing owners.
|
||||
|
||||
## 5. Contribution Fork license
|
||||
## 5. Uses not permitted
|
||||
|
||||
Subject to this license, you may reproduce, modify, and distribute NETfishing
|
||||
Assets within a Contribution Fork. You may also make the nonpublic builds
|
||||
reasonably necessary to develop, test, and review its proposed contribution.
|
||||
Except with separate written permission from both NETfishing owners, you may
|
||||
not use or distribute NETfishing Assets:
|
||||
|
||||
This permission is worldwide, nonexclusive, royalty-free, and available for
|
||||
as long as you comply with this license. Public source-control hosting of the
|
||||
Contribution Fork and the repository history technically necessary for that
|
||||
fork are permitted distributions.
|
||||
|
||||
A Contribution Fork must:
|
||||
|
||||
1. remain primarily directed toward preparing changes for possible inclusion
|
||||
in the official NETfishing project;
|
||||
2. preserve this license and the official NETfishing project's applicable
|
||||
attribution, third-party license, and copyright notices;
|
||||
3. remain visibly linked to or identified as a fork of the official source
|
||||
repository and not be presented as official or endorsed;
|
||||
4. limit test-build access to the owners, maintainers, contributors, and
|
||||
testers directly participating in development or review of the proposed
|
||||
contribution;
|
||||
5. not publish general-audience binaries, releases, packages, or downloads,
|
||||
operate a persistent public alternative service, or monetize access;
|
||||
6. not distribute the NETfishing Assets as a standalone extraction, archive,
|
||||
or general-purpose asset pack; and
|
||||
7. comply with `TRADEMARKS.md`.
|
||||
|
||||
Temporary access-controlled servers may be operated when reasonably necessary
|
||||
to test a proposed contribution. They may not be promoted or maintained as a
|
||||
public alternative to Official NETfishing Software or its services.
|
||||
|
||||
Submitting work from a Contribution Fork is governed separately by
|
||||
`CONTRIBUTOR-TERMS.md` and does not require the NETfishing owners to accept the
|
||||
work. Declining or closing a proposed contribution does not retroactively
|
||||
invalidate good-faith use under this section. A compliant Contribution Fork
|
||||
may remain publicly accessible as an archival development record, but it may
|
||||
not be repurposed for continued development or distribution of a separate
|
||||
product containing NETfishing Assets.
|
||||
|
||||
## 6. Uses not permitted
|
||||
|
||||
Except as expressly permitted by Sections 3, 4, and 5 or by separate written
|
||||
permission from both NETfishing owners, you may not use or distribute
|
||||
NETfishing Assets:
|
||||
|
||||
- in a fork, clone, remake, sequel, or standalone game that is not a qualifying
|
||||
Contribution Fork;
|
||||
- in a fork, clone, remake, sequel, or standalone game;
|
||||
- with a modified distribution of the GPL-covered NETfishing code that is not
|
||||
Official NETfishing Software, except for nonpublic contribution-testing
|
||||
builds permitted by Section 5;
|
||||
Official NETfishing Software;
|
||||
- in another game, application, asset library, template, or merchandise;
|
||||
- as a substantially complete asset extraction or archive;
|
||||
- in a way that implies sponsorship, endorsement, or official status;
|
||||
|
|
@ -143,25 +92,22 @@ NETfishing Assets:
|
|||
identity; or
|
||||
- under terms that claim ownership of the NETfishing Assets themselves.
|
||||
|
||||
The GPL permits forks and redistribution of the GPL-covered code. Except for a
|
||||
qualifying Contribution Fork under Section 5, this asset license does not
|
||||
permit those forks to carry NETfishing Assets. A code fork that is not a
|
||||
Contribution Fork must remove or replace the NETfishing Assets and reserved
|
||||
branding unless both owners grant separate written permission.
|
||||
The GPL permits forks and redistribution of the GPL-covered code. This asset
|
||||
license does not permit those forks to carry NETfishing Assets. A code fork
|
||||
must remove or replace the NETfishing Assets and reserved branding unless both
|
||||
owners grant separate written permission.
|
||||
|
||||
## 7. Ownership of Mods and contribution work
|
||||
## 6. Ownership of Mods
|
||||
|
||||
You retain ownership of your original Mod material and original material
|
||||
created in a Contribution Fork. The NETfishing owners retain ownership of the
|
||||
NETfishing Assets and adaptations of those assets to the extent provided by
|
||||
applicable law.
|
||||
You retain ownership of your original Mod material. The NETfishing owners
|
||||
retain ownership of the NETfishing Assets and adaptations of those assets to
|
||||
the extent provided by applicable law.
|
||||
|
||||
This license does not require you to assign or submit your original material.
|
||||
It also does not grant you ownership of the underlying NETfishing Assets. If
|
||||
you intentionally submit material for inclusion in NETfishing,
|
||||
`CONTRIBUTOR-TERMS.md` applies separately.
|
||||
This license does not require you to assign your original Mod material to the
|
||||
NETfishing owners. It also does not grant you ownership of the underlying
|
||||
NETfishing Assets.
|
||||
|
||||
## 8. Termination and cure
|
||||
## 7. Termination and cure
|
||||
|
||||
Your permissions under this license terminate automatically if you violate
|
||||
its terms. They are reinstated provisionally when you stop the violation and
|
||||
|
|
@ -171,7 +117,7 @@ within sixty days after it has been cured.
|
|||
Termination does not limit remedies available for activity outside this
|
||||
license.
|
||||
|
||||
## 9. No warranty
|
||||
## 8. No warranty
|
||||
|
||||
THE NETFISHING ASSETS ARE PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
|
|
@ -179,7 +125,7 @@ PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT. TO THE MAXIMUM EXTENT
|
|||
PERMITTED BY LAW, THE OWNERS WILL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR
|
||||
OTHER LIABILITY ARISING FROM THIS LICENSE OR USE OF THE NETFISHING ASSETS.
|
||||
|
||||
## 10. Additional permission
|
||||
## 9. Additional permission
|
||||
|
||||
Uses not granted here require written permission from both NETfishing owners.
|
||||
A permission granted to one person or project does not amend this license for
|
||||
|
|
|
|||
|
|
@ -10,21 +10,6 @@ applicable grants and representations in
|
|||
affirmative record of that agreement. Contact the owners before submitting if
|
||||
separate written terms are needed.
|
||||
|
||||
## Contribution forks
|
||||
|
||||
The [NETfishing Asset License](ASSET-LICENSE.md) permits a public source-control
|
||||
fork to retain NETfishing assets when the fork is maintained in good faith to
|
||||
prepare, test, review, or submit changes for possible inclusion in the official
|
||||
project. Keep the hosting platform's fork relationship visible, preserve all
|
||||
license and attribution notices, and do not publish general-audience builds or
|
||||
use the fork as an independent game or distribution.
|
||||
|
||||
Private test builds and temporary access-controlled test servers may be shared
|
||||
with people directly participating in development or review of the proposed
|
||||
contribution. Submission remains subject to `CONTRIBUTOR-TERMS.md`; permission
|
||||
to maintain a Contribution Fork does not guarantee that its changes will be
|
||||
accepted.
|
||||
|
||||
## Change discipline
|
||||
|
||||
- Keep a change focused; avoid unrelated cleanup.
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ world and UI authoring, and testing are consolidated in
|
|||
[GPL-3.0-or-later](LICENSE).
|
||||
- Project-owned artwork, models, music, and other creative assets are covered
|
||||
by the [NETfishing Asset License](ASSET-LICENSE.md). It permits community
|
||||
Mods for official NETfishing software and narrowly scoped contribution forks,
|
||||
but not independent forks, clones, or unrelated products.
|
||||
Mods for official NETfishing software, but not use in forks, clones, or
|
||||
unrelated products.
|
||||
- The NETfishing and Woofmeow names and branding are reserved as described in
|
||||
[TRADEMARKS.md](TRADEMARKS.md).
|
||||
- Third-party materials retain their own terms; see the consolidated
|
||||
|
|
|
|||
|
|
@ -24,24 +24,10 @@ An unofficial Mod may use wording such as “Example Mod for NETfishing” if it
|
|||
name and presentation remain clearly distinct and it displays an unofficial
|
||||
notice.
|
||||
|
||||
## Contribution forks
|
||||
|
||||
A qualifying Contribution Fork under `ASSET-LICENSE.md` may retain the
|
||||
NETfishing and Woofmeow names, logos, icons, and trade dress already present in
|
||||
the official source repository solely as necessary to reproduce, modify, and
|
||||
test the Project while preparing a proposed contribution. The source-control
|
||||
host's visible fork relationship or an equivalent repository notice must make
|
||||
its unofficial contribution purpose clear.
|
||||
|
||||
This permission does not allow a Contribution Fork to use NETfishing or
|
||||
Woofmeow branding to advertise a public build, release, service, community
|
||||
edition, or independent product. It does not imply acceptance, sponsorship,
|
||||
endorsement, or official status.
|
||||
|
||||
## Other forks and redistributed code
|
||||
## Forks and redistributed code
|
||||
|
||||
The GNU GPL permits forks and redistribution of covered code. A fork or
|
||||
modified distribution that is not a qualifying Contribution Fork must:
|
||||
modified distribution must:
|
||||
|
||||
- use a distinct product name;
|
||||
- remove NETfishing and Woofmeow logos, icons, and official trade dress;
|
||||
|
|
|
|||
|
|
@ -53,12 +53,11 @@ alone does not relicense third-party material.
|
|||
The GPL permits study, modification, redistribution, and forks of covered
|
||||
code. It does not grant rights to NETfishing creative assets or branding. The
|
||||
NETfishing Asset License separately permits qualifying noncommercial add-on
|
||||
Mods for official NETfishing software and narrowly scoped Contribution Forks
|
||||
used to prepare changes for possible upstream inclusion. It does not permit
|
||||
those assets to accompany independent code forks, clones, standalone games,
|
||||
unrelated products, or general-purpose asset packs. A GPL fork that is not a
|
||||
qualifying Contribution Fork must replace NETfishing assets and reserved
|
||||
branding with independently licensed material and a distinct identity.
|
||||
Mods for official NETfishing software; it does not permit those assets to
|
||||
accompany code forks, clones, standalone games, unrelated products, or
|
||||
general-purpose asset packs. A GPL fork must replace NETfishing assets and
|
||||
reserved branding with independently licensed material and a distinct
|
||||
identity.
|
||||
|
||||
Project-wide code, asset, branding, or contributor-license changes require
|
||||
written approval from both NETfishing owners under their private joint
|
||||
|
|
|
|||
92
main/main.gd
|
|
@ -409,12 +409,6 @@ func _start_dedicated_server() -> void:
|
|||
):
|
||||
_fail_dedicated_server("The server operator list is invalid.")
|
||||
return
|
||||
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_seed(config.world_seed):
|
||||
_fail_dedicated_server("The configured world seed is invalid.")
|
||||
return
|
||||
_configure_portable_stores()
|
||||
if not _discovery.configure_dedicated_runtime(config.server_name):
|
||||
_fail_dedicated_server("The server room name is invalid.")
|
||||
|
|
@ -1672,24 +1666,14 @@ func _resize_native_overlays() -> void:
|
|||
_shop_backdrop.size = Vector2(get_window().size)
|
||||
|
||||
|
||||
func _on_new_game_requested(world_seed: int) -> void:
|
||||
func _on_new_game_requested() -> void:
|
||||
if _gameplay_started or _quit_in_progress:
|
||||
return
|
||||
if not _apply_generated_world(world_seed, true):
|
||||
_game_ui.get_title_screen().report_network_error(
|
||||
"Could not generate that world seed. Try rolling another world."
|
||||
)
|
||||
return
|
||||
if not _prepare_host_world_seed(world_seed):
|
||||
_game_ui.get_title_screen().report_network_error(
|
||||
"Could not prepare the generated world for hosting."
|
||||
)
|
||||
return
|
||||
if not _prepare_private_host():
|
||||
return
|
||||
if (
|
||||
not _save_manager.delete_progression_save()
|
||||
or not _save_manager.initialize_new_game(world_seed)
|
||||
or not _save_manager.initialize_new_game()
|
||||
):
|
||||
_network_session.disconnect_session("New Game setup failed.")
|
||||
_game_ui.get_title_screen().report_network_error(
|
||||
|
|
@ -1702,22 +1686,14 @@ func _on_new_game_requested(world_seed: int) -> void:
|
|||
func _on_continue_game_requested() -> void:
|
||||
if _gameplay_started or _quit_in_progress:
|
||||
return
|
||||
if not _prepare_private_host():
|
||||
return
|
||||
if not _save_manager.load_player_data():
|
||||
_network_session.disconnect_session("Continue failed.")
|
||||
_game_ui.get_title_screen().report_network_error(
|
||||
"Failed to load save. The original was preserved."
|
||||
)
|
||||
return
|
||||
var world_seed: int = _save_manager.get_world_seed()
|
||||
if (
|
||||
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 generated. The save was preserved."
|
||||
)
|
||||
return
|
||||
if not _prepare_private_host():
|
||||
return
|
||||
_enter_gameplay()
|
||||
|
||||
|
||||
|
|
@ -1740,15 +1716,6 @@ func _prepare_private_host() -> bool:
|
|||
return true
|
||||
|
||||
|
||||
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_seed(world_seed)
|
||||
|
||||
|
||||
func _enter_gameplay() -> void:
|
||||
_fade_out_title_music()
|
||||
_game_ui.get_title_screen().hide()
|
||||
|
|
@ -1838,14 +1805,10 @@ func _on_network_join_authenticated() -> void:
|
|||
)
|
||||
_join_requested_from_title = false
|
||||
return
|
||||
if not _apply_joined_world(server_metadata):
|
||||
return
|
||||
_fade_out_title_music()
|
||||
_game_ui.get_title_screen().hide()
|
||||
_set_gameplay_active(true)
|
||||
elif _join_requested_from_pause:
|
||||
if not _apply_joined_world(server_metadata):
|
||||
return
|
||||
_set_gameplay_active(true)
|
||||
_join_requested_from_title = false
|
||||
_join_requested_from_pause = false
|
||||
|
|
@ -1949,12 +1912,7 @@ func _on_reset_progress_requested() -> void:
|
|||
if not _save_manager.delete_progression_save():
|
||||
pause_menu.report_reset_failure()
|
||||
return
|
||||
var world_seed: int = PlayerSaveManager.roll_world_seed()
|
||||
if not _save_manager.initialize_new_game(world_seed):
|
||||
pause_menu.report_reset_failure()
|
||||
return
|
||||
_network_session.disconnect_session("Progress reset.")
|
||||
if not _apply_generated_world(world_seed, true):
|
||||
if not _save_manager.initialize_new_game():
|
||||
pause_menu.report_reset_failure()
|
||||
return
|
||||
pause_menu.close_for_title_transition()
|
||||
|
|
@ -1963,44 +1921,6 @@ func _on_reset_progress_requested() -> void:
|
|||
_show_title_music(true)
|
||||
|
||||
|
||||
func _apply_joined_world(server_metadata: Dictionary) -> bool:
|
||||
var world_seed: int = int(server_metadata.get("world_seed", 0))
|
||||
if _apply_generated_world(world_seed, true):
|
||||
return true
|
||||
_network_session.disconnect_session("World generation failed.")
|
||||
_join_requested_from_title = false
|
||||
_join_requested_from_pause = false
|
||||
_set_gameplay_active(false)
|
||||
var title_screen: TitleScreenType = _game_ui.get_title_screen()
|
||||
title_screen.reopen_to_menu()
|
||||
title_screen.report_network_error(
|
||||
"The host's generated world could not be built locally."
|
||||
)
|
||||
_show_title_music(true)
|
||||
return false
|
||||
|
||||
|
||||
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 and not _dedicated_runtime:
|
||||
_water_recovery.update_world_context(
|
||||
spawn_transform,
|
||||
_test_world.get_player_water_triggers(),
|
||||
_test_world.get_safe_respawn_points(),
|
||||
)
|
||||
_shoreline_ambience.configure(
|
||||
_player,
|
||||
_test_world.get_saltwater_shoreline_mesh(),
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func _on_water_recovery_starting() -> void:
|
||||
_local_recovery_attempt_id = (
|
||||
"recovery:%s"
|
||||
|
|
|
|||
|
|
@ -298,11 +298,48 @@ unique_name_in_owner = true
|
|||
script = ExtResource("36_player_list")
|
||||
|
||||
[node name="TestWorld" parent="." unique_id=1334932296 instance=ExtResource("1_world")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4475808, 0, -1.8959346)
|
||||
|
||||
[node name="Sun" parent="TestWorld/Environment" index="1"]
|
||||
transform = Transform3D(0.8480481, 0.4287137, -0.31147876, 0, 0.5877853, 0.809017, 0.52991927, -0.68608534, 0.49847022, 0, 0, 0)
|
||||
|
||||
[node name="Shape" parent="TestWorld/Regions/StarterIslandRegion/Terrain/Collision" index="0" unique_id=674742331]
|
||||
shape = SubResource("ConcavePolygonShape3D_hifdc")
|
||||
|
||||
[node name="Pond" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies" index="0" unique_id=995477450]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.4, 2.51, 2.1)
|
||||
|
||||
[node name="VisualWater" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond" index="0" unique_id=1034265960]
|
||||
mesh = SubResource("PlaneMesh_7ny4s")
|
||||
|
||||
[node name="Shape" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond/FishingRegion" index="0" unique_id=1185360438]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2, 0)
|
||||
shape = SubResource("BoxShape3D_agj3o")
|
||||
|
||||
[node name="Shape" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond/RecoveryRegion" index="0" unique_id=1718808476]
|
||||
shape = SubResource("BoxShape3D_l6210")
|
||||
|
||||
[node name="VisualWater" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean" index="0" unique_id=1176408937]
|
||||
mesh = SubResource("PlaneMesh_6upbg")
|
||||
|
||||
[node name="Shape" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/FishingRegions/OceanFishingRegion" index="0" unique_id=357406062]
|
||||
shape = SubResource("BoxShape3D_umo15")
|
||||
|
||||
[node name="WestShape" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/RecoveryRegions/OceanRecoveryRegion" index="0" unique_id=1183368645]
|
||||
shape = SubResource("BoxShape3D_1fdkm")
|
||||
|
||||
[node name="EastShape" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/RecoveryRegions/OceanRecoveryRegion" index="1" unique_id=1742419808]
|
||||
shape = SubResource("BoxShape3D_y2c0j")
|
||||
|
||||
[node name="NorthShape" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/RecoveryRegions/OceanRecoveryRegion" index="2" unique_id=1844920260]
|
||||
shape = SubResource("BoxShape3D_uwwqy")
|
||||
|
||||
[node name="SouthShape" parent="TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/RecoveryRegions/OceanRecoveryRegion" index="3" unique_id=1316495770]
|
||||
shape = SubResource("BoxShape3D_hf28p")
|
||||
|
||||
[node name="FishingShopWorld" parent="TestWorld/Regions/StarterIslandRegion/Interactables" index="0" unique_id=1255373524]
|
||||
transform = Transform3D(-0.9976046, 0, -0.06917416, 0, 1, 0, 0.06917416, 0, -0.9976046, 7.3292284, 3.4012775, 14.888193)
|
||||
|
||||
[node name="BelowWorldFailsafe" parent="TestWorld/Safety" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, -7, 0)
|
||||
|
||||
|
|
@ -399,3 +436,4 @@ unique_name_in_owner = true
|
|||
unique_name_in_owner = true
|
||||
|
||||
[editable path="TestWorld"]
|
||||
[editable path="TestWorld/Regions/StarterIslandRegion"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class_name NetworkProtocol
|
||||
extends RefCounted
|
||||
|
||||
const PROTOCOL_VERSION: int = 9
|
||||
const PROTOCOL_VERSION: int = 8
|
||||
const GAME_BUILD: String = "prealpha"
|
||||
const MAX_GAME_VERSION_LENGTH: int = 64
|
||||
const MAX_DISPLAY_NAME_LENGTH: int = 24
|
||||
|
|
@ -28,9 +28,6 @@ const FISH_QUALITY_CAPABILITY: String = "fish_quality_v1"
|
|||
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 DEFAULT_WORLD_SEED: int = 13001
|
||||
const MAX_WORLD_SEED: int = 2147483646
|
||||
|
||||
enum RejectionCode {
|
||||
NONE,
|
||||
|
|
@ -181,7 +178,6 @@ static func make_client_hello(
|
|||
WORLD_SPAWN_CAPABILITY,
|
||||
APPEARANCE_PREVIEW_CAPABILITY,
|
||||
BACKPACK_SHOP_CAPABILITY,
|
||||
WORLD_GENERATION_CAPABILITY,
|
||||
]),
|
||||
"cosmetic_snapshot": cosmetic_snapshot,
|
||||
"identity_fingerprint": identity_fingerprint,
|
||||
|
|
@ -285,7 +281,6 @@ static func make_server_hello(
|
|||
player_count: int,
|
||||
max_players: int,
|
||||
server_display_name: String = "NETfishing",
|
||||
world_seed: int = DEFAULT_WORLD_SEED,
|
||||
) -> Dictionary:
|
||||
return {
|
||||
"accepted": accepted,
|
||||
|
|
@ -297,7 +292,6 @@ static func make_server_hello(
|
|||
"server_display_name": server_display_name,
|
||||
"player_count": player_count,
|
||||
"max_players": max_players,
|
||||
"world_seed": world_seed,
|
||||
"capability_flags": PackedStringArray([
|
||||
"movement_v1",
|
||||
"fishing_v1",
|
||||
|
|
@ -315,7 +309,6 @@ static func make_server_hello(
|
|||
JOBS_CAPABILITY,
|
||||
WORLD_SPAWN_CAPABILITY,
|
||||
APPEARANCE_PREVIEW_CAPABILITY,
|
||||
WORLD_GENERATION_CAPABILITY,
|
||||
"chat_v1",
|
||||
"mail_v1",
|
||||
"profile_v1",
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ var _last_server_max_players: int = DEFAULT_SESSION_MAX_PLAYERS
|
|||
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 _server_capabilities: PackedStringArray = PackedStringArray()
|
||||
var _profile_ready: bool = false
|
||||
var _player_identity: PlayerIdentityStore
|
||||
|
|
@ -112,7 +111,6 @@ var _configured_operator_fingerprints: Dictionary[String, bool] = {}
|
|||
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
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -274,7 +272,6 @@ func _register_player_host() -> void:
|
|||
NetworkProtocol.JOBS_CAPABILITY,
|
||||
NetworkProtocol.WORLD_SPAWN_CAPABILITY,
|
||||
NetworkProtocol.BACKPACK_SHOP_CAPABILITY,
|
||||
NetworkProtocol.WORLD_GENERATION_CAPABILITY,
|
||||
]),
|
||||
)
|
||||
_registry.update_appearance(1, _local_appearance_snapshot)
|
||||
|
|
@ -530,25 +527,9 @@ func get_last_server_metadata() -> Dictionary:
|
|||
"protocol_version": _last_server_protocol_version,
|
||||
"player_count": _last_server_player_count,
|
||||
"max_players": _last_server_max_players,
|
||||
"world_seed": _last_server_world_seed,
|
||||
}
|
||||
|
||||
|
||||
func set_host_world_seed(seed: int) -> bool:
|
||||
if (
|
||||
state != State.INACTIVE
|
||||
or seed <= 0
|
||||
or seed > NetworkProtocol.MAX_WORLD_SEED
|
||||
):
|
||||
return false
|
||||
_host_world_seed = seed
|
||||
return true
|
||||
|
||||
|
||||
func get_authoritative_world_seed() -> int:
|
||||
return _last_server_world_seed if is_joined_client() else _host_world_seed
|
||||
|
||||
|
||||
func can_use_host_gameplay() -> bool:
|
||||
return is_host()
|
||||
|
||||
|
|
@ -582,7 +563,6 @@ func supports_server_capability(capability: StringName) -> bool:
|
|||
NetworkProtocol.WORLD_WEATHER_CAPABILITY,
|
||||
NetworkProtocol.WORLD_SPAWN_CAPABILITY,
|
||||
NetworkProtocol.APPEARANCE_PREVIEW_CAPABILITY,
|
||||
NetworkProtocol.WORLD_GENERATION_CAPABILITY,
|
||||
"chat_v1",
|
||||
"mail_v1",
|
||||
"profile_v1",
|
||||
|
|
@ -1184,12 +1164,6 @@ func submit_client_hello(data: Dictionary) -> void:
|
|||
NetworkProtocol.RejectionCode.UNSUPPORTED_CLIENT,
|
||||
)
|
||||
return
|
||||
if NetworkProtocol.WORLD_GENERATION_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)
|
||||
|
|
@ -1277,7 +1251,6 @@ func submit_client_hello(data: Dictionary) -> void:
|
|||
_registry.size(),
|
||||
session_max_players,
|
||||
_session_display_name,
|
||||
_host_world_seed,
|
||||
)
|
||||
)
|
||||
receive_spawn_list.rpc_id(sender_id, _build_spawn_list())
|
||||
|
|
@ -1331,7 +1304,6 @@ func receive_server_hello(data: Dictionary) -> void:
|
|||
or typeof(data.get("protocol_version")) != TYPE_INT
|
||||
or typeof(data.get("game_version")) != TYPE_STRING
|
||||
or typeof(data.get("rejection_code")) != TYPE_INT
|
||||
or typeof(data.get("world_seed")) != TYPE_INT
|
||||
):
|
||||
_teardown_peer()
|
||||
_fail("The server sent an invalid handshake response.")
|
||||
|
|
@ -1357,11 +1329,6 @@ func receive_server_hello(data: Dictionary) -> void:
|
|||
_teardown_peer()
|
||||
_fail("The server uses a different network protocol.")
|
||||
return
|
||||
var received_world_seed := int(data["world_seed"])
|
||||
if received_world_seed <= 0 or received_world_seed > NetworkProtocol.MAX_WORLD_SEED:
|
||||
_teardown_peer()
|
||||
_fail("The server sent an invalid world seed.")
|
||||
return
|
||||
_session_id = str(data.get("session_id", ""))
|
||||
_last_server_max_players = int(data.get(
|
||||
"max_players",
|
||||
|
|
@ -1372,7 +1339,6 @@ func receive_server_hello(data: Dictionary) -> void:
|
|||
_last_server_protocol_version = int(data.get(
|
||||
"protocol_version", NetworkProtocol.PROTOCOL_VERSION
|
||||
))
|
||||
_last_server_world_seed = received_world_seed
|
||||
_server_capabilities = PackedStringArray()
|
||||
var advertised_capabilities: Variant = data.get(
|
||||
"capability_flags", PackedStringArray()
|
||||
|
|
@ -1387,10 +1353,6 @@ func receive_server_hello(data: Dictionary) -> void:
|
|||
_teardown_peer()
|
||||
_fail("This server does not support fish quality data.")
|
||||
return
|
||||
if NetworkProtocol.WORLD_GENERATION_CAPABILITY not in _server_capabilities:
|
||||
_teardown_peer()
|
||||
_fail("This server does not support generated worlds.")
|
||||
return
|
||||
var local_peer_id: int = multiplayer.get_unique_id()
|
||||
_registry.clear()
|
||||
_registry.add_peer(
|
||||
|
|
@ -1406,7 +1368,6 @@ func receive_server_hello(data: Dictionary) -> void:
|
|||
NetworkProtocol.WORLD_TIME_CAPABILITY,
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY,
|
||||
NetworkProtocol.BACKPACK_SHOP_CAPABILITY,
|
||||
NetworkProtocol.WORLD_GENERATION_CAPABILITY,
|
||||
]),
|
||||
)
|
||||
_registry.update_appearance(local_peer_id, _local_appearance_snapshot)
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ func setup(
|
|||
_spawn_transform = spawn_transform
|
||||
|
||||
|
||||
func set_spawn_transform(spawn_transform: Transform3D) -> void:
|
||||
_spawn_transform = spawn_transform
|
||||
|
||||
|
||||
func register_local_player(peer_id: int) -> void:
|
||||
if _local_player == null:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -33,11 +33,9 @@ const WorldWeatherServiceType = preload(
|
|||
)
|
||||
const PlayerJobServiceType = preload("res://jobs/player_job_service.gd")
|
||||
|
||||
const SAVE_VERSION: int = 9
|
||||
const SAVE_VERSION: int = 8
|
||||
const BASIC_ROD_ID: StringName = &"basic_fishing_rod"
|
||||
const MAX_SAFE_BALANCE: int = 1000000000000
|
||||
const DEFAULT_WORLD_SEED: int = 13001
|
||||
const MAX_WORLD_SEED: int = 2147483646
|
||||
|
||||
class LoadSnapshot:
|
||||
extends RefCounted
|
||||
|
|
@ -58,7 +56,6 @@ class LoadSnapshot:
|
|||
var cooler_capacity_level: int = 0
|
||||
var art_unlock_mask: int = 0
|
||||
var total_experience: int = 0
|
||||
var world_seed: int = DEFAULT_WORLD_SEED
|
||||
var world_time_hours: float = WorldTimeServiceType.DEFAULT_START_HOUR
|
||||
var has_world_weather_state: bool = false
|
||||
var world_weather: WorldWeatherServiceType.Weather = (
|
||||
|
|
@ -96,7 +93,6 @@ var _autosave_enabled: bool = false
|
|||
var _save_path := ""
|
||||
var _expected_hash := ""
|
||||
var _data_root: PlayerDataRoot
|
||||
var _world_seed: int = DEFAULT_WORLD_SEED
|
||||
|
||||
|
||||
func configure_storage(path: String, data_root: PlayerDataRoot) -> void:
|
||||
|
|
@ -302,7 +298,6 @@ func load_player_data() -> bool:
|
|||
var world_time_restored: bool = (
|
||||
_world_time.restore_persistent_time_hours(snapshot.world_time_hours)
|
||||
)
|
||||
_world_seed = snapshot.world_seed
|
||||
var world_weather_restored: bool = true
|
||||
if snapshot.has_world_weather_state:
|
||||
world_weather_restored = _world_weather.restore_persistent_state(
|
||||
|
|
@ -408,28 +403,14 @@ func inspect_save() -> SaveInspectionType:
|
|||
return result
|
||||
|
||||
|
||||
func initialize_new_game(world_seed: int = DEFAULT_WORLD_SEED) -> bool:
|
||||
func initialize_new_game() -> bool:
|
||||
if not _is_configured:
|
||||
return false
|
||||
if world_seed <= 0 or world_seed > MAX_WORLD_SEED:
|
||||
return false
|
||||
_automatic_saving_blocked = false
|
||||
_restore_defaults()
|
||||
_world_seed = world_seed
|
||||
_is_dirty = true
|
||||
return true
|
||||
|
||||
|
||||
func get_world_seed() -> int:
|
||||
return _world_seed
|
||||
|
||||
|
||||
static func roll_world_seed() -> int:
|
||||
var random := RandomNumberGenerator.new()
|
||||
random.randomize()
|
||||
return random.randi_range(1, MAX_WORLD_SEED)
|
||||
|
||||
|
||||
func delete_progression_save() -> bool:
|
||||
if FileAccess.file_exists(_save_path) and not _remove_if_present(_save_path):
|
||||
return false
|
||||
|
|
@ -603,7 +584,6 @@ func _build_save_dictionary() -> Dictionary:
|
|||
"art": _art_unlocks.to_save_data(),
|
||||
"experience": _experience.to_save_data(),
|
||||
"world": {
|
||||
"seed": _world_seed,
|
||||
"time_hours": _world_time.get_persistent_time_hours(),
|
||||
"weather": int(_world_weather.get_persistent_weather()),
|
||||
"weather_seconds_remaining": (
|
||||
|
|
@ -684,14 +664,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
|
|||
return null
|
||||
|
||||
var snapshot := LoadSnapshot.new()
|
||||
if world_data.has("seed"):
|
||||
snapshot.world_seed = _read_integer(
|
||||
world_data["seed"],
|
||||
-1,
|
||||
MAX_WORLD_SEED,
|
||||
)
|
||||
if snapshot.world_seed <= 0:
|
||||
return null
|
||||
snapshot.inventory_layout_data = inventory_layout_data.duplicate(true)
|
||||
snapshot.wallet_balance = balance
|
||||
snapshot.total_experience = _read_integer(
|
||||
|
|
@ -993,8 +965,6 @@ func _migrate_save(
|
|||
migrated = _migrate_version_6_to_7(migrated)
|
||||
7:
|
||||
migrated = _migrate_version_7_to_8(migrated)
|
||||
8:
|
||||
migrated = _migrate_version_8_to_9(migrated)
|
||||
_:
|
||||
return {}
|
||||
if migrated.is_empty():
|
||||
|
|
@ -1231,17 +1201,6 @@ func _migrate_version_7_to_8(data: Dictionary) -> Dictionary:
|
|||
return migrated
|
||||
|
||||
|
||||
func _migrate_version_8_to_9(data: Dictionary) -> Dictionary:
|
||||
var migrated: Dictionary = data.duplicate(true)
|
||||
var world_data: Dictionary = {}
|
||||
if typeof(migrated.get("world")) == TYPE_DICTIONARY:
|
||||
world_data = (migrated["world"] as Dictionary).duplicate(true)
|
||||
world_data["seed"] = DEFAULT_WORLD_SEED
|
||||
migrated["world"] = world_data
|
||||
migrated["save_version"] = 9
|
||||
return migrated
|
||||
|
||||
|
||||
func _mark_dirty() -> void:
|
||||
if (
|
||||
_is_restoring
|
||||
|
|
@ -1360,7 +1319,6 @@ func _restore_defaults() -> void:
|
|||
WorldTimeServiceType.DEFAULT_START_HOUR
|
||||
)
|
||||
_world_weather.reset_persistent_state()
|
||||
_world_seed = DEFAULT_WORLD_SEED
|
||||
_jobs.reset_to_defaults()
|
||||
_is_restoring = false
|
||||
_is_dirty = false
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ readonly -a QUICK_TESTS=(
|
|||
"tests/fishing_audio_validation.gd"
|
||||
"tests/fishing_surface_validation.gd"
|
||||
"tests/fur_pattern_validation.gd"
|
||||
"tests/generated_world_runtime_validation.gd"
|
||||
"tests/gathering_marker_surface_validation.gd"
|
||||
"tests/inventory_storage_validation.gd"
|
||||
"tests/keyboard_mouse_mapping_validation.gd"
|
||||
|
|
@ -43,9 +42,7 @@ readonly -a QUICK_TESTS=(
|
|||
"tests/shoreline_ambience_validation.gd"
|
||||
"tests/surface_drawing_validation.gd"
|
||||
"tests/tackle_order_validation.gd"
|
||||
"tests/terrain_biome_validation.gd"
|
||||
"tests/terrain_blender_material_validation.gd"
|
||||
"tests/terrain_chunk_generator_validation.gd"
|
||||
"tests/texture_sampling_validation.gd"
|
||||
"tests/tree_gathering_prototype_validation.gd"
|
||||
"tests/unified_inventory_validation.gd"
|
||||
|
|
|
|||
|
|
@ -5,15 +5,12 @@ const DEFAULT_NAME: String = "NETfishing Dedicated Server"
|
|||
const DEFAULT_BIND_ADDRESS: String = "*"
|
||||
const DEFAULT_PORT: int = 7777
|
||||
const DEFAULT_MAX_PLAYERS: int = 8
|
||||
const DEFAULT_WORLD_SEED: int = 13001
|
||||
const MAX_WORLD_SEED: int = 2147483646
|
||||
const MAX_OPERATORS: int = 64
|
||||
|
||||
var server_name: String = DEFAULT_NAME
|
||||
var bind_address: String = DEFAULT_BIND_ADDRESS
|
||||
var port: int = DEFAULT_PORT
|
||||
var max_players: int = DEFAULT_MAX_PLAYERS
|
||||
var world_seed: int = DEFAULT_WORLD_SEED
|
||||
var public_listing: bool = false
|
||||
var discovery_url: String = ""
|
||||
var data_directory: String = ""
|
||||
|
|
@ -61,7 +58,6 @@ func _load_file(path: String) -> bool:
|
|||
max_players = int(file.get_value(
|
||||
"server", "max_players", max_players
|
||||
))
|
||||
world_seed = int(file.get_value("world", "seed", world_seed))
|
||||
public_listing = bool(file.get_value(
|
||||
"server", "public", public_listing
|
||||
))
|
||||
|
|
@ -88,9 +84,6 @@ func _apply_environment() -> void:
|
|||
max_players = _environment_int(
|
||||
"NETFISHING_SERVER_MAX_PLAYERS", max_players
|
||||
)
|
||||
world_seed = _environment_int(
|
||||
"NETFISHING_WORLD_SEED", world_seed
|
||||
)
|
||||
public_listing = _environment_bool(
|
||||
"NETFISHING_SERVER_PUBLIC", public_listing
|
||||
)
|
||||
|
|
@ -118,10 +111,6 @@ func _apply_arguments(arguments: PackedStringArray) -> void:
|
|||
max_players = _parse_int(
|
||||
argument.trim_prefix("--max-players="), max_players
|
||||
)
|
||||
elif argument.begins_with("--world-seed="):
|
||||
world_seed = _parse_int(
|
||||
argument.trim_prefix("--world-seed="), world_seed
|
||||
)
|
||||
elif argument.begins_with("--data-dir="):
|
||||
data_directory = argument.trim_prefix("--data-dir=")
|
||||
elif argument.begins_with("--discovery-url="):
|
||||
|
|
@ -152,8 +141,6 @@ func _validate() -> void:
|
|||
error_message = "Server port must be between 1 and 65535."
|
||||
elif max_players < 1 or max_players > 128:
|
||||
error_message = "Maximum players must be between 1 and 128."
|
||||
elif world_seed <= 0 or world_seed > MAX_WORLD_SEED:
|
||||
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 public_listing and not (
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ func _run() -> void:
|
|||
file.set_value("server", "bind_address", "127.0.0.1")
|
||||
file.set_value("server", "port", 17777)
|
||||
file.set_value("server", "max_players", 12)
|
||||
file.set_value("world", "seed", 928374)
|
||||
file.set_value("server", "public", true)
|
||||
file.set_value("server", "data_directory", "/tmp/configured-server")
|
||||
file.set_value("discovery", "url", "https://discovery.netfishing.org/")
|
||||
|
|
@ -45,7 +44,6 @@ func _run() -> void:
|
|||
assert(str(configured.get("bind_address")) == "127.0.0.1")
|
||||
assert(int(configured.get("port")) == 17777)
|
||||
assert(int(configured.get("max_players")) == 12)
|
||||
assert(int(configured.get("world_seed")) == 928374)
|
||||
assert(bool(configured.get("public_listing")))
|
||||
assert(str(configured.get("discovery_url")) == "https://discovery.netfishing.org")
|
||||
assert(
|
||||
|
|
@ -83,12 +81,6 @@ func _run() -> void:
|
|||
invalid.call("_validate")
|
||||
assert(not invalid.is_valid())
|
||||
|
||||
var invalid_seed := ConfigType.new()
|
||||
invalid_seed.set("world_seed", 0)
|
||||
invalid_seed.set("data_directory", "/tmp/invalid-seed-server")
|
||||
invalid_seed.call("_validate")
|
||||
assert(not invalid_seed.is_valid())
|
||||
|
||||
var discovery := DiscoveryClient.new()
|
||||
assert(
|
||||
str(discovery.call("_default_room_name", "River"))
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ func _run() -> void:
|
|||
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 as Dictionary)["save_version"]) == 9)
|
||||
assert(int((parsed as Dictionary)["save_version"]) == 8)
|
||||
assert(
|
||||
int((parsed as Dictionary)["experience"]["total_experience"])
|
||||
== 125
|
||||
|
|
@ -185,7 +185,7 @@ func _run() -> void:
|
|||
var invalid_state: Dictionary = valid_state.duplicate(true)
|
||||
invalid_state["display_scale"] = 1000.0
|
||||
assert(not NetworkFishShowcaseProtocol.validate_state(invalid_state))
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 9)
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 8)
|
||||
assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10)
|
||||
assert(
|
||||
NetworkProtocol.FISH_QUALITY_CAPABILITY
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func _run() -> void:
|
|||
_validate_mail_round_trip()
|
||||
_validate_collection_mastery()
|
||||
_validate_version_four_migration()
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 9)
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 8)
|
||||
assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10)
|
||||
assert(NetworkProtocol.FISH_QUALITY_CAPABILITY == "fish_quality_v1")
|
||||
print("Fish quality validation: PASS")
|
||||
|
|
@ -433,7 +433,7 @@ func _validate_version_four_migration() -> void:
|
|||
version_four,
|
||||
4,
|
||||
)
|
||||
assert(int(migrated.get("save_version", -1)) == 9)
|
||||
assert(int(migrated.get("save_version", -1)) == 8)
|
||||
assert(int((migrated["experience"] as Dictionary)["total_experience"]) == 0)
|
||||
assert(
|
||||
is_equal_approx(
|
||||
|
|
@ -441,10 +441,6 @@ func _validate_version_four_migration() -> void:
|
|||
8.0,
|
||||
)
|
||||
)
|
||||
assert(
|
||||
int((migrated["world"] as Dictionary)["seed"])
|
||||
== PlayerSaveManager.DEFAULT_WORLD_SEED
|
||||
)
|
||||
var catches: Array = migrated["inventory"]["catches"]
|
||||
assert(int((catches[0] as Dictionary)["quality"]) == 0)
|
||||
var collection: Dictionary = migrated["collection"]
|
||||
|
|
|
|||
|
|
@ -39,33 +39,19 @@ func _run() -> void:
|
|||
assert(not session.is_open_host())
|
||||
assert(service != null and fishing_spot != null and player != null)
|
||||
assert(player.hotbar.get_selected_item_id() == &"basic_fishing_rod")
|
||||
var fresh_root := main.get_node(
|
||||
"TestWorld/Regions/GeneratedWorldRegion/WaterBodies/FreshWaterBodies"
|
||||
var pond := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond"
|
||||
) as Node3D
|
||||
var pond: WaterBodyAuthoring
|
||||
for child: Node in fresh_root.get_children():
|
||||
var candidate := child as WaterBodyAuthoring
|
||||
if candidate != null and &"pond" in candidate.location_tags:
|
||||
pond = candidate
|
||||
break
|
||||
assert(pond != null)
|
||||
var pond_region := pond.get_node("FishingRegion") as FishableWaterRegion
|
||||
assert(pond_region != null)
|
||||
assert(pond != null and pond_region != null)
|
||||
var pond_surface_y: float = pond_region.get_surface_height()
|
||||
|
||||
player.global_position = pond.global_transform * Vector3(
|
||||
pond.surface_size.x * 0.5 + 0.8,
|
||||
1.44,
|
||||
0.0,
|
||||
)
|
||||
var pond_direction := pond.global_position - player.global_position
|
||||
pond_direction.y = 0.0
|
||||
pond_direction = pond_direction.normalized()
|
||||
player.global_position = pond.global_position + Vector3(8.9, 1.44, 0.0)
|
||||
var visuals := player.get_node("Visuals") as Node3D
|
||||
visuals.global_rotation.y = atan2(-pond_direction.x, -pond_direction.z)
|
||||
visuals.rotation.y = PI * 0.5
|
||||
for _frame: int in 4:
|
||||
await physics_frame
|
||||
assert(player.get_facing_direction().dot(pond_direction) > 0.99)
|
||||
assert(player.get_facing_direction().dot(Vector3.LEFT) > 0.99)
|
||||
|
||||
fishing_spot.call("_begin_aiming", player)
|
||||
assert(fishing_spot.state == FishingSpotType.FishingState.AIMING_CAST)
|
||||
|
|
@ -73,9 +59,7 @@ func _run() -> void:
|
|||
fishing_spot.set("_cast_charge", 0.32)
|
||||
fishing_spot.call("_update_cast_charge", 0.0)
|
||||
var aimed_target: Vector3 = fishing_spot.get("_cast_target")
|
||||
var aimed_direction := aimed_target - player.global_position
|
||||
aimed_direction.y = 0.0
|
||||
assert(aimed_direction.normalized().dot(pond_direction) > 0.99)
|
||||
assert(aimed_target.x < player.global_position.x - 0.85)
|
||||
assert(is_equal_approx(aimed_target.y, pond_surface_y))
|
||||
assert(fishing_spot.is_target_fishable(aimed_target))
|
||||
fishing_spot.call("_confirm_cast")
|
||||
|
|
|
|||
|
|
@ -1,510 +0,0 @@
|
|||
extends SceneTree
|
||||
|
||||
const RegionScene: PackedScene = preload(
|
||||
"res://world/generation/generated_world_region.tscn"
|
||||
)
|
||||
const FIRST_SEED := 13001
|
||||
const SECOND_SEED := 13004
|
||||
const EXPECTED_PROP_IDS: Array[StringName] = [
|
||||
&"prop_bridge",
|
||||
&"prop_dock",
|
||||
&"prop_log",
|
||||
&"prop_log_post_1",
|
||||
&"prop_log_post_2",
|
||||
&"prop_mushroom",
|
||||
&"prop_palm",
|
||||
&"prop_pine",
|
||||
&"prop_timber_1",
|
||||
&"prop_timber_2",
|
||||
&"prop_tree_1",
|
||||
&"prop_tree_2",
|
||||
&"prop_tree_3",
|
||||
]
|
||||
const EXPECTED_BIOME_IDS: Array[StringName] = [
|
||||
&"biome_plains",
|
||||
&"biome_forest",
|
||||
&"biome_pine_forest",
|
||||
&"biome_coast",
|
||||
]
|
||||
const PROCEDURAL_PROP_IDS: Array[StringName] = [
|
||||
&"prop_log",
|
||||
&"prop_mushroom",
|
||||
&"prop_palm",
|
||||
&"prop_pine",
|
||||
&"prop_tree_1",
|
||||
&"prop_tree_2",
|
||||
&"prop_tree_3",
|
||||
]
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred(&"_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var region := RegionScene.instantiate() as GeneratedWorldRegion
|
||||
assert(region != null)
|
||||
root.add_child(region)
|
||||
await process_frame
|
||||
await physics_frame
|
||||
|
||||
var generator := region.get_node(
|
||||
"Terrain/TerrainChunkGenerator"
|
||||
) as TerrainChunkGenerator
|
||||
assert(generator != null)
|
||||
assert(region.get_generation_seed() == FIRST_SEED)
|
||||
_validate_generated_region(region, generator)
|
||||
|
||||
var first_layout := generator.placement_keys()
|
||||
var first_biomes := _biome_signature(region, generator)
|
||||
var first_decorations := _decoration_signature(region)
|
||||
assert(region.generate_world(FIRST_SEED))
|
||||
assert(generator.placement_keys() == first_layout)
|
||||
assert(_biome_signature(region, generator) == first_biomes)
|
||||
assert(_decoration_signature(region) == first_decorations)
|
||||
|
||||
assert(region.generate_world(SECOND_SEED))
|
||||
await process_frame
|
||||
await physics_frame
|
||||
await physics_frame
|
||||
_validate_generated_region(region, generator)
|
||||
assert(generator.placement_keys() != first_layout)
|
||||
assert(_biome_signature(region, generator) != first_biomes)
|
||||
|
||||
region.queue_free()
|
||||
await process_frame
|
||||
print("Generated world runtime validation: PASS")
|
||||
quit()
|
||||
|
||||
|
||||
func _validate_generated_region(
|
||||
region: GeneratedWorldRegion,
|
||||
generator: TerrainChunkGenerator,
|
||||
) -> void:
|
||||
var placements := generator.placement_keys()
|
||||
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_0004") >= 1)
|
||||
assert(_placement_count(placements, "chunk_0005") >= 1)
|
||||
assert(_placement_count(placements, "chunk_0006") >= 1)
|
||||
assert(_placement_count(placements, "chunk_0007") >= 1)
|
||||
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
|
||||
assert(shop != null and shop.has_node("Shopkeeper"))
|
||||
assert(storage != null and storage.has_node("InteractionArea"))
|
||||
assert(absf(shop.position.x) < 5.0 and absf(shop.position.z) < 5.0)
|
||||
assert(absf(storage.position.x) < 5.0 and absf(storage.position.z) < 5.0)
|
||||
assert(region.get_fishing_shop() != null)
|
||||
assert(region.get_player_storage() != null)
|
||||
assert(region.get_player_spawn_transform().origin.y > 0.0)
|
||||
_validate_prop_catalog(region)
|
||||
_validate_biome_catalog(region, generator)
|
||||
|
||||
var ocean := region.get_node("WaterBodies/OceanWater") as WaterBodyAuthoring
|
||||
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)
|
||||
var fresh_placement_count := 0
|
||||
for record: Dictionary in generator.placement_records():
|
||||
var tags: PackedStringArray = record.get("tags", PackedStringArray())
|
||||
if "coast" in tags:
|
||||
_validate_ocean_facing_record(record, generator.grid_size)
|
||||
if "fresh_water" in tags:
|
||||
fresh_placement_count += 1
|
||||
assert(fresh_root.get_child_count() == fresh_placement_count)
|
||||
for child: Node in fresh_root.get_children():
|
||||
var fresh := child as WaterBodyAuthoring
|
||||
assert(fresh != null)
|
||||
assert(fresh.water_type == WaterType.Type.FRESH_WATER)
|
||||
assert(fresh.visible)
|
||||
assert(fresh.surface_size.x < 10.0 or fresh.surface_size.y < 10.0)
|
||||
assert(not fresh.visual_surface_enabled)
|
||||
assert(not (fresh.get_node("VisualWater") as MeshInstance3D).visible)
|
||||
|
||||
_validate_authored_chunk_surfaces(region, generator)
|
||||
|
||||
var decorations := region.get_node("Decorations") as Node3D
|
||||
var anchors := region.get_node(
|
||||
"GatherableAnchors/ReachableTreeTrunks"
|
||||
) as GatherableAnchorSet3D
|
||||
assert(decorations != null and decorations.get_child_count() > 0)
|
||||
assert(anchors != null)
|
||||
assert(anchors.get_spawn_positions().size() > 0)
|
||||
for child: Node in decorations.get_children():
|
||||
var prop_id := StringName(child.get_meta(&"terrain_prop_id", &""))
|
||||
assert(PROCEDURAL_PROP_IDS.has(prop_id))
|
||||
var definition := region.get_prop_catalog().definition_for_id(prop_id)
|
||||
assert(definition != null)
|
||||
var biome_id := StringName(
|
||||
child.get_meta(&"terrain_biome_id", &"")
|
||||
)
|
||||
var biome := region.get_biome_catalog().definition_for_id(biome_id)
|
||||
assert(biome != null)
|
||||
var biome_rule := biome.prop_rule_for_group(
|
||||
definition.procedural_group
|
||||
)
|
||||
assert(biome_rule != null and biome_rule.allows_prop(definition))
|
||||
var coordinate: Vector2i = child.get_meta(
|
||||
&"terrain_chunk_coordinate",
|
||||
Vector2i.ZERO,
|
||||
)
|
||||
var center := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
)
|
||||
var spawn_distance := (
|
||||
absi(coordinate.x - center.x)
|
||||
+ absi(coordinate.y - center.y)
|
||||
)
|
||||
assert(spawn_distance >= definition.minimum_spawn_chunk_distance)
|
||||
_validate_decoration_transform(
|
||||
region,
|
||||
child as Node3D,
|
||||
definition,
|
||||
)
|
||||
assert(_decoration_group_count(region, &"grass_tree") > 0)
|
||||
assert(_decoration_group_count(region, &"sand_tree") > 0)
|
||||
assert(
|
||||
_decoration_biome_group_count(
|
||||
region,
|
||||
&"biome_forest",
|
||||
&"grass_tree",
|
||||
) >= 2
|
||||
)
|
||||
assert(
|
||||
_decoration_biome_group_count(
|
||||
region,
|
||||
&"biome_pine_forest",
|
||||
&"grass_tree",
|
||||
) >= 2
|
||||
)
|
||||
|
||||
|
||||
func _validate_ocean_facing_record(
|
||||
record: Dictionary,
|
||||
grid_size: Vector2i,
|
||||
) -> void:
|
||||
var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO)
|
||||
var ocean_edges := int(record.get("ocean_facing_edges", 0))
|
||||
assert(ocean_edges != 0)
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
if (ocean_edges & (1 << edge_value)) == 0:
|
||||
continue
|
||||
var ocean_coordinate := (
|
||||
coordinate
|
||||
+ TerrainChunkTopology.grid_offset(
|
||||
edge_value as TerrainChunkTopology.Edge
|
||||
)
|
||||
)
|
||||
assert(
|
||||
ocean_coordinate.x < 0
|
||||
or ocean_coordinate.y < 0
|
||||
or ocean_coordinate.x >= grid_size.x
|
||||
or ocean_coordinate.y >= grid_size.y
|
||||
)
|
||||
|
||||
|
||||
func _validate_prop_catalog(region: GeneratedWorldRegion) -> void:
|
||||
var catalog := region.get_prop_catalog()
|
||||
assert(catalog != null)
|
||||
assert(catalog.validation_errors().is_empty())
|
||||
assert(catalog.definitions.size() == EXPECTED_PROP_IDS.size())
|
||||
for stable_id: StringName in EXPECTED_PROP_IDS:
|
||||
var definition := catalog.definition_for_id(stable_id)
|
||||
assert(definition != null and definition.packed_scene != null)
|
||||
var instance := definition.packed_scene.instantiate() as Node3D
|
||||
assert(instance != null)
|
||||
assert(instance.position.is_zero_approx())
|
||||
assert(_find_mesh_instance(instance) != null)
|
||||
instance.free()
|
||||
|
||||
|
||||
func _validate_biome_catalog(
|
||||
region: GeneratedWorldRegion,
|
||||
generator: TerrainChunkGenerator,
|
||||
) -> void:
|
||||
var catalog := region.get_biome_catalog()
|
||||
assert(catalog != null)
|
||||
assert(catalog.validation_errors().is_empty())
|
||||
assert(catalog.definitions.size() == EXPECTED_BIOME_IDS.size())
|
||||
var counts: Dictionary[StringName, int] = {}
|
||||
for record: Dictionary in generator.placement_records():
|
||||
var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO)
|
||||
var biome_id := region.get_biome_at(coordinate)
|
||||
var tags: PackedStringArray = record.get("tags", PackedStringArray())
|
||||
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))
|
||||
counts[biome_id] = counts.get(biome_id, 0) + 1
|
||||
else:
|
||||
assert(biome_id == &"")
|
||||
for biome_id: StringName in EXPECTED_BIOME_IDS:
|
||||
assert(counts.get(biome_id, 0) > 0)
|
||||
var center := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
)
|
||||
assert(region.get_biome_at(center) == &"biome_plains")
|
||||
for child: Node in generator.get_generated_chunks_root().get_children():
|
||||
var coordinate: Vector2i = child.get_meta(
|
||||
&"terrain_chunk_coordinate",
|
||||
Vector2i.ZERO,
|
||||
)
|
||||
assert(
|
||||
StringName(child.get_meta(&"terrain_biome_id", &""))
|
||||
== region.get_biome_at(coordinate)
|
||||
)
|
||||
|
||||
|
||||
func _validate_decoration_transform(
|
||||
region: GeneratedWorldRegion,
|
||||
prop: Node3D,
|
||||
definition: TerrainPropDefinition,
|
||||
) -> void:
|
||||
assert(prop != null)
|
||||
assert(prop.scale.is_equal_approx(Vector3.ONE))
|
||||
var visual_root := prop.get_node_or_null("Visual") as Node3D
|
||||
var visual := _find_mesh_instance(visual_root)
|
||||
var collision := prop.get_node_or_null(
|
||||
"TrunkCollision/CollisionShape"
|
||||
) as CollisionShape3D
|
||||
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)
|
||||
assert(
|
||||
collision.shape is CylinderShape3D
|
||||
or collision.shape is BoxShape3D
|
||||
)
|
||||
assert(collision.global_basis.get_scale().is_equal_approx(Vector3.ONE))
|
||||
else:
|
||||
assert(collision == null)
|
||||
var query := PhysicsRayQueryParameters3D.create(
|
||||
prop.global_position + Vector3.UP * 8.0,
|
||||
prop.global_position + Vector3.DOWN * 8.0,
|
||||
1,
|
||||
)
|
||||
var prop_body := prop.get_node_or_null(
|
||||
"TrunkCollision",
|
||||
) as CollisionObject3D
|
||||
if prop_body != null:
|
||||
query.exclude = [prop_body.get_rid()]
|
||||
var hit := region.get_world_3d().direct_space_state.intersect_ray(query)
|
||||
assert(
|
||||
not hit.is_empty(),
|
||||
"No terrain collision below %s (%s) at %s."
|
||||
% [prop.name, definition.stable_id, prop.global_position],
|
||||
)
|
||||
var ground_position: Vector3 = hit["position"]
|
||||
assert(absf(ground_position.y - prop.global_position.y) <= 0.01)
|
||||
|
||||
|
||||
func _find_mesh_instance(root_node: Node) -> MeshInstance3D:
|
||||
if root_node is MeshInstance3D:
|
||||
return root_node as MeshInstance3D
|
||||
if root_node == null:
|
||||
return null
|
||||
for child: Node in root_node.get_children():
|
||||
var mesh_instance := _find_mesh_instance(child)
|
||||
if mesh_instance != null:
|
||||
return mesh_instance
|
||||
return null
|
||||
|
||||
|
||||
func _validate_authored_chunk_surfaces(
|
||||
region: GeneratedWorldRegion,
|
||||
generator: TerrainChunkGenerator,
|
||||
) -> void:
|
||||
var generated := generator.get_generated_chunks_root()
|
||||
var found_beach := false
|
||||
var found_pond := false
|
||||
var found_grass_ocean_edge := false
|
||||
var found_grass_beach_transition := false
|
||||
var found_grass_ocean_corner := false
|
||||
for chunk_root: Node in generated.get_children():
|
||||
if chunk_root.name.begins_with("chunk_0002r"):
|
||||
var beach := chunk_root.find_child(
|
||||
"chunk_0002", true, false
|
||||
) as MeshInstance3D
|
||||
assert(beach != null and beach.mesh != null)
|
||||
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_0004r"):
|
||||
var pond := chunk_root.find_child(
|
||||
"chunk_0004", true, false
|
||||
) as MeshInstance3D
|
||||
assert(pond != null and pond.mesh != null)
|
||||
for surface_index: int in pond.mesh.get_surface_count():
|
||||
var arrays := pond.mesh.surface_get_arrays(surface_index)
|
||||
var normals := arrays[Mesh.ARRAY_NORMAL] as PackedVector3Array
|
||||
for normal: Vector3 in normals:
|
||||
assert(normal.y >= -0.001)
|
||||
_validate_pond_collision(region, pond)
|
||||
found_pond = true
|
||||
elif chunk_root.name.begins_with("chunk_0005r"):
|
||||
var grass_ocean_edge := chunk_root.find_child(
|
||||
"chunk_0005", true, false
|
||||
) as MeshInstance3D
|
||||
assert(grass_ocean_edge != null and grass_ocean_edge.mesh != null)
|
||||
var edge_materials := _material_names(grass_ocean_edge)
|
||||
assert("grass_lite" in edge_materials)
|
||||
assert("cliff_wall" in edge_materials)
|
||||
found_grass_ocean_edge = true
|
||||
elif chunk_root.name.begins_with("chunk_0006r"):
|
||||
var grass_beach_transition := chunk_root.find_child(
|
||||
"chunk_0006", true, false
|
||||
) as MeshInstance3D
|
||||
assert(
|
||||
grass_beach_transition != null
|
||||
and grass_beach_transition.mesh != null
|
||||
)
|
||||
var transition_materials := _material_names(
|
||||
grass_beach_transition
|
||||
)
|
||||
assert("sand" in transition_materials)
|
||||
assert("grass_lite" in transition_materials)
|
||||
assert("cliff_wall" in transition_materials)
|
||||
found_grass_beach_transition = true
|
||||
elif chunk_root.name.begins_with("chunk_0007r"):
|
||||
var grass_ocean_corner := chunk_root.find_child(
|
||||
"chunk_0007", true, false
|
||||
) as MeshInstance3D
|
||||
assert(grass_ocean_corner != null and grass_ocean_corner.mesh != null)
|
||||
var corner_materials := _material_names(grass_ocean_corner)
|
||||
assert("grass_lite" in corner_materials)
|
||||
assert("cliff_wall" in corner_materials)
|
||||
found_grass_ocean_corner = true
|
||||
assert(found_beach)
|
||||
assert(found_pond)
|
||||
assert(found_grass_ocean_edge)
|
||||
assert(found_grass_beach_transition)
|
||||
assert(found_grass_ocean_corner)
|
||||
|
||||
|
||||
func _material_names(mesh_instance: MeshInstance3D) -> PackedStringArray:
|
||||
var result := PackedStringArray()
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var material := mesh_instance.get_active_material(surface_index)
|
||||
if material != null:
|
||||
result.append(material.resource_name)
|
||||
return result
|
||||
|
||||
|
||||
func _validate_pond_collision(
|
||||
region: GeneratedWorldRegion,
|
||||
pond: MeshInstance3D,
|
||||
) -> void:
|
||||
var best_centroid := Vector3.ZERO
|
||||
var best_height := -INF
|
||||
for surface_index: int in pond.mesh.get_surface_count():
|
||||
var arrays := pond.mesh.surface_get_arrays(surface_index)
|
||||
var vertices := arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array
|
||||
var normals := arrays[Mesh.ARRAY_NORMAL] as PackedVector3Array
|
||||
var indices := arrays[Mesh.ARRAY_INDEX] as PackedInt32Array
|
||||
var triangle_count := (
|
||||
indices.size() / 3 if not indices.is_empty() else vertices.size() / 3
|
||||
)
|
||||
for triangle_index: int in triangle_count:
|
||||
var offset := triangle_index * 3
|
||||
var index_a: int = indices[offset] if not indices.is_empty() else offset
|
||||
var index_b: int = (
|
||||
indices[offset + 1] if not indices.is_empty() else offset + 1
|
||||
)
|
||||
var index_c: int = (
|
||||
indices[offset + 2] if not indices.is_empty() else offset + 2
|
||||
)
|
||||
var a: Vector3 = vertices[index_a]
|
||||
var b: Vector3 = vertices[index_b]
|
||||
var c: Vector3 = vertices[index_c]
|
||||
var normal := (
|
||||
(normals[index_a] + normals[index_b] + normals[index_c]) / 3.0
|
||||
).normalized()
|
||||
var centroid := (a + b + c) / 3.0
|
||||
if normal.y > 0.5 and centroid.y > best_height:
|
||||
best_height = centroid.y
|
||||
best_centroid = centroid
|
||||
assert(best_height > -INF)
|
||||
var target := pond.global_transform * best_centroid
|
||||
var query := PhysicsRayQueryParameters3D.create(
|
||||
target + Vector3.UP * 2.0,
|
||||
target + Vector3.DOWN * 2.0,
|
||||
1,
|
||||
)
|
||||
var hit := region.get_world_3d().direct_space_state.intersect_ray(query)
|
||||
assert(not hit.is_empty())
|
||||
var collider := hit.get("collider") as Node
|
||||
assert(collider != null and collider.get_parent() == pond)
|
||||
|
||||
|
||||
func _decoration_group_count(
|
||||
region: GeneratedWorldRegion,
|
||||
group: StringName,
|
||||
) -> int:
|
||||
var count := 0
|
||||
var decorations := region.get_node("Decorations") as Node3D
|
||||
for child: Node in decorations.get_children():
|
||||
var stable_id := StringName(child.get_meta(&"terrain_prop_id", &""))
|
||||
var definition := region.get_prop_catalog().definition_for_id(stable_id)
|
||||
if definition != null and definition.procedural_group == group:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _decoration_biome_group_count(
|
||||
region: GeneratedWorldRegion,
|
||||
biome_id: StringName,
|
||||
group: StringName,
|
||||
) -> int:
|
||||
var count := 0
|
||||
var decorations := region.get_node("Decorations") as Node3D
|
||||
for child: Node in decorations.get_children():
|
||||
if StringName(child.get_meta(&"terrain_biome_id", &"")) != biome_id:
|
||||
continue
|
||||
var prop_id := StringName(child.get_meta(&"terrain_prop_id", &""))
|
||||
var definition := region.get_prop_catalog().definition_for_id(prop_id)
|
||||
if definition != null and definition.procedural_group == group:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _biome_signature(
|
||||
region: GeneratedWorldRegion,
|
||||
generator: TerrainChunkGenerator,
|
||||
) -> PackedStringArray:
|
||||
var result := PackedStringArray()
|
||||
for row: int in generator.grid_size.y:
|
||||
for column: int in generator.grid_size.x:
|
||||
result.append(
|
||||
String(region.get_biome_at(Vector2i(column, row)))
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
func _placement_count(keys: PackedStringArray, stable_id: String) -> int:
|
||||
var count := 0
|
||||
for key: String in keys:
|
||||
if key.begins_with(stable_id + "@"):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _decoration_signature(region: GeneratedWorldRegion) -> PackedStringArray:
|
||||
var result := PackedStringArray()
|
||||
var decorations := region.get_node("Decorations") as Node3D
|
||||
for child: Node in decorations.get_children():
|
||||
var prop := child as Node3D
|
||||
result.append("%s:%s:%s" % [
|
||||
prop.name,
|
||||
prop.position,
|
||||
prop.rotation,
|
||||
])
|
||||
return result
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://opamwsr0t7px
|
||||
|
|
@ -22,8 +22,7 @@ func _run() -> void:
|
|||
var save_manager := main.get("_save_manager") as PlayerSaveManager
|
||||
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))
|
||||
assert(save_manager.initialize_new_game())
|
||||
save_manager.set_autosave_enabled(true)
|
||||
assert(player.bag.add_item(&"art_kit"))
|
||||
assert(player.bag.add_item(&"coffee"))
|
||||
|
|
@ -40,14 +39,12 @@ func _run() -> void:
|
|||
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))
|
||||
assert(save_manager.load_player_data())
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ func _run() -> void:
|
|||
save_file.close()
|
||||
assert(typeof(parsed) == TYPE_DICTIONARY)
|
||||
var save_data: Dictionary = parsed
|
||||
assert(int(save_data.get("save_version", -1)) == 9)
|
||||
assert(int(save_data.get("save_version", -1)) == 8)
|
||||
assert(PlayerJobService.validate_save_data(save_data.get("jobs", {})))
|
||||
|
||||
_validate_pause_session_switch(main, session)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ const MainScene: PackedScene = preload("res://main/main.tscn")
|
|||
const RuntimePerformanceProfileType = preload(
|
||||
"res://main/runtime_performance_profile.gd"
|
||||
)
|
||||
const FOLIAGE_WIND_SHADER: Shader = preload(
|
||||
"res://world/materials/foliage_wind.gdshader"
|
||||
)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
|
@ -72,19 +77,27 @@ func _validate_main_profile(main: Node) -> void:
|
|||
var screen_grid := pixelation.get_node("ScreenGrid") as ColorRect
|
||||
assert(screen_grid != null and not screen_grid.visible)
|
||||
|
||||
var pond := _first_fresh_water_visual(main)
|
||||
var ocean := main.get_node(
|
||||
"TestWorld/Regions/GeneratedWorldRegion/WaterBodies/OceanWater/VisualWater"
|
||||
var pond := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond/VisualWater"
|
||||
) as MeshInstance3D
|
||||
assert(not pond.visible)
|
||||
var ocean := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/VisualWater"
|
||||
) as MeshInstance3D
|
||||
assert(pond.material_override is StandardMaterial3D)
|
||||
assert(ocean.material_override is StandardMaterial3D)
|
||||
assert(not pond.material_override is ShaderMaterial)
|
||||
assert(not ocean.material_override is ShaderMaterial)
|
||||
assert(ocean is WaterSurfaceMotion)
|
||||
assert(not ocean.is_processing())
|
||||
assert(is_zero_approx(ocean.position.y))
|
||||
_validate_ocean_fishing_coverage(main, ocean)
|
||||
var region := main.get_node(
|
||||
"TestWorld/Regions/GeneratedWorldRegion"
|
||||
) as GeneratedWorldRegion
|
||||
assert(region != null)
|
||||
assert(region.get_node("Decorations").get_child_count() > 0)
|
||||
var island := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion"
|
||||
) as StarterIslandRegion
|
||||
assert(island != null and not island.is_foliage_wind_enabled())
|
||||
assert(_count_foliage_wind_overrides(
|
||||
island.get_node("Terrain/Visual")
|
||||
) == 0)
|
||||
|
||||
var title_background := main.get_node("%TitleBackground") as ColorRect
|
||||
var title_material := title_background.material as ShaderMaterial
|
||||
|
|
@ -130,18 +143,24 @@ func _validate_normal_profile(main: Node) -> void:
|
|||
assert(is_equal_approx(root.scaling_3d_scale, 1.0))
|
||||
var pixelation: Node = main.get_node("%WorldPixelationPostprocess")
|
||||
assert(not bool(pixelation.call("is_light_performance_profile")))
|
||||
var pond := _first_fresh_water_visual(main)
|
||||
var ocean := main.get_node(
|
||||
"TestWorld/Regions/GeneratedWorldRegion/WaterBodies/OceanWater/VisualWater"
|
||||
var pond := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond/VisualWater"
|
||||
) as MeshInstance3D
|
||||
assert(not pond.visible)
|
||||
var ocean := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/VisualWater"
|
||||
) as MeshInstance3D
|
||||
assert(pond.material_override is ShaderMaterial)
|
||||
assert(ocean.material_override is ShaderMaterial)
|
||||
assert(ocean is WaterSurfaceMotion)
|
||||
assert(ocean.is_processing())
|
||||
_validate_ocean_fishing_coverage(main, ocean)
|
||||
var region := main.get_node(
|
||||
"TestWorld/Regions/GeneratedWorldRegion"
|
||||
) as GeneratedWorldRegion
|
||||
assert(region != null)
|
||||
assert(region.get_node("Decorations").get_child_count() > 0)
|
||||
var island := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion"
|
||||
) as StarterIslandRegion
|
||||
assert(island != null and island.is_foliage_wind_enabled())
|
||||
assert(_count_foliage_wind_overrides(
|
||||
island.get_node("Terrain/Visual")
|
||||
) > 0)
|
||||
var game_ui := main.get_node("%GameUI") as GameUI
|
||||
var title_screen := game_ui.get_title_screen()
|
||||
assert(title_screen != null)
|
||||
|
|
@ -192,14 +211,6 @@ func _validate_normal_profile(main: Node) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _first_fresh_water_visual(main: Node) -> MeshInstance3D:
|
||||
var root := main.get_node(
|
||||
"TestWorld/Regions/GeneratedWorldRegion/WaterBodies/FreshWaterBodies"
|
||||
) as Node3D
|
||||
assert(root != null and root.get_child_count() > 0)
|
||||
return root.get_child(0).get_node("VisualWater") as MeshInstance3D
|
||||
|
||||
|
||||
func _validate_ocean_fishing_coverage(
|
||||
main: Node,
|
||||
ocean: MeshInstance3D,
|
||||
|
|
@ -207,19 +218,34 @@ func _validate_ocean_fishing_coverage(
|
|||
var ocean_mesh := ocean.mesh as PlaneMesh
|
||||
assert(ocean_mesh != null)
|
||||
var shape_node := main.get_node(
|
||||
"TestWorld/Regions/GeneratedWorldRegion/WaterBodies/OceanWater/"
|
||||
+ "FishingRegion/Shape"
|
||||
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/"
|
||||
+ "FishingRegions/OceanFishingRegion/Shape"
|
||||
) as CollisionShape3D
|
||||
assert(shape_node != null)
|
||||
var fishing_shape := shape_node.shape as BoxShape3D
|
||||
assert(fishing_shape != null)
|
||||
assert(fishing_shape.size.is_equal_approx(Vector3(
|
||||
ocean_mesh.size.x,
|
||||
4.0,
|
||||
2.0,
|
||||
ocean_mesh.size.y,
|
||||
)))
|
||||
|
||||
|
||||
func _count_foliage_wind_overrides(root_node: Node) -> int:
|
||||
var count: int = 0
|
||||
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 material := mesh_instance.get_surface_override_material(
|
||||
surface_index
|
||||
) as ShaderMaterial
|
||||
if material != null and material.shader == FOLIAGE_WIND_SHADER:
|
||||
count += 1
|
||||
for child: Node in root_node.get_children():
|
||||
count += _count_foliage_wind_overrides(child)
|
||||
return count
|
||||
|
||||
|
||||
func _stop_audio_players(root_node: Node) -> void:
|
||||
if root_node is AudioStreamPlayer:
|
||||
(root_node as AudioStreamPlayer).stop()
|
||||
|
|
|
|||
|
|
@ -1,124 +0,0 @@
|
|||
extends SceneTree
|
||||
|
||||
const CATALOG: TerrainChunkCatalog = preload(
|
||||
"res://world/generation/chunks/terrain_chunk_catalog.tres"
|
||||
)
|
||||
const BIOME_CATALOG: TerrainBiomeCatalog = preload(
|
||||
"res://world/generation/biomes/terrain_biome_catalog.tres"
|
||||
)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred(&"_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var generator := _make_generator()
|
||||
root.add_child(generator)
|
||||
if not generator._prepare_catalog():
|
||||
generator.free()
|
||||
quit(1)
|
||||
return
|
||||
var variants: Array[TerrainChunkVariant] = []
|
||||
variants.assign(generator._solver_variants)
|
||||
|
||||
print(
|
||||
"Terrain chunk rotations: %d authored, %d distinct constraints"
|
||||
% [generator._variants.size(), variants.size()]
|
||||
)
|
||||
for variant: TerrainChunkVariant in variants:
|
||||
print("\n", variant.stable_key())
|
||||
var ocean_edges := variant.rotated_edge_mask(
|
||||
variant.definition.ocean_facing_edges
|
||||
)
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
var edge := edge_value as TerrainChunkTopology.Edge
|
||||
var matches := PackedStringArray()
|
||||
for other: TerrainChunkVariant in variants:
|
||||
var opposite := TerrainChunkTopology.opposite_edge(edge)
|
||||
if generator._edges_are_compatible(
|
||||
variant,
|
||||
edge,
|
||||
other,
|
||||
opposite,
|
||||
):
|
||||
matches.append(
|
||||
"%s.%s"
|
||||
% [
|
||||
other.stable_key(),
|
||||
TerrainChunkTopology.edge_name(opposite),
|
||||
]
|
||||
)
|
||||
print(
|
||||
" %s%s [%d points]: %s"
|
||||
% [
|
||||
TerrainChunkTopology.edge_name(edge),
|
||||
(
|
||||
" [ocean]"
|
||||
if (ocean_edges & (1 << edge_value)) != 0
|
||||
else ""
|
||||
),
|
||||
variant.profile(edge).points.size(),
|
||||
", ".join(matches) if not matches.is_empty() else "NONE",
|
||||
]
|
||||
)
|
||||
|
||||
if generator.generate():
|
||||
var summary := generator._build_summary()
|
||||
print(
|
||||
(
|
||||
"\nSeeded diagnostic layout %s "
|
||||
+ "(%d backtracks, %d repeated edges):"
|
||||
)
|
||||
% [
|
||||
str(summary["layout_fingerprint"]).substr(0, 12),
|
||||
int(summary["backtracks"]),
|
||||
int(summary["adjacent_repeat_edges"]),
|
||||
]
|
||||
)
|
||||
var keys := generator.placement_keys()
|
||||
for row: int in generator.grid_size.y:
|
||||
var cells := PackedStringArray()
|
||||
for column: int in generator.grid_size.x:
|
||||
cells.append(keys[row * generator.grid_size.x + column])
|
||||
print(" ", " ".join(cells))
|
||||
var biomes := TerrainBiomeAssigner.assign(
|
||||
BIOME_CATALOG,
|
||||
generator.placement_records(),
|
||||
generator.generation_seed,
|
||||
)
|
||||
print("\nBiome layout:")
|
||||
for row: int in generator.grid_size.y:
|
||||
var cells := PackedStringArray()
|
||||
for column: int in generator.grid_size.x:
|
||||
var biome_id: StringName = biomes.get(
|
||||
Vector2i(column, row),
|
||||
&"",
|
||||
)
|
||||
cells.append(String(biome_id).trim_prefix("biome_"))
|
||||
print(" ", " ".join(cells))
|
||||
generator.free()
|
||||
quit(0)
|
||||
|
||||
|
||||
func _make_generator() -> TerrainChunkGenerator:
|
||||
var generator := TerrainChunkGenerator.new()
|
||||
generator.catalog = CATALOG
|
||||
generator.grid_size = Vector2i(7, 7)
|
||||
generator.generation_seed = 13001
|
||||
generator.generate_on_ready = false
|
||||
generator.build_collision = false
|
||||
generator.force_center_chunk_id = &"chunk_spawn"
|
||||
generator.required_chunk_ids = PackedStringArray(
|
||||
[
|
||||
"chunk_spawn",
|
||||
"chunk_0001",
|
||||
"chunk_0002",
|
||||
"chunk_0003",
|
||||
"chunk_0004",
|
||||
"chunk_0005",
|
||||
"chunk_0006",
|
||||
"chunk_0007",
|
||||
]
|
||||
)
|
||||
return generator
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://di4yaor4f3s4e
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
extends Node3D
|
||||
|
||||
@onready var _generator: TerrainChunkGenerator = $TerrainChunkGenerator
|
||||
@onready var _status: Label = $Instructions/Status
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed(&"ui_cancel"):
|
||||
get_viewport().set_input_as_handled()
|
||||
get_tree().quit()
|
||||
return
|
||||
var key_event := event as InputEventKey
|
||||
if (
|
||||
key_event != null
|
||||
and key_event.pressed
|
||||
and not key_event.echo
|
||||
and key_event.physical_keycode == KEY_R
|
||||
):
|
||||
get_viewport().set_input_as_handled()
|
||||
_generator.generation_seed += 1
|
||||
_generator.generate()
|
||||
|
||||
|
||||
func _on_generation_completed(summary: Dictionary) -> void:
|
||||
_status.text = (
|
||||
"Terrain generator diagnostic • seed %d • %d chunks • "
|
||||
+ "%d rotations / %d constraints • %d backtracks • "
|
||||
+ "%d repeated edges\nlayout %s • R: regenerate • Esc/B: close"
|
||||
) % [
|
||||
int(summary["seed"]),
|
||||
int(summary["chunk_count"]),
|
||||
int(summary["variant_count"]),
|
||||
int(summary["solver_variant_count"]),
|
||||
int(summary["backtracks"]),
|
||||
int(summary["adjacent_repeat_edges"]),
|
||||
str(summary["layout_fingerprint"]).substr(0, 12),
|
||||
]
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://0dmjswsm038h
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
[gd_scene load_steps=7 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.gd" id="1_test"]
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_chunk_generator.gd" id="2_generator"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/terrain_chunk_catalog.tres" id="3_catalog"]
|
||||
[ext_resource type="PackedScene" path="res://player/player.tscn" id="4_player"]
|
||||
[ext_resource type="Environment" path="res://world/environment/netfishing_environment.tres" id="5_environment"]
|
||||
|
||||
[node name="TerrainChunkGeneratorTest" type="Node3D"]
|
||||
script = ExtResource("1_test")
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = ExtResource("5_environment")
|
||||
|
||||
[node name="Sun" type="DirectionalLight3D" parent="."]
|
||||
rotation_degrees = Vector3(-54, -32, 0)
|
||||
light_color = Color(0.88, 0.94, 0.96, 1)
|
||||
light_energy = 1.0
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="TerrainChunkGenerator" type="Node3D" parent="."]
|
||||
script = ExtResource("2_generator")
|
||||
catalog = ExtResource("3_catalog")
|
||||
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")
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("4_player")]
|
||||
position = Vector3(0, 0.3, 0)
|
||||
|
||||
[node name="Instructions" type="CanvasLayer" parent="."]
|
||||
layer = 20
|
||||
|
||||
[node name="Status" type="Label" parent="Instructions"]
|
||||
offset_left = 18.0
|
||||
offset_top = 18.0
|
||||
offset_right = 930.0
|
||||
offset_bottom = 74.0
|
||||
text = "Terrain generator diagnostic\nR: regenerate • Esc/B: close"
|
||||
theme_override_colors/font_color = Color(0.76, 0.88, 0.9, 1)
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[connection signal="generation_completed" from="TerrainChunkGenerator" to="." method="_on_generation_completed"]
|
||||
|
|
@ -143,15 +143,11 @@ func _validate_save_migration() -> void:
|
|||
version_five,
|
||||
5,
|
||||
)
|
||||
assert(int(migrated.get("save_version", -1)) == 9)
|
||||
assert(int(migrated.get("save_version", -1)) == 8)
|
||||
var experience_data: Dictionary = migrated.get("experience", {})
|
||||
assert(int(experience_data.get("total_experience", -1)) == 0)
|
||||
var world_data: Dictionary = migrated.get("world", {})
|
||||
assert(is_equal_approx(float(world_data.get("time_hours", -1.0)), 8.0))
|
||||
assert(
|
||||
int(world_data.get("seed", 0))
|
||||
== PlayerSaveManager.DEFAULT_WORLD_SEED
|
||||
)
|
||||
assert(
|
||||
PlayerJobService.validate_save_data(migrated.get("jobs", {}))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,135 +0,0 @@
|
|||
extends SceneTree
|
||||
|
||||
const BIOME_CATALOG: TerrainBiomeCatalog = preload(
|
||||
"res://world/generation/biomes/terrain_biome_catalog.tres"
|
||||
)
|
||||
const PROP_CATALOG: TerrainPropCatalog = preload(
|
||||
"res://world/generation/props/terrain_prop_catalog.tres"
|
||||
)
|
||||
const EXPECTED_BIOMES: Array[StringName] = [
|
||||
&"biome_plains",
|
||||
&"biome_forest",
|
||||
&"biome_pine_forest",
|
||||
&"biome_coast",
|
||||
]
|
||||
const TEST_SEED := 13001
|
||||
|
||||
var _failures: Array[String] = []
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred(&"_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_validate_catalog()
|
||||
_validate_assignment()
|
||||
_finish()
|
||||
|
||||
|
||||
func _validate_catalog() -> void:
|
||||
for error: String in BIOME_CATALOG.validation_errors():
|
||||
_check(false, error)
|
||||
_check(
|
||||
BIOME_CATALOG.definitions.size() == EXPECTED_BIOMES.size(),
|
||||
"The biome catalog must contain all initial authored profiles.",
|
||||
)
|
||||
for biome_id: StringName in EXPECTED_BIOMES:
|
||||
var biome := BIOME_CATALOG.definition_for_id(biome_id)
|
||||
_check(biome != null, "The biome catalog is missing %s." % biome_id)
|
||||
if biome == null:
|
||||
continue
|
||||
for rule: TerrainBiomePropRule in biome.prop_rules:
|
||||
for prop_id_value: String in rule.allowed_prop_ids:
|
||||
var prop := PROP_CATALOG.definition_for_id(
|
||||
StringName(prop_id_value)
|
||||
)
|
||||
_check(
|
||||
prop != null and rule.allows_prop(prop),
|
||||
"%s has an invalid %s prop rule."
|
||||
% [biome_id, prop_id_value],
|
||||
)
|
||||
|
||||
|
||||
func _validate_assignment() -> void:
|
||||
var records := _synthetic_records()
|
||||
var first := TerrainBiomeAssigner.assign(
|
||||
BIOME_CATALOG,
|
||||
records,
|
||||
TEST_SEED,
|
||||
)
|
||||
var second := TerrainBiomeAssigner.assign(
|
||||
BIOME_CATALOG,
|
||||
records,
|
||||
TEST_SEED,
|
||||
)
|
||||
_check(first == second, "Biome assignment must be deterministic by seed.")
|
||||
_check(
|
||||
TerrainBiomeAssigner.fingerprint(first).length() == 64,
|
||||
"Biome assignments must expose a SHA-256 fingerprint.",
|
||||
)
|
||||
var counts := TerrainBiomeAssigner.counts(first)
|
||||
for biome_id: StringName in EXPECTED_BIOMES:
|
||||
_check(
|
||||
counts.get(biome_id, 0) > 0,
|
||||
"The synthetic map must contain %s." % biome_id,
|
||||
)
|
||||
_check(
|
||||
first.get(Vector2i(3, 3), &"") == &"biome_plains",
|
||||
"The spawn-tagged center must remain plains.",
|
||||
)
|
||||
for record: Dictionary in records:
|
||||
var coordinate: Vector2i = record["coordinate"]
|
||||
var tags: PackedStringArray = record["tags"]
|
||||
var biome_id: StringName = first.get(coordinate, &"")
|
||||
if "fresh_water" in tags:
|
||||
_check(
|
||||
biome_id == &"",
|
||||
"Freshwater-only chunks must not inherit a land-prop biome.",
|
||||
)
|
||||
continue
|
||||
var biome := BIOME_CATALOG.definition_for_id(biome_id)
|
||||
_check(
|
||||
biome != null and biome.supports_chunk_tags(tags),
|
||||
"Biome %s does not support chunk %s."
|
||||
% [biome_id, coordinate],
|
||||
)
|
||||
|
||||
|
||||
func _synthetic_records() -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
for row: int in 7:
|
||||
for column: int in 7:
|
||||
var coordinate := Vector2i(column, row)
|
||||
var tags := PackedStringArray(["land", "walkable", "grass"])
|
||||
if row == 0:
|
||||
tags = PackedStringArray(["land", "walkable", "sand"])
|
||||
elif coordinate == Vector2i(0, 6):
|
||||
tags = PackedStringArray([
|
||||
"land",
|
||||
"walkable",
|
||||
"fresh_water",
|
||||
"pond",
|
||||
])
|
||||
elif coordinate == Vector2i(3, 3):
|
||||
tags.append("spawn")
|
||||
result.append({
|
||||
"coordinate": coordinate,
|
||||
"tags": tags,
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
_failures.append(message)
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
if _failures.is_empty():
|
||||
print("Terrain biome validation: PASS")
|
||||
quit(0)
|
||||
return
|
||||
for failure: String in _failures:
|
||||
printerr("Terrain biome validation: ", failure)
|
||||
quit(1)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://cxioi62f6oap0
|
||||
|
|
@ -1,850 +0,0 @@
|
|||
extends SceneTree
|
||||
|
||||
const CATALOG: TerrainChunkCatalog = preload(
|
||||
"res://world/generation/chunks/terrain_chunk_catalog.tres"
|
||||
)
|
||||
const EXPECTED_IDS: Array[String] = [
|
||||
"chunk_0000",
|
||||
"chunk_0001",
|
||||
"chunk_0002",
|
||||
"chunk_0003",
|
||||
"chunk_0004",
|
||||
"chunk_0005",
|
||||
"chunk_0006",
|
||||
"chunk_0007",
|
||||
"chunk_spawn",
|
||||
]
|
||||
const REQUIRED_IDS: Array[String] = [
|
||||
"chunk_spawn",
|
||||
"chunk_0001",
|
||||
"chunk_0002",
|
||||
"chunk_0003",
|
||||
"chunk_0004",
|
||||
"chunk_0005",
|
||||
"chunk_0006",
|
||||
"chunk_0007",
|
||||
]
|
||||
const TEST_SEED := 13001
|
||||
const CONNECTOR_STRESS_SEED := 287
|
||||
const EXPECTED_SOLVER_VARIANT_COUNT := 27
|
||||
const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = {
|
||||
&"chunk_0000": 4,
|
||||
&"chunk_0001": 4,
|
||||
&"chunk_0002": 4,
|
||||
&"chunk_0003": 4,
|
||||
&"chunk_0004": 4,
|
||||
&"chunk_0005": 4,
|
||||
&"chunk_0006": 4,
|
||||
&"chunk_0007": 4,
|
||||
&"chunk_spawn": 1,
|
||||
}
|
||||
|
||||
var _failures: Array[String] = []
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred(&"_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_validate_catalog()
|
||||
_validate_profiles()
|
||||
_validate_generation()
|
||||
_finish()
|
||||
|
||||
|
||||
func _validate_catalog() -> void:
|
||||
for error: String in CATALOG.validation_errors():
|
||||
_check(false, error)
|
||||
_check(
|
||||
CATALOG.definitions.size() == EXPECTED_IDS.size(),
|
||||
"Catalog must contain the authored chunks and spawn definition.",
|
||||
)
|
||||
for stable_id: String in EXPECTED_IDS:
|
||||
var definition := CATALOG.definition_for_id(StringName(stable_id))
|
||||
_check(definition != null, "Catalog is missing %s." % stable_id)
|
||||
if definition == null:
|
||||
continue
|
||||
_check(
|
||||
definition.packed_scene != null,
|
||||
"%s must reference an imported GLB." % stable_id,
|
||||
)
|
||||
var grass := CATALOG.definition_for_id(&"chunk_0000")
|
||||
var sand := CATALOG.definition_for_id(&"chunk_0001")
|
||||
var beach := CATALOG.definition_for_id(&"chunk_0002")
|
||||
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 spawn := CATALOG.definition_for_id(&"chunk_spawn")
|
||||
_check(
|
||||
(
|
||||
grass != null
|
||||
and sand != null
|
||||
and beach != null
|
||||
and grass_ocean_edge != null
|
||||
and grass_beach_transition != null
|
||||
and grass_ocean_corner != null
|
||||
and spawn != null
|
||||
),
|
||||
"The authored biome-rule definitions must be available.",
|
||||
)
|
||||
if (
|
||||
grass != null
|
||||
and sand != null
|
||||
and beach != null
|
||||
and grass_ocean_edge != null
|
||||
and grass_beach_transition != null
|
||||
and grass_ocean_corner != null
|
||||
and spawn != null
|
||||
):
|
||||
_check(
|
||||
sand.allows_non_water_neighbor(beach),
|
||||
"Flat sand must permit an adjacent authored beach.",
|
||||
)
|
||||
_check(
|
||||
sand.allows_non_water_neighbor(grass),
|
||||
"Flat sand must permit the inland transition to grass.",
|
||||
)
|
||||
_check(
|
||||
grass.allows_non_water_neighbor(sand),
|
||||
"Grass must permit the inland side of a flat-sand transition.",
|
||||
)
|
||||
_check(
|
||||
"coast" in sand.required_neighbor_tags,
|
||||
"Every flat-sand chunk must remain attached to an authored beach.",
|
||||
)
|
||||
_check(
|
||||
spawn.minimum_required_neighbors == 3,
|
||||
"Spawn must retain its three-sided safe-grass requirement.",
|
||||
)
|
||||
_check(
|
||||
beach.ocean_facing_edges
|
||||
== (1 << int(TerrainChunkTopology.Edge.EAST)),
|
||||
"The authored beach must descend eastward into ocean before rotation.",
|
||||
)
|
||||
_check(
|
||||
grass_ocean_edge.ocean_facing_edges
|
||||
== (1 << int(TerrainChunkTopology.Edge.EAST)),
|
||||
"The authored grass cliff must face eastward ocean before rotation.",
|
||||
)
|
||||
_check(
|
||||
grass_beach_transition.ocean_facing_edges
|
||||
== (1 << int(TerrainChunkTopology.Edge.EAST)),
|
||||
"The authored grass/beach join must face eastward ocean before rotation.",
|
||||
)
|
||||
_check(
|
||||
grass_beach_transition.minimum_required_neighbors == 2
|
||||
and "coast" in grass_beach_transition.required_neighbor_tags,
|
||||
"The grass/beach join must remain between two coastline chunks.",
|
||||
)
|
||||
_check(
|
||||
grass_beach_transition.allows_non_water_neighbor_on_edge(
|
||||
grass,
|
||||
TerrainChunkTopology.Edge.WEST,
|
||||
)
|
||||
and not grass_beach_transition.allows_non_water_neighbor_on_edge(
|
||||
sand,
|
||||
TerrainChunkTopology.Edge.WEST,
|
||||
),
|
||||
"The grass-backed transition edge must accept grass and reject sand.",
|
||||
)
|
||||
_check(
|
||||
grass_beach_transition.allows_non_water_neighbor_on_edge(
|
||||
beach,
|
||||
TerrainChunkTopology.Edge.NORTH,
|
||||
)
|
||||
and grass_beach_transition.allows_non_water_neighbor_on_edge(
|
||||
grass_ocean_edge,
|
||||
TerrainChunkTopology.Edge.SOUTH,
|
||||
),
|
||||
"The transition's coastline seams must retain their authored roles.",
|
||||
)
|
||||
_check(
|
||||
not grass_ocean_edge.allows_non_water_neighbor_on_edge(
|
||||
sand,
|
||||
TerrainChunkTopology.Edge.WEST,
|
||||
),
|
||||
"The straight grass coast's inland edge must reject sand.",
|
||||
)
|
||||
_check(
|
||||
grass_ocean_corner.ocean_facing_edges
|
||||
== (
|
||||
(1 << int(TerrainChunkTopology.Edge.EAST))
|
||||
| (1 << int(TerrainChunkTopology.Edge.SOUTH))
|
||||
),
|
||||
"The authored grass corner must expose east and south to ocean.",
|
||||
)
|
||||
_check(
|
||||
grass_ocean_corner.minimum_required_neighbors == 2
|
||||
and (
|
||||
"grass_ocean_edge"
|
||||
in grass_ocean_corner.required_neighbor_tags
|
||||
),
|
||||
"The grass corner must join two straight grass ocean edges.",
|
||||
)
|
||||
_check(
|
||||
grass_ocean_corner.allows_non_water_neighbor_on_edge(
|
||||
grass_ocean_edge,
|
||||
TerrainChunkTopology.Edge.NORTH,
|
||||
)
|
||||
and grass_ocean_corner.allows_non_water_neighbor_on_edge(
|
||||
grass_ocean_edge,
|
||||
TerrainChunkTopology.Edge.WEST,
|
||||
),
|
||||
"Both corner land seams must accept straight grass ocean edges.",
|
||||
)
|
||||
|
||||
|
||||
func _validate_profiles() -> void:
|
||||
for definition: TerrainChunkDefinition in CATALOG.definitions:
|
||||
var variants := TerrainChunkAnalyzer.create_variants(
|
||||
definition,
|
||||
CATALOG.chunk_size,
|
||||
)
|
||||
_check(
|
||||
not variants.is_empty(),
|
||||
"%s must produce at least one rotation variant."
|
||||
% definition.stable_id,
|
||||
)
|
||||
for variant: TerrainChunkVariant in variants:
|
||||
_check(
|
||||
variant.edge_profiles.size() == 4,
|
||||
"%s must expose four edge profiles." % variant.stable_key(),
|
||||
)
|
||||
var ocean_edges := variant.rotated_edge_mask(
|
||||
definition.ocean_facing_edges
|
||||
)
|
||||
for edge_index: int in variant.edge_profiles.size():
|
||||
var profile := variant.edge_profiles[edge_index]
|
||||
_check(
|
||||
(
|
||||
not profile.points.is_empty()
|
||||
or (ocean_edges & (1 << edge_index)) != 0
|
||||
),
|
||||
(
|
||||
"%s has an empty non-ocean edge profile."
|
||||
% variant.stable_key()
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
func _validate_generation() -> void:
|
||||
var first := _make_generator()
|
||||
root.add_child(first)
|
||||
var first_solved := first.generate()
|
||||
_check(first_solved, "Generator must solve the prototype 7x7 grid.")
|
||||
if not first_solved:
|
||||
first.free()
|
||||
return
|
||||
var first_keys := first.placement_keys()
|
||||
_check(first_keys.size() == 49, "Generator must place exactly 49 chunks.")
|
||||
for stable_id: String in REQUIRED_IDS:
|
||||
_check(
|
||||
_placements_contain(first_keys, stable_id),
|
||||
"Generated diagnostic must contain %s." % stable_id,
|
||||
)
|
||||
_check(
|
||||
_all_neighbor_edges_match(first),
|
||||
"Every generated neighboring edge must match.",
|
||||
)
|
||||
_check(
|
||||
_count_placements(first_keys, "chunk_spawn") == 1,
|
||||
"Generated layouts must contain exactly one spawn chunk.",
|
||||
)
|
||||
_check(
|
||||
first_keys[24].begins_with("chunk_spawn@"),
|
||||
"The spawn chunk must occupy the center cell.",
|
||||
)
|
||||
_validate_layout_rules(first)
|
||||
_validate_authored_rotations(first)
|
||||
_validate_generation_summary(first)
|
||||
var first_generated_root := first.get_generated_chunks_root()
|
||||
_check(
|
||||
first.generate(),
|
||||
"Repeated generation on one generator must solve.",
|
||||
)
|
||||
_check(
|
||||
first.placement_keys() == first_keys,
|
||||
"Repeated generation on one generator must remain deterministic.",
|
||||
)
|
||||
_check(
|
||||
first.get_generated_chunks_root() != first_generated_root,
|
||||
"Successful regeneration must replace the generated scene atomically.",
|
||||
)
|
||||
|
||||
var second := _make_generator()
|
||||
root.add_child(second)
|
||||
var second_solved := second.generate()
|
||||
_check(second_solved, "Repeated deterministic generation must solve.")
|
||||
if not second_solved:
|
||||
first.free()
|
||||
second.free()
|
||||
return
|
||||
_check(
|
||||
second.placement_keys() == first_keys,
|
||||
"The same seed and catalog must produce the same layout.",
|
||||
)
|
||||
var restored := _make_generator()
|
||||
restored.generation_seed = TEST_SEED + 999
|
||||
root.add_child(restored)
|
||||
_check(
|
||||
restored.generate_from_placement_keys(first_keys),
|
||||
"A resolved placement manifest must rebuild successfully.",
|
||||
)
|
||||
_check(
|
||||
restored.placement_keys() == first_keys,
|
||||
"A resolved placement manifest must preserve exact rotations.",
|
||||
)
|
||||
first.free()
|
||||
second.free()
|
||||
restored.free()
|
||||
|
||||
for seed_offset: int in range(1, 6):
|
||||
var generator := _make_generator()
|
||||
generator.generation_seed = TEST_SEED + seed_offset
|
||||
root.add_child(generator)
|
||||
var solved := generator.generate()
|
||||
_check(
|
||||
solved,
|
||||
"Generator must solve seed %d." % generator.generation_seed,
|
||||
)
|
||||
if solved:
|
||||
var keys := generator.placement_keys()
|
||||
for stable_id: String in REQUIRED_IDS:
|
||||
_check(
|
||||
_placements_contain(keys, stable_id),
|
||||
"Seed %d must contain %s."
|
||||
% [generator.generation_seed, stable_id],
|
||||
)
|
||||
_check(
|
||||
_all_neighbor_edges_match(generator),
|
||||
"Seed %d has a mismatched neighboring edge."
|
||||
% generator.generation_seed,
|
||||
)
|
||||
_validate_layout_rules(generator)
|
||||
generator.free()
|
||||
|
||||
var connector_stress := _make_generator()
|
||||
connector_stress.generation_seed = CONNECTOR_STRESS_SEED
|
||||
connector_stress.maximum_backtracks = 100
|
||||
root.add_child(connector_stress)
|
||||
_check(
|
||||
connector_stress.generate(),
|
||||
"Connector propagation must solve the known stress seed.",
|
||||
)
|
||||
_check(
|
||||
connector_stress._backtrack_count < 100,
|
||||
"Connector propagation must reject unsupported branches early.",
|
||||
)
|
||||
connector_stress.free()
|
||||
|
||||
|
||||
func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
||||
var counts: Dictionary[StringName, int] = {}
|
||||
for variant: TerrainChunkVariant in generator._variants:
|
||||
var stable_id := variant.definition.stable_id
|
||||
counts[stable_id] = counts.get(stable_id, 0) + 1
|
||||
for stable_id: StringName in EXPECTED_VARIANT_COUNTS:
|
||||
_check(
|
||||
counts.get(stable_id, 0) == EXPECTED_VARIANT_COUNTS[stable_id],
|
||||
"%s must preserve every explicitly allowed rotation." % stable_id,
|
||||
)
|
||||
|
||||
var stream := CATALOG.definition_for_id(&"chunk_0003")
|
||||
_check(stream != null, "The stream definition must be available.")
|
||||
if stream == null:
|
||||
return
|
||||
for quarter_turns: int in 4:
|
||||
var stream_variant := _find_variant(
|
||||
generator,
|
||||
&"chunk_0003",
|
||||
quarter_turns,
|
||||
)
|
||||
_check(
|
||||
stream_variant != null,
|
||||
"The stream must support rotation %d." % quarter_turns,
|
||||
)
|
||||
if stream_variant == null:
|
||||
continue
|
||||
var expected_inlet := 1 << int(TerrainChunkTopology.rotated_edge(
|
||||
TerrainChunkTopology.Edge.NORTH,
|
||||
quarter_turns,
|
||||
))
|
||||
var expected_outlet := 1 << int(TerrainChunkTopology.rotated_edge(
|
||||
TerrainChunkTopology.Edge.SOUTH,
|
||||
quarter_turns,
|
||||
))
|
||||
_check(
|
||||
stream_variant.rotated_edge_mask(stream.water_inlet_edges)
|
||||
== expected_inlet,
|
||||
"The rotated stream inlet must follow its authored orientation.",
|
||||
)
|
||||
_check(
|
||||
stream_variant.rotated_edge_mask(stream.water_outlet_edges)
|
||||
== expected_outlet,
|
||||
"The rotated stream outlet must follow its authored orientation.",
|
||||
)
|
||||
|
||||
var beach := CATALOG.definition_for_id(&"chunk_0002")
|
||||
_check(beach != null, "The beach definition must be available.")
|
||||
if beach == null:
|
||||
return
|
||||
for quarter_turns: int in 4:
|
||||
var beach_variant := _find_variant(
|
||||
generator,
|
||||
&"chunk_0002",
|
||||
quarter_turns,
|
||||
)
|
||||
_check(
|
||||
beach_variant != null,
|
||||
"The beach must support rotation %d." % quarter_turns,
|
||||
)
|
||||
if beach_variant == null:
|
||||
continue
|
||||
var expected_ocean_edge := 1 << int(
|
||||
TerrainChunkTopology.rotated_edge(
|
||||
TerrainChunkTopology.Edge.EAST,
|
||||
quarter_turns,
|
||||
)
|
||||
)
|
||||
_check(
|
||||
beach_variant.rotated_edge_mask(beach.ocean_facing_edges)
|
||||
== expected_ocean_edge,
|
||||
"The rotated beach must keep its low edge facing the ocean.",
|
||||
)
|
||||
_check(
|
||||
not generator._variant_respects_ocean_boundary(
|
||||
beach_variant,
|
||||
Vector2i(3, 3),
|
||||
),
|
||||
"A beach slope must never direct its low edge into the map.",
|
||||
)
|
||||
var boundary_coordinate := Vector2i(3, 3)
|
||||
match TerrainChunkTopology.rotated_edge(
|
||||
TerrainChunkTopology.Edge.EAST,
|
||||
quarter_turns,
|
||||
):
|
||||
TerrainChunkTopology.Edge.NORTH:
|
||||
boundary_coordinate.y = 0
|
||||
TerrainChunkTopology.Edge.EAST:
|
||||
boundary_coordinate.x = generator.grid_size.x - 1
|
||||
TerrainChunkTopology.Edge.SOUTH:
|
||||
boundary_coordinate.y = generator.grid_size.y - 1
|
||||
TerrainChunkTopology.Edge.WEST:
|
||||
boundary_coordinate.x = 0
|
||||
_check(
|
||||
generator._variant_respects_ocean_boundary(
|
||||
beach_variant,
|
||||
boundary_coordinate,
|
||||
),
|
||||
"A beach slope must be legal when its low edge faces open ocean.",
|
||||
)
|
||||
|
||||
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")
|
||||
_check(
|
||||
(
|
||||
grass_ocean_edge != null
|
||||
and grass_beach_transition != null
|
||||
and grass_ocean_corner != null
|
||||
),
|
||||
"The authored coastline additions must be available.",
|
||||
)
|
||||
if (
|
||||
grass_ocean_edge == null
|
||||
or grass_beach_transition == null
|
||||
or grass_ocean_corner == null
|
||||
):
|
||||
return
|
||||
var beach_zero := _find_variant(generator, &"chunk_0002", 0)
|
||||
var grass_edge_zero := _find_variant(generator, &"chunk_0005", 0)
|
||||
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)
|
||||
_check(
|
||||
beach_zero != null
|
||||
and grass_edge_zero != null
|
||||
and grass_edge_south != null
|
||||
and transition_zero != null
|
||||
and corner_zero != null,
|
||||
"The unrotated coastline variants must be available.",
|
||||
)
|
||||
if (
|
||||
beach_zero != null
|
||||
and grass_edge_zero != null
|
||||
and grass_edge_south != null
|
||||
and transition_zero != null
|
||||
and corner_zero != null
|
||||
):
|
||||
_check(
|
||||
grass_edge_zero.profile(
|
||||
TerrainChunkTopology.Edge.EAST
|
||||
).points.is_empty(),
|
||||
"The grass cliff's authored east edge must remain open to ocean.",
|
||||
)
|
||||
_check(
|
||||
transition_zero.profile(
|
||||
TerrainChunkTopology.Edge.NORTH
|
||||
).matches(
|
||||
beach_zero.profile(TerrainChunkTopology.Edge.SOUTH),
|
||||
generator.edge_match_tolerance,
|
||||
),
|
||||
"The transition's north side must match the authored beach.",
|
||||
)
|
||||
_check(
|
||||
transition_zero.profile(
|
||||
TerrainChunkTopology.Edge.SOUTH
|
||||
).matches(
|
||||
grass_edge_zero.profile(TerrainChunkTopology.Edge.NORTH),
|
||||
generator.edge_match_tolerance,
|
||||
),
|
||||
"The transition's south side must match the grass cliff.",
|
||||
)
|
||||
_check(
|
||||
corner_zero.profile(TerrainChunkTopology.Edge.EAST).points.is_empty()
|
||||
and corner_zero.profile(
|
||||
TerrainChunkTopology.Edge.SOUTH
|
||||
).points.is_empty(),
|
||||
"The corner's authored east and south edges must remain open to ocean.",
|
||||
)
|
||||
_check(
|
||||
corner_zero.profile(TerrainChunkTopology.Edge.NORTH).matches(
|
||||
grass_edge_zero.profile(TerrainChunkTopology.Edge.SOUTH),
|
||||
generator.edge_match_tolerance,
|
||||
),
|
||||
"The corner's north side must join a straight grass ocean edge.",
|
||||
)
|
||||
_check(
|
||||
corner_zero.profile(TerrainChunkTopology.Edge.WEST).matches(
|
||||
grass_edge_south.profile(TerrainChunkTopology.Edge.EAST),
|
||||
generator.edge_match_tolerance,
|
||||
),
|
||||
"The corner's west side must join a straight grass ocean edge.",
|
||||
)
|
||||
var flat_grass := _find_variant(generator, &"chunk_0000", 0)
|
||||
var flat_sand := _find_variant(generator, &"chunk_0001", 0)
|
||||
_check(
|
||||
flat_grass != null and flat_sand != null,
|
||||
"Flat grass and sand variants must be available for seam validation.",
|
||||
)
|
||||
if flat_grass != null and flat_sand != null:
|
||||
for quarter_turns: int in 4:
|
||||
var transition := _find_variant(
|
||||
generator,
|
||||
&"chunk_0006",
|
||||
quarter_turns,
|
||||
)
|
||||
if transition == null:
|
||||
continue
|
||||
var inland_edge := TerrainChunkTopology.rotated_edge(
|
||||
TerrainChunkTopology.Edge.WEST,
|
||||
quarter_turns,
|
||||
)
|
||||
var neighbor_edge := TerrainChunkTopology.opposite_edge(inland_edge)
|
||||
_check(
|
||||
generator._edges_are_compatible(
|
||||
transition,
|
||||
inland_edge,
|
||||
flat_grass,
|
||||
neighbor_edge,
|
||||
),
|
||||
"Every transition rotation must accept grass behind it.",
|
||||
)
|
||||
_check(
|
||||
not generator._edges_are_compatible(
|
||||
transition,
|
||||
inland_edge,
|
||||
flat_sand,
|
||||
neighbor_edge,
|
||||
),
|
||||
"No transition rotation may accept sand behind it.",
|
||||
)
|
||||
for stable_id: StringName in [&"chunk_0005", &"chunk_0006", &"chunk_0007"]:
|
||||
for quarter_turns: int in 4:
|
||||
var coast_variant := _find_variant(
|
||||
generator,
|
||||
stable_id,
|
||||
quarter_turns,
|
||||
)
|
||||
_check(
|
||||
coast_variant != null,
|
||||
"%s must support rotation %d." % [stable_id, quarter_turns],
|
||||
)
|
||||
if coast_variant == null:
|
||||
continue
|
||||
_check(
|
||||
not generator._variant_respects_ocean_boundary(
|
||||
coast_variant,
|
||||
Vector2i(3, 3),
|
||||
),
|
||||
"%s must never place its ocean edge inside the map." % stable_id,
|
||||
)
|
||||
if stable_id != &"chunk_0007":
|
||||
continue
|
||||
var boundary_coordinate := Vector2i(3, 3)
|
||||
var ocean_edges := coast_variant.rotated_edge_mask(
|
||||
grass_ocean_corner.ocean_facing_edges
|
||||
)
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
if (ocean_edges & (1 << edge_value)) == 0:
|
||||
continue
|
||||
match edge_value as TerrainChunkTopology.Edge:
|
||||
TerrainChunkTopology.Edge.NORTH:
|
||||
boundary_coordinate.y = 0
|
||||
TerrainChunkTopology.Edge.EAST:
|
||||
boundary_coordinate.x = generator.grid_size.x - 1
|
||||
TerrainChunkTopology.Edge.SOUTH:
|
||||
boundary_coordinate.y = generator.grid_size.y - 1
|
||||
TerrainChunkTopology.Edge.WEST:
|
||||
boundary_coordinate.x = 0
|
||||
_check(
|
||||
generator._variant_respects_ocean_boundary(
|
||||
coast_variant,
|
||||
boundary_coordinate,
|
||||
),
|
||||
"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)) == 33,
|
||||
"The current catalog must expose all 33 authored rotations.",
|
||||
)
|
||||
_check(
|
||||
int(summary.get("solver_variant_count", 0))
|
||||
== EXPECTED_SOLVER_VARIANT_COUNT,
|
||||
"Equivalent visual rotations must share one solver constraint.",
|
||||
)
|
||||
_check(
|
||||
int(summary.get("adjacent_repeat_edges", -1))
|
||||
== generator._count_adjacent_repeat_edges(),
|
||||
"The summary must report adjacent repeated chunk definitions.",
|
||||
)
|
||||
_check(
|
||||
str(summary.get("layout_fingerprint", ""))
|
||||
== generator.placement_fingerprint(),
|
||||
"The summary must identify the exact resolved layout.",
|
||||
)
|
||||
_check(
|
||||
generator.placement_fingerprint().length() == 64,
|
||||
"The resolved layout fingerprint must be SHA-256.",
|
||||
)
|
||||
var variant_counts: Dictionary = summary.get("variant_counts", {})
|
||||
for stable_id: StringName in EXPECTED_VARIANT_COUNTS:
|
||||
_check(
|
||||
int(variant_counts.get(stable_id, 0))
|
||||
== EXPECTED_VARIANT_COUNTS[stable_id],
|
||||
"The summary must report %s rotation candidates." % stable_id,
|
||||
)
|
||||
|
||||
|
||||
func _find_variant(
|
||||
generator: TerrainChunkGenerator,
|
||||
stable_id: StringName,
|
||||
quarter_turns: int,
|
||||
) -> TerrainChunkVariant:
|
||||
for variant: TerrainChunkVariant in generator._variants:
|
||||
if (
|
||||
variant.definition.stable_id == stable_id
|
||||
and variant.quarter_turns == quarter_turns
|
||||
):
|
||||
return variant
|
||||
return null
|
||||
|
||||
|
||||
func _make_generator() -> TerrainChunkGenerator:
|
||||
var generator := TerrainChunkGenerator.new()
|
||||
generator.catalog = CATALOG
|
||||
generator.grid_size = Vector2i(7, 7)
|
||||
generator.generation_seed = TEST_SEED
|
||||
generator.generate_on_ready = false
|
||||
generator.build_collision = false
|
||||
generator.force_center_chunk_id = &"chunk_spawn"
|
||||
generator.required_chunk_ids = PackedStringArray(REQUIRED_IDS)
|
||||
generator.maximum_backtracks = 100000
|
||||
return generator
|
||||
|
||||
|
||||
func _placements_contain(keys: PackedStringArray, stable_id: String) -> bool:
|
||||
for key: String in keys:
|
||||
if key.begins_with(stable_id + "@"):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _count_placements(keys: PackedStringArray, stable_id: String) -> int:
|
||||
var count := 0
|
||||
for key: String in keys:
|
||||
if key.begins_with(stable_id + "@"):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _all_neighbor_edges_match(generator: TerrainChunkGenerator) -> bool:
|
||||
for index: int in generator._placements.size():
|
||||
var coordinate := Vector2i(
|
||||
index % generator.grid_size.x,
|
||||
index / generator.grid_size.x,
|
||||
)
|
||||
var current: TerrainChunkVariant = generator._placements[index]
|
||||
if coordinate.x > 0:
|
||||
var west: TerrainChunkVariant = generator._placements[index - 1]
|
||||
if not generator._edges_are_compatible(
|
||||
current,
|
||||
TerrainChunkTopology.Edge.WEST,
|
||||
west,
|
||||
TerrainChunkTopology.Edge.EAST,
|
||||
):
|
||||
return false
|
||||
if coordinate.y > 0:
|
||||
var north: TerrainChunkVariant = (
|
||||
generator._placements[index - generator.grid_size.x]
|
||||
)
|
||||
if not generator._edges_are_compatible(
|
||||
current,
|
||||
TerrainChunkTopology.Edge.NORTH,
|
||||
north,
|
||||
TerrainChunkTopology.Edge.SOUTH,
|
||||
):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _validate_layout_rules(generator: TerrainChunkGenerator) -> void:
|
||||
_check(
|
||||
generator._neighbor_requirement_validation_error(
|
||||
generator._placements
|
||||
).is_empty(),
|
||||
"Every generated chunk must satisfy its authored neighbor count.",
|
||||
)
|
||||
_check(
|
||||
generator._walkable_connectivity_validation_error(
|
||||
generator._placements
|
||||
).is_empty(),
|
||||
"All walkable generated terrain must remain connected to spawn.",
|
||||
)
|
||||
var center := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
)
|
||||
var safe_spawn_neighbors := 0
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
var neighbor_coordinate := (
|
||||
center
|
||||
+ TerrainChunkTopology.grid_offset(
|
||||
edge_value as TerrainChunkTopology.Edge
|
||||
)
|
||||
)
|
||||
var neighbor := generator._placements[
|
||||
neighbor_coordinate.y * generator.grid_size.x
|
||||
+ neighbor_coordinate.x
|
||||
]
|
||||
if "spawn_safe" in neighbor.definition.tags:
|
||||
safe_spawn_neighbors += 1
|
||||
_check(
|
||||
safe_spawn_neighbors >= 3,
|
||||
"At least three cardinal spawn neighbors must be safe flat grass.",
|
||||
)
|
||||
for index: int in generator._placements.size():
|
||||
var placement := generator._placements[index]
|
||||
var coordinate := Vector2i(
|
||||
index % generator.grid_size.x,
|
||||
index / generator.grid_size.x,
|
||||
)
|
||||
if "coast" in placement.definition.tags:
|
||||
_check(
|
||||
generator._variant_respects_ocean_boundary(
|
||||
placement,
|
||||
coordinate,
|
||||
),
|
||||
"Every coastline chunk must direct its ocean edge outside the grid.",
|
||||
)
|
||||
if placement.definition.stable_id == &"chunk_0006":
|
||||
var touches_beach := false
|
||||
var touches_grass_edge := false
|
||||
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
|
||||
]
|
||||
touches_beach = (
|
||||
touches_beach
|
||||
or neighbor.definition.stable_id == &"chunk_0002"
|
||||
)
|
||||
touches_grass_edge = (
|
||||
touches_grass_edge
|
||||
or neighbor.definition.stable_id == &"chunk_0005"
|
||||
)
|
||||
_check(
|
||||
touches_beach and touches_grass_edge,
|
||||
"Every grass/beach transition must join both authored edge types.",
|
||||
)
|
||||
if placement.definition.stable_id == &"chunk_0007":
|
||||
var straight_edge_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_0005":
|
||||
straight_edge_neighbors += 1
|
||||
_check(
|
||||
straight_edge_neighbors == 2,
|
||||
"Every grass corner must join two straight grass ocean edges.",
|
||||
)
|
||||
if placement.definition.stable_id != &"chunk_0001":
|
||||
continue
|
||||
var touches_coast := false
|
||||
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 "coast" in neighbor.definition.tags:
|
||||
touches_coast = true
|
||||
_check(
|
||||
touches_coast,
|
||||
"Every flat-sand chunk must remain directly attached to a beach.",
|
||||
)
|
||||
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
_failures.append(message)
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
if _failures.is_empty():
|
||||
print("Terrain chunk generator validation: PASS")
|
||||
quit(0)
|
||||
return
|
||||
for failure: String in _failures:
|
||||
printerr("Terrain chunk generator validation: ", failure)
|
||||
quit(1)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bt6pjgkomx76s
|
||||
|
|
@ -16,7 +16,7 @@ const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
|||
|
||||
|
||||
func _initialize() -> void:
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 9)
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 8)
|
||||
assert(
|
||||
NetworkWorldSpawnProtocol.CAPABILITY
|
||||
== NetworkProtocol.WORLD_SPAWN_CAPABILITY
|
||||
|
|
|
|||
|
|
@ -1,805 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Export convention-named terrain chunk collections to individual GLBs.
|
||||
|
||||
Run through Blender rather than a standalone Python interpreter:
|
||||
|
||||
blender --background terrain_chunks.blend \
|
||||
--python tools/blender/export_terrain_chunks.py -- \
|
||||
--output /path/to/terrain_chunks
|
||||
|
||||
Collections use ``chunk_####_description`` and contain one primary terrain
|
||||
mesh named ``chunk_####``. Additional production objects may live in the same
|
||||
collection. Reusable procedural props use individual ``prop_description``
|
||||
mesh objects. They may be arranged anywhere in the source file because each
|
||||
object's authored origin becomes the exported runtime anchor. A same-named
|
||||
``prop_description`` collection remains supported for multi-object props.
|
||||
Unrelated objects and collections are ignored.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
CHUNK_SIZE_METERS = 10.0
|
||||
# Match the runtime boundary analyzer's five-millimeter authoring tolerance.
|
||||
DEFAULT_TOLERANCE = 0.005
|
||||
COLLECTION_PATTERN = re.compile(
|
||||
r"^chunk_(?P<number>\d{4})(?:_(?P<label>[a-z0-9]+(?:_[a-z0-9]+)*))?$"
|
||||
)
|
||||
PROP_COLLECTION_PATTERN = re.compile(
|
||||
r"^prop_(?P<label>[a-z0-9]+(?:_[a-z0-9]+)*)$"
|
||||
)
|
||||
ALLOWED_OBJECT_TYPES = {"EMPTY", "MESH"}
|
||||
|
||||
|
||||
class ExportValidationError(RuntimeError):
|
||||
"""Raised when the source file does not satisfy the chunk convention."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Bounds:
|
||||
minimum: tuple[float, float, float]
|
||||
maximum: tuple[float, float, float]
|
||||
|
||||
@property
|
||||
def size(self) -> tuple[float, float, float]:
|
||||
return tuple(
|
||||
self.maximum[axis] - self.minimum[axis] for axis in range(3)
|
||||
)
|
||||
|
||||
@property
|
||||
def center(self) -> tuple[float, float, float]:
|
||||
return tuple(
|
||||
(self.minimum[axis] + self.maximum[axis]) * 0.5
|
||||
for axis in range(3)
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChunkSource:
|
||||
number: int
|
||||
stable_id: str
|
||||
label: str
|
||||
collection: bpy.types.Collection
|
||||
primary_mesh: bpy.types.Object
|
||||
objects: tuple[bpy.types.Object, ...]
|
||||
bounds: Bounds
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PropSource:
|
||||
stable_id: str
|
||||
label: str
|
||||
source_kind: str
|
||||
source_name: str
|
||||
collection: bpy.types.Collection
|
||||
primary_mesh: bpy.types.Object
|
||||
objects: tuple[bpy.types.Object, ...]
|
||||
bounds: Bounds
|
||||
anchor_world: tuple[float, float, float]
|
||||
|
||||
|
||||
ExportSource = ChunkSource | PropSource
|
||||
|
||||
|
||||
def _parse_arguments() -> argparse.Namespace:
|
||||
script_arguments: list[str] = []
|
||||
if "--" in sys.argv:
|
||||
script_arguments = sys.argv[sys.argv.index("--") + 1 :]
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Export NETfishing terrain chunk collections to GLB files."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
required=True,
|
||||
help=(
|
||||
"Directory that will receive chunk_####.glb, prop_*.glb, and "
|
||||
"chunks.json."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tolerance",
|
||||
type=float,
|
||||
default=DEFAULT_TOLERANCE,
|
||||
help="World-space validation tolerance in meters.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Validate and report chunks without writing output files.",
|
||||
)
|
||||
return parser.parse_args(script_arguments)
|
||||
|
||||
|
||||
def _close_enough(actual: float, expected: float, tolerance: float) -> bool:
|
||||
return math.isclose(actual, expected, rel_tol=0.0, abs_tol=tolerance)
|
||||
|
||||
|
||||
def _format_vector(values: Iterable[float]) -> str:
|
||||
return "(" + ", ".join(f"{value:.4f}" for value in values) + ")"
|
||||
|
||||
|
||||
def _world_bounds(mesh_object: bpy.types.Object) -> Bounds:
|
||||
evaluated_object = mesh_object.evaluated_get(
|
||||
bpy.context.evaluated_depsgraph_get()
|
||||
)
|
||||
world_corners = [
|
||||
evaluated_object.matrix_world @ Vector(corner)
|
||||
for corner in evaluated_object.bound_box
|
||||
]
|
||||
minimum = tuple(
|
||||
min(corner[axis] for corner in world_corners) for axis in range(3)
|
||||
)
|
||||
maximum = tuple(
|
||||
max(corner[axis] for corner in world_corners) for axis in range(3)
|
||||
)
|
||||
return Bounds(minimum=minimum, maximum=maximum)
|
||||
|
||||
|
||||
def _validate_transform(
|
||||
mesh_object: bpy.types.Object,
|
||||
tolerance: float,
|
||||
) -> list[str]:
|
||||
problems: list[str] = []
|
||||
for axis, value in zip("XYZ", mesh_object.location):
|
||||
if not _close_enough(value, 0.0, tolerance):
|
||||
problems.append(f"location {axis} is {value:.6f}, expected 0")
|
||||
for axis, value in zip("XYZ", mesh_object.rotation_euler):
|
||||
if not _close_enough(value, 0.0, tolerance):
|
||||
problems.append(f"rotation {axis} is {value:.6f}, expected 0")
|
||||
for axis, value in zip("XYZ", mesh_object.scale):
|
||||
if not _close_enough(value, 1.0, tolerance):
|
||||
problems.append(f"scale {axis} is {value:.6f}, expected 1")
|
||||
return problems
|
||||
|
||||
|
||||
def _validate_bounds(
|
||||
bounds: Bounds,
|
||||
tolerance: float,
|
||||
allow_open_east_edge: bool = False,
|
||||
allow_open_south_edge: bool = False,
|
||||
) -> list[str]:
|
||||
problems: list[str] = []
|
||||
expected_half_size = CHUNK_SIZE_METERS * 0.5
|
||||
expected_values = {
|
||||
"minimum X": (bounds.minimum[0], -expected_half_size),
|
||||
"maximum Y": (bounds.maximum[1], expected_half_size),
|
||||
}
|
||||
if not allow_open_east_edge:
|
||||
expected_values["maximum X"] = (
|
||||
bounds.maximum[0],
|
||||
expected_half_size,
|
||||
)
|
||||
if not allow_open_south_edge:
|
||||
expected_values["minimum Y"] = (
|
||||
bounds.minimum[1],
|
||||
-expected_half_size,
|
||||
)
|
||||
for label, (actual, expected) in expected_values.items():
|
||||
if not _close_enough(actual, expected, tolerance):
|
||||
problems.append(f"{label} is {actual:.6f}, expected {expected:.6f}")
|
||||
if (
|
||||
allow_open_east_edge
|
||||
and bounds.maximum[0] > expected_half_size + tolerance
|
||||
):
|
||||
problems.append(
|
||||
f"maximum X is {bounds.maximum[0]:.6f}, expected no greater than "
|
||||
f"{expected_half_size:.6f}"
|
||||
)
|
||||
if (
|
||||
allow_open_south_edge
|
||||
and bounds.minimum[1] < -expected_half_size - tolerance
|
||||
):
|
||||
problems.append(
|
||||
f"minimum Y is {bounds.minimum[1]:.6f}, expected no less than "
|
||||
f"{-expected_half_size:.6f}"
|
||||
)
|
||||
return problems
|
||||
|
||||
|
||||
def _discover_chunks(tolerance: float) -> list[ChunkSource]:
|
||||
chunks: list[ChunkSource] = []
|
||||
errors: list[str] = []
|
||||
claimed_objects: dict[str, str] = {}
|
||||
claimed_numbers: dict[int, str] = {}
|
||||
|
||||
for collection in sorted(bpy.data.collections, key=lambda item: item.name):
|
||||
match = COLLECTION_PATTERN.fullmatch(collection.name)
|
||||
if match is None:
|
||||
continue
|
||||
|
||||
number = int(match.group("number"))
|
||||
stable_id = f"chunk_{number:04d}"
|
||||
label = match.group("label") or ""
|
||||
if number in claimed_numbers:
|
||||
errors.append(
|
||||
f"{collection.name}: duplicates numeric ID already used by "
|
||||
f"{claimed_numbers[number]}"
|
||||
)
|
||||
continue
|
||||
claimed_numbers[number] = collection.name
|
||||
|
||||
objects = tuple(sorted(collection.all_objects, key=lambda item: item.name))
|
||||
if not objects:
|
||||
errors.append(f"{collection.name}: collection is empty")
|
||||
continue
|
||||
|
||||
invalid_objects = [
|
||||
item for item in objects if item.type not in ALLOWED_OBJECT_TYPES
|
||||
]
|
||||
if invalid_objects:
|
||||
details = ", ".join(
|
||||
f"{item.name} ({item.type})" for item in invalid_objects
|
||||
)
|
||||
errors.append(
|
||||
f"{collection.name}: contains unsupported objects: {details}"
|
||||
)
|
||||
|
||||
primary_matches = [
|
||||
item
|
||||
for item in objects
|
||||
if item.name == stable_id and item.type == "MESH"
|
||||
]
|
||||
if len(primary_matches) != 1:
|
||||
errors.append(
|
||||
f"{collection.name}: expected exactly one primary mesh named "
|
||||
f"{stable_id}, found {len(primary_matches)}"
|
||||
)
|
||||
continue
|
||||
primary_mesh = primary_matches[0]
|
||||
|
||||
for item in objects:
|
||||
previous_collection = claimed_objects.get(item.name)
|
||||
if previous_collection is not None:
|
||||
errors.append(
|
||||
f"{collection.name}: object {item.name} is also exported by "
|
||||
f"{previous_collection}"
|
||||
)
|
||||
else:
|
||||
claimed_objects[item.name] = collection.name
|
||||
|
||||
transform_problems = _validate_transform(primary_mesh, tolerance)
|
||||
errors.extend(
|
||||
f"{collection.name}/{primary_mesh.name}: {problem}"
|
||||
for problem in transform_problems
|
||||
)
|
||||
|
||||
bounds = _world_bounds(primary_mesh)
|
||||
# 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.
|
||||
allow_open_east_edge = "ocean_edge" in label
|
||||
# Corner pieces additionally leave canonical negative Y open. Their
|
||||
# two marked ocean edges rotate together at runtime.
|
||||
allow_open_south_edge = "ocean_edge_corner" in label
|
||||
bound_problems = _validate_bounds(
|
||||
bounds,
|
||||
tolerance,
|
||||
allow_open_east_edge=allow_open_east_edge,
|
||||
allow_open_south_edge=allow_open_south_edge,
|
||||
)
|
||||
errors.extend(
|
||||
f"{collection.name}/{primary_mesh.name}: {problem}"
|
||||
for problem in bound_problems
|
||||
)
|
||||
|
||||
if len(primary_mesh.data.vertices) < 3:
|
||||
errors.append(
|
||||
f"{collection.name}/{primary_mesh.name}: mesh has fewer than "
|
||||
"three vertices"
|
||||
)
|
||||
if len(primary_mesh.data.materials) == 0:
|
||||
errors.append(
|
||||
f"{collection.name}/{primary_mesh.name}: mesh has no material"
|
||||
)
|
||||
|
||||
chunks.append(
|
||||
ChunkSource(
|
||||
number=number,
|
||||
stable_id=stable_id,
|
||||
label=label,
|
||||
collection=collection,
|
||||
primary_mesh=primary_mesh,
|
||||
objects=objects,
|
||||
bounds=bounds,
|
||||
)
|
||||
)
|
||||
|
||||
if not chunks:
|
||||
errors.append(
|
||||
"No collections matched chunk_####_description (for example, "
|
||||
"chunk_0000_grass)."
|
||||
)
|
||||
if errors:
|
||||
raise ExportValidationError("\n".join(errors))
|
||||
return sorted(chunks, key=lambda item: item.number)
|
||||
|
||||
|
||||
def _validate_prop_bounds(bounds: Bounds, tolerance: float) -> list[str]:
|
||||
problems: list[str] = []
|
||||
for axis, size in zip("XYZ", bounds.size):
|
||||
if size <= tolerance:
|
||||
problems.append(f"bounds size {axis} is {size:.6f}, expected positive")
|
||||
return problems
|
||||
|
||||
|
||||
def _rebased_prop_bounds(
|
||||
mesh_objects: list[bpy.types.Object],
|
||||
anchor_world: Vector,
|
||||
) -> Bounds:
|
||||
corners: list[Vector] = []
|
||||
dependency_graph = bpy.context.evaluated_depsgraph_get()
|
||||
for mesh_object in mesh_objects:
|
||||
evaluated_object = mesh_object.evaluated_get(dependency_graph)
|
||||
corners.extend(
|
||||
evaluated_object.matrix_world @ Vector(corner) - anchor_world
|
||||
for corner in evaluated_object.bound_box
|
||||
)
|
||||
minimum = tuple(
|
||||
min(corner[axis] for corner in corners) for axis in range(3)
|
||||
)
|
||||
maximum = tuple(
|
||||
max(corner[axis] for corner in corners) for axis in range(3)
|
||||
)
|
||||
return Bounds(minimum=minimum, maximum=maximum)
|
||||
|
||||
|
||||
def _prop_transform_problems(
|
||||
source_objects: tuple[bpy.types.Object, ...],
|
||||
tolerance: float,
|
||||
) -> list[str]:
|
||||
problems: list[str] = []
|
||||
source_set = set(source_objects)
|
||||
for item in source_objects:
|
||||
if item.parent is not None and item.parent not in source_set:
|
||||
problems.append(
|
||||
f"{item.name} is parented outside its exported prop source"
|
||||
)
|
||||
for label, values in (
|
||||
("location", item.location),
|
||||
("rotation", item.rotation_euler),
|
||||
("scale", item.scale),
|
||||
):
|
||||
if not all(math.isfinite(value) for value in values):
|
||||
problems.append(f"{item.name} has a non-finite {label}")
|
||||
for axis, value in zip("XYZ", item.scale):
|
||||
if abs(value) <= tolerance:
|
||||
problems.append(
|
||||
f"{item.name} scale {axis} is {value:.6f}, expected nonzero"
|
||||
)
|
||||
return problems
|
||||
|
||||
|
||||
def _make_prop_source(
|
||||
stable_id: str,
|
||||
label: str,
|
||||
source_kind: str,
|
||||
source_name: str,
|
||||
collection: bpy.types.Collection,
|
||||
objects: tuple[bpy.types.Object, ...],
|
||||
tolerance: float,
|
||||
) -> tuple[PropSource | None, list[str]]:
|
||||
errors: list[str] = []
|
||||
invalid_objects = [
|
||||
item for item in objects if item.type not in ALLOWED_OBJECT_TYPES
|
||||
]
|
||||
if invalid_objects:
|
||||
details = ", ".join(
|
||||
f"{item.name} ({item.type})" for item in invalid_objects
|
||||
)
|
||||
errors.append(f"{source_name}: contains unsupported objects: {details}")
|
||||
|
||||
mesh_objects = [item for item in objects if item.type == "MESH"]
|
||||
named_meshes = [item for item in mesh_objects if item.name == stable_id]
|
||||
if len(named_meshes) == 1:
|
||||
primary_mesh = named_meshes[0]
|
||||
elif len(mesh_objects) == 1:
|
||||
primary_mesh = mesh_objects[0]
|
||||
else:
|
||||
errors.append(
|
||||
f"{source_name}: expected one mesh (preferably named "
|
||||
f"{stable_id}), found {len(mesh_objects)}"
|
||||
)
|
||||
return None, errors
|
||||
|
||||
errors.extend(
|
||||
f"{source_name}: {problem}"
|
||||
for problem in _prop_transform_problems(objects, tolerance)
|
||||
)
|
||||
anchor_vector = primary_mesh.matrix_world.translation.copy()
|
||||
bounds = _rebased_prop_bounds(mesh_objects, anchor_vector)
|
||||
errors.extend(
|
||||
f"{source_name}/{primary_mesh.name}: {problem}"
|
||||
for problem in _validate_prop_bounds(bounds, tolerance)
|
||||
)
|
||||
for mesh_object in mesh_objects:
|
||||
if len(mesh_object.data.vertices) < 3:
|
||||
errors.append(
|
||||
f"{source_name}/{mesh_object.name}: has fewer than three vertices"
|
||||
)
|
||||
if len(mesh_object.data.materials) == 0:
|
||||
errors.append(
|
||||
f"{source_name}/{mesh_object.name}: has no material"
|
||||
)
|
||||
|
||||
return (
|
||||
PropSource(
|
||||
stable_id=stable_id,
|
||||
label=label,
|
||||
source_kind=source_kind,
|
||||
source_name=source_name,
|
||||
collection=collection,
|
||||
primary_mesh=primary_mesh,
|
||||
objects=objects,
|
||||
bounds=bounds,
|
||||
anchor_world=tuple(anchor_vector),
|
||||
),
|
||||
errors,
|
||||
)
|
||||
|
||||
|
||||
def _discover_props(tolerance: float) -> list[PropSource]:
|
||||
props: list[PropSource] = []
|
||||
errors: list[str] = []
|
||||
claimed_objects: dict[int, str] = {}
|
||||
claimed_ids: dict[str, str] = {}
|
||||
|
||||
for collection in sorted(bpy.data.collections, key=lambda item: item.name):
|
||||
match = PROP_COLLECTION_PATTERN.fullmatch(collection.name)
|
||||
if match is None:
|
||||
continue
|
||||
stable_id = collection.name
|
||||
label = match.group("label")
|
||||
objects = tuple(sorted(collection.all_objects, key=lambda item: item.name))
|
||||
if not objects:
|
||||
errors.append(f"{collection.name}: collection is empty")
|
||||
continue
|
||||
for item in objects:
|
||||
object_key = item.as_pointer()
|
||||
previous_source = claimed_objects.get(object_key)
|
||||
if previous_source is not None:
|
||||
errors.append(
|
||||
f"{collection.name}: object {item.name} is also exported by "
|
||||
f"{previous_source}"
|
||||
)
|
||||
else:
|
||||
claimed_objects[object_key] = collection.name
|
||||
prop, source_errors = _make_prop_source(
|
||||
stable_id,
|
||||
label,
|
||||
"collection",
|
||||
collection.name,
|
||||
collection,
|
||||
objects,
|
||||
tolerance,
|
||||
)
|
||||
errors.extend(source_errors)
|
||||
if prop is not None:
|
||||
props.append(prop)
|
||||
claimed_ids[stable_id] = collection.name
|
||||
|
||||
for item in sorted(bpy.data.objects, key=lambda value: value.name):
|
||||
match = PROP_COLLECTION_PATTERN.fullmatch(item.name)
|
||||
if match is None or item.as_pointer() in claimed_objects:
|
||||
continue
|
||||
stable_id = item.name
|
||||
if stable_id in claimed_ids:
|
||||
errors.append(
|
||||
f"{item.name}: duplicates prop ID already used by "
|
||||
f"{claimed_ids[stable_id]}"
|
||||
)
|
||||
continue
|
||||
if item.type != "MESH":
|
||||
errors.append(f"{item.name}: prop object must be a mesh")
|
||||
continue
|
||||
collections = sorted(item.users_collection, key=lambda value: value.name)
|
||||
if not collections:
|
||||
errors.append(f"{item.name}: prop object belongs to no collection")
|
||||
continue
|
||||
prop, source_errors = _make_prop_source(
|
||||
stable_id,
|
||||
match.group("label"),
|
||||
"object",
|
||||
item.name,
|
||||
collections[0],
|
||||
(item,),
|
||||
tolerance,
|
||||
)
|
||||
errors.extend(source_errors)
|
||||
if prop is not None:
|
||||
props.append(prop)
|
||||
claimed_ids[stable_id] = item.name
|
||||
claimed_objects[item.as_pointer()] = item.name
|
||||
|
||||
if errors:
|
||||
raise ExportValidationError("\n".join(errors))
|
||||
return sorted(props, key=lambda item: item.stable_id)
|
||||
|
||||
|
||||
def _validate_distinct_source_membership(
|
||||
chunks: list[ChunkSource],
|
||||
props: list[PropSource],
|
||||
) -> None:
|
||||
claimed_objects: dict[int, str] = {}
|
||||
errors: list[str] = []
|
||||
for source in [*chunks, *props]:
|
||||
source_name = (
|
||||
source.collection.name
|
||||
if isinstance(source, ChunkSource)
|
||||
else source.source_name
|
||||
)
|
||||
for item in source.objects:
|
||||
object_key = item.as_pointer()
|
||||
previous_source = claimed_objects.get(object_key)
|
||||
if previous_source is not None:
|
||||
errors.append(
|
||||
f"{source_name}: object {item.name} is also exported by "
|
||||
f"{previous_source}"
|
||||
)
|
||||
else:
|
||||
claimed_objects[object_key] = source_name
|
||||
if errors:
|
||||
raise ExportValidationError("\n".join(errors))
|
||||
|
||||
|
||||
def _select_source(source: ExportSource) -> None:
|
||||
if bpy.context.object is not None and bpy.context.object.mode != "OBJECT":
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
# Blender's selection operator can miss objects after a glTF export has
|
||||
# temporarily changed visibility/context. Set every object explicitly so
|
||||
# one chunk can never leak into a later chunk's selected-only export.
|
||||
selected_objects = set(source.objects)
|
||||
for item in bpy.context.view_layer.objects:
|
||||
item.select_set(item in selected_objects)
|
||||
for item in source.objects:
|
||||
item.hide_select = False
|
||||
item.hide_viewport = False
|
||||
item.hide_set(False)
|
||||
item.select_set(True)
|
||||
bpy.context.view_layer.objects.active = source.primary_mesh
|
||||
|
||||
|
||||
def _export_source(source: ExportSource, output_path: Path) -> None:
|
||||
_select_source(source)
|
||||
saved_root_matrices: dict[bpy.types.Object, object] = {}
|
||||
if isinstance(source, PropSource):
|
||||
source_objects = set(source.objects)
|
||||
anchor = Vector(source.anchor_world)
|
||||
for item in source.objects:
|
||||
if item.parent in source_objects:
|
||||
continue
|
||||
saved_root_matrices[item] = item.matrix_world.copy()
|
||||
rebased_matrix = item.matrix_world.copy()
|
||||
rebased_matrix.translation -= anchor
|
||||
item.matrix_world = rebased_matrix
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
try:
|
||||
result = bpy.ops.export_scene.gltf(
|
||||
filepath=str(output_path),
|
||||
check_existing=False,
|
||||
export_format="GLB",
|
||||
use_selection=True,
|
||||
export_apply=True,
|
||||
export_animations=False,
|
||||
export_cameras=False,
|
||||
export_lights=False,
|
||||
export_materials="EXPORT",
|
||||
export_morph=False,
|
||||
export_normals=True,
|
||||
export_skins=False,
|
||||
export_tangents=False,
|
||||
export_texcoords=True,
|
||||
export_yup=True,
|
||||
export_extras=True,
|
||||
will_save_settings=False,
|
||||
)
|
||||
finally:
|
||||
for item, matrix in saved_root_matrices.items():
|
||||
item.matrix_world = matrix
|
||||
if saved_root_matrices:
|
||||
bpy.context.view_layer.update()
|
||||
if result != {"FINISHED"}:
|
||||
source_name = (
|
||||
source.collection.name
|
||||
if isinstance(source, ChunkSource)
|
||||
else source.source_name
|
||||
)
|
||||
raise RuntimeError(
|
||||
f"Blender failed to export {source_name}: {result}"
|
||||
)
|
||||
_validate_exported_object_membership(source, output_path)
|
||||
|
||||
|
||||
def _validate_exported_object_membership(
|
||||
source: ExportSource,
|
||||
output_path: Path,
|
||||
) -> None:
|
||||
"""Confirm selected-only export contains exactly the intended objects."""
|
||||
with output_path.open("rb") as binary_file:
|
||||
header = binary_file.read(20)
|
||||
if len(header) != 20 or header[:4] != b"glTF":
|
||||
raise RuntimeError(f"{output_path.name} is not a valid GLB file")
|
||||
json_length, json_kind = struct.unpack_from("<II", header, 12)
|
||||
if json_kind != 0x4E4F534A:
|
||||
raise RuntimeError(
|
||||
f"{output_path.name} does not begin with a GLB JSON chunk"
|
||||
)
|
||||
document = json.loads(binary_file.read(json_length))
|
||||
exported_names = {
|
||||
str(node.get("name", ""))
|
||||
for node in document.get("nodes", [])
|
||||
if str(node.get("name", ""))
|
||||
}
|
||||
expected_names = {item.name for item in source.objects}
|
||||
if exported_names != expected_names:
|
||||
raise RuntimeError(
|
||||
f"{output_path.name} object membership mismatch: expected "
|
||||
f"{sorted(expected_names)}, exported {sorted(exported_names)}"
|
||||
)
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as source:
|
||||
for block in iter(lambda: source.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _round_vector(values: Iterable[float]) -> list[float]:
|
||||
return [round(value, 6) for value in values]
|
||||
|
||||
|
||||
def _manifest_entry(chunk: ChunkSource, output_path: Path) -> dict[str, object]:
|
||||
return {
|
||||
"id": chunk.stable_id,
|
||||
"number": chunk.number,
|
||||
"label": chunk.label,
|
||||
"collection": chunk.collection.name,
|
||||
"primary_mesh": chunk.primary_mesh.name,
|
||||
"objects": [item.name for item in chunk.objects],
|
||||
"file": output_path.name,
|
||||
"sha256": _sha256(output_path),
|
||||
"bounds": {
|
||||
"minimum": _round_vector(chunk.bounds.minimum),
|
||||
"maximum": _round_vector(chunk.bounds.maximum),
|
||||
"size": _round_vector(chunk.bounds.size),
|
||||
"center": _round_vector(chunk.bounds.center),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _prop_manifest_entry(
|
||||
prop: PropSource,
|
||||
output_path: Path,
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"id": prop.stable_id,
|
||||
"label": prop.label,
|
||||
"source_kind": prop.source_kind,
|
||||
"source_name": prop.source_name,
|
||||
"collection": prop.collection.name,
|
||||
"primary_mesh": prop.primary_mesh.name,
|
||||
"objects": [item.name for item in prop.objects],
|
||||
"source_anchor_world": _round_vector(prop.anchor_world),
|
||||
"file": output_path.name,
|
||||
"sha256": _sha256(output_path),
|
||||
"bounds": {
|
||||
"minimum": _round_vector(prop.bounds.minimum),
|
||||
"maximum": _round_vector(prop.bounds.maximum),
|
||||
"size": _round_vector(prop.bounds.size),
|
||||
"center": _round_vector(prop.bounds.center),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _run() -> int:
|
||||
arguments = _parse_arguments()
|
||||
if arguments.tolerance <= 0.0:
|
||||
raise ExportValidationError("--tolerance must be greater than zero")
|
||||
|
||||
chunks = _discover_chunks(arguments.tolerance)
|
||||
props = _discover_props(arguments.tolerance)
|
||||
_validate_distinct_source_membership(chunks, props)
|
||||
print(
|
||||
f"Validated {len(chunks)} terrain chunks and {len(props)} reusable "
|
||||
f"props from "
|
||||
f"{Path(bpy.data.filepath).resolve()}"
|
||||
)
|
||||
for chunk in chunks:
|
||||
print(
|
||||
f" {chunk.stable_id}: {chunk.label or '(no label)'} | "
|
||||
f"bounds {_format_vector(chunk.bounds.minimum)} to "
|
||||
f"{_format_vector(chunk.bounds.maximum)} | "
|
||||
f"{len(chunk.objects)} object(s)"
|
||||
)
|
||||
for prop in props:
|
||||
print(
|
||||
f" {prop.stable_id}: reusable {prop.source_kind} prop | bounds "
|
||||
f"{_format_vector(prop.bounds.minimum)} to "
|
||||
f"{_format_vector(prop.bounds.maximum)} | "
|
||||
f"{len(prop.objects)} object(s)"
|
||||
)
|
||||
|
||||
if arguments.dry_run:
|
||||
print("Dry run complete; no files written.")
|
||||
return 0
|
||||
|
||||
output_directory = arguments.output.expanduser().resolve()
|
||||
output_directory.mkdir(parents=True, exist_ok=True)
|
||||
entries: list[dict[str, object]] = []
|
||||
prop_entries: list[dict[str, object]] = []
|
||||
expected_files: set[str] = set()
|
||||
for chunk in chunks:
|
||||
output_path = output_directory / f"{chunk.stable_id}.glb"
|
||||
_export_source(chunk, output_path)
|
||||
expected_files.add(output_path.name)
|
||||
entry = _manifest_entry(chunk, output_path)
|
||||
entries.append(entry)
|
||||
print(
|
||||
f"Exported {output_path.name} | {output_path.stat().st_size} bytes | "
|
||||
f"{entry['sha256']}"
|
||||
)
|
||||
|
||||
for prop in props:
|
||||
output_path = output_directory / f"{prop.stable_id}.glb"
|
||||
_export_source(prop, output_path)
|
||||
expected_files.add(output_path.name)
|
||||
entry = _prop_manifest_entry(prop, output_path)
|
||||
prop_entries.append(entry)
|
||||
print(
|
||||
f"Exported {output_path.name} | {output_path.stat().st_size} bytes | "
|
||||
f"{entry['sha256']}"
|
||||
)
|
||||
|
||||
manifest = {
|
||||
"format_version": 2,
|
||||
"chunk_size_meters": CHUNK_SIZE_METERS,
|
||||
"source_blend": str(Path(bpy.data.filepath).resolve()),
|
||||
"blender_version": bpy.app.version_string,
|
||||
"chunks": entries,
|
||||
"props": prop_entries,
|
||||
}
|
||||
manifest_path = output_directory / "chunks.json"
|
||||
manifest_path.write_text(
|
||||
json.dumps(manifest, indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
expected_files.add(manifest_path.name)
|
||||
|
||||
stale_files = sorted({
|
||||
path.name
|
||||
for pattern in ("chunk_*.glb", "prop_*.glb")
|
||||
for path in output_directory.glob(pattern)
|
||||
if path.name not in expected_files
|
||||
})
|
||||
if stale_files:
|
||||
print(
|
||||
"WARNING: stale generated-world outputs were retained: "
|
||||
+ ", ".join(stale_files)
|
||||
)
|
||||
print(f"Wrote manifest {manifest_path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(_run())
|
||||
except ExportValidationError as error:
|
||||
print(f"EXPORT VALIDATION FAILED:\n{error}", file=sys.stderr)
|
||||
raise SystemExit(2) from error
|
||||
|
|
@ -94,7 +94,7 @@ 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_seed: int)
|
||||
signal new_game_requested
|
||||
signal continue_game_requested
|
||||
signal quit_requested
|
||||
signal join_game_requested(endpoint: String)
|
||||
|
|
@ -147,7 +147,6 @@ enum ConfirmationAction {
|
|||
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()
|
||||
|
|
@ -1020,6 +1019,9 @@ func _on_new_game_pressed() -> void:
|
|||
):
|
||||
return
|
||||
_hide_continue_stats_context()
|
||||
if _inspection.status == SaveInspectionType.Status.MISSING:
|
||||
new_game_requested.emit()
|
||||
return
|
||||
if _inspection.status == SaveInspectionType.Status.UNSUPPORTED_VERSION:
|
||||
_feedback_label.text = _center_feedback_text(
|
||||
"this save is from a newer game version. "
|
||||
|
|
@ -1031,14 +1033,9 @@ func _on_new_game_pressed() -> void:
|
|||
"the existing save cannot be accessed safely."
|
||||
)
|
||||
return
|
||||
_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 a new game? existing progression will be deleted.",
|
||||
"start\nnew game",
|
||||
true
|
||||
)
|
||||
|
|
@ -1076,7 +1073,7 @@ func _on_confirmation_accepted() -> void:
|
|||
_confirmation_transition_generation += 1
|
||||
_cancel_confirmation_transition()
|
||||
_confirmation_page.hide_page()
|
||||
new_game_requested.emit(_pending_new_game_seed)
|
||||
new_game_requested.emit()
|
||||
_action_in_progress = false
|
||||
return
|
||||
if not _save_manager.delete_progression_save():
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
[gd_resource type="Resource" script_class="TerrainBiomeDefinition" load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_definition.gd" id="1_definition"]
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_prop_rule.gd" id="2_rule"]
|
||||
|
||||
[sub_resource type="Resource" id="Rule_sand_tree"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"sand_tree"
|
||||
placement_chance = 4200
|
||||
adjacency_bonus = 1200
|
||||
maximum_density = 5000
|
||||
minimum_placements = 1
|
||||
allowed_prop_ids = PackedStringArray("prop_palm")
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"biome_coast"
|
||||
label = "coast"
|
||||
required_chunk_tags = PackedStringArray("sand")
|
||||
prop_rules = Array[ExtResource("2_rule")]([SubResource("Rule_sand_tree")])
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
[gd_resource type="Resource" script_class="TerrainBiomeDefinition" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_definition.gd" id="1_definition"]
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_prop_rule.gd" id="2_rule"]
|
||||
|
||||
[sub_resource type="Resource" id="Rule_grass_tree"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"grass_tree"
|
||||
placement_chance = 9000
|
||||
adjacency_bonus = 1000
|
||||
maximum_density = 10000
|
||||
minimum_placements = 2
|
||||
allowed_prop_ids = PackedStringArray("prop_tree_1", "prop_tree_2", "prop_tree_3")
|
||||
|
||||
[sub_resource type="Resource" id="Rule_grass_detail"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"grass_detail"
|
||||
placement_chance = 3500
|
||||
adjacency_bonus = 1200
|
||||
maximum_density = 5000
|
||||
minimum_placements = 1
|
||||
allowed_prop_ids = PackedStringArray("prop_log", "prop_mushroom")
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"biome_forest"
|
||||
label = "forest"
|
||||
required_chunk_tags = PackedStringArray("grass")
|
||||
forbidden_chunk_tags = PackedStringArray("spawn")
|
||||
region_weight = 1.4
|
||||
prop_rules = Array[ExtResource("2_rule")]([SubResource("Rule_grass_tree"), SubResource("Rule_grass_detail")])
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
[gd_resource type="Resource" script_class="TerrainBiomeDefinition" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_definition.gd" id="1_definition"]
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_prop_rule.gd" id="2_rule"]
|
||||
|
||||
[sub_resource type="Resource" id="Rule_grass_tree"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"grass_tree"
|
||||
placement_chance = 9000
|
||||
adjacency_bonus = 1000
|
||||
maximum_density = 10000
|
||||
minimum_placements = 2
|
||||
allowed_prop_ids = PackedStringArray("prop_pine")
|
||||
|
||||
[sub_resource type="Resource" id="Rule_grass_detail"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"grass_detail"
|
||||
placement_chance = 2500
|
||||
adjacency_bonus = 1000
|
||||
maximum_density = 4000
|
||||
minimum_placements = 1
|
||||
allowed_prop_ids = PackedStringArray("prop_log", "prop_mushroom")
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"biome_pine_forest"
|
||||
label = "pine forest"
|
||||
required_chunk_tags = PackedStringArray("grass")
|
||||
forbidden_chunk_tags = PackedStringArray("spawn")
|
||||
region_weight = 1.2
|
||||
prop_rules = Array[ExtResource("2_rule")]([SubResource("Rule_grass_tree"), SubResource("Rule_grass_detail")])
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
[gd_resource type="Resource" script_class="TerrainBiomeDefinition" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_definition.gd" id="1_definition"]
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_prop_rule.gd" id="2_rule"]
|
||||
|
||||
[sub_resource type="Resource" id="Rule_grass_tree"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"grass_tree"
|
||||
placement_chance = 1600
|
||||
adjacency_bonus = 900
|
||||
maximum_density = 2200
|
||||
allowed_prop_ids = PackedStringArray("prop_tree_1", "prop_tree_2", "prop_tree_3")
|
||||
|
||||
[sub_resource type="Resource" id="Rule_grass_detail"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"grass_detail"
|
||||
placement_chance = 700
|
||||
adjacency_bonus = 400
|
||||
maximum_density = 1200
|
||||
allowed_prop_ids = PackedStringArray("prop_log", "prop_mushroom")
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"biome_plains"
|
||||
label = "plains"
|
||||
required_chunk_tags = PackedStringArray("grass")
|
||||
region_weight = 1.0
|
||||
prop_rules = Array[ExtResource("2_rule")]([SubResource("Rule_grass_tree"), SubResource("Rule_grass_detail")])
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
[gd_resource type="Resource" script_class="TerrainBiomeCatalog" load_steps=7 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_catalog.gd" id="1_catalog"]
|
||||
[ext_resource type="Resource" path="res://world/generation/biomes/definitions/biome_plains.tres" id="2_plains"]
|
||||
[ext_resource type="Resource" path="res://world/generation/biomes/definitions/biome_forest.tres" id="3_forest"]
|
||||
[ext_resource type="Resource" path="res://world/generation/biomes/definitions/biome_pine_forest.tres" id="4_pine"]
|
||||
[ext_resource type="Resource" path="res://world/generation/biomes/definitions/biome_coast.tres" id="5_coast"]
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_biome_definition.gd" id="6_definition_script"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_catalog")
|
||||
definitions = Array[ExtResource("6_definition_script")]([ExtResource("2_plains"), ExtResource("3_forest"), ExtResource("4_pine"), ExtResource("5_coast")])
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://jv50rtyjn3j1"
|
||||
path="res://.godot/imported/chunk_0000.glb-8221861eeebadbe0547c1162b0dc4018.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0000.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0000.glb-8221861eeebadbe0547c1162b0dc4018.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
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://doju2lbj2ghwa"
|
||||
path.s3tc="res://.godot/imported/chunk_0000_grass_lite.png-12306f832f5f55ac4ec98276bdf209b9.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0000_grass_lite.png-12306f832f5f55ac4ec98276bdf209b9.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_0000_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0000_grass_lite.png-12306f832f5f55ac4ec98276bdf209b9.s3tc.ctex", "res://.godot/imported/chunk_0000_grass_lite.png-12306f832f5f55ac4ec98276bdf209b9.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
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://uxb30e8bfw5v"
|
||||
path="res://.godot/imported/chunk_0001.glb-a498b960fb79406faee2341d1edecb1a.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0001.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0001.glb-a498b960fb79406faee2341d1edecb1a.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
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c3s1ebrqrsylj"
|
||||
path.s3tc="res://.godot/imported/chunk_0001_sand.png-67653175c1ccdaa529250778f806d3f8.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0001_sand.png-67653175c1ccdaa529250778f806d3f8.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_0001_sand.png"
|
||||
dest_files=["res://.godot/imported/chunk_0001_sand.png-67653175c1ccdaa529250778f806d3f8.s3tc.ctex", "res://.godot/imported/chunk_0001_sand.png-67653175c1ccdaa529250778f806d3f8.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
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://dgd530dol7mfd"
|
||||
path="res://.godot/imported/chunk_0002.glb-0a0d64db0049184f39957f77063387f4.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0002.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0002.glb-0a0d64db0049184f39957f77063387f4.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
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://delam1fb8dqwx"
|
||||
path.s3tc="res://.godot/imported/chunk_0002_grass_lite.png-c1692a06eb751734c5b996f92a9ca1af.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0002_grass_lite.png-c1692a06eb751734c5b996f92a9ca1af.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_0002_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0002_grass_lite.png-c1692a06eb751734c5b996f92a9ca1af.s3tc.ctex", "res://.godot/imported/chunk_0002_grass_lite.png-c1692a06eb751734c5b996f92a9ca1af.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
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bjsjdf3lknj1g"
|
||||
path.s3tc="res://.godot/imported/chunk_0002_sand.png-044d6a274c3b25b166e2b43f026482ed.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0002_sand.png-044d6a274c3b25b166e2b43f026482ed.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_0002_sand.png"
|
||||
dest_files=["res://.godot/imported/chunk_0002_sand.png-044d6a274c3b25b166e2b43f026482ed.s3tc.ctex", "res://.godot/imported/chunk_0002_sand.png-044d6a274c3b25b166e2b43f026482ed.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
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://2wl647bi18v5"
|
||||
path="res://.godot/imported/chunk_0003.glb-913e99fdd0f30f24832fe5a7c1b7b865.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0003.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0003.glb-913e99fdd0f30f24832fe5a7c1b7b865.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
|
||||
|
Before Width: | Height: | Size: 5.5 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://614ano6tj7ny"
|
||||
path.s3tc="res://.godot/imported/chunk_0003_dirt.png-aec7f6dc456d2c2fe0bb22bdba6b692f.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0003_dirt.png-aec7f6dc456d2c2fe0bb22bdba6b692f.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_0003_dirt.png"
|
||||
dest_files=["res://.godot/imported/chunk_0003_dirt.png-aec7f6dc456d2c2fe0bb22bdba6b692f.s3tc.ctex", "res://.godot/imported/chunk_0003_dirt.png-aec7f6dc456d2c2fe0bb22bdba6b692f.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
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c2votr034k2g3"
|
||||
path.s3tc="res://.godot/imported/chunk_0003_dirt_wall.png-19ae8be57bf9c33db0df849cb088b83c.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0003_dirt_wall.png-19ae8be57bf9c33db0df849cb088b83c.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_0003_dirt_wall.png"
|
||||
dest_files=["res://.godot/imported/chunk_0003_dirt_wall.png-19ae8be57bf9c33db0df849cb088b83c.s3tc.ctex", "res://.godot/imported/chunk_0003_dirt_wall.png-19ae8be57bf9c33db0df849cb088b83c.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
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cynuisx0jdsq5"
|
||||
path.s3tc="res://.godot/imported/chunk_0003_grass_lite.png-4a0a167fc9e6752477003141049a1d21.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0003_grass_lite.png-4a0a167fc9e6752477003141049a1d21.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_0003_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0003_grass_lite.png-4a0a167fc9e6752477003141049a1d21.s3tc.ctex", "res://.godot/imported/chunk_0003_grass_lite.png-4a0a167fc9e6752477003141049a1d21.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
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://codcyn5ta6aj"
|
||||
path="res://.godot/imported/chunk_0004.glb-0ea01a6391edb5f687fee1991ba71993.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0004.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0004.glb-0ea01a6391edb5f687fee1991ba71993.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
|
||||
|
Before Width: | Height: | Size: 5.5 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cdpfqe4vn7235"
|
||||
path.s3tc="res://.godot/imported/chunk_0004_dirt.png-9c64a7ac5c9e75161623be94b3197f43.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0004_dirt.png-9c64a7ac5c9e75161623be94b3197f43.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_0004_dirt.png"
|
||||
dest_files=["res://.godot/imported/chunk_0004_dirt.png-9c64a7ac5c9e75161623be94b3197f43.s3tc.ctex", "res://.godot/imported/chunk_0004_dirt.png-9c64a7ac5c9e75161623be94b3197f43.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
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cm42r4jmqis7h"
|
||||
path.s3tc="res://.godot/imported/chunk_0004_dirt_wall.png-8cbb5921c02a9dc6be9e3fff14c91409.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0004_dirt_wall.png-8cbb5921c02a9dc6be9e3fff14c91409.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_0004_dirt_wall.png"
|
||||
dest_files=["res://.godot/imported/chunk_0004_dirt_wall.png-8cbb5921c02a9dc6be9e3fff14c91409.s3tc.ctex", "res://.godot/imported/chunk_0004_dirt_wall.png-8cbb5921c02a9dc6be9e3fff14c91409.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
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://kvyeovkwx72h"
|
||||
path.s3tc="res://.godot/imported/chunk_0004_grass_lite.png-152b0775592d1fbccc26c10057d15004.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0004_grass_lite.png-152b0775592d1fbccc26c10057d15004.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_0004_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0004_grass_lite.png-152b0775592d1fbccc26c10057d15004.s3tc.ctex", "res://.godot/imported/chunk_0004_grass_lite.png-152b0775592d1fbccc26c10057d15004.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
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://cg6jtwiq41mbr"
|
||||
path="res://.godot/imported/chunk_0005.glb-fccbd2231305ec7b7f5a0903feee3737.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0005.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0005.glb-fccbd2231305ec7b7f5a0903feee3737.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
|
||||
|
Before Width: | Height: | Size: 8.8 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://diybhc4ciplal"
|
||||
path.s3tc="res://.godot/imported/chunk_0005_cliff_wall.png-ce0994f179fdecaed785c59a34d7b735.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0005_cliff_wall.png-ce0994f179fdecaed785c59a34d7b735.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_0005_cliff_wall.png"
|
||||
dest_files=["res://.godot/imported/chunk_0005_cliff_wall.png-ce0994f179fdecaed785c59a34d7b735.s3tc.ctex", "res://.godot/imported/chunk_0005_cliff_wall.png-ce0994f179fdecaed785c59a34d7b735.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=true
|
||||
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
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://i6vbptsv8276"
|
||||
path.s3tc="res://.godot/imported/chunk_0005_grass_lite.png-ed24123dd98452d08e5de6e6b0eaad0d.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0005_grass_lite.png-ed24123dd98452d08e5de6e6b0eaad0d.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_0005_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0005_grass_lite.png-ed24123dd98452d08e5de6e6b0eaad0d.s3tc.ctex", "res://.godot/imported/chunk_0005_grass_lite.png-ed24123dd98452d08e5de6e6b0eaad0d.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=true
|
||||
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
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://ddjqd4kl4tkhg"
|
||||
path="res://.godot/imported/chunk_0006.glb-3c19b65d4a38bd1731728ce1ea2dda2b.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0006.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0006.glb-3c19b65d4a38bd1731728ce1ea2dda2b.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
|
||||
|
Before Width: | Height: | Size: 8.8 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://beiaoue0vy8c7"
|
||||
path.s3tc="res://.godot/imported/chunk_0006_cliff_wall.png-ac5bf5875f430089a760275aac53f7c5.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0006_cliff_wall.png-ac5bf5875f430089a760275aac53f7c5.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_0006_cliff_wall.png"
|
||||
dest_files=["res://.godot/imported/chunk_0006_cliff_wall.png-ac5bf5875f430089a760275aac53f7c5.s3tc.ctex", "res://.godot/imported/chunk_0006_cliff_wall.png-ac5bf5875f430089a760275aac53f7c5.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=true
|
||||
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
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://djrg33nkvpnih"
|
||||
path.s3tc="res://.godot/imported/chunk_0006_grass_lite.png-965daad85495f67f8a702417724c30a5.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0006_grass_lite.png-965daad85495f67f8a702417724c30a5.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_0006_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0006_grass_lite.png-965daad85495f67f8a702417724c30a5.s3tc.ctex", "res://.godot/imported/chunk_0006_grass_lite.png-965daad85495f67f8a702417724c30a5.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=true
|
||||
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
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://vxomtbcs2mc0"
|
||||
path.s3tc="res://.godot/imported/chunk_0006_sand.png-b81070e87c8688cdfae71416692c7e7b.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0006_sand.png-b81070e87c8688cdfae71416692c7e7b.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_0006_sand.png"
|
||||
dest_files=["res://.godot/imported/chunk_0006_sand.png-b81070e87c8688cdfae71416692c7e7b.s3tc.ctex", "res://.godot/imported/chunk_0006_sand.png-b81070e87c8688cdfae71416692c7e7b.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=true
|
||||
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
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://cniughsb3gacd"
|
||||
path="res://.godot/imported/chunk_0007.glb-ba9f86d0e228adfa3c9dcc6c2c6d2bea.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0007.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0007.glb-ba9f86d0e228adfa3c9dcc6c2c6d2bea.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
|
||||
|
Before Width: | Height: | Size: 8.8 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b2xro1br3b8hr"
|
||||
path.s3tc="res://.godot/imported/chunk_0007_cliff_wall.png-b91883984675adf65a5448c549fa5414.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0007_cliff_wall.png-b91883984675adf65a5448c549fa5414.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_0007_cliff_wall.png"
|
||||
dest_files=["res://.godot/imported/chunk_0007_cliff_wall.png-b91883984675adf65a5448c549fa5414.s3tc.ctex", "res://.godot/imported/chunk_0007_cliff_wall.png-b91883984675adf65a5448c549fa5414.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=true
|
||||
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
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://3skvv046r4cn"
|
||||
path.s3tc="res://.godot/imported/chunk_0007_grass_lite.png-2d878613a1a7bd71881adeafa310116a.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0007_grass_lite.png-2d878613a1a7bd71881adeafa310116a.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_0007_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0007_grass_lite.png-2d878613a1a7bd71881adeafa310116a.s3tc.ctex", "res://.godot/imported/chunk_0007_grass_lite.png-2d878613a1a7bd71881adeafa310116a.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=true
|
||||
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
|
||||
|
|
@ -1,16 +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_0000.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0000"
|
||||
label = "grass"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0000"
|
||||
allowed_rotation_mask = 15
|
||||
selection_weight = 4.0
|
||||
tags = PackedStringArray("land", "walkable", "grass", "flat", "spawn_safe")
|
||||
allowed_neighbor_tags = PackedStringArray("grass", "sand", "coast", "fresh_water")
|
||||
preferred_neighbor_tags = PackedStringArray("grass")
|
||||
|
|
@ -1,18 +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_0001.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0001"
|
||||
label = "sand"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0001"
|
||||
allowed_rotation_mask = 15
|
||||
selection_weight = 1.5
|
||||
tags = PackedStringArray("land", "walkable", "sand", "flat")
|
||||
allowed_neighbor_tags = PackedStringArray("sand", "grass")
|
||||
required_neighbor_tags = PackedStringArray("coast")
|
||||
minimum_required_neighbors = 1
|
||||
preferred_neighbor_tags = PackedStringArray("coast", "sand")
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkDefinition" format=3 uid="uid://b2ha4ekenoiaj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ck6m4auf5epde" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" uid="uid://dgd530dol7mfd" path="res://world/generation/chunks/assets/chunk_0002.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0002"
|
||||
label = "beach"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0002"
|
||||
selection_weight = 0.75
|
||||
maximum_placements = 20
|
||||
tags = PackedStringArray("land", "walkable", "sand", "coast", "slope")
|
||||
allowed_neighbor_tags = PackedStringArray("sand", "grass")
|
||||
required_neighbor_tags = PackedStringArray("sand")
|
||||
minimum_required_neighbors = 1
|
||||
preferred_neighbor_tags = PackedStringArray("sand", "grass")
|
||||
ocean_facing_edges = 2
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkDefinition" format=3 uid="uid://dl7tsxml6g6na"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ck6m4auf5epde" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" uid="uid://2wl647bi18v5" path="res://world/generation/chunks/assets/chunk_0003.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0003"
|
||||
label = "stream"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0003"
|
||||
selection_weight = 0.6
|
||||
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)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkDefinition" format=3 uid="uid://duotyi05ua5gp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ck6m4auf5epde" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" uid="uid://codcyn5ta6aj" path="res://world/generation/chunks/assets/chunk_0004.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0004"
|
||||
label = "pond"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0004"
|
||||
selection_weight = 0.35
|
||||
tags = PackedStringArray("land", "walkable", "fresh_water", "pond")
|
||||
allowed_neighbor_tags = PackedStringArray("grass")
|
||||
preferred_neighbor_tags = PackedStringArray("grass")
|
||||
water_inlet_edges = 1
|
||||
water_surface_size = Vector2(6.7, 7.75)
|
||||
water_surface_offset = Vector2(-0.32, -1.03)
|
||||
|
|
@ -1,21 +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" uid="uid://cg6jtwiq41mbr" path="res://world/generation/chunks/assets/chunk_0005.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0005"
|
||||
label = "grass ocean edge"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0005"
|
||||
selection_weight = 1.0
|
||||
maximum_placements = 20
|
||||
tags = PackedStringArray("land", "walkable", "grass", "coast", "cliff", "grass_ocean_edge")
|
||||
allowed_neighbor_tags = PackedStringArray("grass", "sand", "coast")
|
||||
north_allowed_neighbor_tags = PackedStringArray("coast")
|
||||
south_allowed_neighbor_tags = PackedStringArray("coast")
|
||||
west_allowed_neighbor_tags = PackedStringArray("grass")
|
||||
preferred_neighbor_tags = PackedStringArray("grass", "coast")
|
||||
prefers_map_boundary = true
|
||||
ocean_facing_edges = 2
|
||||
|
|
@ -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" uid="uid://ddjqd4kl4tkhg" path="res://world/generation/chunks/assets/chunk_0006.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0006"
|
||||
label = "grass to beach ocean edge"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0006"
|
||||
selection_weight = 0.35
|
||||
maximum_placements = 8
|
||||
tags = PackedStringArray("land", "walkable", "sand", "coast", "transition", "slope")
|
||||
allowed_neighbor_tags = PackedStringArray("grass", "sand", "coast")
|
||||
north_allowed_neighbor_tags = PackedStringArray("coast")
|
||||
south_allowed_neighbor_tags = PackedStringArray("grass_ocean_edge")
|
||||
west_allowed_neighbor_tags = PackedStringArray("grass")
|
||||
required_neighbor_tags = PackedStringArray("coast")
|
||||
minimum_required_neighbors = 2
|
||||
preferred_neighbor_tags = PackedStringArray("coast")
|
||||
prefers_map_boundary = true
|
||||
ocean_facing_edges = 2
|
||||
|
|
@ -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" uid="uid://cniughsb3gacd" path="res://world/generation/chunks/assets/chunk_0007.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0007"
|
||||
label = "grass ocean corner"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0007"
|
||||
selection_weight = 0.2
|
||||
maximum_placements = 4
|
||||
tags = PackedStringArray("land", "walkable", "grass", "coast", "cliff", "corner")
|
||||
allowed_neighbor_tags = PackedStringArray("grass", "coast")
|
||||
north_allowed_neighbor_tags = PackedStringArray("grass_ocean_edge")
|
||||
west_allowed_neighbor_tags = PackedStringArray("grass_ocean_edge")
|
||||
required_neighbor_tags = PackedStringArray("grass_ocean_edge")
|
||||
minimum_required_neighbors = 2
|
||||
preferred_neighbor_tags = PackedStringArray("coast")
|
||||
prefers_map_boundary = true
|
||||
ocean_facing_edges = 6
|
||||
|
|
@ -1,19 +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_0000.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_spawn"
|
||||
label = "spawn"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0000"
|
||||
allowed_rotation_mask = 1
|
||||
selection_weight = 0.01
|
||||
maximum_placements = 1
|
||||
tags = PackedStringArray("land", "walkable", "grass", "flat", "spawn")
|
||||
allowed_neighbor_tags = PackedStringArray("grass", "coast")
|
||||
required_neighbor_tags = PackedStringArray("spawn_safe")
|
||||
minimum_required_neighbors = 3
|
||||
preferred_neighbor_tags = PackedStringArray("spawn_safe")
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
[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"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0001.tres" id="3_sand"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0002.tres" id="4_beach"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0003.tres" id="5_stream"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0004.tres" id="6_pond"]
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="7_definition_script"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_spawn.tres" id="8_spawn"]
|
||||
[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"]
|
||||
|
||||
[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("6_pond"), ExtResource("9_grass_ocean_edge"), ExtResource("10_grass_beach_transition"), ExtResource("11_grass_ocean_corner"), ExtResource("8_spawn")])
|
||||