Add seasonal tree palettes
This commit is contained in:
parent
1163cef320
commit
23cf53fa14
35 changed files with 861 additions and 43 deletions
22
main/main.gd
22
main/main.gd
|
|
@ -108,6 +108,7 @@ const NetworkProfileServiceType = preload(
|
|||
"res://network/network_profile_service.gd"
|
||||
)
|
||||
const WorldTimeServiceType = preload("res://world/world_time_service.gd")
|
||||
const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
||||
const WorldTimeVisualControllerType = preload(
|
||||
"res://world/world_time_visual_controller.gd"
|
||||
)
|
||||
|
|
@ -406,6 +407,20 @@ func _on_world_weather_changed(
|
|||
_apply_weather_visual_motion(weather)
|
||||
|
||||
|
||||
func _on_world_calendar_date_changed(_date_id: String) -> void:
|
||||
_apply_world_calendar_season()
|
||||
|
||||
|
||||
func _apply_world_calendar_season() -> void:
|
||||
var season: int = _world_time.get_season()
|
||||
if season == CalendarSeasonType.UNKNOWN:
|
||||
var system_date: Dictionary = Time.get_date_dict_from_system()
|
||||
season = CalendarSeasonType.from_month(
|
||||
int(system_date.get("month", 0))
|
||||
)
|
||||
_test_world.set_calendar_season(season)
|
||||
|
||||
|
||||
func _apply_weather_visual_motion(
|
||||
weather: WorldWeatherService.Weather,
|
||||
) -> void:
|
||||
|
|
@ -659,6 +674,13 @@ func _initialize_application(dedicated: bool) -> void:
|
|||
if _application_initialized:
|
||||
return
|
||||
_application_initialized = true
|
||||
if not _world_time.calendar_date_changed.is_connected(
|
||||
_on_world_calendar_date_changed
|
||||
):
|
||||
_world_time.calendar_date_changed.connect(
|
||||
_on_world_calendar_date_changed
|
||||
)
|
||||
_apply_world_calendar_season()
|
||||
_player.global_transform = _test_world.get_player_spawn_transform()
|
||||
_player.velocity = Vector3.ZERO
|
||||
_player_spawn_service.setup(
|
||||
|
|
|
|||
|
|
@ -21,12 +21,16 @@ const Gatherables: GatherableCatalog = preload(
|
|||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
||||
const FOLIAGE_WIND_SHADER: Shader = preload(
|
||||
"res://world/materials/foliage_wind.gdshader"
|
||||
)
|
||||
const FOLIAGE_WIND_SOURCE_MATERIALS_META: StringName = (
|
||||
&"foliage_wind_source_materials"
|
||||
)
|
||||
const PROP_PRIMARY_MATERIAL_ROLL_META: StringName = (
|
||||
&"terrain_prop_primary_material_roll"
|
||||
)
|
||||
const FIRST_SEED := 13001
|
||||
const SECOND_SEED := 13002
|
||||
const RIVER_FALLBACK_SEED := 13012
|
||||
|
|
@ -81,6 +85,7 @@ func _run() -> void:
|
|||
configured_generator.elevated_cliff_third_tier_base_chance = 0.0
|
||||
configured_generator.elevated_cliff_third_tier_stack_chance = 1.0
|
||||
configured_generator.elevated_cliff_double_third_tier_chance = 0.0
|
||||
region.set_calendar_season(CalendarSeasonType.Season.FALL)
|
||||
root.add_child(region)
|
||||
await process_frame
|
||||
assert(region.generate_world(FIRST_SEED))
|
||||
|
|
@ -757,24 +762,23 @@ func _validate_generated_region(
|
|||
float(child.get_meta(&"terrain_prop_visual_scale", 1.0))
|
||||
)
|
||||
tree_yaws[snappedf((child as Node3D).rotation.y, 0.01)] = true
|
||||
if prop_id in [
|
||||
&"prop_tree_1",
|
||||
&"prop_tree_2",
|
||||
&"prop_tree_3",
|
||||
&"prop_tree_large",
|
||||
]:
|
||||
if not definition.variant_material_slot_names.is_empty():
|
||||
assert(
|
||||
_has_material_variant_override(
|
||||
child.get_node_or_null("Visual"),
|
||||
definition.material_variants,
|
||||
definition.primary_material_variants_for_season(
|
||||
CalendarSeasonType.Season.FALL
|
||||
),
|
||||
)
|
||||
)
|
||||
if not definition.secondary_material_variants.is_empty():
|
||||
assert(
|
||||
_has_material_variant_override(
|
||||
child.get_node_or_null("Visual"),
|
||||
definition.secondary_material_variants,
|
||||
)
|
||||
)
|
||||
_validate_seasonal_foliage_switch(region, decorations)
|
||||
assert(not palm_clusters.is_empty())
|
||||
for members: Array in palm_clusters.values():
|
||||
assert(members.size() >= 1 and members.size() <= 3)
|
||||
|
|
@ -1169,17 +1173,33 @@ func _validate_prop_catalog(region: GeneratedWorldRegion) -> void:
|
|||
assert(tree.minimum_visual_scale < tree.maximum_visual_scale)
|
||||
assert(tree.material_variants.size() >= 3)
|
||||
assert(not tree.variant_material_slot_names.is_empty())
|
||||
assert(tree.summer_material_variants.size() == 3)
|
||||
assert(tree.fall_material_variants.size() == 4)
|
||||
assert(
|
||||
tree.primary_material_variants_for_season(
|
||||
CalendarSeasonType.Season.SPRING
|
||||
) == tree.summer_material_variants
|
||||
)
|
||||
assert(
|
||||
tree.primary_material_variants_for_season(
|
||||
CalendarSeasonType.Season.WINTER
|
||||
) == tree.summer_material_variants
|
||||
)
|
||||
assert(tree.secondary_material_variants.size() >= 3)
|
||||
assert(not tree.secondary_variant_material_slot_names.is_empty())
|
||||
var pine := catalog.definition_for_id(&"prop_pine")
|
||||
assert(pine != null)
|
||||
assert(pine.minimum_visual_scale < pine.maximum_visual_scale)
|
||||
assert(pine.material_variants.is_empty())
|
||||
assert(pine.summer_material_variants.size() == 1)
|
||||
assert(pine.fall_material_variants.size() == 1)
|
||||
assert(not pine.has_gatherable_surface())
|
||||
var large_pine := catalog.definition_for_id(&"prop_pine_large")
|
||||
assert(large_pine != null)
|
||||
assert(large_pine.minimum_visual_scale < large_pine.maximum_visual_scale)
|
||||
assert(large_pine.material_variants.is_empty())
|
||||
assert(large_pine.summer_material_variants.size() == 1)
|
||||
assert(large_pine.fall_material_variants.size() == 1)
|
||||
assert(not large_pine.has_gatherable_surface())
|
||||
var palm := catalog.definition_for_id(&"prop_palm")
|
||||
assert(palm != null)
|
||||
|
|
@ -1189,6 +1209,8 @@ func _validate_prop_catalog(region: GeneratedWorldRegion) -> void:
|
|||
assert(palm.maximum_cluster_size == 3)
|
||||
assert(palm.minimum_cluster_radius > palm.clearance_radius)
|
||||
assert(palm.maximum_cluster_radius > palm.minimum_cluster_radius)
|
||||
assert(palm.summer_material_variants.size() == 1)
|
||||
assert(palm.fall_material_variants.size() == 1)
|
||||
assert(palm.prefer_ocean_facing)
|
||||
assert(not palm.local_overhang_direction.is_zero_approx())
|
||||
var aligned_tree_collisions: Dictionary[StringName, Vector2] = {
|
||||
|
|
@ -1594,6 +1616,82 @@ func _find_mesh_instance(root_node: Node) -> MeshInstance3D:
|
|||
return null
|
||||
|
||||
|
||||
func _validate_seasonal_foliage_switch(
|
||||
region: GeneratedWorldRegion,
|
||||
decorations: Node3D,
|
||||
) -> void:
|
||||
var original_signature := _decoration_signature(region)
|
||||
var expected_rolls: Dictionary[String, float] = {}
|
||||
var found_broadleaf := false
|
||||
var found_pine := false
|
||||
var found_palm := false
|
||||
for prop_node: Node in decorations.get_children():
|
||||
var fall_prop := prop_node as Node3D
|
||||
var prop_id := StringName(fall_prop.get_meta(&"terrain_prop_id", &""))
|
||||
var fall_definition := region.get_prop_catalog().definition_for_id(prop_id)
|
||||
if (
|
||||
fall_definition == null
|
||||
or fall_definition.variant_material_slot_names.is_empty()
|
||||
):
|
||||
continue
|
||||
assert(fall_prop.has_meta(PROP_PRIMARY_MATERIAL_ROLL_META))
|
||||
var roll := float(fall_prop.get_meta(PROP_PRIMARY_MATERIAL_ROLL_META))
|
||||
assert(roll >= 0.0 and roll < 1.0)
|
||||
expected_rolls[fall_prop.name] = roll
|
||||
assert(_has_material_variant_override(
|
||||
fall_prop.get_node_or_null("Visual"),
|
||||
fall_definition.primary_material_variants_for_season(
|
||||
CalendarSeasonType.Season.FALL
|
||||
),
|
||||
))
|
||||
found_broadleaf = found_broadleaf or String(prop_id).begins_with(
|
||||
"prop_tree_"
|
||||
)
|
||||
found_pine = found_pine or prop_id in [
|
||||
&"prop_pine",
|
||||
&"prop_pine_large",
|
||||
]
|
||||
found_palm = found_palm or prop_id == &"prop_palm"
|
||||
assert(found_broadleaf and found_pine and found_palm)
|
||||
|
||||
region.set_calendar_season(CalendarSeasonType.Season.SUMMER)
|
||||
assert(_decoration_signature(region) == original_signature)
|
||||
for prop_node: Node in decorations.get_children():
|
||||
var summer_prop := prop_node as Node3D
|
||||
if not expected_rolls.has(summer_prop.name):
|
||||
continue
|
||||
assert(is_equal_approx(
|
||||
float(summer_prop.get_meta(PROP_PRIMARY_MATERIAL_ROLL_META)),
|
||||
expected_rolls[summer_prop.name],
|
||||
))
|
||||
var summer_definition := region.get_prop_catalog().definition_for_id(
|
||||
StringName(summer_prop.get_meta(&"terrain_prop_id", &""))
|
||||
)
|
||||
assert(_has_material_variant_override(
|
||||
summer_prop.get_node_or_null("Visual"),
|
||||
summer_definition.primary_material_variants_for_season(
|
||||
CalendarSeasonType.Season.SUMMER
|
||||
),
|
||||
))
|
||||
assert(_first_foliage_wind_material(decorations) != null)
|
||||
|
||||
region.set_calendar_season(CalendarSeasonType.Season.FALL)
|
||||
assert(_decoration_signature(region) == original_signature)
|
||||
for prop_node: Node in decorations.get_children():
|
||||
var restored_prop := prop_node as Node3D
|
||||
if not expected_rolls.has(restored_prop.name):
|
||||
continue
|
||||
var restored_definition := region.get_prop_catalog().definition_for_id(
|
||||
StringName(restored_prop.get_meta(&"terrain_prop_id", &""))
|
||||
)
|
||||
assert(_has_material_variant_override(
|
||||
restored_prop.get_node_or_null("Visual"),
|
||||
restored_definition.primary_material_variants_for_season(
|
||||
CalendarSeasonType.Season.FALL
|
||||
),
|
||||
))
|
||||
|
||||
|
||||
func _first_foliage_wind_material(root_node: Node) -> ShaderMaterial:
|
||||
var mesh_instance := root_node as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.mesh != null:
|
||||
|
|
@ -1652,8 +1750,15 @@ func _has_material_variant_override(
|
|||
surface_index,
|
||||
null,
|
||||
) as Material
|
||||
if source_material != null and source_material in variants:
|
||||
return true
|
||||
var source_standard := source_material as StandardMaterial3D
|
||||
if source_standard != null and source_material in variants:
|
||||
var visible_color: Color = wind_material.get_shader_parameter(
|
||||
"albedo_color"
|
||||
)
|
||||
if visible_color.is_equal_approx(
|
||||
source_standard.albedo_color
|
||||
):
|
||||
return true
|
||||
for child: Node in root_node.get_children():
|
||||
if _has_material_variant_override(child, variants):
|
||||
return true
|
||||
|
|
|
|||
274
tests/terrain_seasonal_material_validation.gd
Normal file
274
tests/terrain_seasonal_material_validation.gd
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
extends SceneTree
|
||||
|
||||
const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
||||
const PropCatalog: TerrainPropCatalog = preload(
|
||||
"res://world/generation/props/terrain_prop_catalog.tres"
|
||||
)
|
||||
const RegionScene: PackedScene = preload(
|
||||
"res://world/generation/generated_world_region.tscn"
|
||||
)
|
||||
|
||||
const BROADLEAF_IDS: Array[StringName] = [
|
||||
&"prop_tree_1",
|
||||
&"prop_tree_2",
|
||||
&"prop_tree_3",
|
||||
&"prop_tree_large",
|
||||
]
|
||||
const IMPORTED_FOLIAGE_NAMES: Dictionary = {
|
||||
&"prop_tree_1": &"leaf_autumn_light",
|
||||
&"prop_tree_2": &"leaf_autumn",
|
||||
&"prop_tree_3": &"leaf_autumn_dark",
|
||||
&"prop_tree_large": &"leaf_autumn_mid",
|
||||
&"prop_pine": &"pine_summer",
|
||||
&"prop_pine_large": &"pine_autumn",
|
||||
&"prop_palm": &"palm_autumn",
|
||||
}
|
||||
const PROP_PRIMARY_MATERIAL_ROLL_META: StringName = (
|
||||
&"terrain_prop_primary_material_roll"
|
||||
)
|
||||
const PROP_LEGACY_PRIMARY_MATERIAL_INDEX_META: StringName = (
|
||||
&"terrain_prop_legacy_primary_material_index"
|
||||
)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred(&"_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
assert(PropCatalog.validation_errors().is_empty())
|
||||
for stable_id: StringName in BROADLEAF_IDS:
|
||||
var definition := PropCatalog.definition_for_id(stable_id)
|
||||
assert(definition != null)
|
||||
_assert_palette(
|
||||
definition.summer_material_variants,
|
||||
PackedStringArray([
|
||||
"leaf_variant_light",
|
||||
"leaf_variant_mid",
|
||||
"leaf_variant_dark",
|
||||
]),
|
||||
[
|
||||
Color(0.3387566, 0.7186728, 0, 1),
|
||||
Color(0.3146831, 0.4631305, 0, 1),
|
||||
Color(0.1712407, 0.2520192, 0, 1),
|
||||
],
|
||||
)
|
||||
_assert_palette(
|
||||
definition.fall_material_variants,
|
||||
PackedStringArray([
|
||||
"leaf_autumn_light",
|
||||
"leaf_autumn",
|
||||
"leaf_autumn_mid",
|
||||
"leaf_autumn_dark",
|
||||
]),
|
||||
[
|
||||
Color(0.8222013, 0.794216, 0, 1),
|
||||
Color(0.6186816, 0.4523746, 0, 1),
|
||||
Color(0.6841143, 0.3515543, 0, 1),
|
||||
Color(0.3853541, 0.00134692, 0, 1),
|
||||
],
|
||||
)
|
||||
assert(
|
||||
definition.primary_material_variants_for_season(
|
||||
CalendarSeasonType.Season.SPRING
|
||||
) == definition.summer_material_variants
|
||||
)
|
||||
assert(
|
||||
definition.primary_material_variants_for_season(
|
||||
CalendarSeasonType.Season.WINTER
|
||||
) == definition.summer_material_variants
|
||||
)
|
||||
|
||||
_assert_single_seasonal_tree(
|
||||
&"prop_pine",
|
||||
&"pine_summer",
|
||||
Color(0.02171588, 0.2242201, 0.1360403, 1),
|
||||
&"pine_autumn",
|
||||
Color(0.02388079, 0.1285609, 0, 1),
|
||||
)
|
||||
_assert_single_seasonal_tree(
|
||||
&"prop_pine_large",
|
||||
&"pine_summer",
|
||||
Color(0.02171588, 0.2242201, 0.1360403, 1),
|
||||
&"pine_autumn",
|
||||
Color(0.02388079, 0.1285609, 0, 1),
|
||||
)
|
||||
_assert_single_seasonal_tree(
|
||||
&"prop_palm",
|
||||
&"palm_summer",
|
||||
Color(0.3146831, 0.4631305, 0, 1),
|
||||
&"palm_autumn",
|
||||
Color(0.2951757, 0.3575695, 0.162893, 1),
|
||||
)
|
||||
|
||||
for stable_id: StringName in IMPORTED_FOLIAGE_NAMES:
|
||||
var definition := PropCatalog.definition_for_id(stable_id)
|
||||
var instance := definition.packed_scene.instantiate()
|
||||
var material_names := PackedStringArray()
|
||||
_collect_material_names(instance, material_names)
|
||||
assert(IMPORTED_FOLIAGE_NAMES[stable_id] in material_names)
|
||||
instance.free()
|
||||
|
||||
var region := RegionScene.instantiate() as GeneratedWorldRegion
|
||||
assert(region != null)
|
||||
assert(bool(region.call(
|
||||
"_material_name_matches_targets",
|
||||
&"leaf_autumn_mid",
|
||||
PackedStringArray(["leaf"]),
|
||||
)))
|
||||
assert(bool(region.call(
|
||||
"_material_name_matches_targets",
|
||||
&"pine_winter",
|
||||
PackedStringArray(["pine"]),
|
||||
)))
|
||||
assert(bool(region.call(
|
||||
"_material_name_matches_targets",
|
||||
&"palm_spring",
|
||||
PackedStringArray(["palm"]),
|
||||
)))
|
||||
_validate_palette_roll_mapping(region)
|
||||
_validate_primary_roll_random_state(region)
|
||||
region.free()
|
||||
print("Terrain seasonal material validation: PASS")
|
||||
quit()
|
||||
|
||||
|
||||
func _assert_single_seasonal_tree(
|
||||
stable_id: StringName,
|
||||
summer_name: StringName,
|
||||
summer_color: Color,
|
||||
fall_name: StringName,
|
||||
fall_color: Color,
|
||||
) -> void:
|
||||
var definition := PropCatalog.definition_for_id(stable_id)
|
||||
assert(definition != null)
|
||||
_assert_palette(
|
||||
definition.summer_material_variants,
|
||||
PackedStringArray([summer_name]),
|
||||
[summer_color],
|
||||
)
|
||||
_assert_palette(
|
||||
definition.fall_material_variants,
|
||||
PackedStringArray([fall_name]),
|
||||
[fall_color],
|
||||
)
|
||||
|
||||
|
||||
func _assert_palette(
|
||||
materials: Array[Material],
|
||||
expected_names: PackedStringArray,
|
||||
expected_colors: Array[Color],
|
||||
) -> void:
|
||||
assert(materials.size() == expected_names.size())
|
||||
assert(materials.size() == expected_colors.size())
|
||||
for index: int in materials.size():
|
||||
var material := materials[index] as StandardMaterial3D
|
||||
assert(material != null)
|
||||
assert(material.resource_name == expected_names[index])
|
||||
assert(material.albedo_color.is_equal_approx(expected_colors[index]))
|
||||
|
||||
|
||||
func _validate_palette_roll_mapping(region: GeneratedWorldRegion) -> void:
|
||||
var definition := PropCatalog.definition_for_id(&"prop_tree_1")
|
||||
assert(definition != null)
|
||||
var instance := definition.packed_scene.instantiate()
|
||||
var palette := definition.fall_material_variants
|
||||
for index: int in palette.size():
|
||||
var roll := (float(index) + 0.5) / float(palette.size())
|
||||
region.call(
|
||||
"_apply_material_variant_for_roll",
|
||||
instance,
|
||||
definition.variant_material_slot_names,
|
||||
palette,
|
||||
roll,
|
||||
)
|
||||
assert(_has_surface_override(instance, palette[index]))
|
||||
instance.free()
|
||||
|
||||
|
||||
func _validate_primary_roll_random_state(
|
||||
region: GeneratedWorldRegion,
|
||||
) -> void:
|
||||
var broadleaf := PropCatalog.definition_for_id(&"prop_tree_1")
|
||||
var baseline := RandomNumberGenerator.new()
|
||||
baseline.seed = 29092026
|
||||
var actual := RandomNumberGenerator.new()
|
||||
actual.seed = baseline.seed
|
||||
var legacy_index := baseline.randi_range(
|
||||
0,
|
||||
broadleaf.material_variants.size() - 1,
|
||||
)
|
||||
var selection := region.call(
|
||||
"_primary_material_selection",
|
||||
broadleaf,
|
||||
actual,
|
||||
) as Dictionary
|
||||
var roll := float(selection.get("roll", -1.0))
|
||||
assert(roll >= 0.0 and roll < 1.0)
|
||||
assert(int(selection.get("legacy_index", -1)) == legacy_index)
|
||||
assert(actual.state == baseline.state)
|
||||
var prop := Node3D.new()
|
||||
prop.set_meta(PROP_PRIMARY_MATERIAL_ROLL_META, 0.125)
|
||||
prop.set_meta(
|
||||
PROP_LEGACY_PRIMARY_MATERIAL_INDEX_META,
|
||||
legacy_index,
|
||||
)
|
||||
var summer_roll := float(region.call(
|
||||
"_primary_material_roll_for_palette",
|
||||
prop,
|
||||
broadleaf,
|
||||
broadleaf.summer_material_variants,
|
||||
))
|
||||
assert(
|
||||
floori(summer_roll * broadleaf.summer_material_variants.size())
|
||||
== legacy_index
|
||||
)
|
||||
var fall_roll := float(region.call(
|
||||
"_primary_material_roll_for_palette",
|
||||
prop,
|
||||
broadleaf,
|
||||
broadleaf.fall_material_variants,
|
||||
))
|
||||
assert(is_equal_approx(fall_roll, 0.125))
|
||||
prop.free()
|
||||
|
||||
var pine := PropCatalog.definition_for_id(&"prop_pine")
|
||||
var pine_random := RandomNumberGenerator.new()
|
||||
pine_random.seed = 29092026
|
||||
var pine_initial_state := pine_random.state
|
||||
var pine_selection := region.call(
|
||||
"_primary_material_selection",
|
||||
pine,
|
||||
pine_random,
|
||||
) as Dictionary
|
||||
assert(int(pine_selection.get("legacy_index", -2)) == -1)
|
||||
assert(pine_random.state == pine_initial_state)
|
||||
|
||||
|
||||
func _has_surface_override(root_node: Node, expected: Material) -> bool:
|
||||
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():
|
||||
if (
|
||||
mesh_instance.get_surface_override_material(surface_index)
|
||||
== expected
|
||||
):
|
||||
return true
|
||||
for child: Node in root_node.get_children():
|
||||
if _has_surface_override(child, expected):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _collect_material_names(
|
||||
node: Node,
|
||||
material_names: PackedStringArray,
|
||||
) -> void:
|
||||
var mesh_instance := 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.mesh.surface_get_material(surface_index)
|
||||
if material != null:
|
||||
material_names.append(material.resource_name)
|
||||
for child: Node in node.get_children():
|
||||
_collect_material_names(child, material_names)
|
||||
1
tests/terrain_seasonal_material_validation.gd.uid
Normal file
1
tests/terrain_seasonal_material_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dicorkv8nbkb5
|
||||
|
|
@ -4,6 +4,7 @@ const TestWorldScene: PackedScene = preload("res://world/test_world.tscn")
|
|||
const SetupPageScene: PackedScene = preload(
|
||||
"res://ui/new_game_setup_page.tscn"
|
||||
)
|
||||
const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
|
|
@ -84,6 +85,7 @@ func _validate_world_switching() -> void:
|
|||
root.add_child(world)
|
||||
await process_frame
|
||||
assert(world.get_world_layout() == WorldLayout.GENERATED)
|
||||
world.set_calendar_season(CalendarSeasonType.Season.FALL)
|
||||
world.set_foliage_wind_strength_multiplier(2.0)
|
||||
world.set_water_motion_strength_multiplier(2.0)
|
||||
var initial_generator := world.get_node(
|
||||
|
|
@ -134,6 +136,15 @@ func _validate_world_switching() -> void:
|
|||
"Regions/GeneratedWorldRegion"
|
||||
) as GeneratedWorldRegion
|
||||
assert(generated_region != null)
|
||||
assert(
|
||||
int(generated_region.get("_calendar_season"))
|
||||
== CalendarSeasonType.Season.FALL
|
||||
)
|
||||
world.set_calendar_season(CalendarSeasonType.Season.SUMMER)
|
||||
assert(
|
||||
int(generated_region.get("_calendar_season"))
|
||||
== CalendarSeasonType.Season.SUMMER
|
||||
)
|
||||
assert(is_equal_approx(
|
||||
float(generated_region.get("_foliage_wind_strength_multiplier")), 2.0
|
||||
))
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const RVUpgradeInteractionType = preload(
|
|||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
||||
const FOLIAGE_WIND_SHADER: Shader = preload(
|
||||
"res://world/materials/foliage_wind.gdshader"
|
||||
)
|
||||
|
|
@ -88,7 +89,14 @@ const FOLIAGE_MATERIAL_NAMES: Array[StringName] = [
|
|||
&"leaf_variant_light",
|
||||
&"leaf_variant_mid",
|
||||
&"leaf_variant_dark",
|
||||
&"palm",
|
||||
]
|
||||
const PROP_PRIMARY_MATERIAL_ROLL_META: StringName = (
|
||||
&"terrain_prop_primary_material_roll"
|
||||
)
|
||||
const PROP_LEGACY_PRIMARY_MATERIAL_INDEX_META: StringName = (
|
||||
&"terrain_prop_legacy_primary_material_index"
|
||||
)
|
||||
# Retain the exact generated material override while foliage wind is active so
|
||||
# profile changes restore both the selected palette and authored surface state.
|
||||
const FOLIAGE_WIND_SOURCE_MATERIALS_META: StringName = (
|
||||
|
|
@ -138,6 +146,7 @@ var _water_recovery_triangles_by_coordinate: Dictionary[Vector2i, Array] = {}
|
|||
var _home_accessible_elevations_by_coordinate: Dictionary[Vector2i, Array] = {}
|
||||
var _foliage_wind_enabled: bool = false
|
||||
var _foliage_wind_strength_multiplier: float = 1.0
|
||||
var _calendar_season: int = CalendarSeasonType.UNKNOWN
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -148,6 +157,11 @@ func _ready() -> void:
|
|||
_set_foliage_wind_enabled(not _light_performance_profile)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if _decorations != null:
|
||||
_apply_foliage_wind_to_node(_decorations, false)
|
||||
|
||||
|
||||
func generate_world(world_seed: int) -> bool:
|
||||
if world_seed <= 0 or world_seed > PlayerSaveManager.MAX_WORLD_SEED:
|
||||
return false
|
||||
|
|
@ -232,6 +246,18 @@ func set_water_motion_strength_multiplier(multiplier: float) -> void:
|
|||
surface_motion.set_motion_strength_multiplier(multiplier)
|
||||
|
||||
|
||||
func set_calendar_season(season: int) -> void:
|
||||
var normalized_season := (
|
||||
season
|
||||
if season >= 0 and season < CalendarSeasonType.Season.size()
|
||||
else CalendarSeasonType.UNKNOWN
|
||||
)
|
||||
if normalized_season == _calendar_season:
|
||||
return
|
||||
_calendar_season = normalized_season
|
||||
_refresh_seasonal_prop_materials()
|
||||
|
||||
|
||||
func _get_ocean_surface_motion() -> WaterSurfaceMotion:
|
||||
return _ocean.get_node_or_null(^"VisualWater") as WaterSurfaceMotion
|
||||
|
||||
|
|
@ -319,6 +345,7 @@ func _on_generation_completed(summary: Dictionary) -> void:
|
|||
_cache_home_accessible_elevations(records)
|
||||
_assign_biomes(records, summary)
|
||||
var spawn_position := Vector3.ZERO
|
||||
_apply_foliage_wind_to_node(_decorations, false)
|
||||
_clear_children(_decorations)
|
||||
_clear_children(_tree_anchors)
|
||||
_placed_prop_positions.clear()
|
||||
|
|
@ -655,7 +682,7 @@ func _apply_foliage_wind_to_mesh(mesh_instance: MeshInstance3D) -> void:
|
|||
)
|
||||
if not source_material is StandardMaterial3D:
|
||||
continue
|
||||
if not FOLIAGE_MATERIAL_NAMES.has(
|
||||
if not _is_foliage_material_name(
|
||||
StringName(source_material.resource_name)
|
||||
):
|
||||
continue
|
||||
|
|
@ -1156,6 +1183,18 @@ func _instantiate_prop(
|
|||
prop.set_meta(&"terrain_prop_visual_scale", visual_scale)
|
||||
prop.set_meta(&"terrain_prop_cluster_id", cluster_id)
|
||||
prop.set_meta(&"terrain_prop_cluster_member_index", cluster_member_index)
|
||||
var primary_material_selection := _primary_material_selection(
|
||||
definition,
|
||||
random,
|
||||
)
|
||||
prop.set_meta(
|
||||
PROP_PRIMARY_MATERIAL_ROLL_META,
|
||||
float(primary_material_selection.get("roll", 0.5)),
|
||||
)
|
||||
prop.set_meta(
|
||||
PROP_LEGACY_PRIMARY_MATERIAL_INDEX_META,
|
||||
int(primary_material_selection.get("legacy_index", -1)),
|
||||
)
|
||||
prop.position = placement
|
||||
prop.rotation.y = yaw
|
||||
_decorations.add_child(prop)
|
||||
|
|
@ -1164,7 +1203,7 @@ func _instantiate_prop(
|
|||
visual_root.position = definition.visual_offset
|
||||
visual_root.scale = Vector3.ONE * visual_scale
|
||||
_configure_prop_visuals(visual_root)
|
||||
_apply_prop_material_variant(visual_root, definition, random)
|
||||
_apply_prop_material_variants(prop, visual_root, definition, random)
|
||||
_add_prop_collision(prop, definition, visual_scale)
|
||||
_placed_prop_positions.append(placement)
|
||||
_placed_prop_clearance_radii.append(
|
||||
|
|
@ -1526,16 +1565,24 @@ func _configure_prop_visuals(root_node: Node) -> void:
|
|||
_configure_prop_visuals(child)
|
||||
|
||||
|
||||
func _apply_prop_material_variant(
|
||||
func _apply_prop_material_variants(
|
||||
prop: Node3D,
|
||||
root_node: Node,
|
||||
definition: TerrainPropDefinition,
|
||||
random: RandomNumberGenerator,
|
||||
) -> void:
|
||||
_apply_random_material_variant(
|
||||
var primary_variants := definition.primary_material_variants_for_season(
|
||||
_calendar_season
|
||||
)
|
||||
_apply_material_variant_for_roll(
|
||||
root_node,
|
||||
definition.variant_material_slot_names,
|
||||
definition.material_variants,
|
||||
random,
|
||||
primary_variants,
|
||||
_primary_material_roll_for_palette(
|
||||
prop,
|
||||
definition,
|
||||
primary_variants,
|
||||
),
|
||||
)
|
||||
_apply_random_material_variant(
|
||||
root_node,
|
||||
|
|
@ -1545,6 +1592,69 @@ func _apply_prop_material_variant(
|
|||
)
|
||||
|
||||
|
||||
func _primary_material_selection(
|
||||
definition: TerrainPropDefinition,
|
||||
random: RandomNumberGenerator,
|
||||
) -> Dictionary:
|
||||
var preview_random := RandomNumberGenerator.new()
|
||||
preview_random.state = random.state
|
||||
var roll := preview_random.randf()
|
||||
# Preserve the legacy foliage draw so this presentation-only feature never
|
||||
# changes which props a known world seed places afterward. Retain its chosen
|
||||
# index too, so existing summer worlds keep their exact historical tint.
|
||||
var legacy_index := -1
|
||||
if not definition.material_variants.is_empty():
|
||||
legacy_index = random.randi_range(
|
||||
0,
|
||||
definition.material_variants.size() - 1,
|
||||
)
|
||||
# New seasonal-only definitions such as pine and palm peek at the current
|
||||
# deterministic state without consuming it, preserving their old layout.
|
||||
return {
|
||||
"roll": roll,
|
||||
"legacy_index": legacy_index,
|
||||
}
|
||||
|
||||
|
||||
func _primary_material_roll_for_palette(
|
||||
prop: Node3D,
|
||||
definition: TerrainPropDefinition,
|
||||
variants: Array[Material],
|
||||
) -> float:
|
||||
var legacy_index := int(prop.get_meta(
|
||||
PROP_LEGACY_PRIMARY_MATERIAL_INDEX_META,
|
||||
-1,
|
||||
))
|
||||
var uses_summer_palette := (
|
||||
not definition.summer_material_variants.is_empty()
|
||||
and variants == definition.summer_material_variants
|
||||
)
|
||||
if uses_summer_palette and legacy_index >= 0:
|
||||
var clamped_index := mini(legacy_index, variants.size() - 1)
|
||||
return (float(clamped_index) + 0.5) / float(variants.size())
|
||||
return float(prop.get_meta(PROP_PRIMARY_MATERIAL_ROLL_META, 0.5))
|
||||
|
||||
|
||||
func _apply_material_variant_for_roll(
|
||||
root_node: Node,
|
||||
target_material_names: PackedStringArray,
|
||||
variants: Array[Material],
|
||||
roll: float,
|
||||
) -> void:
|
||||
if variants.is_empty() or target_material_names.is_empty():
|
||||
return
|
||||
var variant_index := mini(
|
||||
floori(clampf(roll, 0.0, 0.999999) * float(variants.size())),
|
||||
variants.size() - 1,
|
||||
)
|
||||
_apply_material_variant_to_meshes(
|
||||
root_node,
|
||||
target_material_names,
|
||||
variants[variant_index],
|
||||
true,
|
||||
)
|
||||
|
||||
|
||||
func _apply_random_material_variant(
|
||||
root_node: Node,
|
||||
target_material_names: PackedStringArray,
|
||||
|
|
@ -1558,6 +1668,7 @@ func _apply_random_material_variant(
|
|||
root_node,
|
||||
target_material_names,
|
||||
variant,
|
||||
false,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1565,16 +1676,28 @@ func _apply_material_variant_to_meshes(
|
|||
root_node: Node,
|
||||
target_material_names: PackedStringArray,
|
||||
variant: Material,
|
||||
match_material_family: bool,
|
||||
) -> void:
|
||||
var mesh_instance := root_node as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.mesh != null:
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var active_material := mesh_instance.get_active_material(surface_index)
|
||||
if (
|
||||
active_material != null
|
||||
and active_material.resource_name in target_material_names
|
||||
):
|
||||
mesh_instance.set_surface_override_material(
|
||||
var source_material := mesh_instance.mesh.surface_get_material(
|
||||
surface_index
|
||||
)
|
||||
var material_matches := (
|
||||
source_material != null
|
||||
and (
|
||||
_material_name_matches_targets(
|
||||
StringName(source_material.resource_name),
|
||||
target_material_names,
|
||||
)
|
||||
if match_material_family
|
||||
else source_material.resource_name in target_material_names
|
||||
)
|
||||
)
|
||||
if material_matches:
|
||||
_apply_material_variant_to_surface(
|
||||
mesh_instance,
|
||||
surface_index,
|
||||
variant,
|
||||
)
|
||||
|
|
@ -1583,6 +1706,107 @@ func _apply_material_variant_to_meshes(
|
|||
child,
|
||||
target_material_names,
|
||||
variant,
|
||||
match_material_family,
|
||||
)
|
||||
|
||||
|
||||
func _apply_material_variant_to_surface(
|
||||
mesh_instance: MeshInstance3D,
|
||||
surface_index: int,
|
||||
variant: Material,
|
||||
) -> void:
|
||||
var wind_material := mesh_instance.get_surface_override_material(
|
||||
surface_index
|
||||
) as ShaderMaterial
|
||||
var standard_variant := variant as StandardMaterial3D
|
||||
if (
|
||||
wind_material != null
|
||||
and wind_material.shader == FOLIAGE_WIND_SHADER
|
||||
):
|
||||
# Seasonal foliage is intentionally authored as StandardMaterial3D. Keep
|
||||
# the current wind presentation intact if a malformed catalog entry slips
|
||||
# through instead of silently replacing it with an incompatible shader.
|
||||
if standard_variant == null:
|
||||
return
|
||||
var source_materials: Dictionary = mesh_instance.get_meta(
|
||||
FOLIAGE_WIND_SOURCE_MATERIALS_META,
|
||||
{},
|
||||
)
|
||||
source_materials[surface_index] = variant
|
||||
mesh_instance.set_meta(
|
||||
FOLIAGE_WIND_SOURCE_MATERIALS_META,
|
||||
source_materials,
|
||||
)
|
||||
wind_material.set_shader_parameter(
|
||||
"albedo_color",
|
||||
standard_variant.albedo_color,
|
||||
)
|
||||
wind_material.set_shader_parameter(
|
||||
"material_roughness",
|
||||
standard_variant.roughness,
|
||||
)
|
||||
wind_material.set_shader_parameter(
|
||||
"material_metallic",
|
||||
standard_variant.metallic,
|
||||
)
|
||||
return
|
||||
mesh_instance.set_surface_override_material(surface_index, variant)
|
||||
|
||||
|
||||
func _material_name_matches_targets(
|
||||
material_name: StringName,
|
||||
target_material_names: PackedStringArray,
|
||||
) -> bool:
|
||||
var normalized_name := String(material_name).to_lower()
|
||||
for target_name: String in target_material_names:
|
||||
var normalized_target := target_name.to_lower()
|
||||
if (
|
||||
normalized_name == normalized_target
|
||||
or normalized_name.begins_with(normalized_target + "_")
|
||||
):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _is_foliage_material_name(material_name: StringName) -> bool:
|
||||
var normalized_name := String(material_name).to_lower()
|
||||
for target_name: StringName in FOLIAGE_MATERIAL_NAMES:
|
||||
var normalized_target := String(target_name).to_lower()
|
||||
if (
|
||||
normalized_name == normalized_target
|
||||
or normalized_name.begins_with(normalized_target + "_")
|
||||
):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _refresh_seasonal_prop_materials() -> void:
|
||||
if _decorations == null:
|
||||
return
|
||||
for prop_node: Node in _decorations.get_children():
|
||||
var prop := prop_node as Node3D
|
||||
if prop == null:
|
||||
continue
|
||||
var definition := prop_catalog.definition_for_id(
|
||||
StringName(prop.get_meta(&"terrain_prop_id", &""))
|
||||
) if prop_catalog != null else null
|
||||
if definition == null:
|
||||
continue
|
||||
var visual_root := prop.get_node_or_null("Visual")
|
||||
if visual_root == null:
|
||||
continue
|
||||
var primary_variants := definition.primary_material_variants_for_season(
|
||||
_calendar_season
|
||||
)
|
||||
_apply_material_variant_for_roll(
|
||||
visual_root,
|
||||
definition.variant_material_slot_names,
|
||||
primary_variants,
|
||||
_primary_material_roll_for_palette(
|
||||
prop,
|
||||
definition,
|
||||
primary_variants,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,7 +1,9 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_palm.glb" id="2_scene"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/palm_summer.tres" id="3_palm_summer"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/palm_autumn.tres" id="4_palm_autumn"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -18,6 +20,9 @@ minimum_cluster_size = 2
|
|||
maximum_cluster_size = 3
|
||||
minimum_cluster_radius = 1.4
|
||||
maximum_cluster_radius = 3.0
|
||||
variant_material_slot_names = PackedStringArray("leaf", "palm")
|
||||
summer_material_variants = Array[Material]([ExtResource("3_palm_summer")])
|
||||
fall_material_variants = Array[Material]([ExtResource("4_palm_autumn")])
|
||||
prefer_ocean_facing = true
|
||||
local_overhang_direction = Vector2(-0.883, 0.469)
|
||||
ocean_facing_spread_degrees = 55.0
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_pine.glb" id="2_scene"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/pine_summer.tres" id="3_pine_summer"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/pine_autumn.tres" id="4_pine_autumn"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -15,6 +17,9 @@ minimum_spawn_chunk_distance = 2
|
|||
clearance_radius = 1.55
|
||||
minimum_visual_scale = 0.65
|
||||
maximum_visual_scale = 1.2
|
||||
variant_material_slot_names = PackedStringArray("pine")
|
||||
summer_material_variants = Array[Material]([ExtResource("3_pine_summer")])
|
||||
fall_material_variants = Array[Material]([ExtResource("4_pine_autumn")])
|
||||
collision_radius = 0.85
|
||||
collision_height = 4.0
|
||||
collision_offset = Vector3(-0.044, 0, 0.083)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_pine_large.glb" id="2_scene"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/pine_summer.tres" id="3_pine_summer"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/pine_autumn.tres" id="4_pine_autumn"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -15,6 +17,9 @@ minimum_spawn_chunk_distance = 2
|
|||
clearance_radius = 1.65
|
||||
minimum_visual_scale = 0.75
|
||||
maximum_visual_scale = 1.2
|
||||
variant_material_slot_names = PackedStringArray("pine")
|
||||
summer_material_variants = Array[Material]([ExtResource("3_pine_summer")])
|
||||
fall_material_variants = Array[Material]([ExtResource("4_pine_autumn")])
|
||||
collision_radius = 0.75
|
||||
collision_height = 9.5
|
||||
collision_offset = Vector3(-0.04, 0, 0.075)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=9 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=13 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_1.glb" id="2_scene"]
|
||||
|
|
@ -8,6 +8,10 @@
|
|||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_light.tres" id="6_wood_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_mid.tres" id="7_wood_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_dark.tres" id="8_wood_dark"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_light.tres" id="9_leaf_autumn_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn.tres" id="10_leaf_autumn"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_mid.tres" id="11_leaf_autumn_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_dark.tres" id="12_leaf_autumn_dark"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -21,8 +25,10 @@ minimum_spawn_chunk_distance = 2
|
|||
clearance_radius = 1.3
|
||||
minimum_visual_scale = 0.65
|
||||
maximum_visual_scale = 1.2
|
||||
variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark")
|
||||
variant_material_slot_names = PackedStringArray("leaf")
|
||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
summer_material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
fall_material_variants = Array[Material]([ExtResource("9_leaf_autumn_light"), ExtResource("10_leaf_autumn"), ExtResource("11_leaf_autumn_mid"), ExtResource("12_leaf_autumn_dark")])
|
||||
secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light", "wood_mid")
|
||||
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
||||
collision_radius = 0.4
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=9 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=13 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_2.glb" id="2_scene"]
|
||||
|
|
@ -8,6 +8,10 @@
|
|||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_light.tres" id="6_wood_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_mid.tres" id="7_wood_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_dark.tres" id="8_wood_dark"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_light.tres" id="9_leaf_autumn_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn.tres" id="10_leaf_autumn"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_mid.tres" id="11_leaf_autumn_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_dark.tres" id="12_leaf_autumn_dark"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -21,8 +25,10 @@ minimum_spawn_chunk_distance = 2
|
|||
clearance_radius = 1.5
|
||||
minimum_visual_scale = 0.7
|
||||
maximum_visual_scale = 1.2
|
||||
variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark")
|
||||
variant_material_slot_names = PackedStringArray("leaf")
|
||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
summer_material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
fall_material_variants = Array[Material]([ExtResource("9_leaf_autumn_light"), ExtResource("10_leaf_autumn"), ExtResource("11_leaf_autumn_mid"), ExtResource("12_leaf_autumn_dark")])
|
||||
secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light", "wood_mid")
|
||||
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
||||
collision_radius = 0.55
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=9 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=13 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_3.glb" id="2_scene"]
|
||||
|
|
@ -8,6 +8,10 @@
|
|||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_light.tres" id="6_wood_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_mid.tres" id="7_wood_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_dark.tres" id="8_wood_dark"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_light.tres" id="9_leaf_autumn_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn.tres" id="10_leaf_autumn"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_mid.tres" id="11_leaf_autumn_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_dark.tres" id="12_leaf_autumn_dark"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -21,8 +25,10 @@ minimum_spawn_chunk_distance = 2
|
|||
clearance_radius = 1.8
|
||||
minimum_visual_scale = 0.7
|
||||
maximum_visual_scale = 1.2
|
||||
variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark")
|
||||
variant_material_slot_names = PackedStringArray("leaf")
|
||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
summer_material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
fall_material_variants = Array[Material]([ExtResource("9_leaf_autumn_light"), ExtResource("10_leaf_autumn"), ExtResource("11_leaf_autumn_mid"), ExtResource("12_leaf_autumn_dark")])
|
||||
secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light", "wood_mid")
|
||||
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
||||
collision_radius = 0.55
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=9 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=13 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_large.glb" id="2_scene"]
|
||||
|
|
@ -8,6 +8,10 @@
|
|||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_light.tres" id="6_wood_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_mid.tres" id="7_wood_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/wood_dark.tres" id="8_wood_dark"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_light.tres" id="9_leaf_autumn_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn.tres" id="10_leaf_autumn"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_mid.tres" id="11_leaf_autumn_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_autumn_dark.tres" id="12_leaf_autumn_dark"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -21,8 +25,10 @@ minimum_spawn_chunk_distance = 2
|
|||
clearance_radius = 2.5
|
||||
minimum_visual_scale = 0.75
|
||||
maximum_visual_scale = 1.2
|
||||
variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark")
|
||||
variant_material_slot_names = PackedStringArray("leaf")
|
||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
summer_material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
fall_material_variants = Array[Material]([ExtResource("9_leaf_autumn_light"), ExtResource("10_leaf_autumn"), ExtResource("11_leaf_autumn_mid"), ExtResource("12_leaf_autumn_dark")])
|
||||
secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light", "wood_mid")
|
||||
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
||||
collision_radius = 1.05
|
||||
|
|
|
|||
9
world/generation/props/materials/leaf_autumn.tres
Normal file
9
world/generation/props/materials/leaf_autumn.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "leaf_autumn"
|
||||
albedo_color = Color(0.6186816, 0.4523746, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
9
world/generation/props/materials/leaf_autumn_dark.tres
Normal file
9
world/generation/props/materials/leaf_autumn_dark.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "leaf_autumn_dark"
|
||||
albedo_color = Color(0.3853541, 0.00134692, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
9
world/generation/props/materials/leaf_autumn_light.tres
Normal file
9
world/generation/props/materials/leaf_autumn_light.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "leaf_autumn_light"
|
||||
albedo_color = Color(0.8222013, 0.794216, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
9
world/generation/props/materials/leaf_autumn_mid.tres
Normal file
9
world/generation/props/materials/leaf_autumn_mid.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "leaf_autumn_mid"
|
||||
albedo_color = Color(0.6841143, 0.3515543, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[resource]
|
||||
resource_name = "leaf_variant_dark"
|
||||
albedo_color = Color(0.1712, 0.252, 0, 1)
|
||||
albedo_color = Color(0.1712407, 0.2520192, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[resource]
|
||||
resource_name = "leaf_variant_light"
|
||||
albedo_color = Color(0.3388, 0.7187, 0, 1)
|
||||
albedo_color = Color(0.3387566, 0.7186728, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[resource]
|
||||
resource_name = "leaf_variant_mid"
|
||||
albedo_color = Color(0.3147, 0.4631, 0, 1)
|
||||
albedo_color = Color(0.3146831, 0.4631305, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
|
|
|
|||
9
world/generation/props/materials/palm_autumn.tres
Normal file
9
world/generation/props/materials/palm_autumn.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "palm_autumn"
|
||||
albedo_color = Color(0.2951757, 0.3575695, 0.162893, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
9
world/generation/props/materials/palm_summer.tres
Normal file
9
world/generation/props/materials/palm_summer.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "palm_summer"
|
||||
albedo_color = Color(0.3146831, 0.4631305, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
9
world/generation/props/materials/pine_autumn.tres
Normal file
9
world/generation/props/materials/pine_autumn.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "pine_autumn"
|
||||
albedo_color = Color(0.02388079, 0.1285609, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
9
world/generation/props/materials/pine_summer.tres
Normal file
9
world/generation/props/materials/pine_summer.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "pine_summer"
|
||||
albedo_color = Color(0.02171588, 0.2242201, 0.1360403, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
|
|
@ -113,20 +113,40 @@ func validation_errors() -> PackedStringArray:
|
|||
"%s has an invalid loose-cluster radius."
|
||||
% definition.stable_id
|
||||
)
|
||||
var primary_material_palettes: Array[Array] = [
|
||||
definition.material_variants,
|
||||
definition.spring_material_variants,
|
||||
definition.summer_material_variants,
|
||||
definition.fall_material_variants,
|
||||
definition.winter_material_variants,
|
||||
]
|
||||
var has_primary_material_variants := false
|
||||
for palette: Array in primary_material_palettes:
|
||||
has_primary_material_variants = (
|
||||
has_primary_material_variants or not palette.is_empty()
|
||||
)
|
||||
for material: Material in palette:
|
||||
if material == null:
|
||||
errors.append(
|
||||
"%s contains an empty primary material variant."
|
||||
% definition.stable_id
|
||||
)
|
||||
elif not material is StandardMaterial3D:
|
||||
errors.append(
|
||||
(
|
||||
"%s primary material variants must use "
|
||||
+ "StandardMaterial3D so foliage wind is retained."
|
||||
)
|
||||
% definition.stable_id
|
||||
)
|
||||
if (
|
||||
not definition.material_variants.is_empty()
|
||||
has_primary_material_variants
|
||||
and definition.variant_material_slot_names.is_empty()
|
||||
):
|
||||
errors.append(
|
||||
"%s has material variants but no target material slots."
|
||||
"%s has primary material variants but no target material slots."
|
||||
% definition.stable_id
|
||||
)
|
||||
for material: Material in definition.material_variants:
|
||||
if material == null:
|
||||
errors.append(
|
||||
"%s contains an empty material variant."
|
||||
% definition.stable_id
|
||||
)
|
||||
if (
|
||||
not definition.secondary_material_variants.is_empty()
|
||||
and definition.secondary_variant_material_slot_names.is_empty()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
class_name TerrainPropDefinition
|
||||
extends Resource
|
||||
|
||||
const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
||||
|
||||
@export var stable_id: StringName
|
||||
@export var label := ""
|
||||
@export var packed_scene: PackedScene
|
||||
|
|
@ -33,7 +35,15 @@ extends Resource
|
|||
## A single material is chosen per prop and applied only to matching imported
|
||||
## material slots. This keeps trunks untouched while varying foliage.
|
||||
@export var variant_material_slot_names := PackedStringArray()
|
||||
## Legacy/default palette. It remains the final fallback for definitions that
|
||||
## have not been migrated to explicit seasonal palettes.
|
||||
@export var material_variants: Array[Material] = []
|
||||
@export_group("Seasonal Primary Materials")
|
||||
@export var spring_material_variants: Array[Material] = []
|
||||
@export var summer_material_variants: Array[Material] = []
|
||||
@export var fall_material_variants: Array[Material] = []
|
||||
@export var winter_material_variants: Array[Material] = []
|
||||
@export_group("")
|
||||
## An independent second material channel, used when both foliage and wood
|
||||
## should vary without coupling their color choices.
|
||||
@export var secondary_variant_material_slot_names := PackedStringArray()
|
||||
|
|
@ -71,6 +81,28 @@ func supports_chunk_tags(chunk_tags: PackedStringArray) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func primary_material_variants_for_season(
|
||||
season: int,
|
||||
) -> Array[Material]:
|
||||
var requested: Array[Material] = []
|
||||
match season:
|
||||
CalendarSeasonType.Season.SPRING:
|
||||
requested = spring_material_variants
|
||||
CalendarSeasonType.Season.SUMMER:
|
||||
requested = summer_material_variants
|
||||
CalendarSeasonType.Season.FALL:
|
||||
requested = fall_material_variants
|
||||
CalendarSeasonType.Season.WINTER:
|
||||
requested = winter_material_variants
|
||||
if not requested.is_empty():
|
||||
return requested
|
||||
# Spring and winter intentionally retain summer foliage until their authored
|
||||
# palettes arrive. Unknown dates use the same stable visual fallback.
|
||||
if not summer_material_variants.is_empty():
|
||||
return summer_material_variants
|
||||
return material_variants
|
||||
|
||||
|
||||
func has_collision() -> bool:
|
||||
return has_cylinder_collision() or has_box_collision()
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ func set_water_motion_strength_multiplier(_multiplier: float) -> void:
|
|||
pass
|
||||
|
||||
|
||||
func set_calendar_season(_season: int) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func get_playable_half_extents() -> Vector2:
|
||||
return Vector2(50.0, 50.0)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const RVUpgradeInteractionType = preload(
|
|||
"res://world/rv_upgrade_interaction.gd"
|
||||
)
|
||||
const WorldLayoutType = preload("res://world/world_layout.gd")
|
||||
const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
||||
const GENERATED_REGION_SCENE: PackedScene = preload(
|
||||
"res://world/generation/generated_world_region.tscn"
|
||||
)
|
||||
|
|
@ -38,6 +39,7 @@ var _world_seed: int = PlayerSaveManager.DEFAULT_WORLD_SEED
|
|||
var _light_performance_profile: bool = false
|
||||
var _foliage_wind_strength_multiplier: float = 1.0
|
||||
var _water_motion_strength_multiplier: float = 1.0
|
||||
var _calendar_season: int = CalendarSeasonType.UNKNOWN
|
||||
var _dedicated_simulation: bool = false
|
||||
var _local_home_interior_presentation_active: bool = false
|
||||
|
||||
|
|
@ -118,6 +120,16 @@ func set_water_motion_strength_multiplier(multiplier: float) -> void:
|
|||
)
|
||||
|
||||
|
||||
func set_calendar_season(season: int) -> void:
|
||||
_calendar_season = (
|
||||
season
|
||||
if season >= 0 and season < CalendarSeasonType.Season.size()
|
||||
else CalendarSeasonType.UNKNOWN
|
||||
)
|
||||
if _active_region != null:
|
||||
_active_region.set_calendar_season(_calendar_season)
|
||||
|
||||
|
||||
func set_local_home_interior_presentation_active(enabled: bool) -> void:
|
||||
_local_home_interior_presentation_active = enabled
|
||||
if _active_region != null and not _dedicated_simulation:
|
||||
|
|
@ -296,6 +308,7 @@ func get_gatherable_spawn_positions(
|
|||
func _ready() -> void:
|
||||
if _active_region != null:
|
||||
_world_layout = _active_region.region_id
|
||||
_active_region.set_calendar_season(_calendar_season)
|
||||
_configure_world_coverage()
|
||||
|
||||
|
||||
|
|
@ -318,6 +331,7 @@ func _replace_active_region(layout: StringName, world_seed: int) -> bool:
|
|||
return false
|
||||
if layout == WorldLayoutType.GENERATED:
|
||||
replacement.set("initial_seed", world_seed)
|
||||
replacement.set_calendar_season(_calendar_season)
|
||||
if _active_region != null:
|
||||
_regions_root.remove_child(_active_region)
|
||||
_active_region.free()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue