netfishing/fish/fish_catch.gd

174 lines
4.3 KiB
GDScript3
Raw Permalink Normal View History

class_name FishCatch
extends Resource
const FishDataType = preload("res://fish/fish_data.gd")
const MAX_SAFE_WEIGHT_LB: float = 1000000.0
const MAX_SAFE_DISPLAY_SCALE: float = 10000.0
const MAX_SAFE_SALE_VALUE: int = 1000000000
const MAX_SAFE_SEQUENCE: int = 2147483647
@export var fish: FishDataType
@export var fish_id: StringName
2026-07-25 16:02:34 -04:00
@export var catch_id: StringName
@export var catch_sequence: int = 0
@export var weight_lb: float = 0.0
@export var display_scale: float = 1.0
@export var sale_value: int = 0
@export var is_favorited: bool = false
2026-07-25 16:02:34 -04:00
func ensure_identity() -> void:
if not catch_id.is_empty():
return
var random_bytes: PackedByteArray = Crypto.new().generate_random_bytes(16)
var identity_suffix: String = random_bytes.hex_encode()
if identity_suffix.is_empty():
identity_suffix = "%d:%d:%d" % [
int(Time.get_unix_time_from_system()),
Time.get_ticks_usec(),
get_instance_id(),
]
2026-07-25 16:02:34 -04:00
catch_id = StringName(
"%s:%s" % [fish_id, identity_suffix]
2026-07-25 16:02:34 -04:00
)
func is_valid() -> bool:
return (
fish != null
and not fish_id.is_empty()
2026-07-25 16:02:34 -04:00
and not catch_id.is_empty()
and fish.id == fish_id
and catch_sequence >= 0
and catch_sequence < MAX_SAFE_SEQUENCE
and is_finite(weight_lb)
and weight_lb > 0.0
and weight_lb <= MAX_SAFE_WEIGHT_LB
and is_finite(display_scale)
and display_scale > 0.0
and display_scale <= MAX_SAFE_DISPLAY_SCALE
and sale_value >= 0
and sale_value <= MAX_SAFE_SALE_VALUE
)
func to_save_dict() -> Dictionary:
return {
"catch_id": String(catch_id),
"catch_sequence": catch_sequence,
"fish_id": String(fish_id),
"weight_lb": weight_lb,
"display_scale": display_scale,
"sale_value": sale_value,
"is_favorited": is_favorited,
}
static func from_save_dict(
data: Dictionary,
resolved_fish: FishDataType,
) -> FishCatch:
if resolved_fish == null:
return null
for required_key: String in [
"catch_id",
"catch_sequence",
"fish_id",
"weight_lb",
"display_scale",
"sale_value",
"is_favorited",
]:
if not data.has(required_key):
return null
if (
typeof(data["catch_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]
or typeof(data["fish_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]
):
return null
var loaded_catch_id: StringName = StringName(str(data["catch_id"]))
var loaded_fish_id: StringName = StringName(str(data["fish_id"]))
var loaded_sequence: int = _read_safe_integer(
data["catch_sequence"],
-1,
MAX_SAFE_SEQUENCE
)
var loaded_sale_value: int = _read_safe_integer(
data["sale_value"],
-1,
MAX_SAFE_SALE_VALUE
)
var loaded_weight: float = _read_safe_float(
data["weight_lb"],
0.0,
MAX_SAFE_WEIGHT_LB
)
var loaded_scale: float = _read_safe_float(
data["display_scale"],
0.0,
MAX_SAFE_DISPLAY_SCALE
)
var loaded_favorite: Variant = data["is_favorited"]
if (
loaded_catch_id.is_empty()
or loaded_fish_id != resolved_fish.id
or loaded_sequence <= 0
or loaded_sequence >= MAX_SAFE_SEQUENCE
or loaded_sale_value < 0
or loaded_weight <= 0.0
or loaded_scale <= 0.0
or typeof(loaded_favorite) != TYPE_BOOL
):
return null
var fish_catch := FishCatch.new()
fish_catch.fish = resolved_fish
fish_catch.fish_id = loaded_fish_id
fish_catch.catch_id = loaded_catch_id
fish_catch.catch_sequence = loaded_sequence
fish_catch.weight_lb = loaded_weight
fish_catch.display_scale = loaded_scale
fish_catch.sale_value = loaded_sale_value
fish_catch.is_favorited = bool(loaded_favorite)
return fish_catch if fish_catch.is_valid() else null
static func _read_safe_integer(
value: Variant,
invalid_value: int,
maximum_value: int,
) -> int:
if typeof(value) == TYPE_INT:
var integer_value: int = int(value)
if integer_value >= 0 and integer_value <= maximum_value:
return integer_value
elif typeof(value) == TYPE_FLOAT:
var float_value: float = float(value)
if (
is_finite(float_value)
and float_value >= 0.0
and float_value <= float(maximum_value)
and is_equal_approx(float_value, round(float_value))
):
return int(round(float_value))
return invalid_value
static func _read_safe_float(
value: Variant,
invalid_value: float,
maximum_value: float,
) -> float:
if typeof(value) not in [TYPE_INT, TYPE_FLOAT]:
return invalid_value
var float_value: float = float(value)
if (
not is_finite(float_value)
or float_value <= 0.0
or float_value > maximum_value
):
return invalid_value
return float_value