- Introduced `StageUseTargetCaptureBar` class for managing user interactions with props and their use spots. - Implemented UI elements including buttons for changing props and canceling actions. - Added `PropUseSpotData` class for validation and serialization of prop use spots. - Created tests for validating use spot data migration, serialization, and rule matching. - Ensured proper handling of edge cases in use spot data, including unique IDs and finite positions.
110 lines
3.8 KiB
GDScript
110 lines
3.8 KiB
GDScript
class_name PropUseSpotData
|
|
extends RefCounted
|
|
## Pure validation and conversion helpers for placed-prop Use spots.
|
|
|
|
static func validate_saved(raw_spots: Variant, raw_next: Variant) -> String:
|
|
if not raw_spots is Array:
|
|
return "Prop Use spots must be an array."
|
|
if not _is_integer_number(raw_next):
|
|
return "Prop Use spot counter must be an integer."
|
|
var next_value: float = float(raw_next)
|
|
if not is_finite(next_value) or next_value < 1.0:
|
|
return "Prop Use spot counter must be a positive integer."
|
|
var ids: Dictionary = {}
|
|
var numbers: Dictionary = {}
|
|
var highest: int = 0
|
|
for raw: Variant in raw_spots as Array:
|
|
if not raw is Dictionary:
|
|
return "Prop Use spot entries must be objects."
|
|
var spot: Dictionary = raw
|
|
var raw_id: Variant = spot.get("id", null)
|
|
var raw_number: Variant = spot.get("number", null)
|
|
var raw_name: Variant = spot.get("name", null)
|
|
if not raw_id is String or not _is_integer_number(raw_number) or not raw_name is String:
|
|
return "Prop Use spot fields are invalid."
|
|
var numeric_number: float = float(raw_number)
|
|
if not is_finite(numeric_number) or numeric_number < 1.0:
|
|
return "Prop Use spot numbers must be positive integers."
|
|
var number: int = int(numeric_number)
|
|
var spot_id: String = String(raw_id)
|
|
if spot_id != "spot_%d" % number or ids.has(spot_id) or numbers.has(number):
|
|
return "Prop Use spot IDs and numbers must be unique and canonical."
|
|
if not _is_finite_point(spot.get("position", null)):
|
|
return "Prop Use spot positions must be finite points."
|
|
ids[spot_id] = true
|
|
numbers[number] = true
|
|
highest = maxi(highest, number)
|
|
if int(next_value) <= highest:
|
|
return "Prop Use spot counter must exceed every existing spot number."
|
|
return ""
|
|
|
|
|
|
static func decode(raw_spots: Array) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for raw: Variant in raw_spots:
|
|
if not raw is Dictionary:
|
|
continue
|
|
var spot: Dictionary = raw
|
|
var number: int = int(spot.get("number", 0))
|
|
var name: String = String(spot.get("name", "")).strip_edges()
|
|
result.append({
|
|
"id": String(spot.get("id", "spot_%d" % number)),
|
|
"number": number,
|
|
"name": name if not name.is_empty() else fallback_name(number),
|
|
"position": _point_from_data(spot.get("position", null)),
|
|
})
|
|
return result
|
|
|
|
|
|
static func encode(spots: Array[Dictionary]) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for spot: Dictionary in spots:
|
|
var number: int = int(spot.get("number", 0))
|
|
var position: Variant = spot.get("position", Vector2.ZERO)
|
|
if not position is Vector2:
|
|
continue
|
|
var name: String = String(spot.get("name", "")).strip_edges()
|
|
result.append({
|
|
"id": String(spot.get("id", "spot_%d" % number)),
|
|
"number": number,
|
|
"name": name if not name.is_empty() else fallback_name(number),
|
|
"position": {"x": position.x, "y": position.y},
|
|
})
|
|
return result
|
|
|
|
|
|
static func fallback_name(number: int) -> String:
|
|
return "Spot %d" % maxi(1, number)
|
|
|
|
|
|
static func find(spots: Array[Dictionary], spot_id: String) -> Dictionary:
|
|
for spot: Dictionary in spots:
|
|
if String(spot.get("id", "")) == spot_id:
|
|
return spot.duplicate(true)
|
|
return {}
|
|
|
|
|
|
static func _is_finite_point(value: Variant) -> bool:
|
|
if not value is Dictionary:
|
|
return false
|
|
var point: Dictionary = value
|
|
var x: Variant = point.get("x", null)
|
|
var y: Variant = point.get("y", null)
|
|
return (x is int or x is float) and (y is int or y is float) and is_finite(float(x)) and is_finite(float(y))
|
|
|
|
|
|
static func _is_integer_number(value: Variant) -> bool:
|
|
if value is int:
|
|
return true
|
|
if not value is float:
|
|
return false
|
|
var number: float = value
|
|
return is_finite(number) and absf(number) <= 9007199254740991.0 and floorf(number) == number
|
|
|
|
|
|
static func _point_from_data(value: Variant) -> Vector2:
|
|
if not _is_finite_point(value):
|
|
return Vector2.ZERO
|
|
var point: Dictionary = value
|
|
return Vector2(float(point["x"]), float(point["y"]))
|