Updating character location based on occupied tile
All checks were successful
Deploy Promiscuity Auth API / deploy (push) Successful in 45s
Deploy Promiscuity Character API / deploy (push) Successful in 57s
Deploy Promiscuity Locations API / deploy (push) Successful in 44s
k8s smoke test / test (push) Successful in 7s

This commit is contained in:
Zeeshaun 2026-03-13 21:34:59 -05:00
parent 6ca0f306bb
commit e79f473ce4
8 changed files with 122 additions and 14 deletions

View File

@ -25,6 +25,10 @@ var _camera_start_offset := Vector3(0.0, 6.0, 10.0)
var _border_material: StandardMaterial3D var _border_material: StandardMaterial3D
var _known_locations: Dictionary = {} var _known_locations: Dictionary = {}
var _locations_loaded := false 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: func _ready() -> void:
@ -41,6 +45,8 @@ func _ready() -> void:
var start_coord := SelectedCharacter.get_coord() var start_coord := SelectedCharacter.get_coord()
_center_coord = Vector2i(roundi(start_coord.x), roundi(start_coord.y)) _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 _block.visible = false
await _load_existing_locations() await _load_existing_locations()
@ -58,6 +64,7 @@ func _process(_delta: float) -> void:
return return
_center_coord = target_coord _center_coord = target_coord
_rebuild_tiles(_center_coord) _rebuild_tiles(_center_coord)
_queue_coord_sync(_center_coord)
func _get_stream_position() -> Vector3: func _get_stream_position() -> Vector3:
@ -204,8 +211,7 @@ func _load_existing_locations() -> void:
_locations_loaded = false _locations_loaded = false
_known_locations.clear() _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.") push_warning("Selected character is missing an id; cannot load visible locations.")
_locations_loaded = true _locations_loaded = true
return return
@ -217,7 +223,7 @@ func _load_existing_locations() -> void:
if not AuthState.access_token.is_empty(): if not AuthState.access_token.is_empty():
headers.append("Authorization: Bearer %s" % AuthState.access_token) 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: if err != OK:
push_warning("Failed to request visible locations: %s" % err) push_warning("Failed to request visible locations: %s" % err)
request.queue_free() request.queue_free()
@ -257,8 +263,45 @@ func _load_existing_locations() -> void:
_known_locations[coord] = location_name _known_locations[coord] = location_name
loaded_count += 1 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: 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 _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)

View File

@ -11,9 +11,19 @@ func create_character(character_name: String) -> Dictionary:
}) })
return await _request(HTTPClient.METHOD_POST, CHARACTER_API_URL, payload) return await _request(HTTPClient.METHOD_POST, CHARACTER_API_URL, payload)
func delete_character(character_id: String) -> Dictionary: func delete_character(character_id: String) -> Dictionary:
var url := "%s/%s" % [CHARACTER_API_URL, character_id] var url := "%s/%s" % [CHARACTER_API_URL, character_id]
return await _request(HTTPClient.METHOD_DELETE, url) 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: func _request(method: int, url: String, body: String = "") -> Dictionary:
var request := HTTPRequest.new() var request := HTTPRequest.new()

View File

@ -14,3 +14,9 @@ func get_coord() -> Vector2:
float(coord.get("x", 0)), float(coord.get("x", 0)),
float(coord.get("y", 0)) float(coord.get("y", 0))
) )
func set_coord(coord: Vector2i) -> void:
character["coord"] = {
"x": coord.x,
"y": coord.y
}

View File

@ -104,6 +104,31 @@ public class CharactersController : ControllerBase
return Ok(locations); return Ok(locations);
} }
[HttpPut("{id}/coord")]
[Authorize(Roles = "USER,SUPER")]
public async Task<IActionResult> 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}")] [HttpDelete("{id}")]
[Authorize(Roles = "USER,SUPER")] [Authorize(Roles = "USER,SUPER")]
public async Task<IActionResult> Delete(string id) public async Task<IActionResult> Delete(string id)

View File

@ -4,12 +4,21 @@ This service expects JSON request bodies for character creation and stores
character documents in MongoDB. character documents in MongoDB.
Inbound JSON documents Inbound JSON documents
- CreateCharacterRequest (`POST /api/characters`) - CreateCharacterRequest (`POST /api/characters`)
```json ```json
{ {
"name": "string" "name": "string"
} }
``` ```
- UpdateCharacterCoordRequest (`PUT /api/characters/{id}/coord`)
```json
{
"coord": {
"x": "number",
"y": "number"
}
}
```
Stored documents (MongoDB) Stored documents (MongoDB)
- Character - Character

View File

@ -0,0 +1,6 @@
namespace CharacterApi.Models;
public class UpdateCharacterCoordRequest
{
public required Coord Coord { get; set; }
}

View File

@ -6,5 +6,6 @@ See `DOCUMENTS.md` for request payloads and stored document shapes.
## Endpoints ## Endpoints
- `POST /api/characters` Create a character. - `POST /api/characters` Create a character.
- `GET /api/characters` List characters for the current user. - `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. - `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. - `DELETE /api/characters/{id}` Delete a character owned by the current user.

View File

@ -34,6 +34,14 @@ public class CharacterStore
public async Task<Character?> GetByIdAsync(string id) => public async Task<Character?> GetByIdAsync(string id) =>
await _col.Find(c => c.Id == id).FirstOrDefaultAsync(); await _col.Find(c => c.Id == id).FirstOrDefaultAsync();
public async Task<bool> UpdateCoordAsync(string id, Coord coord)
{
var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
var update = Builders<Character>.Update.Set(c => c.Coord, coord);
var result = await _col.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0 || result.MatchedCount > 0;
}
public Task<List<VisibleLocation>> GetVisibleLocationsAsync(Character character) public Task<List<VisibleLocation>> GetVisibleLocationsAsync(Character character)
{ {
return GetVisibleLocationsInternalAsync(character, ensureGenerated: false); return GetVisibleLocationsInternalAsync(character, ensureGenerated: false);