Harden home exits and interior presentation

This commit is contained in:
Alexander Sellite 2026-09-01 17:30:34 -04:00
parent 76b4f19e4d
commit ac82a28f3b
4 changed files with 114 additions and 32 deletions

View file

@ -1,6 +1,8 @@
class_name HomeDecorController
extends Node
signal exit_requested
const DecorCatalogType = preload("res://homes/decor_catalog.gd")
const DRAG_RESPONSE: float = 22.0
const DRAG_POSITION_EPSILON: float = 0.005
@ -64,6 +66,8 @@ func setup(
_toolbar.rotate_requested.connect(_rotate_selected)
_toolbar.store_requested.connect(_store_selected)
_toolbar.cancel_requested.connect(_clear_selection)
if not _toolbar.exit_requested.is_connected(exit_requested.emit):
_toolbar.exit_requested.connect(exit_requested.emit)
_toolbar.set_mode(_mode)
_refresh_toolbar()

View file

@ -39,6 +39,13 @@ const INTERIOR_SPACING: float = 14.0
const INTERIOR_MAX_SPAWN_SLOTS: int = 64
const TENT_INTERIOR_SIZE := Vector2(4.0, 4.0)
const STARTER_RV_INTERIOR_SIZE := Vector2(4.0, 6.0)
const STARTER_RV_EXIT_LOCAL_X: float = 1.45
const INTERIOR_EXIT_LOCAL_Y: float = 0.35
const INTERIOR_EXIT_DOORWAY_HALF_WIDTH: float = 0.48
const INTERIOR_EXIT_TRIGGER_DEPTH: float = 0.45
const INTERIOR_SHELL_COLLISION_META: StringName = (
&"home_interior_shell_collision"
)
const MIN_EXTERIOR_TRIANGLE_AREA: float = 1.0
const MIN_EXTERIOR_SEPARATION: float = 14.6
const EXTERIOR_FOOTPRINT_HALF_EXTENTS := Vector2(6.65, 3.73)
@ -585,9 +592,14 @@ func is_avatar_near_interior_exit(
) -> bool:
if avatar == null:
return false
return avatar.global_position.distance_to(
get_interior_exit_position(owner_fingerprint)
) <= INTERIOR_EXIT_DISTANCE
for exit_position: Vector3 in get_interior_exit_positions(
owner_fingerprint
):
if avatar.global_position.distance_to(exit_position) <= (
INTERIOR_EXIT_DISTANCE
):
return true
return false
func has_avatar_crossed_interior_exit_threshold(
@ -597,22 +609,30 @@ func has_avatar_crossed_interior_exit_threshold(
if avatar == null:
return false
var local_position: Vector3 = interior_global_to_local(
owner_fingerprint,
avatar.global_position,
owner_fingerprint, avatar.global_position
)
var threshold_position: Vector3 = interior_global_to_local(
owner_fingerprint,
get_interior_exit_position(owner_fingerprint),
)
var exit_direction: Vector3 = get_interior_exit_direction(
for exit_position: Vector3 in get_interior_exit_positions(
owner_fingerprint
)
return (
(local_position - threshold_position).dot(exit_direction) >= 0.0
and absf(local_position.z - threshold_position.z) <= 0.85
and local_position.y >= -0.25
and local_position.y <= 2.25
)
):
var threshold_position: Vector3 = interior_global_to_local(
owner_fingerprint, exit_position
)
var exit_direction: Vector3 = get_interior_exit_direction_near(
owner_fingerprint, exit_position
)
var outward_distance: float = (
(local_position - threshold_position).dot(exit_direction)
)
if (
outward_distance >= 0.0
and outward_distance <= INTERIOR_EXIT_TRIGGER_DEPTH
and absf(local_position.z - threshold_position.z)
<= INTERIOR_EXIT_DOORWAY_HALF_WIDTH
and local_position.y >= -0.25
and local_position.y <= 2.25
):
return true
return false
func get_interior_exit_direction(owner_fingerprint: String) -> Vector3:
@ -625,11 +645,41 @@ func get_interior_exit_direction(owner_fingerprint: String) -> Vector3:
func get_interior_exit_position(owner_fingerprint: String) -> Vector3:
var tier: int = _home_tier(owner_fingerprint)
return get_interior_anchor(owner_fingerprint) + (
Vector3(-STARTER_RV_INTERIOR_SIZE.x * 0.5 + 0.6, 0.35, 0.0)
if tier >= PlayerHomeStateType.STARTER_RV_TIER
else Vector3(TENT_INTERIOR_SIZE.x * 0.5 - 0.55, 0.35, 0.0)
return get_interior_exit_positions(owner_fingerprint)[0]
func get_interior_exit_positions(
owner_fingerprint: String,
) -> Array[Vector3]:
var anchor: Vector3 = get_interior_anchor(owner_fingerprint)
if _home_tier(owner_fingerprint) >= PlayerHomeStateType.STARTER_RV_TIER:
return [
anchor + Vector3(
-STARTER_RV_EXIT_LOCAL_X, INTERIOR_EXIT_LOCAL_Y, 0.0
),
anchor + Vector3(
STARTER_RV_EXIT_LOCAL_X, INTERIOR_EXIT_LOCAL_Y, 0.0
),
]
return [
anchor + Vector3(
TENT_INTERIOR_SIZE.x * 0.5 - 0.55,
INTERIOR_EXIT_LOCAL_Y,
0.0,
),
]
func get_interior_exit_direction_near(
owner_fingerprint: String,
world_position: Vector3,
) -> Vector3:
if _home_tier(owner_fingerprint) < PlayerHomeStateType.STARTER_RV_TIER:
return Vector3.RIGHT
return (
Vector3.RIGHT
if world_position.x >= get_interior_anchor(owner_fingerprint).x
else Vector3.LEFT
)
@ -1200,9 +1250,14 @@ func _add_imported_mesh_collision(mesh_instance: MeshInstance3D) -> void:
var shape: ConcavePolygonShape3D = mesh_instance.mesh.create_trimesh_shape()
if shape == null:
return
# Imported room shells are authored to render from both sides, but concave
# collision defaults to front faces only. Interior walls therefore need
# back-face collision or the player can walk through their inward side.
shape.backface_collision = true
var body := StaticBody3D.new()
body.name = "%sCollision" % mesh_instance.name
body.transform = mesh_instance.transform
body.set_meta(INTERIOR_SHELL_COLLISION_META, true)
var collision := CollisionShape3D.new()
collision.shape = shape
body.add_child(collision)

View file

@ -63,6 +63,8 @@ func setup(
_session.peer_authenticated.connect(_on_peer_authenticated)
if not _session.peer_removed.is_connected(_on_peer_removed):
_session.peer_removed.connect(_on_peer_removed)
if not _spawn_service.avatar_spawned.is_connected(_on_avatar_spawned):
_spawn_service.avatar_spawned.connect(_on_avatar_spawned)
if not _local_home.changed.is_connected(_on_local_home_changed):
_local_home.changed.connect(_on_local_home_changed)
@ -1126,11 +1128,8 @@ func _process_transition_request(peer_id: int, data: Dictionary) -> void:
if (
_session.get_peer_space(peer_id)
!= NetworkHomeProtocolType.make_space_id(owner_fingerprint)
or not _world_service.is_avatar_near_interior_exit(
avatar, owner_fingerprint
)
):
_send_transition_rejection(peer_id, "Move closer to the home door.")
_send_transition_rejection(peer_id, "You are not inside that home.")
return
_apply_host_transition(
peer_id,
@ -1276,6 +1275,15 @@ func _refresh_peer_visibility() -> void:
)
func _on_avatar_spawned(peer_id: int, _avatar: Player) -> void:
var local_peer_id: int = _session.get_local_peer_id()
if local_peer_id <= 0 or peer_id == local_peer_id:
return
_spawn_service.set_peer_presentation_visible(
peer_id, _session.peers_share_space(local_peer_id, peer_id)
)
func _on_local_home_changed() -> void:
if _suppress_local_submission or _publish_queued:
return

View file

@ -5,26 +5,34 @@ signal placement_mode_requested(mode: int)
signal rotate_requested
signal store_requested
signal cancel_requested
signal exit_requested
var _mode_button: Button
var _selection_label: Label
var _rotate_button: Button
var _store_button: Button
var _clear_button: Button
var _exit_button: Button
var _mode: PlayerHomeState.PlacementMode = PlayerHomeState.PlacementMode.GRID
var _interactive: bool = true
var _selection_active: bool = false
var _active: bool = false
var _menu_obscured: bool = false
func _ready() -> void:
_build_ui()
hide()
_refresh_visibility()
func set_active(active: bool) -> void:
if visible == active:
return
visible = active
_active = active
_refresh_visibility()
func set_menu_obscured(obscured: bool) -> void:
_menu_obscured = obscured
_refresh_visibility()
func set_interactive(interactive: bool) -> void:
@ -59,13 +67,17 @@ func report_status(message: String) -> void:
_selection_label.text = message
func _refresh_visibility() -> void:
visible = _active and not _menu_obscured
func _build_ui() -> void:
set_anchors_preset(Control.PRESET_TOP_RIGHT)
offset_left = -500.0
offset_left = -610.0
offset_top = 0.0
offset_right = 0.0
offset_bottom = 106.0
custom_minimum_size = Vector2(500.0, 0.0)
custom_minimum_size = Vector2(610.0, 0.0)
z_index = 85
mouse_filter = Control.MOUSE_FILTER_STOP
var panel_style: StyleBoxFlat = UtilityPageStyle.panel_style()
@ -102,6 +114,9 @@ func _build_ui() -> void:
_store_button.pressed.connect(store_requested.emit)
_clear_button = _add_tool_button(row, "clear", "clear selection", 82.0)
_clear_button.pressed.connect(cancel_requested.emit)
_exit_button = _add_tool_button(row, "Exit RV", "exit the RV", 100.0)
_exit_button.name = "ExitRVButton"
_exit_button.pressed.connect(exit_requested.emit)
_selection_label = Label.new()
_selection_label.text = "use held decor to place; click or drag to edit"
_selection_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART