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 @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 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(), ] catch_id = StringName( "%s:%s" % [fish_id, identity_suffix] ) func is_valid() -> bool: return ( fish != null and not fish_id.is_empty() 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