refactor: harden client runtime and exports

This commit is contained in:
ANG-Server
2026-07-23 00:59:00 +08:00
parent 5d2339a29b
commit fc872fe8af
27 changed files with 902 additions and 856 deletions

View File

@@ -13,8 +13,6 @@ const CAMERA_LIMIT_LEFT: int = -538
const CAMERA_LIMIT_TOP: int = -358
const CAMERA_LIMIT_RIGHT: int = 538
const CAMERA_LIMIT_BOTTOM: int = 358
const NetworkConfig = preload("res://_Core/utils/NetworkConfig.gd")
const TEXT_COLOR: Color = Color(0.102, 0.224, 0.376)
const PANEL_COLOR: Color = Color(0.970, 0.992, 1.0, 0.96)
const ACCENT_COLOR: Color = Color(0.086, 0.608, 0.922)
@@ -25,6 +23,8 @@ const DECOR_COLLISION_MASK: int = 1
const ROOM_GRID_SIZE: float = 16.0
const FLOOR_PLACE_BOUNDS: Rect2 = Rect2(-426.0, -176.0, 852.0, 388.0)
const WALL_PLACE_BOUNDS: Rect2 = Rect2(-378.0, -258.0, 756.0, 66.0)
const RoomDecorEditHistory = preload("res://_Core/room_decor/RoomDecorEditHistory.gd")
const RoomDecorCollisionRegistry = preload("res://_Core/room_decor/RoomDecorCollisionRegistry.gd")
@onready var player: PlayerController = $YSortWorld/Characters/Players/Player
@onready var playerCamera: Camera2D = $YSortWorld/Characters/Players/Player/Camera2D
@@ -34,7 +34,6 @@ const WALL_PLACE_BOUNDS: Rect2 = Rect2(-378.0, -258.0, 756.0, 66.0)
var _decorLayer: Node2D
var _gridOverlay: Node2D
var _inventoryRequest: HTTPRequest
var _inventoryPanel: PanelContainer
var _inventoryGrid: GridContainer
var _dragSurface: Control
@@ -61,7 +60,7 @@ var _dragOriginalItem: Dictionary = {}
var _decorItems: Dictionary = {}
var _serverDecorItems: Dictionary = {}
var _decorNodes: Dictionary = {}
var _decorCollisionBodies: Dictionary = {}
var _decorCollisionRegistry: RefCounted
var _remoteDecorDefinitions: Dictionary = {}
var _inventoryRequestInFlight: bool = false
var _inventoryRequestReportsErrors: bool = false
@@ -71,8 +70,7 @@ var _visitorOwnerId: String = ""
var _visitorOwnerName: String = ""
var _editMode: bool = false
var _showPlacementGrid: bool = false
var _undoHistory: Array[Dictionary] = []
var _redoHistory: Array[Dictionary] = []
var _editHistory: RefCounted = RoomDecorEditHistory.new()
var _dirtyDecorIds: Dictionary = {}
var _saveAwaitingIds: Dictionary = {}
var _saveFailedIds: Dictionary = {}
@@ -86,8 +84,8 @@ func _ready() -> void:
_leave_multiplayer_world()
_apply_spawn_point()
_configure_camera()
_decorCollisionRegistry = RoomDecorCollisionRegistry.new(staticCollision, DECOR_COLLISION_LAYER, DECOR_COLLISION_MASK)
_build_decor_layer()
_build_room_decor_requests()
_build_room_decor_ui()
_connect_room_decor_events()
_fetch_room_decor_inventory()
@@ -119,8 +117,8 @@ 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()
if saveManager.decor_revision_conflict.is_connected(_on_decor_revision_conflict):
saveManager.decor_revision_conflict.disconnect(_on_decor_revision_conflict)
func _input(event: InputEvent) -> void:
if get_viewport().is_input_handled():
@@ -237,13 +235,6 @@ func _add_grid_line(from: Vector2, to: Vector2, color: Color, width: float) -> v
line.antialiased = true
_gridOverlay.add_child(line)
func _build_room_decor_requests() -> void:
_inventoryRequest = HTTPRequest.new()
_inventoryRequest.name = "roomDecorInventoryRequest"
_inventoryRequest.timeout = 12.0
_inventoryRequest.request_completed.connect(_on_inventory_request_completed)
add_child(_inventoryRequest)
func _build_room_decor_ui() -> void:
_build_room_toolbar()
_build_unsaved_exit_dialog()
@@ -495,6 +486,8 @@ func _connect_room_decor_events() -> void:
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)
if not saveManager.decor_revision_conflict.is_connected(_on_decor_revision_conflict):
saveManager.decor_revision_conflict.connect(_on_decor_revision_conflict)
func _on_backpack_toggle_requested(_data: Variant = null) -> void:
if _visitorMode:
@@ -525,35 +518,22 @@ func _fetch_room_decor_inventory(reportErrors: bool = false) -> void:
_inventoryRequestInFlight = true
_inventoryRequestReportsErrors = reportErrors
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:
var api_client := get_node_or_null("/root/ApiClient")
if api_client == null or not api_client.has_method("get_json"):
_inventoryRequestInFlight = false
if _inventoryRequestReportsErrors:
_show_toast("家具背包请求发送失败")
_show_toast("家具背包服务不可用")
_inventoryRequestReportsErrors = false
return
api_client.call("get_json", endpoint, _on_inventory_request_completed, true)
func _on_inventory_request_completed(result: int, responseCode: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
func _on_inventory_request_completed(success: bool, response: Dictionary, error_info: Dictionary) -> void:
_inventoryRequestInFlight = false
var reportErrors := _inventoryRequestReportsErrors
_inventoryRequestReportsErrors = false
if result != HTTPRequest.RESULT_SUCCESS or responseCode < 200 or responseCode >= 300:
if not success:
if reportErrors:
_show_toast("家具背包读取失败")
return
var json := JSON.new()
if json.parse(body.get_string_from_utf8()) != OK:
if reportErrors:
_show_toast("家具背包响应解析失败")
return
var responseVariant: Variant = json.data
if not (responseVariant is Dictionary):
if reportErrors:
_show_toast("家具背包响应格式错误")
return
var response: Dictionary = responseVariant
if not bool(response.get("success", true)):
if reportErrors:
_show_toast(str(response.get("message", "家具背包读取失败")))
_show_toast(str(error_info.get("message", "家具背包读取失败")))
return
var dataVariant: Variant = response.get("data", {})
if dataVariant is Dictionary:
@@ -580,6 +560,9 @@ func _apply_inventory_payload(data: Dictionary) -> void:
if not decorId.is_empty():
_decorItems[decorId] = _merge_decor_definition(item)
if not _visitorMode:
var save_manager := get_node_or_null("/root/RoomDecorSaveManager")
if save_manager != null and save_manager.has_method("set_layout_revision"):
save_manager.call("set_layout_revision", int(data.get("revision", 0)), true)
_serverDecorItems = _duplicate_item_map(_decorItems)
_dirtyDecorIds.clear()
_saveFailedIds.clear()
@@ -946,6 +929,17 @@ func _on_decor_save_succeeded(savedItem: Dictionary) -> void:
_render_inventory_list()
_finalize_save_if_ready()
func _on_decor_revision_conflict(_current_revision: int, message: String) -> void:
_saveAwaitingIds.clear()
_saveFailedIds.clear()
_saveInProgress = false
_exitAfterSave = false
_dirtyDecorIds.clear()
_editHistory.call("clear")
_update_editor_toolbar()
_show_toast(message)
_fetch_room_decor_inventory(true)
func _can_edit_layout() -> bool:
return not _visitorMode and _editMode and not _saveInProgress
@@ -959,8 +953,7 @@ func _set_edit_mode(enabled: bool) -> void:
return
_editMode = true
_selectedDecorId = ""
_undoHistory.clear()
_redoHistory.clear()
_editHistory.call("clear")
_resetArmed = false
_showPlacementGrid = false
_update_placement_grid_visibility()
@@ -984,8 +977,7 @@ func _request_finish_edit() -> void:
func _end_edit_session() -> void:
_editMode = false
_selectedDecorId = ""
_undoHistory.clear()
_redoHistory.clear()
_editHistory.call("clear")
_resetArmed = false
_exitAfterSave = false
_showPlacementGrid = false
@@ -1038,10 +1030,10 @@ func _update_editor_toolbar() -> void:
_finishEditButton.disabled = _saveInProgress
if is_instance_valid(_undoButton):
_undoButton.visible = _editMode
_undoButton.disabled = not _can_edit_layout() or _undoHistory.is_empty()
_undoButton.disabled = not _can_edit_layout() or not bool(_editHistory.call("can_undo"))
if is_instance_valid(_redoButton):
_redoButton.visible = _editMode
_redoButton.disabled = not _can_edit_layout() or _redoHistory.is_empty()
_redoButton.disabled = not _can_edit_layout() or not bool(_editHistory.call("can_redo"))
if is_instance_valid(_rotateButton):
_rotateButton.visible = _editMode
_rotateButton.disabled = not _can_edit_layout() or not selected_is_placed
@@ -1086,43 +1078,25 @@ func _update_placement_grid_visibility() -> void:
_gridOverlay.visible = _editMode and _showPlacementGrid
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()
if bool(_editHistory.call("record_item", before, after)):
_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()
if bool(_editHistory.call("record_bulk", before_items, after_items)):
_update_editor_toolbar()
func _undo_last_change() -> void:
if not _can_edit_layout() or _undoHistory.is_empty():
if not _can_edit_layout() or not bool(_editHistory.call("can_undo")):
return
var entry: Dictionary = _undoHistory.pop_back()
var entry: Dictionary = _editHistory.call("take_undo")
_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():
if not _can_edit_layout() or not bool(_editHistory.call("can_redo")):
return
var entry: Dictionary = _redoHistory.pop_back()
var entry: Dictionary = _editHistory.call("take_redo")
_apply_history_entry(entry, true)
_undoHistory.append(entry)
_update_editor_toolbar()
func _apply_history_entry(entry: Dictionary, use_after: bool) -> void:
@@ -1190,6 +1164,9 @@ func _request_reset_layout() -> void:
get_tree().create_timer(3.0).timeout.connect(func() -> void: _resetArmed = false)
return
_resetArmed = false
var save_manager := get_node_or_null("/root/RoomDecorSaveManager")
if save_manager != null and save_manager.has_method("clear_pending_saves"):
save_manager.call("clear_pending_saves")
var before_items := _duplicate_item_map(_decorItems)
for decor_id_variant in _decorItems.keys():
var decor_id := str(decor_id_variant)
@@ -1411,56 +1388,16 @@ func _is_point_inside_sprite(node: Sprite2D, worldPosition: Vector2) -> bool:
return rect.has_point(localPoint)
func _sync_decor_collision(decorId: String, item: Dictionary) -> void:
var collisionSize := _variant_to_vector2(item.get("collision_size", Vector2.ZERO))
if collisionSize == Vector2.ZERO:
_remove_decor_collision(decorId)
return
var collisionOffset := _variant_to_vector2(item.get("collision_offset", Vector2.ZERO))
var body := _decorCollisionBodies.get(decorId, null) as StaticBody2D
var shapeNode: CollisionShape2D
if body == null or not is_instance_valid(body):
body = StaticBody2D.new()
body.name = "DecorCollision_%s" % decorId
body.collision_layer = DECOR_COLLISION_LAYER
body.collision_mask = DECOR_COLLISION_MASK
shapeNode = CollisionShape2D.new()
shapeNode.name = "CollisionShape2D"
var rectShape := RectangleShape2D.new()
shapeNode.shape = rectShape
body.add_child(shapeNode)
staticCollision.add_child(body)
_decorCollisionBodies[decorId] = body
else:
shapeNode = body.get_node_or_null("CollisionShape2D") as CollisionShape2D
if shapeNode == null:
shapeNode = CollisionShape2D.new()
shapeNode.name = "CollisionShape2D"
shapeNode.shape = RectangleShape2D.new()
body.add_child(shapeNode)
var scale := _get_item_float(item, "scale", _get_item_float(item, "default_scale", 1.0))
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:
rectShape = RectangleShape2D.new()
shapeNode.shape = rectShape
rectShape.size = Vector2(max(8.0, collisionSize.x * scale), max(8.0, collisionSize.y * scale))
if _decorCollisionRegistry != null:
_decorCollisionRegistry.call("sync", decorId, item)
func _set_decor_collision_disabled(decorId: String, disabled: bool) -> void:
var body := _decorCollisionBodies.get(decorId, null) as StaticBody2D
if body == null or not is_instance_valid(body):
return
var shapeNode := body.get_node_or_null("CollisionShape2D") as CollisionShape2D
if shapeNode != null:
shapeNode.disabled = disabled
if _decorCollisionRegistry != null:
_decorCollisionRegistry.call("set_disabled", decorId, disabled)
func _remove_decor_collision(decorId: String) -> void:
var body := _decorCollisionBodies.get(decorId, null) as Node
if body != null and is_instance_valid(body):
body.queue_free()
_decorCollisionBodies.erase(decorId)
if _decorCollisionRegistry != null:
_decorCollisionRegistry.call("remove", decorId)
func _variant_to_vector2(value: Variant) -> Vector2:
if value is Vector2:
@@ -1508,14 +1445,6 @@ func _is_authenticated() -> bool:
var authManager := get_node_or_null("/root/AuthManager")
return authManager != null and authManager.has_method("is_authenticated") and bool(authManager.call("is_authenticated"))
func _auth_headers() -> PackedStringArray:
var authManager := get_node_or_null("/root/AuthManager")
var accessToken := str(authManager.call("get_access_token")).strip_edges() if authManager != null and authManager.has_method("get_access_token") else ""
return PackedStringArray([
"Content-Type: application/json",
"Authorization: Bearer %s" % accessToken,
])
func _load_texture(path: String) -> Texture2D:
if path.is_empty():
return null