NEO-32: fix cooldown HUD anchor when serverTimeUtc parse fails
parent
f4ba4cdd92
commit
56f7ae8a81
|
|
@ -4,6 +4,8 @@ extends RefCounted
|
||||||
## Uses [code]serverTimeUtc[/code] + receive tick to approximate server "now" between polls.
|
## Uses [code]serverTimeUtc[/code] + receive tick to approximate server "now" between polls.
|
||||||
|
|
||||||
const SLOT_COUNT: int = 8
|
const SLOT_COUNT: int = 8
|
||||||
|
## Reject bogus [method Time.get_unix_time_from_datetime_string] results (failed parse → 0).
|
||||||
|
const MIN_VALID_UNIX_ANCHOR: float = 1_000_000_000.0
|
||||||
|
|
||||||
var _slot_end_unix: PackedFloat64Array = PackedFloat64Array()
|
var _slot_end_unix: PackedFloat64Array = PackedFloat64Array()
|
||||||
var _server_unix_at_receive: float = 0.0
|
var _server_unix_at_receive: float = 0.0
|
||||||
|
|
@ -19,11 +21,19 @@ func _init() -> void:
|
||||||
func apply_snapshot(snapshot: Dictionary) -> void:
|
func apply_snapshot(snapshot: Dictionary) -> void:
|
||||||
for i in range(SLOT_COUNT):
|
for i in range(SLOT_COUNT):
|
||||||
_slot_end_unix[i] = 0.0
|
_slot_end_unix[i] = 0.0
|
||||||
var st: Variant = snapshot.get("serverTimeUtc", "")
|
var st: Variant = snapshot.get("serverTimeUtc", null)
|
||||||
|
var anchor: float = 0.0
|
||||||
if st is String:
|
if st is String:
|
||||||
var su: String = st as String
|
var su: String = (st as String).strip_edges()
|
||||||
if not su.is_empty():
|
if not su.is_empty():
|
||||||
_server_unix_at_receive = Time.get_unix_time_from_datetime_string(su)
|
anchor = Time.get_unix_time_from_datetime_string(su)
|
||||||
|
elif st is float:
|
||||||
|
anchor = st as float
|
||||||
|
elif st is int:
|
||||||
|
anchor = float(st as int)
|
||||||
|
if anchor < MIN_VALID_UNIX_ANCHOR:
|
||||||
|
anchor = Time.get_unix_time_from_system()
|
||||||
|
_server_unix_at_receive = anchor
|
||||||
_ticks_msec_at_receive = Time.get_ticks_msec()
|
_ticks_msec_at_receive = Time.get_ticks_msec()
|
||||||
var slots: Variant = snapshot.get("slots", [])
|
var slots: Variant = snapshot.get("slots", [])
|
||||||
if slots is Array:
|
if slots is Array:
|
||||||
|
|
@ -36,8 +46,10 @@ func apply_snapshot(snapshot: Dictionary) -> void:
|
||||||
continue
|
continue
|
||||||
var end_v: Variant = d.get("cooldownEndsAtUtc", null)
|
var end_v: Variant = d.get("cooldownEndsAtUtc", null)
|
||||||
if end_v is String and not (end_v as String).is_empty():
|
if end_v is String and not (end_v as String).is_empty():
|
||||||
var end_s: String = end_v as String
|
var end_s: String = (end_v as String).strip_edges()
|
||||||
_slot_end_unix[idx] = Time.get_unix_time_from_datetime_string(end_s)
|
var end_unix: float = Time.get_unix_time_from_datetime_string(end_s)
|
||||||
|
if end_unix >= MIN_VALID_UNIX_ANCHOR:
|
||||||
|
_slot_end_unix[idx] = end_unix
|
||||||
|
|
||||||
|
|
||||||
func _effective_server_unix() -> float:
|
func _effective_server_unix() -> float:
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ func _physics_process(_delta: float) -> void:
|
||||||
var p: Vector3 = _player.global_position
|
var p: Vector3 = _player.global_position
|
||||||
_player_pos_label.text = _build_player_position_text(p)
|
_player_pos_label.text = _build_player_position_text(p)
|
||||||
_render_target_lock_label(p)
|
_render_target_lock_label(p)
|
||||||
if is_instance_valid(_cooldown_state) and _cooldown_state.has_method("any_slot_cooling"):
|
if _cooldown_state != null and _cooldown_state.has_method("any_slot_cooling"):
|
||||||
if bool(_cooldown_state.call("any_slot_cooling")):
|
if bool(_cooldown_state.call("any_slot_cooling")):
|
||||||
_render_cooldown_slots_label()
|
_render_cooldown_slots_label()
|
||||||
|
|
||||||
|
|
@ -317,7 +317,7 @@ func _on_cooldown_poll_timeout() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _on_cooldown_snapshot_received(snapshot: Dictionary) -> void:
|
func _on_cooldown_snapshot_received(snapshot: Dictionary) -> void:
|
||||||
if not is_instance_valid(_cooldown_state) or not _cooldown_state.has_method("apply_snapshot"):
|
if _cooldown_state == null or not _cooldown_state.has_method("apply_snapshot"):
|
||||||
return
|
return
|
||||||
_cooldown_state.call("apply_snapshot", snapshot)
|
_cooldown_state.call("apply_snapshot", snapshot)
|
||||||
_render_cooldown_slots_label()
|
_render_cooldown_slots_label()
|
||||||
|
|
@ -334,7 +334,7 @@ func _on_cooldown_snapshot_received(snapshot: Dictionary) -> void:
|
||||||
func _render_cooldown_slots_label() -> void:
|
func _render_cooldown_slots_label() -> void:
|
||||||
if not is_instance_valid(_cooldown_slots_label):
|
if not is_instance_valid(_cooldown_slots_label):
|
||||||
return
|
return
|
||||||
if not is_instance_valid(_cooldown_state) or not _cooldown_state.has_method("build_hud_line"):
|
if _cooldown_state == null or not _cooldown_state.has_method("build_hud_line"):
|
||||||
return
|
return
|
||||||
var line: Variant = _cooldown_state.call("build_hud_line")
|
var line: Variant = _cooldown_state.call("build_hud_line")
|
||||||
if line is String:
|
if line is String:
|
||||||
|
|
@ -466,7 +466,7 @@ func _request_hotbar_cast_slot(slot_index: int) -> void:
|
||||||
var ability_id: String = outcome[HotbarCastSlotResolver.KEY_ABILITY_ID] as String
|
var ability_id: String = outcome[HotbarCastSlotResolver.KEY_ABILITY_ID] as String
|
||||||
var resolved_slot: int = int(outcome.get(HotbarCastSlotResolver.KEY_SLOT_INDEX, slot_index))
|
var resolved_slot: int = int(outcome.get(HotbarCastSlotResolver.KEY_SLOT_INDEX, slot_index))
|
||||||
var target_id: Variant = outcome.get(HotbarCastSlotResolver.KEY_TARGET_ID, null)
|
var target_id: Variant = outcome.get(HotbarCastSlotResolver.KEY_TARGET_ID, null)
|
||||||
if is_instance_valid(_cooldown_state) and _cooldown_state.has_method("is_slot_cooling"):
|
if _cooldown_state != null and _cooldown_state.has_method("is_slot_cooling"):
|
||||||
if bool(_cooldown_state.call("is_slot_cooling", resolved_slot)):
|
if bool(_cooldown_state.call("is_slot_cooling", resolved_slot)):
|
||||||
if is_instance_valid(_cast_feedback_label):
|
if is_instance_valid(_cast_feedback_label):
|
||||||
_cast_feedback_label.text = "ability_cast_denied: %s (client guard)" % COOLDOWN_REASON_ON_CD
|
_cast_feedback_label.text = "ability_cast_denied: %s (client guard)" % COOLDOWN_REASON_ON_CD
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,16 @@ func test_remaining_positive_when_end_after_server_time() -> void:
|
||||||
# Assert
|
# Assert
|
||||||
assert_that(rem).is_greater(9.0)
|
assert_that(rem).is_greater(9.0)
|
||||||
assert_that(rem).is_less(11.0)
|
assert_that(rem).is_less(11.0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_invalid_server_time_string_does_not_lock_slot_ready() -> void:
|
||||||
|
# Arrange: bogus ISO would parse as 0 in Godot; anchor must fall back so slot 0 is not stuck cooling.
|
||||||
|
var state: RefCounted = CooldownStateScript.new()
|
||||||
|
var snap := {"serverTimeUtc": "__not_iso__", "slots": [{"slotIndex": 0}]}
|
||||||
|
|
||||||
|
# Act
|
||||||
|
state.call("apply_snapshot", snap)
|
||||||
|
var cooling: bool = bool(state.call("is_slot_cooling", 0))
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_that(cooling).is_false()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue