2026-07-27 03:30:03 -04:00
|
|
|
@tool
|
2026-07-27 00:13:07 -04:00
|
|
|
class_name StarterIslandRegion
|
2026-07-27 03:30:03 -04:00
|
|
|
extends WorldRegion
|
2026-07-27 00:13:07 -04:00
|
|
|
|
|
|
|
|
const FishingShopInteractionType = preload(
|
|
|
|
|
"res://world/fishing_shop_interaction.gd"
|
|
|
|
|
)
|
2026-07-29 13:25:06 -04:00
|
|
|
const GRASS_SURFACE_NAME: String = "grass_lite"
|
2026-07-29 15:05:52 -04:00
|
|
|
const SAND_SURFACE_NAME: String = "sand"
|
2026-07-27 00:13:07 -04:00
|
|
|
|
2026-07-27 03:30:03 -04:00
|
|
|
@export_group("Owned Nodes")
|
|
|
|
|
@export_node_path("MeshInstance3D")
|
|
|
|
|
var visual_mesh_path: NodePath = (
|
|
|
|
|
^"Terrain/Visual/starter_island"
|
2026-07-27 00:13:07 -04:00
|
|
|
)
|
2026-07-27 03:30:03 -04:00
|
|
|
@export_node_path("CollisionShape3D")
|
|
|
|
|
var terrain_collision_shape_path: NodePath = (
|
|
|
|
|
^"Terrain/Collision/Shape"
|
2026-07-27 00:13:07 -04:00
|
|
|
)
|
2026-07-27 03:30:03 -04:00
|
|
|
@export_node_path("Marker3D")
|
|
|
|
|
var player_spawn_path: NodePath = ^"PlayerSpawn"
|
|
|
|
|
@export_node_path("Area3D")
|
|
|
|
|
var fishing_shop_path: NodePath = (
|
|
|
|
|
^"Interactables/FishingShopWorld/InteractionArea"
|
2026-07-27 00:13:07 -04:00
|
|
|
)
|
2026-07-27 03:30:03 -04:00
|
|
|
@export_node_path("Node3D")
|
|
|
|
|
var pelican_landmark_path: NodePath = (
|
2026-07-27 00:13:07 -04:00
|
|
|
^"Interactables/PelicanCoolerPerch"
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-27 03:29:35 -04:00
|
|
|
@export_group("Grass Surface")
|
|
|
|
|
@export var grass_material: Material
|
|
|
|
|
@export_range(0, 16, 1) var grass_surface_index: int = 0
|
|
|
|
|
|
2026-07-29 15:05:52 -04:00
|
|
|
@export_group("Sand Surface")
|
|
|
|
|
@export var sand_material: Material
|
|
|
|
|
@export_range(0, 16, 1) var sand_surface_index: int = 2
|
|
|
|
|
|
2026-07-27 00:13:07 -04:00
|
|
|
|
|
|
|
|
func _ready() -> void:
|
2026-07-27 03:29:35 -04:00
|
|
|
_apply_grass_material()
|
2026-07-29 15:05:52 -04:00
|
|
|
_apply_sand_material()
|
2026-07-27 00:13:07 -04:00
|
|
|
_build_terrain_collision()
|
2026-07-27 03:30:03 -04:00
|
|
|
if Engine.is_editor_hint():
|
|
|
|
|
update_configuration_warnings()
|
2026-07-27 00:13:07 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func get_player_spawn_transform() -> Transform3D:
|
|
|
|
|
var spawn: Marker3D = get_node_or_null(player_spawn_path) as Marker3D
|
|
|
|
|
return spawn.global_transform if spawn != null else global_transform
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func get_fishing_shop() -> FishingShopInteractionType:
|
|
|
|
|
return get_node_or_null(fishing_shop_path) as FishingShopInteractionType
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func get_pelican_landmark() -> Node3D:
|
|
|
|
|
return get_node_or_null(pelican_landmark_path) as Node3D
|
|
|
|
|
|
|
|
|
|
|
2026-08-03 15:44:56 -04:00
|
|
|
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
|
|
|
|
return get_node_or_null(^"ShorelineRibbons/Ocean") as MeshInstance3D
|
|
|
|
|
|
|
|
|
|
|
2026-07-27 00:13:07 -04:00
|
|
|
func has_terrain_collision() -> bool:
|
|
|
|
|
var collision_shape: CollisionShape3D = (
|
|
|
|
|
get_node_or_null(terrain_collision_shape_path) as CollisionShape3D
|
|
|
|
|
)
|
|
|
|
|
return collision_shape != null and collision_shape.shape != null
|
|
|
|
|
|
|
|
|
|
|
2026-07-27 03:29:35 -04:00
|
|
|
func _apply_grass_material() -> void:
|
|
|
|
|
var visual_mesh: MeshInstance3D = (
|
|
|
|
|
get_node_or_null(visual_mesh_path) as MeshInstance3D
|
|
|
|
|
)
|
|
|
|
|
if visual_mesh == null or visual_mesh.mesh == null:
|
|
|
|
|
push_error("Starter island visual mesh is unavailable.")
|
|
|
|
|
return
|
|
|
|
|
if grass_material == null:
|
|
|
|
|
push_error("Starter island grass material is unavailable.")
|
|
|
|
|
return
|
|
|
|
|
if grass_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
|
|
|
push_error("Starter island grass surface index is invalid.")
|
|
|
|
|
return
|
2026-07-29 13:25:06 -04:00
|
|
|
if (
|
|
|
|
|
visual_mesh.mesh.surface_get_name(grass_surface_index)
|
|
|
|
|
!= GRASS_SURFACE_NAME
|
|
|
|
|
):
|
2026-07-27 03:29:35 -04:00
|
|
|
push_error("Starter island grass surface name does not match.")
|
|
|
|
|
return
|
|
|
|
|
visual_mesh.set_surface_override_material(
|
|
|
|
|
grass_surface_index,
|
|
|
|
|
grass_material
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 15:05:52 -04:00
|
|
|
func _apply_sand_material() -> void:
|
|
|
|
|
var visual_mesh: MeshInstance3D = (
|
|
|
|
|
get_node_or_null(visual_mesh_path) as MeshInstance3D
|
|
|
|
|
)
|
|
|
|
|
if visual_mesh == null or visual_mesh.mesh == null:
|
|
|
|
|
push_error("Starter island visual mesh is unavailable.")
|
|
|
|
|
return
|
|
|
|
|
if sand_material == null:
|
|
|
|
|
push_error("Starter island sand material is unavailable.")
|
|
|
|
|
return
|
|
|
|
|
if sand_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
|
|
|
push_error("Starter island sand surface index is invalid.")
|
|
|
|
|
return
|
|
|
|
|
if (
|
|
|
|
|
visual_mesh.mesh.surface_get_name(sand_surface_index)
|
|
|
|
|
!= SAND_SURFACE_NAME
|
|
|
|
|
):
|
|
|
|
|
push_error("Starter island sand surface name does not match.")
|
|
|
|
|
return
|
|
|
|
|
visual_mesh.set_surface_override_material(
|
|
|
|
|
sand_surface_index,
|
|
|
|
|
sand_material
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-07-27 00:13:07 -04:00
|
|
|
func _build_terrain_collision() -> void:
|
|
|
|
|
var visual_mesh: MeshInstance3D = (
|
|
|
|
|
get_node_or_null(visual_mesh_path) as MeshInstance3D
|
|
|
|
|
)
|
|
|
|
|
var collision_shape: CollisionShape3D = (
|
|
|
|
|
get_node_or_null(terrain_collision_shape_path) as CollisionShape3D
|
|
|
|
|
)
|
|
|
|
|
if visual_mesh == null or visual_mesh.mesh == null:
|
|
|
|
|
push_error("Starter island visual mesh is unavailable.")
|
|
|
|
|
return
|
|
|
|
|
if collision_shape == null:
|
|
|
|
|
push_error("Starter island collision owner is unavailable.")
|
|
|
|
|
return
|
|
|
|
|
var terrain_shape: ConcavePolygonShape3D = (
|
|
|
|
|
visual_mesh.mesh.create_trimesh_shape()
|
|
|
|
|
)
|
|
|
|
|
if terrain_shape == null or terrain_shape.get_faces().is_empty():
|
|
|
|
|
push_error("Starter island terrain collision could not be created.")
|
|
|
|
|
return
|
|
|
|
|
collision_shape.shape = terrain_shape
|
2026-07-27 03:30:03 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _get_configuration_warnings() -> PackedStringArray:
|
|
|
|
|
var warnings := PackedStringArray()
|
|
|
|
|
var visual_mesh := get_node_or_null(visual_mesh_path) as MeshInstance3D
|
|
|
|
|
if visual_mesh == null or visual_mesh.mesh == null:
|
|
|
|
|
warnings.append("Terrain/Visual must provide the island mesh.")
|
|
|
|
|
elif grass_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
|
|
|
warnings.append("Grass surface index is outside the terrain mesh.")
|
2026-07-29 13:25:06 -04:00
|
|
|
elif (
|
|
|
|
|
visual_mesh.mesh.surface_get_name(grass_surface_index)
|
|
|
|
|
!= GRASS_SURFACE_NAME
|
|
|
|
|
):
|
2026-07-27 03:30:03 -04:00
|
|
|
warnings.append("Grass surface index must identify the grass surface.")
|
|
|
|
|
if grass_material == null:
|
|
|
|
|
warnings.append("Assign the starter-island grass material.")
|
2026-07-29 15:05:52 -04:00
|
|
|
if visual_mesh != null and visual_mesh.mesh != null:
|
|
|
|
|
if sand_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
|
|
|
warnings.append("Sand surface index is outside the terrain mesh.")
|
|
|
|
|
elif (
|
|
|
|
|
visual_mesh.mesh.surface_get_name(sand_surface_index)
|
|
|
|
|
!= SAND_SURFACE_NAME
|
|
|
|
|
):
|
|
|
|
|
warnings.append("Sand surface index must identify the sand surface.")
|
|
|
|
|
if sand_material == null:
|
|
|
|
|
warnings.append("Assign the starter-island sand material.")
|
2026-07-27 03:30:03 -04:00
|
|
|
if get_node_or_null(terrain_collision_shape_path) == null:
|
|
|
|
|
warnings.append("Terrain/Collision must provide a collision shape.")
|
|
|
|
|
if get_node_or_null(player_spawn_path) == null:
|
|
|
|
|
warnings.append("PlayerSpawn marker is missing.")
|
|
|
|
|
if get_node_or_null(fishing_shop_path) == null:
|
|
|
|
|
warnings.append("Fishing Shop placement is missing.")
|
|
|
|
|
if get_node_or_null(pelican_landmark_path) == null:
|
|
|
|
|
warnings.append("Pelican placement is missing.")
|
|
|
|
|
return warnings
|