2026-07-25 16:27:07 -04:00
|
|
|
class_name FishSaleService
|
|
|
|
|
extends Node
|
|
|
|
|
|
|
|
|
|
const FishCatchType = preload("res://fish/fish_catch.gd")
|
|
|
|
|
const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd")
|
|
|
|
|
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
|
|
|
|
const FishSaleResultType = preload("res://economy/fish_sale_result.gd")
|
|
|
|
|
const PlayerWalletType = preload("res://economy/player_wallet.gd")
|
|
|
|
|
|
|
|
|
|
signal sale_completed(result: FishSaleResultType)
|
|
|
|
|
|
|
|
|
|
var _inventory: FishInventoryType
|
|
|
|
|
var _wallet: PlayerWalletType
|
2026-07-30 14:01:07 -04:00
|
|
|
var _reservations: PlayerAssetReservationService
|
2026-07-25 16:27:07 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func setup(
|
|
|
|
|
inventory: FishInventoryType,
|
|
|
|
|
wallet: PlayerWalletType,
|
2026-07-30 14:01:07 -04:00
|
|
|
reservations: PlayerAssetReservationService = null,
|
2026-07-25 16:27:07 -04:00
|
|
|
) -> void:
|
|
|
|
|
_inventory = inventory
|
|
|
|
|
_wallet = wallet
|
2026-07-30 14:01:07 -04:00
|
|
|
_reservations = reservations
|
2026-07-25 16:27:07 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func can_sell(
|
|
|
|
|
catch_id: StringName,
|
|
|
|
|
buyer: FishBuyerProfileType,
|
|
|
|
|
) -> bool:
|
2026-07-26 23:18:51 -04:00
|
|
|
return preview_batch([catch_id], buyer).is_success()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func can_sell_batch(
|
|
|
|
|
catch_ids: Array[StringName],
|
|
|
|
|
buyer: FishBuyerProfileType,
|
|
|
|
|
) -> bool:
|
|
|
|
|
return preview_batch(catch_ids, buyer).is_success()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func preview_batch(
|
|
|
|
|
catch_ids: Array[StringName],
|
|
|
|
|
buyer: FishBuyerProfileType,
|
|
|
|
|
) -> FishSaleResultType:
|
|
|
|
|
return _validate_batch(catch_ids, buyer)
|
2026-07-25 16:27:07 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func sell(
|
|
|
|
|
catch_id: StringName,
|
|
|
|
|
buyer: FishBuyerProfileType,
|
|
|
|
|
) -> FishSaleResultType:
|
2026-07-26 23:18:51 -04:00
|
|
|
return sell_batch([catch_id], buyer)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func sell_batch(
|
|
|
|
|
catch_ids: Array[StringName],
|
|
|
|
|
buyer: FishBuyerProfileType,
|
|
|
|
|
) -> FishSaleResultType:
|
|
|
|
|
var result: FishSaleResultType = _validate_batch(catch_ids, buyer)
|
2026-07-25 16:27:07 -04:00
|
|
|
if not result.is_success():
|
|
|
|
|
return result
|
|
|
|
|
|
2026-07-26 23:18:51 -04:00
|
|
|
var inventory_snapshot: Array[FishCatchType] = _inventory.get_all_catches()
|
|
|
|
|
var next_sequence_snapshot: int = _inventory.get_next_catch_sequence()
|
|
|
|
|
var removed_catches: Array[FishCatchType] = (
|
|
|
|
|
_inventory.remove_catches_by_ids(result.catch_ids)
|
|
|
|
|
)
|
|
|
|
|
if removed_catches.size() != result.catch_ids.size():
|
2026-07-25 16:27:07 -04:00
|
|
|
result.success = false
|
2026-07-26 23:18:51 -04:00
|
|
|
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
2026-07-25 16:27:07 -04:00
|
|
|
return result
|
|
|
|
|
if not _wallet.credit(result.payout):
|
2026-07-26 23:18:51 -04:00
|
|
|
if not _inventory.replace_all_catches(
|
|
|
|
|
inventory_snapshot,
|
|
|
|
|
next_sequence_snapshot
|
|
|
|
|
):
|
|
|
|
|
push_error("Unable to roll back a failed fish batch sale.")
|
2026-07-25 16:27:07 -04:00
|
|
|
result.success = false
|
|
|
|
|
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
|
|
|
|
return result
|
|
|
|
|
sale_completed.emit(result)
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2026-07-26 23:18:51 -04:00
|
|
|
func _validate_batch(
|
|
|
|
|
catch_ids: Array[StringName],
|
2026-07-25 16:27:07 -04:00
|
|
|
buyer: FishBuyerProfileType,
|
|
|
|
|
) -> FishSaleResultType:
|
|
|
|
|
var result := FishSaleResultType.new()
|
2026-07-26 23:18:51 -04:00
|
|
|
result.catch_ids = catch_ids.duplicate()
|
|
|
|
|
result.fish_count = catch_ids.size()
|
|
|
|
|
if _inventory == null or _wallet == null:
|
|
|
|
|
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
|
|
|
|
return result
|
|
|
|
|
if catch_ids.is_empty():
|
|
|
|
|
result.status = FishSaleResultType.Status.INVALID_SELECTION
|
2026-07-25 16:27:07 -04:00
|
|
|
return result
|
|
|
|
|
if buyer == null or not buyer.is_valid():
|
|
|
|
|
result.status = FishSaleResultType.Status.INVALID_BUYER
|
|
|
|
|
return result
|
|
|
|
|
result.buyer_id = buyer.id
|
|
|
|
|
result.buyer_display_name = buyer.display_name
|
|
|
|
|
result.buyer_animal_name = (
|
|
|
|
|
buyer.animal_name_plural
|
|
|
|
|
if not buyer.animal_name_plural.is_empty()
|
|
|
|
|
else buyer.display_name
|
|
|
|
|
)
|
2026-07-26 23:18:51 -04:00
|
|
|
var seen_ids: Dictionary[StringName, bool] = {}
|
|
|
|
|
var contains_favorite: bool = false
|
|
|
|
|
const MAX_SAFE_TOTAL: int = 9223372036854775807
|
|
|
|
|
for catch_id: StringName in catch_ids:
|
|
|
|
|
if catch_id.is_empty() or seen_ids.has(catch_id):
|
|
|
|
|
result.status = FishSaleResultType.Status.INVALID_SELECTION
|
|
|
|
|
return result
|
|
|
|
|
seen_ids[catch_id] = true
|
|
|
|
|
var fish_catch: FishCatchType = _inventory.get_catch_by_id(catch_id)
|
|
|
|
|
if fish_catch == null:
|
|
|
|
|
result.status = FishSaleResultType.Status.NOT_FOUND
|
|
|
|
|
return result
|
2026-07-30 14:01:07 -04:00
|
|
|
if (
|
|
|
|
|
_reservations != null
|
|
|
|
|
and _reservations.is_fish_reserved(catch_id)
|
|
|
|
|
):
|
|
|
|
|
result.status = FishSaleResultType.Status.RESERVED
|
|
|
|
|
return result
|
2026-07-26 23:18:51 -04:00
|
|
|
if fish_catch.is_favorited:
|
|
|
|
|
contains_favorite = true
|
|
|
|
|
if fish_catch.sale_value < 0:
|
|
|
|
|
result.status = FishSaleResultType.Status.INVALID_VALUE
|
|
|
|
|
return result
|
2026-08-02 17:44:11 -04:00
|
|
|
var ordinary_value: int = (
|
|
|
|
|
fish_catch.fish.get_sale_value_for_weight(
|
|
|
|
|
fish_catch.weight_lb
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
var offer: int = buyer.get_quality_offer(
|
|
|
|
|
ordinary_value,
|
|
|
|
|
fish_catch.quality,
|
|
|
|
|
)
|
2026-07-26 23:18:51 -04:00
|
|
|
if offer < 0:
|
|
|
|
|
result.status = FishSaleResultType.Status.INVALID_OFFER
|
|
|
|
|
return result
|
|
|
|
|
if (
|
|
|
|
|
result.base_value > MAX_SAFE_TOTAL - fish_catch.sale_value
|
|
|
|
|
or result.payout > MAX_SAFE_TOTAL - offer
|
|
|
|
|
):
|
|
|
|
|
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
|
|
|
|
return result
|
|
|
|
|
result.base_value += fish_catch.sale_value
|
|
|
|
|
result.payout += offer
|
|
|
|
|
if result.fish_count == 1:
|
|
|
|
|
result.catch_id = fish_catch.catch_id
|
|
|
|
|
result.fish_name = fish_catch.fish.display_name
|
|
|
|
|
if contains_favorite:
|
2026-07-25 16:27:07 -04:00
|
|
|
result.status = FishSaleResultType.Status.FAVORITED
|
2026-07-26 23:18:51 -04:00
|
|
|
return result
|
|
|
|
|
if not _wallet.can_credit(result.payout):
|
2026-07-25 16:27:07 -04:00
|
|
|
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
2026-07-26 23:18:51 -04:00
|
|
|
return result
|
|
|
|
|
result.status = FishSaleResultType.Status.SUCCESS
|
|
|
|
|
result.success = true
|
|
|
|
|
if result.fish_count == 1:
|
2026-07-25 16:27:07 -04:00
|
|
|
result.sale_message = buyer.get_sale_message(
|
|
|
|
|
result.fish_name,
|
|
|
|
|
result.payout
|
|
|
|
|
)
|
2026-07-26 23:18:51 -04:00
|
|
|
else:
|
2026-08-13 14:53:39 -04:00
|
|
|
result.sale_message = (
|
|
|
|
|
"you sold %d fish to the %s." % [
|
2026-07-26 23:18:51 -04:00
|
|
|
result.fish_count,
|
|
|
|
|
result.buyer_animal_name,
|
2026-08-13 14:53:39 -04:00
|
|
|
]
|
|
|
|
|
)
|
2026-07-25 16:27:07 -04:00
|
|
|
return result
|