forked from xiangwang25/whale-town-front-v2
feat: polish personal space editor
This commit is contained in:
@@ -22,6 +22,8 @@ const MUTED_COLOR: Color = Color(0.400, 0.500, 0.620)
|
||||
const DECOR_TEXTURE_FILTER: int = CanvasItem.TEXTURE_FILTER_LINEAR
|
||||
const DECOR_COLLISION_LAYER: int = 1
|
||||
const DECOR_COLLISION_MASK: int = 1
|
||||
const ROOM_GRID_SIZE: float = 16.0
|
||||
const ROOM_PLACE_BOUNDS: Rect2 = Rect2(-426.0, -186.0, 852.0, 398.0)
|
||||
|
||||
@onready var player: PlayerController = $YSortWorld/Characters/Players/Player
|
||||
@onready var playerCamera: Camera2D = $YSortWorld/Characters/Players/Player/Camera2D
|
||||
@@ -35,18 +37,48 @@ var _inventoryPanel: PanelContainer
|
||||
var _inventoryGrid: GridContainer
|
||||
var _dragSurface: Control
|
||||
var _toastLabel: Label
|
||||
var _roomToolbar: PanelContainer
|
||||
var _roomTitleLabel: Label
|
||||
var _layoutStatusLabel: Label
|
||||
var _escHintLabel: Label
|
||||
var _editButton: Button
|
||||
var _backpackButton: Button
|
||||
var _undoButton: Button
|
||||
var _redoButton: Button
|
||||
var _rotateButton: Button
|
||||
var _collectButton: Button
|
||||
var _resetButton: Button
|
||||
var _saveButton: Button
|
||||
var _finishEditButton: Button
|
||||
var _unsavedDialog: PanelContainer
|
||||
var _selectedDecorId: String = ""
|
||||
var _draggedDecor: Sprite2D
|
||||
var _dragOffset: Vector2 = Vector2.ZERO
|
||||
var _dragOriginalItem: Dictionary = {}
|
||||
var _decorItems: Dictionary = {}
|
||||
var _serverDecorItems: Dictionary = {}
|
||||
var _decorNodes: Dictionary = {}
|
||||
var _decorCollisionBodies: Dictionary = {}
|
||||
var _remoteDecorDefinitions: Dictionary = {}
|
||||
var _inventoryRequestInFlight: bool = false
|
||||
var _inventoryRequestReportsErrors: bool = false
|
||||
var _inventoryLoaded: bool = false
|
||||
var _visitorMode: bool = false
|
||||
var _visitorOwnerId: String = ""
|
||||
var _visitorOwnerName: String = ""
|
||||
var _editMode: bool = false
|
||||
var _undoHistory: Array[Dictionary] = []
|
||||
var _redoHistory: Array[Dictionary] = []
|
||||
var _dirtyDecorIds: Dictionary = {}
|
||||
var _saveAwaitingIds: Dictionary = {}
|
||||
var _saveFailedIds: Dictionary = {}
|
||||
var _saveInProgress: bool = false
|
||||
var _exitAfterSave: bool = false
|
||||
var _resetArmed: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("whaletown_escape_dismissible")
|
||||
_configure_visit_mode()
|
||||
_leave_multiplayer_world()
|
||||
_apply_spawn_point()
|
||||
_configure_camera()
|
||||
@@ -59,6 +91,8 @@ func _ready() -> void:
|
||||
set_process(false)
|
||||
|
||||
func _discover_destination() -> void:
|
||||
if _visitorMode:
|
||||
return
|
||||
var destination_id := SceneManager.get_next_destination_id()
|
||||
if destination_id.is_empty():
|
||||
destination_id = "personal_room"
|
||||
@@ -81,11 +115,15 @@ func _exit_tree() -> void:
|
||||
saveManager.decor_save_succeeded.disconnect(_on_decor_save_succeeded)
|
||||
if saveManager.decor_save_failed.is_connected(_on_decor_save_failed):
|
||||
saveManager.decor_save_failed.disconnect(_on_decor_save_failed)
|
||||
if is_instance_valid(_inventoryRequest):
|
||||
_inventoryRequest.cancel_request()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if get_viewport().is_input_handled():
|
||||
return
|
||||
if event is InputEventMouseButton:
|
||||
if not _can_edit_layout():
|
||||
return
|
||||
var mouseEvent := event as InputEventMouseButton
|
||||
if mouseEvent.button_index != MOUSE_BUTTON_LEFT:
|
||||
return
|
||||
@@ -104,17 +142,23 @@ func _process(_delta: float) -> void:
|
||||
_update_dragged_decor_position()
|
||||
|
||||
func is_escape_dismissible() -> bool:
|
||||
return _draggedDecor != null or (is_instance_valid(_inventoryPanel) and _inventoryPanel.visible)
|
||||
return _draggedDecor != null or (is_instance_valid(_inventoryPanel) and _inventoryPanel.visible) or (is_instance_valid(_unsavedDialog) and _unsavedDialog.visible) or _editMode
|
||||
|
||||
func get_escape_priority() -> int:
|
||||
return 500
|
||||
|
||||
func request_escape_close() -> void:
|
||||
if _draggedDecor != null:
|
||||
_finish_decor_drag()
|
||||
_cancel_decor_drag()
|
||||
return
|
||||
if is_instance_valid(_inventoryPanel):
|
||||
if is_instance_valid(_inventoryPanel) and _inventoryPanel.visible:
|
||||
_inventoryPanel.visible = false
|
||||
return
|
||||
if is_instance_valid(_unsavedDialog) and _unsavedDialog.visible:
|
||||
_unsavedDialog.visible = false
|
||||
return
|
||||
if _editMode:
|
||||
_request_finish_edit()
|
||||
|
||||
func _apply_spawn_point() -> void:
|
||||
if player.has_scene_position_override:
|
||||
@@ -126,6 +170,12 @@ func _apply_spawn_point() -> void:
|
||||
marker = $Markers/DefaultSpawn
|
||||
player.global_position = marker.global_position
|
||||
|
||||
func _configure_visit_mode() -> void:
|
||||
var context: Dictionary = SceneManager.get_room_visit_context()
|
||||
_visitorOwnerId = str(context.get("owner_user_id", "")).strip_edges()
|
||||
_visitorOwnerName = str(context.get("owner_nickname", "")).strip_edges()
|
||||
_visitorMode = not _visitorOwnerId.is_empty()
|
||||
|
||||
func _configure_camera() -> void:
|
||||
playerCamera.zoom = CAMERA_ZOOM
|
||||
playerCamera.position_smoothing_enabled = true
|
||||
@@ -150,6 +200,8 @@ func _build_room_decor_requests() -> void:
|
||||
add_child(_inventoryRequest)
|
||||
|
||||
func _build_room_decor_ui() -> void:
|
||||
_build_room_toolbar()
|
||||
_build_unsaved_exit_dialog()
|
||||
_inventoryPanel = PanelContainer.new()
|
||||
_inventoryPanel.visible = false
|
||||
_inventoryPanel.custom_minimum_size = Vector2(560, 560)
|
||||
@@ -223,19 +275,6 @@ func _build_room_decor_ui() -> void:
|
||||
_inventoryGrid.add_theme_constant_override("v_separation", 14)
|
||||
scroll.add_child(_inventoryGrid)
|
||||
|
||||
var removeButton := Button.new()
|
||||
removeButton.text = "收回选中家具"
|
||||
removeButton.custom_minimum_size = Vector2(0, 46)
|
||||
removeButton.add_theme_font_size_override("font_size", 17)
|
||||
removeButton.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
removeButton.add_theme_color_override("font_hover_color", ACCENT_COLOR)
|
||||
removeButton.add_theme_color_override("font_pressed_color", Color(0.060, 0.270, 0.540, 1.0))
|
||||
removeButton.add_theme_color_override("font_disabled_color", Color(0.520, 0.600, 0.680, 0.55))
|
||||
removeButton.add_theme_stylebox_override("normal", _create_panel_style(Color(0.900, 0.960, 1.0, 0.92), 16, false))
|
||||
removeButton.add_theme_stylebox_override("hover", _create_panel_style(Color(0.820, 0.925, 1.0, 1.0), 16, false))
|
||||
removeButton.pressed.connect(_remove_selected_decor)
|
||||
root.add_child(removeButton)
|
||||
|
||||
_toastLabel = Label.new()
|
||||
_toastLabel.visible = false
|
||||
_toastLabel.offset_left = 360
|
||||
@@ -257,6 +296,146 @@ func _build_room_decor_ui() -> void:
|
||||
_dragSurface.gui_input.connect(_on_drag_surface_gui_input)
|
||||
uiLayer.add_child(_dragSurface)
|
||||
|
||||
func _build_room_toolbar() -> void:
|
||||
_roomToolbar = PanelContainer.new()
|
||||
_roomToolbar.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_roomToolbar.add_theme_stylebox_override("panel", _create_panel_style(Color(0.970, 0.992, 1.0, 0.94), 18, true))
|
||||
if _visitorMode:
|
||||
_roomToolbar.set_anchors_preset(Control.PRESET_CENTER_TOP)
|
||||
_roomToolbar.offset_left = -430
|
||||
_roomToolbar.offset_top = 26
|
||||
_roomToolbar.offset_right = 430
|
||||
_roomToolbar.offset_bottom = 88
|
||||
else:
|
||||
_roomToolbar.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)
|
||||
_roomToolbar.offset_left = -540
|
||||
_roomToolbar.offset_top = -96
|
||||
_roomToolbar.offset_right = 540
|
||||
_roomToolbar.offset_bottom = -28
|
||||
uiLayer.add_child(_roomToolbar)
|
||||
|
||||
var margin := MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 16)
|
||||
margin.add_theme_constant_override("margin_top", 8)
|
||||
margin.add_theme_constant_override("margin_right", 16)
|
||||
margin.add_theme_constant_override("margin_bottom", 8)
|
||||
_roomToolbar.add_child(margin)
|
||||
var row := HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 8)
|
||||
margin.add_child(row)
|
||||
|
||||
_roomTitleLabel = Label.new()
|
||||
_roomTitleLabel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_roomTitleLabel.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_roomTitleLabel.add_theme_font_size_override("font_size", 19)
|
||||
_roomTitleLabel.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
_roomTitleLabel.text = "%s的个人空间 · 只读参观" % (_visitorOwnerName if not _visitorOwnerName.is_empty() else "这位玩家") if _visitorMode else "我的个人空间"
|
||||
row.add_child(_roomTitleLabel)
|
||||
|
||||
if _visitorMode:
|
||||
row.add_child(_create_toolbar_button("离开参观", _leave_visited_room))
|
||||
return
|
||||
|
||||
_layoutStatusLabel = Label.new()
|
||||
_layoutStatusLabel.custom_minimum_size = Vector2(128, 0)
|
||||
_layoutStatusLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
_layoutStatusLabel.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_layoutStatusLabel.add_theme_font_size_override("font_size", 14)
|
||||
_layoutStatusLabel.add_theme_color_override("font_color", MUTED_COLOR)
|
||||
row.add_child(_layoutStatusLabel)
|
||||
|
||||
_escHintLabel = Label.new()
|
||||
_escHintLabel.text = "Esc 结束编辑"
|
||||
_escHintLabel.custom_minimum_size = Vector2(92, 0)
|
||||
_escHintLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_escHintLabel.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_escHintLabel.add_theme_font_size_override("font_size", 13)
|
||||
_escHintLabel.add_theme_color_override("font_color", MUTED_COLOR)
|
||||
row.add_child(_escHintLabel)
|
||||
|
||||
_editButton = _create_toolbar_button("编辑布局", func() -> void: _set_edit_mode(true))
|
||||
_backpackButton = _create_toolbar_button("背包", _toggle_inventory_panel)
|
||||
_undoButton = _create_toolbar_button("撤销", _undo_last_change)
|
||||
_redoButton = _create_toolbar_button("重做", _redo_last_change)
|
||||
_rotateButton = _create_toolbar_button("旋转", _rotate_selected_decor)
|
||||
_collectButton = _create_toolbar_button("收回", _remove_selected_decor)
|
||||
_resetButton = _create_toolbar_button("重置布局", _request_reset_layout)
|
||||
_saveButton = _create_toolbar_button("保存", _save_layout)
|
||||
_finishEditButton = _create_toolbar_button("完成编辑", _request_finish_edit)
|
||||
row.add_child(_editButton)
|
||||
row.add_child(_backpackButton)
|
||||
row.add_child(_undoButton)
|
||||
row.add_child(_redoButton)
|
||||
row.add_child(_rotateButton)
|
||||
row.add_child(_collectButton)
|
||||
row.add_child(_resetButton)
|
||||
row.add_child(_saveButton)
|
||||
row.add_child(_finishEditButton)
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _build_unsaved_exit_dialog() -> void:
|
||||
_unsavedDialog = PanelContainer.new()
|
||||
_unsavedDialog.name = "UnsavedLayoutDialog"
|
||||
_unsavedDialog.visible = false
|
||||
_unsavedDialog.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_unsavedDialog.set_anchors_preset(Control.PRESET_CENTER)
|
||||
_unsavedDialog.offset_left = -250
|
||||
_unsavedDialog.offset_top = -132
|
||||
_unsavedDialog.offset_right = 250
|
||||
_unsavedDialog.offset_bottom = 132
|
||||
_unsavedDialog.add_theme_stylebox_override("panel", _create_panel_style(Color(0.965, 0.990, 1.0, 0.99), 24, true))
|
||||
uiLayer.add_child(_unsavedDialog)
|
||||
|
||||
var margin := MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 28)
|
||||
margin.add_theme_constant_override("margin_top", 26)
|
||||
margin.add_theme_constant_override("margin_right", 28)
|
||||
margin.add_theme_constant_override("margin_bottom", 24)
|
||||
_unsavedDialog.add_child(margin)
|
||||
var content := VBoxContainer.new()
|
||||
content.add_theme_constant_override("separation", 14)
|
||||
margin.add_child(content)
|
||||
var title := Label.new()
|
||||
title.text = "布局还没有保存"
|
||||
title.add_theme_font_size_override("font_size", 24)
|
||||
title.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
content.add_child(title)
|
||||
var detail := Label.new()
|
||||
detail.text = "你可以先保存本次摆放,或者放弃未保存的改动。"
|
||||
detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
detail.add_theme_font_size_override("font_size", 16)
|
||||
detail.add_theme_color_override("font_color", MUTED_COLOR)
|
||||
content.add_child(detail)
|
||||
var actions := HBoxContainer.new()
|
||||
actions.alignment = BoxContainer.ALIGNMENT_END
|
||||
actions.add_theme_constant_override("separation", 10)
|
||||
content.add_child(actions)
|
||||
var continueButton := _create_toolbar_button("继续编辑", func() -> void: _hide_unsaved_dialog())
|
||||
var discardButton := _create_toolbar_button("放弃改动", _discard_and_finish_edit)
|
||||
discardButton.add_theme_color_override("font_hover_color", Color(0.850, 0.300, 0.340))
|
||||
var saveExitButton := _create_toolbar_button("保存并退出", func() -> void: _save_layout(true))
|
||||
saveExitButton.add_theme_color_override("font_color", Color.WHITE)
|
||||
saveExitButton.add_theme_stylebox_override("normal", _create_panel_style(ACCENT_COLOR, 12, false))
|
||||
saveExitButton.add_theme_stylebox_override("hover", _create_panel_style(Color(0.150, 0.680, 1.0, 1.0), 12, false))
|
||||
actions.add_child(continueButton)
|
||||
actions.add_child(discardButton)
|
||||
actions.add_child(saveExitButton)
|
||||
|
||||
func _create_toolbar_button(label_text: String, callback: Callable) -> Button:
|
||||
var button := Button.new()
|
||||
button.text = label_text
|
||||
button.custom_minimum_size = Vector2(74, 38)
|
||||
button.focus_mode = Control.FOCUS_NONE
|
||||
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
button.add_theme_font_size_override("font_size", 15)
|
||||
button.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
button.add_theme_color_override("font_hover_color", ACCENT_COLOR)
|
||||
button.add_theme_color_override("font_disabled_color", Color(0.52, 0.60, 0.68, 0.65))
|
||||
button.add_theme_stylebox_override("normal", _create_panel_style(Color(0.900, 0.960, 1.0, 0.92), 12, false))
|
||||
button.add_theme_stylebox_override("hover", _create_panel_style(Color(0.820, 0.925, 1.0, 1.0), 12, false))
|
||||
button.pressed.connect(callback)
|
||||
return button
|
||||
|
||||
func _connect_room_decor_events() -> void:
|
||||
var eventSystem := _get_event_system()
|
||||
if eventSystem == null:
|
||||
@@ -264,18 +443,27 @@ func _connect_room_decor_events() -> void:
|
||||
else:
|
||||
eventSystem.call("connect_event", EventNames.HUD_BACKPACK_TOGGLE, _on_backpack_toggle_requested, self)
|
||||
var saveManager := get_node_or_null("/root/RoomDecorSaveManager")
|
||||
if saveManager != null:
|
||||
if not _visitorMode and saveManager != null:
|
||||
if not saveManager.decor_save_succeeded.is_connected(_on_decor_save_succeeded):
|
||||
saveManager.decor_save_succeeded.connect(_on_decor_save_succeeded)
|
||||
if not saveManager.decor_save_failed.is_connected(_on_decor_save_failed):
|
||||
saveManager.decor_save_failed.connect(_on_decor_save_failed)
|
||||
|
||||
func _on_backpack_toggle_requested(_data: Variant = null) -> void:
|
||||
if _visitorMode:
|
||||
_show_toast("参观模式不能编辑布局")
|
||||
return
|
||||
if not _editMode:
|
||||
_show_toast("请先点击“编辑布局”")
|
||||
return
|
||||
_toggle_inventory_panel()
|
||||
|
||||
func _toggle_inventory_panel() -> void:
|
||||
if not _can_edit_layout():
|
||||
_show_toast("请先进入布局编辑")
|
||||
return
|
||||
_inventoryPanel.visible = not _inventoryPanel.visible
|
||||
if _inventoryPanel.visible:
|
||||
if _inventoryPanel.visible and not _inventoryLoaded:
|
||||
_fetch_room_decor_inventory(true)
|
||||
|
||||
func _fetch_room_decor_inventory(reportErrors: bool = false) -> void:
|
||||
@@ -289,7 +477,8 @@ func _fetch_room_decor_inventory(reportErrors: bool = false) -> void:
|
||||
return
|
||||
_inventoryRequestInFlight = true
|
||||
_inventoryRequestReportsErrors = reportErrors
|
||||
var err := _inventoryRequest.request("%s/rooms/me/decor-placements" % NetworkConfig.get_api_base_url(), _auth_headers(), HTTPClient.METHOD_GET, "")
|
||||
var endpoint: String = "/rooms/%s/decor-placements" % _visitorOwnerId if _visitorMode else "/rooms/me/decor-placements"
|
||||
var err := _inventoryRequest.request("%s%s" % [NetworkConfig.get_api_base_url(), endpoint], _auth_headers(), HTTPClient.METHOD_GET, "")
|
||||
if err != OK:
|
||||
_inventoryRequestInFlight = false
|
||||
if _inventoryRequestReportsErrors:
|
||||
@@ -324,7 +513,16 @@ func _on_inventory_request_completed(result: int, responseCode: int, _headers: P
|
||||
_apply_inventory_payload(dataVariant as Dictionary)
|
||||
|
||||
func _apply_inventory_payload(data: Dictionary) -> void:
|
||||
if _editMode and not _dirtyDecorIds.is_empty():
|
||||
return
|
||||
_decorItems.clear()
|
||||
if _visitorMode:
|
||||
var owner_variant: Variant = data.get("owner", {})
|
||||
if owner_variant is Dictionary:
|
||||
var owner: Dictionary = owner_variant
|
||||
_visitorOwnerName = str(owner.get("nickname", owner.get("username", _visitorOwnerName))).strip_edges()
|
||||
if is_instance_valid(_roomTitleLabel):
|
||||
_roomTitleLabel.text = "%s的个人空间 · 只读参观" % (_visitorOwnerName if not _visitorOwnerName.is_empty() else "这位玩家")
|
||||
_apply_remote_decor_definitions(data.get("definitions", []))
|
||||
var itemsVariant: Variant = data.get("items", [])
|
||||
if itemsVariant is Array:
|
||||
@@ -334,13 +532,23 @@ func _apply_inventory_payload(data: Dictionary) -> void:
|
||||
var decorId := str(item.get("decor_id", "")).strip_edges()
|
||||
if not decorId.is_empty():
|
||||
_decorItems[decorId] = _merge_decor_definition(item)
|
||||
if not _visitorMode:
|
||||
_serverDecorItems = _duplicate_item_map(_decorItems)
|
||||
_dirtyDecorIds.clear()
|
||||
_saveFailedIds.clear()
|
||||
_inventoryLoaded = true
|
||||
_render_inventory_list()
|
||||
_render_placed_decors()
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _apply_local_empty_inventory() -> void:
|
||||
_decorItems.clear()
|
||||
_serverDecorItems.clear()
|
||||
_dirtyDecorIds.clear()
|
||||
_inventoryLoaded = true
|
||||
_render_inventory_list()
|
||||
_render_placed_decors()
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _merge_decor_definition(item: Dictionary) -> Dictionary:
|
||||
var decorId := str(item.get("decor_id", "")).strip_edges()
|
||||
@@ -370,6 +578,8 @@ func _apply_remote_decor_definitions(definitionsVariant: Variant) -> void:
|
||||
_remoteDecorDefinitions[decorId] = definition
|
||||
|
||||
func _render_inventory_list() -> void:
|
||||
if _visitorMode:
|
||||
return
|
||||
_clear_children(_inventoryGrid)
|
||||
if _decorItems.is_empty():
|
||||
var empty := _create_empty_inventory_card()
|
||||
@@ -471,11 +681,13 @@ func _render_placed_decors() -> void:
|
||||
var item: Dictionary = _decorItems[decorId]
|
||||
if bool(item.get("placed", false)):
|
||||
_ensure_decor_node(str(decorId), item)
|
||||
_refresh_decor_selection()
|
||||
|
||||
func _begin_inventory_decor_drag(decorId: String) -> void:
|
||||
if not _decorItems.has(decorId):
|
||||
if not _can_edit_layout() or not _decorItems.has(decorId):
|
||||
return
|
||||
_selectedDecorId = decorId
|
||||
_dragOriginalItem = (_decorItems[decorId] as Dictionary).duplicate(true)
|
||||
var item := _ensure_decor_placed_for_drag(decorId)
|
||||
var node := _ensure_decor_node(decorId, item)
|
||||
if node == null:
|
||||
@@ -487,6 +699,7 @@ func _begin_inventory_decor_drag(decorId: String) -> void:
|
||||
_inventoryPanel.visible = false
|
||||
_enable_drag_surface()
|
||||
_update_dragged_decor_position()
|
||||
_refresh_decor_selection()
|
||||
_pulse_node(_draggedDecor)
|
||||
|
||||
func _ensure_decor_placed_for_drag(decorId: String) -> Dictionary:
|
||||
@@ -497,6 +710,7 @@ func _ensure_decor_placed_for_drag(decorId: String) -> Dictionary:
|
||||
item["position_x"] = defaultPosition.x
|
||||
item["position_y"] = defaultPosition.y
|
||||
item["scale"] = _get_item_float(item, "default_scale", 1.0)
|
||||
item["rotation_degrees"] = _get_item_int(item, "default_rotation_degrees", 0)
|
||||
item["z_index"] = _get_item_int(item, "default_z_index", 0)
|
||||
_decorItems[decorId] = item
|
||||
return item
|
||||
@@ -517,11 +731,14 @@ func _ensure_decor_node(decorId: String, item: Dictionary) -> Sprite2D:
|
||||
node.global_position = Vector2(_to_float(item.get("position_x", 0.0), 0.0), _to_float(item.get("position_y", 0.0), 0.0))
|
||||
var itemScale := _get_item_float(item, "scale", _get_item_float(item, "default_scale", 1.0))
|
||||
node.scale = Vector2(itemScale, itemScale)
|
||||
node.rotation_degrees = _get_item_float(item, "rotation_degrees", _get_item_float(item, "default_rotation_degrees", 0.0))
|
||||
node.z_index = _get_item_int(item, "z_index", _get_item_int(item, "default_z_index", 0))
|
||||
_sync_decor_collision(decorId, item)
|
||||
return node
|
||||
|
||||
func _try_begin_decor_drag(worldPosition: Vector2) -> bool:
|
||||
if not _can_edit_layout():
|
||||
return false
|
||||
var bestNode: Sprite2D = null
|
||||
var bestZ := -100000
|
||||
for decorId in _decorNodes.keys():
|
||||
@@ -537,10 +754,13 @@ func _try_begin_decor_drag(worldPosition: Vector2) -> bool:
|
||||
return false
|
||||
_draggedDecor = bestNode
|
||||
_selectedDecorId = str(bestNode.get_meta("decor_id", ""))
|
||||
_dragOriginalItem = (_decorItems.get(_selectedDecorId, {}) as Dictionary).duplicate(true)
|
||||
_dragOffset = bestNode.global_position - worldPosition
|
||||
_set_decor_collision_disabled(_selectedDecorId, true)
|
||||
_draggedDecor.z_index = max(_draggedDecor.z_index, 50)
|
||||
_enable_drag_surface()
|
||||
_refresh_decor_selection()
|
||||
_update_editor_toolbar()
|
||||
return true
|
||||
|
||||
func _finish_decor_drag() -> void:
|
||||
@@ -550,23 +770,30 @@ func _finish_decor_drag() -> void:
|
||||
var decorId := str(_draggedDecor.get_meta("decor_id", ""))
|
||||
if _decorItems.has(decorId):
|
||||
var item: Dictionary = _decorItems[decorId]
|
||||
item["position_x"] = _draggedDecor.global_position.x
|
||||
item["position_y"] = _draggedDecor.global_position.y
|
||||
var snapped_position := _snap_to_room_grid(_draggedDecor.global_position)
|
||||
_draggedDecor.global_position = snapped_position
|
||||
item["position_x"] = snapped_position.x
|
||||
item["position_y"] = snapped_position.y
|
||||
item["placed"] = true
|
||||
item["scale"] = _draggedDecor.scale.x
|
||||
item["rotation_degrees"] = _draggedDecor.rotation_degrees
|
||||
item["z_index"] = _get_item_int(item, "default_z_index", 0)
|
||||
_decorItems[decorId] = item
|
||||
_draggedDecor.z_index = _get_item_int(item, "z_index", 0)
|
||||
_sync_decor_collision(decorId, item)
|
||||
_set_decor_collision_disabled(decorId, false)
|
||||
_save_decor_item(item)
|
||||
_record_item_change(_dragOriginalItem, item)
|
||||
_mark_decor_dirty(decorId)
|
||||
_render_inventory_list()
|
||||
_draggedDecor = null
|
||||
_dragOriginalItem.clear()
|
||||
_disable_drag_surface()
|
||||
_refresh_decor_selection()
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _update_dragged_decor_position() -> void:
|
||||
if _draggedDecor != null:
|
||||
_draggedDecor.global_position = get_global_mouse_position() + _dragOffset
|
||||
_draggedDecor.global_position = _constrain_room_position(get_global_mouse_position() + _dragOffset)
|
||||
var decorId := str(_draggedDecor.get_meta("decor_id", ""))
|
||||
if not decorId.is_empty() and _decorItems.has(decorId):
|
||||
var item: Dictionary = _decorItems[decorId]
|
||||
@@ -574,6 +801,21 @@ func _update_dragged_decor_position() -> void:
|
||||
item["position_y"] = _draggedDecor.global_position.y
|
||||
_sync_decor_collision(decorId, item)
|
||||
|
||||
func _cancel_decor_drag() -> void:
|
||||
if _draggedDecor == null:
|
||||
_disable_drag_surface()
|
||||
return
|
||||
var decor_id: String = str(_draggedDecor.get_meta("decor_id", ""))
|
||||
if not decor_id.is_empty() and not _dragOriginalItem.is_empty():
|
||||
_decorItems[decor_id] = _dragOriginalItem.duplicate(true)
|
||||
_render_placed_decors()
|
||||
_render_inventory_list()
|
||||
_draggedDecor = null
|
||||
_dragOriginalItem.clear()
|
||||
_disable_drag_surface()
|
||||
_refresh_decor_selection()
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _enable_drag_surface() -> void:
|
||||
if is_instance_valid(_dragSurface):
|
||||
_dragSurface.visible = true
|
||||
@@ -618,10 +860,14 @@ func _control_blocks_pointer(control: Control, mousePosition: Vector2) -> bool:
|
||||
return false
|
||||
|
||||
func _remove_selected_decor() -> void:
|
||||
if not _can_edit_layout():
|
||||
_show_toast("请先进入编辑布局")
|
||||
return
|
||||
if _selectedDecorId.is_empty() or not _decorItems.has(_selectedDecorId):
|
||||
_show_toast("请先选择一个家具")
|
||||
return
|
||||
var item: Dictionary = _decorItems[_selectedDecorId]
|
||||
var before: Dictionary = item.duplicate(true)
|
||||
item["placed"] = false
|
||||
_decorItems[_selectedDecorId] = item
|
||||
if _decorNodes.has(_selectedDecorId):
|
||||
@@ -630,28 +876,433 @@ func _remove_selected_decor() -> void:
|
||||
node.queue_free()
|
||||
_decorNodes.erase(_selectedDecorId)
|
||||
_remove_decor_collision(_selectedDecorId)
|
||||
_save_decor_item(item)
|
||||
_record_item_change(before, item)
|
||||
_mark_decor_dirty(_selectedDecorId)
|
||||
_render_inventory_list()
|
||||
_refresh_decor_selection()
|
||||
_update_editor_toolbar()
|
||||
_show_toast("已收回家具")
|
||||
|
||||
func _save_decor_item(item: Dictionary) -> void:
|
||||
if not _is_authenticated():
|
||||
_show_toast("请先登录后保存摆放")
|
||||
return
|
||||
var saveManager := get_node_or_null("/root/RoomDecorSaveManager")
|
||||
if saveManager == null or not saveManager.has_method("enqueue_save"):
|
||||
_show_toast("家具保存服务不可用")
|
||||
return
|
||||
saveManager.call("enqueue_save", item)
|
||||
|
||||
func _on_decor_save_succeeded(savedItem: Dictionary) -> void:
|
||||
var item := _merge_decor_definition(savedItem.duplicate(true))
|
||||
var decorId := str(item.get("decor_id", ""))
|
||||
if not decorId.is_empty():
|
||||
_decorItems[decorId] = item
|
||||
if decorId.is_empty() or not _saveAwaitingIds.has(decorId):
|
||||
return
|
||||
_serverDecorItems[decorId] = item.duplicate(true)
|
||||
_saveAwaitingIds.erase(decorId)
|
||||
if _decorItems.has(decorId) and _placement_signature(_decorItems[decorId]) == _placement_signature(item):
|
||||
_dirtyDecorIds.erase(decorId)
|
||||
else:
|
||||
_dirtyDecorIds[decorId] = true
|
||||
_finalize_save_if_ready()
|
||||
|
||||
func _on_decor_save_failed(_item: Dictionary, message: String) -> void:
|
||||
_show_toast(message)
|
||||
func _can_edit_layout() -> bool:
|
||||
return not _visitorMode and _editMode and not _saveInProgress
|
||||
|
||||
func _set_edit_mode(enabled: bool) -> void:
|
||||
if _visitorMode:
|
||||
return
|
||||
if not enabled:
|
||||
_request_finish_edit()
|
||||
return
|
||||
if _editMode:
|
||||
return
|
||||
_editMode = true
|
||||
_selectedDecorId = ""
|
||||
_undoHistory.clear()
|
||||
_redoHistory.clear()
|
||||
_resetArmed = false
|
||||
_refresh_decor_selection()
|
||||
_update_editor_toolbar()
|
||||
_show_toast("布局编辑已开启;改动会在点击保存后提交")
|
||||
|
||||
func _request_finish_edit() -> void:
|
||||
if not _editMode:
|
||||
return
|
||||
if _draggedDecor != null:
|
||||
_finish_decor_drag()
|
||||
if _saveInProgress:
|
||||
_show_toast("正在保存布局,请稍候")
|
||||
return
|
||||
if _has_unsaved_changes():
|
||||
_show_unsaved_exit_dialog()
|
||||
return
|
||||
_end_edit_session()
|
||||
|
||||
func _end_edit_session() -> void:
|
||||
_editMode = false
|
||||
_selectedDecorId = ""
|
||||
_undoHistory.clear()
|
||||
_redoHistory.clear()
|
||||
_resetArmed = false
|
||||
_exitAfterSave = false
|
||||
if is_instance_valid(_inventoryPanel):
|
||||
_inventoryPanel.visible = false
|
||||
_hide_unsaved_dialog()
|
||||
_refresh_decor_selection()
|
||||
_update_editor_toolbar()
|
||||
_show_toast("布局编辑已完成")
|
||||
|
||||
func _show_unsaved_exit_dialog() -> void:
|
||||
if is_instance_valid(_unsavedDialog):
|
||||
_unsavedDialog.visible = true
|
||||
|
||||
func _hide_unsaved_dialog() -> void:
|
||||
if is_instance_valid(_unsavedDialog):
|
||||
_unsavedDialog.visible = false
|
||||
|
||||
func _discard_and_finish_edit() -> void:
|
||||
if _saveInProgress:
|
||||
return
|
||||
_decorItems = _duplicate_item_map(_serverDecorItems)
|
||||
_dirtyDecorIds.clear()
|
||||
_saveFailedIds.clear()
|
||||
_render_inventory_list()
|
||||
_render_placed_decors()
|
||||
_end_edit_session()
|
||||
|
||||
func _has_unsaved_changes() -> bool:
|
||||
return not _dirtyDecorIds.is_empty()
|
||||
|
||||
func _update_editor_toolbar() -> void:
|
||||
if _visitorMode:
|
||||
return
|
||||
var has_selection := not _selectedDecorId.is_empty() and _decorItems.has(_selectedDecorId)
|
||||
var selected_is_placed := has_selection and bool((_decorItems[_selectedDecorId] as Dictionary).get("placed", false))
|
||||
var dirty_count := _dirtyDecorIds.size()
|
||||
if is_instance_valid(_editButton):
|
||||
_editButton.visible = not _editMode
|
||||
if is_instance_valid(_backpackButton):
|
||||
_backpackButton.visible = _editMode
|
||||
_backpackButton.disabled = _saveInProgress
|
||||
if is_instance_valid(_finishEditButton):
|
||||
_finishEditButton.visible = _editMode
|
||||
_finishEditButton.disabled = _saveInProgress
|
||||
if is_instance_valid(_undoButton):
|
||||
_undoButton.visible = _editMode
|
||||
_undoButton.disabled = not _can_edit_layout() or _undoHistory.is_empty()
|
||||
if is_instance_valid(_redoButton):
|
||||
_redoButton.visible = _editMode
|
||||
_redoButton.disabled = not _can_edit_layout() or _redoHistory.is_empty()
|
||||
if is_instance_valid(_rotateButton):
|
||||
_rotateButton.visible = _editMode
|
||||
_rotateButton.disabled = not _can_edit_layout() or not selected_is_placed
|
||||
if is_instance_valid(_collectButton):
|
||||
_collectButton.visible = _editMode
|
||||
_collectButton.disabled = not _can_edit_layout() or not selected_is_placed
|
||||
if is_instance_valid(_resetButton):
|
||||
_resetButton.visible = _editMode
|
||||
_resetButton.disabled = not _can_edit_layout()
|
||||
if is_instance_valid(_saveButton):
|
||||
_saveButton.visible = _editMode
|
||||
_saveButton.text = "保存" if dirty_count == 0 else "保存(%d)" % dirty_count
|
||||
_saveButton.disabled = not _editMode or _saveInProgress or dirty_count == 0
|
||||
if is_instance_valid(_layoutStatusLabel):
|
||||
_layoutStatusLabel.visible = true
|
||||
var selected_name := str((_decorItems.get(_selectedDecorId, {}) as Dictionary).get("name", "")) if has_selection else ""
|
||||
var selected_prefix := ("选中:%s · " % selected_name) if not selected_name.is_empty() else ""
|
||||
if _saveInProgress:
|
||||
_layoutStatusLabel.text = "正在保存 %d 项…" % _saveAwaitingIds.size()
|
||||
elif not _saveFailedIds.is_empty():
|
||||
_layoutStatusLabel.text = "%d 项保存失败,可重试" % _saveFailedIds.size()
|
||||
elif dirty_count > 0:
|
||||
_layoutStatusLabel.text = "%s%d 项未保存" % [selected_prefix, dirty_count]
|
||||
elif _editMode:
|
||||
_layoutStatusLabel.text = "%s草稿已保存" % selected_prefix
|
||||
else:
|
||||
_layoutStatusLabel.text = "布局已保存"
|
||||
if is_instance_valid(_escHintLabel):
|
||||
_escHintLabel.visible = _editMode
|
||||
_escHintLabel.text = "Esc 结束编辑" if _editMode else ""
|
||||
|
||||
func _record_item_change(before: Dictionary, after: Dictionary) -> void:
|
||||
if before == after:
|
||||
return
|
||||
var decor_id: String = str(after.get("decor_id", before.get("decor_id", ""))).strip_edges()
|
||||
if decor_id.is_empty():
|
||||
return
|
||||
_undoHistory.append({
|
||||
"decor_id": decor_id,
|
||||
"before": before.duplicate(true),
|
||||
"after": after.duplicate(true),
|
||||
})
|
||||
_redoHistory.clear()
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _record_bulk_change(before_items: Dictionary, after_items: Dictionary) -> void:
|
||||
if before_items == after_items:
|
||||
return
|
||||
_undoHistory.append({
|
||||
"before_items": _duplicate_item_map(before_items),
|
||||
"after_items": _duplicate_item_map(after_items),
|
||||
})
|
||||
_redoHistory.clear()
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _undo_last_change() -> void:
|
||||
if not _can_edit_layout() or _undoHistory.is_empty():
|
||||
return
|
||||
var entry: Dictionary = _undoHistory.pop_back()
|
||||
_apply_history_entry(entry, false)
|
||||
_redoHistory.append(entry)
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _redo_last_change() -> void:
|
||||
if not _can_edit_layout() or _redoHistory.is_empty():
|
||||
return
|
||||
var entry: Dictionary = _redoHistory.pop_back()
|
||||
_apply_history_entry(entry, true)
|
||||
_undoHistory.append(entry)
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _apply_history_entry(entry: Dictionary, use_after: bool) -> void:
|
||||
var key := "after_items" if use_after else "before_items"
|
||||
var bulk_variant: Variant = entry.get(key, null)
|
||||
if bulk_variant is Dictionary:
|
||||
var bulk_items: Dictionary = bulk_variant as Dictionary
|
||||
for decor_id_variant in bulk_items.keys():
|
||||
var decor_id := str(decor_id_variant)
|
||||
var item_variant: Variant = bulk_items.get(decor_id_variant, {})
|
||||
if item_variant is Dictionary:
|
||||
_decorItems[decor_id] = (item_variant as Dictionary).duplicate(true)
|
||||
_mark_decor_dirty(decor_id)
|
||||
_selectedDecorId = ""
|
||||
else:
|
||||
var item_key := "after" if use_after else "before"
|
||||
var item_variant: Variant = entry.get(item_key, {})
|
||||
if not (item_variant is Dictionary):
|
||||
return
|
||||
var item: Dictionary = (item_variant as Dictionary).duplicate(true)
|
||||
var decor_id: String = str(item.get("decor_id", "")).strip_edges()
|
||||
if decor_id.is_empty():
|
||||
return
|
||||
_decorItems[decor_id] = item
|
||||
_selectedDecorId = decor_id
|
||||
_mark_decor_dirty(decor_id)
|
||||
_render_placed_decors()
|
||||
_render_inventory_list()
|
||||
_refresh_decor_selection()
|
||||
|
||||
func _rotate_selected_decor() -> void:
|
||||
if not _can_edit_layout() or _selectedDecorId.is_empty() or not _decorItems.has(_selectedDecorId):
|
||||
return
|
||||
var item: Dictionary = _decorItems[_selectedDecorId]
|
||||
if not bool(item.get("placed", false)):
|
||||
_show_toast("请先摆放选中的家具")
|
||||
return
|
||||
var steps_variant: Variant = item.get("rotation_steps", [0])
|
||||
if not (steps_variant is Array):
|
||||
return
|
||||
var steps: Array = steps_variant as Array
|
||||
if steps.size() <= 1:
|
||||
_show_toast("该家具的方向已锁定")
|
||||
return
|
||||
var current_rotation: int = _get_item_int(item, "rotation_degrees", _get_item_int(item, "default_rotation_degrees", 0))
|
||||
var current_index: int = 0
|
||||
for index in range(steps.size()):
|
||||
if _to_int(steps[index], 0) == current_rotation:
|
||||
current_index = index
|
||||
break
|
||||
var before: Dictionary = item.duplicate(true)
|
||||
item["rotation_degrees"] = _to_int(steps[(current_index + 1) % steps.size()], 0)
|
||||
_decorItems[_selectedDecorId] = item
|
||||
_render_placed_decors()
|
||||
_record_item_change(before, item)
|
||||
_mark_decor_dirty(_selectedDecorId)
|
||||
_refresh_decor_selection()
|
||||
|
||||
func _request_reset_layout() -> void:
|
||||
if not _can_edit_layout():
|
||||
return
|
||||
if not _resetArmed:
|
||||
_resetArmed = true
|
||||
_show_toast("再次点击“重置布局”确认;家具不会丢失")
|
||||
get_tree().create_timer(3.0).timeout.connect(func() -> void: _resetArmed = false)
|
||||
return
|
||||
_resetArmed = false
|
||||
var before_items := _duplicate_item_map(_decorItems)
|
||||
for decor_id_variant in _decorItems.keys():
|
||||
var decor_id := str(decor_id_variant)
|
||||
var item: Dictionary = (_decorItems[decor_id] as Dictionary).duplicate(true)
|
||||
item["placed"] = false
|
||||
var default_position := _variant_to_vector2(item.get("default_position", Vector2.ZERO))
|
||||
item["position_x"] = default_position.x
|
||||
item["position_y"] = default_position.y
|
||||
item["scale"] = _get_item_float(item, "default_scale", 1.0)
|
||||
item["rotation_degrees"] = _get_item_int(item, "default_rotation_degrees", 0)
|
||||
item["z_index"] = _get_item_int(item, "default_z_index", 0)
|
||||
_decorItems[decor_id] = item
|
||||
_mark_decor_dirty(decor_id)
|
||||
var after_items := _duplicate_item_map(_decorItems)
|
||||
_record_bulk_change(before_items, after_items)
|
||||
_selectedDecorId = ""
|
||||
_render_placed_decors()
|
||||
_render_inventory_list()
|
||||
_refresh_decor_selection()
|
||||
_update_editor_toolbar()
|
||||
_show_toast("布局已重置为草稿;点击保存后生效")
|
||||
|
||||
func _save_layout(exit_after_save: bool = false) -> void:
|
||||
if not _editMode:
|
||||
return
|
||||
if _saveInProgress:
|
||||
_show_toast("正在保存布局,请稍候")
|
||||
return
|
||||
if _dirtyDecorIds.is_empty():
|
||||
_show_toast("当前没有需要保存的改动")
|
||||
if exit_after_save:
|
||||
_end_edit_session()
|
||||
return
|
||||
if not _is_authenticated():
|
||||
_show_toast("请先登录后保存摆放")
|
||||
return
|
||||
var save_manager := get_node_or_null("/root/RoomDecorSaveManager")
|
||||
if save_manager == null or not save_manager.has_method("enqueue_save"):
|
||||
_show_toast("家具保存服务不可用")
|
||||
return
|
||||
var save_ids: Array[String] = []
|
||||
for decor_id_variant in _dirtyDecorIds.keys():
|
||||
var decor_id := str(decor_id_variant)
|
||||
if _decorItems.has(decor_id):
|
||||
save_ids.append(decor_id)
|
||||
if save_ids.is_empty():
|
||||
_dirtyDecorIds.clear()
|
||||
_update_editor_toolbar()
|
||||
return
|
||||
_saveInProgress = true
|
||||
_exitAfterSave = exit_after_save
|
||||
_saveAwaitingIds.clear()
|
||||
_saveFailedIds.clear()
|
||||
for decor_id in save_ids:
|
||||
_saveAwaitingIds[decor_id] = true
|
||||
_update_editor_toolbar()
|
||||
for decor_id in save_ids:
|
||||
var item: Dictionary = (_decorItems[decor_id] as Dictionary).duplicate(true)
|
||||
var accepted: bool = bool(save_manager.call("enqueue_save", item))
|
||||
if not accepted and _saveAwaitingIds.has(decor_id):
|
||||
_saveAwaitingIds.erase(decor_id)
|
||||
_saveFailedIds[decor_id] = "保存请求未能加入队列"
|
||||
_finalize_save_if_ready()
|
||||
|
||||
func _on_decor_save_failed(item: Dictionary, message: String) -> void:
|
||||
var decor_id := str(item.get("decor_id", "")).strip_edges()
|
||||
if decor_id.is_empty() or not _saveAwaitingIds.has(decor_id):
|
||||
if not message.is_empty():
|
||||
_show_toast(message)
|
||||
return
|
||||
_saveAwaitingIds.erase(decor_id)
|
||||
_saveFailedIds[decor_id] = message
|
||||
_dirtyDecorIds[decor_id] = true
|
||||
_finalize_save_if_ready()
|
||||
|
||||
func _finalize_save_if_ready() -> void:
|
||||
if not _saveInProgress or not _saveAwaitingIds.is_empty():
|
||||
return
|
||||
_saveInProgress = false
|
||||
var exit_when_saved := _exitAfterSave and _saveFailedIds.is_empty() and _dirtyDecorIds.is_empty()
|
||||
_exitAfterSave = false
|
||||
_update_editor_toolbar()
|
||||
if not _saveFailedIds.is_empty():
|
||||
_show_toast("%d 项家具未保存,可再次点击保存重试" % _saveFailedIds.size())
|
||||
return
|
||||
_show_toast("布局已保存")
|
||||
if exit_when_saved:
|
||||
_end_edit_session()
|
||||
|
||||
func _mark_decor_dirty(decor_id: String) -> void:
|
||||
if decor_id.is_empty() or not _decorItems.has(decor_id):
|
||||
return
|
||||
var server_variant: Variant = _serverDecorItems.get(decor_id, null)
|
||||
if server_variant is Dictionary and _placement_signature(_decorItems[decor_id]) == _placement_signature(server_variant as Dictionary):
|
||||
_dirtyDecorIds.erase(decor_id)
|
||||
_saveFailedIds.erase(decor_id)
|
||||
else:
|
||||
_dirtyDecorIds[decor_id] = true
|
||||
_update_editor_toolbar()
|
||||
|
||||
func _placement_signature(item: Dictionary) -> String:
|
||||
return "%d|%.3f|%.3f|%.3f|%d|%d" % [
|
||||
1 if bool(item.get("placed", false)) else 0,
|
||||
_get_item_float(item, "position_x", 0.0),
|
||||
_get_item_float(item, "position_y", 0.0),
|
||||
_get_item_float(item, "scale", _get_item_float(item, "default_scale", 1.0)),
|
||||
_get_item_int(item, "rotation_degrees", _get_item_int(item, "default_rotation_degrees", 0)),
|
||||
_get_item_int(item, "z_index", _get_item_int(item, "default_z_index", 0)),
|
||||
]
|
||||
|
||||
func _duplicate_item_map(source: Dictionary) -> Dictionary:
|
||||
var duplicate: Dictionary = {}
|
||||
for decor_id_variant in source.keys():
|
||||
var item_variant: Variant = source.get(decor_id_variant, {})
|
||||
if item_variant is Dictionary:
|
||||
duplicate[str(decor_id_variant)] = (item_variant as Dictionary).duplicate(true)
|
||||
return duplicate
|
||||
|
||||
func _refresh_decor_selection() -> void:
|
||||
for decor_id_variant in _decorNodes.keys():
|
||||
var decor_id := str(decor_id_variant)
|
||||
var node := _decorNodes.get(decor_id, null) as Sprite2D
|
||||
if node == null or not is_instance_valid(node):
|
||||
continue
|
||||
var frame := node.get_node_or_null("SelectionFrame") as Line2D
|
||||
var collision_preview := node.get_node_or_null("CollisionPreview") as Line2D
|
||||
var show_frame := _editMode and decor_id == _selectedDecorId and node.texture != null
|
||||
if not show_frame:
|
||||
if frame != null:
|
||||
frame.queue_free()
|
||||
if collision_preview != null:
|
||||
collision_preview.queue_free()
|
||||
continue
|
||||
if frame == null:
|
||||
frame = Line2D.new()
|
||||
frame.name = "SelectionFrame"
|
||||
frame.antialiased = true
|
||||
frame.default_color = Color(0.040, 0.740, 1.0, 0.96)
|
||||
frame.z_index = 20
|
||||
node.add_child(frame)
|
||||
var half_size: Vector2 = node.texture.get_size() * 0.5
|
||||
frame.points = PackedVector2Array([
|
||||
Vector2(-half_size.x, -half_size.y), Vector2(half_size.x, -half_size.y),
|
||||
Vector2(half_size.x, half_size.y), Vector2(-half_size.x, half_size.y), Vector2(-half_size.x, -half_size.y),
|
||||
])
|
||||
frame.width = 3.0 / maxf(absf(node.scale.x), 0.1)
|
||||
frame.default_color = Color(1.0, 0.750, 0.180, 0.98) if node == _draggedDecor else Color(0.040, 0.740, 1.0, 0.96)
|
||||
var item: Dictionary = _decorItems.get(decor_id, {}) as Dictionary
|
||||
var collision_size := _variant_to_vector2(item.get("collision_size", Vector2.ZERO))
|
||||
if node == _draggedDecor and collision_size != Vector2.ZERO:
|
||||
if collision_preview == null:
|
||||
collision_preview = Line2D.new()
|
||||
collision_preview.name = "CollisionPreview"
|
||||
collision_preview.antialiased = true
|
||||
collision_preview.z_index = 21
|
||||
node.add_child(collision_preview)
|
||||
var collision_half := collision_size * 0.5
|
||||
collision_preview.position = _variant_to_vector2(item.get("collision_offset", Vector2.ZERO))
|
||||
collision_preview.points = PackedVector2Array([
|
||||
Vector2(-collision_half.x, -collision_half.y), Vector2(collision_half.x, -collision_half.y),
|
||||
Vector2(collision_half.x, collision_half.y), Vector2(-collision_half.x, collision_half.y), Vector2(-collision_half.x, -collision_half.y),
|
||||
])
|
||||
collision_preview.width = 2.0 / maxf(absf(node.scale.x), 0.1)
|
||||
collision_preview.default_color = Color(1.0, 0.860, 0.260, 0.92)
|
||||
elif collision_preview != null:
|
||||
collision_preview.queue_free()
|
||||
|
||||
func _leave_visited_room() -> void:
|
||||
if not SceneManager.return_from_room_visit():
|
||||
SceneManager.change_scene("square")
|
||||
|
||||
func _constrain_room_position(position: Vector2) -> Vector2:
|
||||
return Vector2(
|
||||
clampf(position.x, ROOM_PLACE_BOUNDS.position.x, ROOM_PLACE_BOUNDS.end.x),
|
||||
clampf(position.y, ROOM_PLACE_BOUNDS.position.y, ROOM_PLACE_BOUNDS.end.y)
|
||||
)
|
||||
|
||||
func _snap_to_room_grid(position: Vector2) -> Vector2:
|
||||
var constrained := _constrain_room_position(position)
|
||||
return Vector2(
|
||||
roundf(constrained.x / ROOM_GRID_SIZE) * ROOM_GRID_SIZE,
|
||||
roundf(constrained.y / ROOM_GRID_SIZE) * ROOM_GRID_SIZE
|
||||
)
|
||||
|
||||
func _is_point_inside_sprite(node: Sprite2D, worldPosition: Vector2) -> bool:
|
||||
if node.texture == null:
|
||||
@@ -689,7 +1340,9 @@ func _sync_decor_collision(decorId: String, item: Dictionary) -> void:
|
||||
shapeNode.shape = RectangleShape2D.new()
|
||||
body.add_child(shapeNode)
|
||||
var scale := _get_item_float(item, "scale", _get_item_float(item, "default_scale", 1.0))
|
||||
body.global_position = Vector2(_to_float(item.get("position_x", 0.0), 0.0), _to_float(item.get("position_y", 0.0), 0.0)) + collisionOffset * scale
|
||||
var rotation_degrees := _get_item_float(item, "rotation_degrees", _get_item_float(item, "default_rotation_degrees", 0.0))
|
||||
body.rotation_degrees = rotation_degrees
|
||||
body.global_position = Vector2(_to_float(item.get("position_x", 0.0), 0.0), _to_float(item.get("position_y", 0.0), 0.0)) + collisionOffset.rotated(deg_to_rad(rotation_degrees)) * scale
|
||||
shapeNode.position = Vector2.ZERO
|
||||
var rectShape := shapeNode.shape as RectangleShape2D
|
||||
if rectShape == null:
|
||||
|
||||
Reference in New Issue
Block a user