From e79f473ce4778208099f9de76a3d05f085a0c336 Mon Sep 17 00:00:00 2001 From: Zeeshaun Date: Fri, 13 Mar 2026 21:34:59 -0500 Subject: [PATCH] Updating character location based on occupied tile --- game/scenes/Levels/location_level.gd | 53 +++++++++++++++++-- game/scenes/UI/character_service.gd | 16 ++++-- game/scenes/UI/selected_character.gd | 6 +++ .../Controllers/CharactersController.cs | 25 +++++++++ microservices/CharacterApi/DOCUMENTS.md | 21 +++++--- .../Models/UpdateCharacterCoordRequest.cs | 6 +++ microservices/CharacterApi/README.md | 1 + .../CharacterApi/Services/CharacterStore.cs | 8 +++ 8 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 microservices/CharacterApi/Models/UpdateCharacterCoordRequest.cs diff --git a/game/scenes/Levels/location_level.gd b/game/scenes/Levels/location_level.gd index 992c8bb..7da1abb 100644 --- a/game/scenes/Levels/location_level.gd +++ b/game/scenes/Levels/location_level.gd @@ -25,6 +25,10 @@ var _camera_start_offset := Vector3(0.0, 6.0, 10.0) var _border_material: StandardMaterial3D var _known_locations: Dictionary = {} var _locations_loaded := false +var _character_id := "" +var _persisted_coord := Vector2i.ZERO +var _coord_sync_in_flight := false +var _queued_coord_sync: Variant = null func _ready() -> void: @@ -41,6 +45,8 @@ func _ready() -> void: var start_coord := SelectedCharacter.get_coord() _center_coord = Vector2i(roundi(start_coord.x), roundi(start_coord.y)) + _persisted_coord = _center_coord + _character_id = String(SelectedCharacter.character.get("id", SelectedCharacter.character.get("Id", ""))).strip_edges() _block.visible = false await _load_existing_locations() @@ -58,6 +64,7 @@ func _process(_delta: float) -> void: return _center_coord = target_coord _rebuild_tiles(_center_coord) + _queue_coord_sync(_center_coord) func _get_stream_position() -> Vector3: @@ -204,8 +211,7 @@ func _load_existing_locations() -> void: _locations_loaded = false _known_locations.clear() - var character_id := String(SelectedCharacter.character.get("id", SelectedCharacter.character.get("Id", ""))).strip_edges() - if character_id.is_empty(): + if _character_id.is_empty(): push_warning("Selected character is missing an id; cannot load visible locations.") _locations_loaded = true return @@ -217,7 +223,7 @@ func _load_existing_locations() -> void: if not AuthState.access_token.is_empty(): headers.append("Authorization: Bearer %s" % AuthState.access_token) - var err := request.request("%s/%s/visible-locations" % [CHARACTER_API_URL, character_id], headers, HTTPClient.METHOD_GET) + var err := request.request("%s/%s/visible-locations" % [CHARACTER_API_URL, _character_id], headers, HTTPClient.METHOD_GET) if err != OK: push_warning("Failed to request visible locations: %s" % err) request.queue_free() @@ -257,8 +263,45 @@ func _load_existing_locations() -> void: _known_locations[coord] = location_name loaded_count += 1 - print("LocationLevel loaded %d visible locations for character %s." % [loaded_count, character_id]) + print("LocationLevel loaded %d visible locations for character %s." % [loaded_count, _character_id]) if loaded_count == 0: - push_warning("Visible locations request succeeded but returned 0 locations for character %s." % character_id) + push_warning("Visible locations request succeeded but returned 0 locations for character %s." % _character_id) _locations_loaded = true + + +func _queue_coord_sync(coord: Vector2i) -> void: + if coord == _persisted_coord: + return + if _coord_sync_in_flight: + _queued_coord_sync = coord + return + _sync_character_coord(coord) + + +func _sync_character_coord(coord: Vector2i) -> void: + if _character_id.is_empty(): + return + _coord_sync_in_flight = true + _queued_coord_sync = null + _sync_character_coord_async(coord) + + +func _sync_character_coord_async(coord: Vector2i) -> void: + var response := await CharacterService.update_character_coord(_character_id, coord) + if response.get("ok", false): + _persisted_coord = coord + SelectedCharacter.set_coord(coord) + else: + push_warning("Failed to persist character coord to %s,%s: status=%s error=%s body=%s" % [ + coord.x, + coord.y, + response.get("status", "n/a"), + response.get("error", ""), + response.get("body", "") + ]) + + _coord_sync_in_flight = false + if _queued_coord_sync != null and _queued_coord_sync is Vector2i and _queued_coord_sync != _persisted_coord: + var queued_coord: Vector2i = _queued_coord_sync + _sync_character_coord(queued_coord) diff --git a/game/scenes/UI/character_service.gd b/game/scenes/UI/character_service.gd index 56a05e5..ea57882 100644 --- a/game/scenes/UI/character_service.gd +++ b/game/scenes/UI/character_service.gd @@ -11,9 +11,19 @@ func create_character(character_name: String) -> Dictionary: }) return await _request(HTTPClient.METHOD_POST, CHARACTER_API_URL, payload) -func delete_character(character_id: String) -> Dictionary: - var url := "%s/%s" % [CHARACTER_API_URL, character_id] - return await _request(HTTPClient.METHOD_DELETE, url) +func delete_character(character_id: String) -> Dictionary: + var url := "%s/%s" % [CHARACTER_API_URL, character_id] + return await _request(HTTPClient.METHOD_DELETE, url) + +func update_character_coord(character_id: String, coord: Vector2i) -> Dictionary: + var url := "%s/%s/coord" % [CHARACTER_API_URL, character_id] + var payload := JSON.stringify({ + "coord": { + "x": coord.x, + "y": coord.y + } + }) + return await _request(HTTPClient.METHOD_PUT, url, payload) func _request(method: int, url: String, body: String = "") -> Dictionary: var request := HTTPRequest.new() diff --git a/game/scenes/UI/selected_character.gd b/game/scenes/UI/selected_character.gd index 179ac30..5a8336e 100644 --- a/game/scenes/UI/selected_character.gd +++ b/game/scenes/UI/selected_character.gd @@ -14,3 +14,9 @@ func get_coord() -> Vector2: float(coord.get("x", 0)), float(coord.get("y", 0)) ) + +func set_coord(coord: Vector2i) -> void: + character["coord"] = { + "x": coord.x, + "y": coord.y + } diff --git a/microservices/CharacterApi/Controllers/CharactersController.cs b/microservices/CharacterApi/Controllers/CharactersController.cs index c48b879..f9cc70a 100644 --- a/microservices/CharacterApi/Controllers/CharactersController.cs +++ b/microservices/CharacterApi/Controllers/CharactersController.cs @@ -104,6 +104,31 @@ public class CharactersController : ControllerBase return Ok(locations); } + [HttpPut("{id}/coord")] + [Authorize(Roles = "USER,SUPER")] + public async Task UpdateCoord(string id, [FromBody] UpdateCharacterCoordRequest req) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + if (req.Coord is null) + return BadRequest("Coord required"); + + var allowAnyOwner = User.IsInRole("SUPER"); + var character = await _characters.GetByIdAsync(id); + if (character is null) + return NotFound(); + if (!allowAnyOwner && character.OwnerUserId != userId) + return Forbid(); + + character.Coord = req.Coord; + var updated = await _characters.UpdateCoordAsync(id, req.Coord); + if (!updated) + return NotFound(); + + return Ok(character); + } + [HttpDelete("{id}")] [Authorize(Roles = "USER,SUPER")] public async Task Delete(string id) diff --git a/microservices/CharacterApi/DOCUMENTS.md b/microservices/CharacterApi/DOCUMENTS.md index 12c13f5..cf4733e 100644 --- a/microservices/CharacterApi/DOCUMENTS.md +++ b/microservices/CharacterApi/DOCUMENTS.md @@ -4,12 +4,21 @@ This service expects JSON request bodies for character creation and stores character documents in MongoDB. Inbound JSON documents -- CreateCharacterRequest (`POST /api/characters`) - ```json - { - "name": "string" - } - ``` +- CreateCharacterRequest (`POST /api/characters`) + ```json + { + "name": "string" + } + ``` +- UpdateCharacterCoordRequest (`PUT /api/characters/{id}/coord`) + ```json + { + "coord": { + "x": "number", + "y": "number" + } + } + ``` Stored documents (MongoDB) - Character diff --git a/microservices/CharacterApi/Models/UpdateCharacterCoordRequest.cs b/microservices/CharacterApi/Models/UpdateCharacterCoordRequest.cs new file mode 100644 index 0000000..b6d265a --- /dev/null +++ b/microservices/CharacterApi/Models/UpdateCharacterCoordRequest.cs @@ -0,0 +1,6 @@ +namespace CharacterApi.Models; + +public class UpdateCharacterCoordRequest +{ + public required Coord Coord { get; set; } +} diff --git a/microservices/CharacterApi/README.md b/microservices/CharacterApi/README.md index 9eede41..bcbf8df 100644 --- a/microservices/CharacterApi/README.md +++ b/microservices/CharacterApi/README.md @@ -6,5 +6,6 @@ See `DOCUMENTS.md` for request payloads and stored document shapes. ## Endpoints - `POST /api/characters` Create a character. - `GET /api/characters` List characters for the current user. +- `PUT /api/characters/{id}/coord` Update the current location coord for an owned character. - `GET /api/characters/{id}/visible-locations` Ensure and list locations visible to that owned character. - `DELETE /api/characters/{id}` Delete a character owned by the current user. diff --git a/microservices/CharacterApi/Services/CharacterStore.cs b/microservices/CharacterApi/Services/CharacterStore.cs index f16e334..4705e03 100644 --- a/microservices/CharacterApi/Services/CharacterStore.cs +++ b/microservices/CharacterApi/Services/CharacterStore.cs @@ -34,6 +34,14 @@ public class CharacterStore public async Task GetByIdAsync(string id) => await _col.Find(c => c.Id == id).FirstOrDefaultAsync(); + public async Task UpdateCoordAsync(string id, Coord coord) + { + var filter = Builders.Filter.Eq(c => c.Id, id); + var update = Builders.Update.Set(c => c.Coord, coord); + var result = await _col.UpdateOneAsync(filter, update); + return result.ModifiedCount > 0 || result.MatchedCount > 0; + } + public Task> GetVisibleLocationsAsync(Character character) { return GetVisibleLocationsInternalAsync(character, ensureGenerated: false);