Adding player sensing
Deploy Promiscuity Auth API / deploy (push) Successful in 47s
Deploy Promiscuity Character API / deploy (push) Successful in 59s
Deploy Promiscuity Inventory API / deploy (push) Successful in 47s
Deploy Promiscuity Locations API / deploy (push) Successful in 47s
k8s smoke test / test (push) Successful in 9s

This commit is contained in:
2026-03-23 18:39:08 -05:00
parent c596b760cf
commit bca8e3374a
6 changed files with 247 additions and 13 deletions
+148 -12
View File
@@ -6,6 +6,8 @@ const INVENTORY_API_URL := "https://pinv.ranaze.com/api/inventory"
const START_SCREEN_SCENE := "res://scenes/UI/start_screen.tscn"
const SETTINGS_SCENE := "res://scenes/UI/Settings.tscn"
const CHARACTER_SLOT_COUNT := 6
const VISIBLE_CHARACTERS_REFRESH_INTERVAL := 5.0
const HEARTBEAT_INTERVAL := 10.0
@export var tile_size := 8.0
@export var block_height := 1.0
@@ -34,8 +36,9 @@ const CHARACTER_SLOT_COUNT := 6
@onready var _inventory_status_label: Label = $InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/StatusLabel
var _center_coord := Vector2i.ZERO
var _tiles_root: Node3D
var _tracked_node: Node3D
var _tiles_root: Node3D
var _remote_players_root: Node3D
var _tracked_node: Node3D
var _tile_nodes: Dictionary = {}
var _camera_start_offset := Vector3(0.0, 6.0, 10.0)
var _border_material: StandardMaterial3D
@@ -53,12 +56,20 @@ var _inventory_request_in_flight := false
var _character_inventory_items: Array = []
var _selected_character_item_id := ""
var _selected_ground_item_id := ""
var _visible_characters_in_flight := false
var _heartbeat_in_flight := false
var _visible_character_refresh_elapsed := 0.0
var _heartbeat_elapsed := 0.0
var _remote_character_nodes: Dictionary = {}
func _ready() -> void:
_tiles_root = Node3D.new()
_tiles_root.name = "GeneratedTiles"
add_child(_tiles_root)
_remote_players_root = Node3D.new()
_remote_players_root.name = "RemotePlayers"
add_child(_remote_players_root)
if _camera:
_camera_start_offset = _camera.position
@@ -75,6 +86,7 @@ func _ready() -> void:
_block.visible = false
_deactivate_player_for_load()
await _load_existing_locations()
_refresh_visible_characters()
_ensure_selected_location_exists(_center_coord)
_rebuild_tiles(_center_coord)
_move_player_to_coord(_center_coord)
@@ -84,6 +96,14 @@ func _ready() -> void:
func _process(_delta: float) -> void:
if not _locations_loaded:
return
_visible_character_refresh_elapsed += _delta
_heartbeat_elapsed += _delta
if _visible_character_refresh_elapsed >= VISIBLE_CHARACTERS_REFRESH_INTERVAL:
_visible_character_refresh_elapsed = 0.0
_refresh_visible_characters()
if _heartbeat_elapsed >= HEARTBEAT_INTERVAL:
_heartbeat_elapsed = 0.0
_send_presence_heartbeat()
if _inventory_menu.visible:
return
var target_world_pos := _get_stream_position()
@@ -899,7 +919,7 @@ func _selected_location_name(coord: Vector2i) -> String:
return "Location %d,%d" % [coord.x, coord.y]
func _load_existing_locations() -> void:
func _load_existing_locations() -> void:
_locations_refresh_in_flight = true
_locations_loaded = false
_known_locations.clear()
@@ -974,9 +994,10 @@ func _load_existing_locations() -> void:
_locations_loaded = true
_locations_refresh_in_flight = false
_rebuild_tiles(_center_coord)
if _queued_locations_refresh:
_queued_locations_refresh = false
_queue_locations_refresh()
_refresh_visible_characters()
if _queued_locations_refresh:
_queued_locations_refresh = false
_queue_locations_refresh()
func _queue_locations_refresh() -> void:
@@ -1110,12 +1131,13 @@ func _sync_character_coord(coord: Vector2i) -> void:
_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:
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)
_refresh_visible_characters()
else:
push_warning("Failed to persist character coord to %s,%s: status=%s error=%s body=%s" % [
coord.x,
coord.y,
@@ -1224,6 +1246,120 @@ func _fetch_location_inventory(location_id: String) -> Array:
var payload := parsed as Dictionary
return _parse_floor_inventory_items(payload.get("items", []))
func _refresh_visible_characters() -> void:
if _visible_characters_in_flight or _character_id.is_empty():
return
_refresh_visible_characters_async()
func _refresh_visible_characters_async() -> void:
_visible_characters_in_flight = true
var request := HTTPRequest.new()
add_child(request)
var headers := PackedStringArray()
if not AuthState.access_token.is_empty():
headers.append("Authorization: Bearer %s" % AuthState.access_token)
var err := request.request("%s/%s/visible-characters" % [CHARACTER_API_URL, _character_id], headers, HTTPClient.METHOD_GET)
if err != OK:
request.queue_free()
push_warning("Failed to request visible characters: %s" % err)
_visible_characters_in_flight = false
return
var result: Array = await request.request_completed
request.queue_free()
_visible_characters_in_flight = false
var result_code: int = result[0]
var response_code: int = result[1]
var response_body: String = result[3].get_string_from_utf8()
if result_code != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
push_warning("Failed to load visible characters (%s/%s): %s" % [result_code, response_code, response_body])
return
var parsed: Variant = JSON.parse_string(response_body)
if typeof(parsed) != TYPE_ARRAY:
return
_apply_visible_characters(parsed as Array)
func _apply_visible_characters(characters: Array) -> void:
var wanted_ids := {}
for character_variant in characters:
if typeof(character_variant) != TYPE_DICTIONARY:
continue
var character := character_variant as Dictionary
var character_id := String(character.get("id", "")).strip_edges()
if character_id.is_empty() or character_id == _character_id:
continue
wanted_ids[character_id] = true
_upsert_remote_character(character)
for character_id in _remote_character_nodes.keys():
if wanted_ids.has(character_id):
continue
var remote_node := _remote_character_nodes[character_id] as Node3D
if remote_node:
remote_node.queue_free()
_remote_character_nodes.erase(character_id)
func _upsert_remote_character(character: Dictionary) -> void:
var character_id := String(character.get("id", "")).strip_edges()
var coord_value: Variant = character.get("coord", {})
if typeof(coord_value) != TYPE_DICTIONARY:
return
var coord_dict := coord_value as Dictionary
var coord := Vector2i(int(coord_dict.get("x", 0)), int(coord_dict.get("y", 0)))
var remote_root := _remote_character_nodes.get(character_id) as Node3D
if remote_root == null:
remote_root = Node3D.new()
remote_root.name = "RemoteCharacter_%s" % character_id
_remote_players_root.add_child(remote_root)
var visual := _player_visual.duplicate() as Node3D
visual.name = "Visual"
visual.visible = true
remote_root.add_child(visual)
var label := Label3D.new()
label.name = "NameLabel"
label.position = Vector3(0.0, 1.8, 0.0)
label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
label.pixel_size = 0.01
label.outline_size = 10
remote_root.add_child(label)
_remote_character_nodes[character_id] = remote_root
remote_root.position = Vector3(coord.x * tile_size, _get_tile_surface_y(coord), coord.y * tile_size)
var name_label := remote_root.get_node_or_null("NameLabel") as Label3D
if name_label:
name_label.text = String(character.get("name", "Player")).strip_edges()
func _send_presence_heartbeat() -> void:
if _heartbeat_in_flight or _character_id.is_empty():
return
_send_presence_heartbeat_async()
func _send_presence_heartbeat_async() -> void:
_heartbeat_in_flight = true
var response := await CharacterService.heartbeat_character(_character_id)
if not response.get("ok", false):
push_warning("Failed to send presence heartbeat: status=%s error=%s body=%s" % [
response.get("status", "n/a"),
response.get("error", ""),
response.get("body", "")
])
_heartbeat_in_flight = false
func _get_biome_material(tile: MeshInstance3D, biome_key: String) -> Material:
+4
View File
@@ -24,6 +24,10 @@ func update_character_coord(character_id: String, coord: Vector2i) -> Dictionary
}
})
return await _request(HTTPClient.METHOD_PUT, url, payload)
func heartbeat_character(character_id: String) -> Dictionary:
var url := "%s/%s/heartbeat" % [CHARACTER_API_URL, character_id]
return await _request(HTTPClient.METHOD_POST, url, "")
func _request(method: int, url: String, body: String = "") -> Dictionary:
var request := HTTPRequest.new()