Add session mail and player gifts
This commit is contained in:
parent
5dda74c9e4
commit
e53ad1ac38
19 changed files with 1929 additions and 22 deletions
802
network/network_mail_service.gd
Normal file
802
network/network_mail_service.gd
Normal file
|
|
@ -0,0 +1,802 @@
|
|||
class_name NetworkMailService
|
||||
extends Node
|
||||
|
||||
signal mailbox_changed
|
||||
signal unread_count_changed(count: int)
|
||||
signal operation_finished(success: bool, message: String)
|
||||
signal peers_changed
|
||||
|
||||
const MAX_LEDGER: int = 256
|
||||
|
||||
var _session: NetworkSession
|
||||
var _reservations: PlayerAssetReservationService
|
||||
var _wallet: PlayerWallet
|
||||
var _inventory: FishInventory
|
||||
var _bag: PlayerBag
|
||||
var _collection_log: CollectionLog
|
||||
var _cooler_capacity: PlayerCoolerCapacity
|
||||
var _item_catalog: ItemCatalog
|
||||
var _fish_catalog: FishPool
|
||||
var _save_manager: PlayerSaveManager
|
||||
|
||||
var _letters: Dictionary[String, Dictionary] = {}
|
||||
var _local_letters: Dictionary[String, Dictionary] = {}
|
||||
var _send_ledger: Dictionary[int, Dictionary] = {}
|
||||
var _action_ledger: Dictionary[String, bool] = {}
|
||||
var _sequence := 0
|
||||
var _pending_local_send: Dictionary = {}
|
||||
var _pending_transfers: Dictionary[String, Dictionary] = {}
|
||||
var _local_removal_snapshots: Dictionary[String, Dictionary] = {}
|
||||
var _received_awards: Dictionary[String, bool] = {}
|
||||
|
||||
|
||||
func setup(
|
||||
session: NetworkSession,
|
||||
reservations: PlayerAssetReservationService,
|
||||
wallet: PlayerWallet,
|
||||
inventory: FishInventory,
|
||||
bag: PlayerBag,
|
||||
collection_log: CollectionLog,
|
||||
cooler_capacity: PlayerCoolerCapacity,
|
||||
item_catalog: ItemCatalog,
|
||||
fish_catalog: FishPool,
|
||||
save_manager: PlayerSaveManager,
|
||||
) -> void:
|
||||
_session = session
|
||||
_reservations = reservations
|
||||
_wallet = wallet
|
||||
_inventory = inventory
|
||||
_bag = bag
|
||||
_collection_log = collection_log
|
||||
_cooler_capacity = cooler_capacity
|
||||
_item_catalog = item_catalog
|
||||
_fish_catalog = fish_catalog
|
||||
_save_manager = save_manager
|
||||
_session.peer_authenticated.connect(func(_id: int, _name: String) -> void:
|
||||
peers_changed.emit()
|
||||
)
|
||||
_session.peer_removed.connect(_on_peer_removed)
|
||||
_session.peer_display_name_changed.connect(
|
||||
func(_id: int, _name: String) -> void: peers_changed.emit()
|
||||
)
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
|
||||
|
||||
func get_local_letters() -> Array[Dictionary]:
|
||||
var values: Array[Dictionary] = []
|
||||
for letter: Dictionary in _local_letters.values():
|
||||
if int(letter["recipient_peer_id"]) == _session.get_local_peer_id():
|
||||
values.append(letter.duplicate(true))
|
||||
values.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
var a_unread := int(a["state"]) == NetworkMailProtocol.State.SENT_UNREAD
|
||||
var b_unread := int(b["state"]) == NetworkMailProtocol.State.SENT_UNREAD
|
||||
return a_unread != b_unread if a_unread != b_unread else (
|
||||
int(a["sequence"]) > int(b["sequence"])
|
||||
)
|
||||
)
|
||||
return values
|
||||
|
||||
|
||||
func get_letter(mail_id: String) -> Dictionary:
|
||||
return _local_letters.get(mail_id, {}).duplicate(true)
|
||||
|
||||
|
||||
func get_unread_count() -> int:
|
||||
var count := 0
|
||||
for letter: Dictionary in _local_letters.values():
|
||||
if (
|
||||
int(letter["recipient_peer_id"]) == _session.get_local_peer_id()
|
||||
and int(letter["state"]) == NetworkMailProtocol.State.SENT_UNREAD
|
||||
):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func is_local_recipient(letter: Dictionary) -> bool:
|
||||
return (
|
||||
int(letter.get("recipient_peer_id", 0))
|
||||
== _session.get_local_peer_id()
|
||||
)
|
||||
|
||||
|
||||
func get_local_display_name() -> String:
|
||||
var record := _session.get_peer_record(_session.get_local_peer_id())
|
||||
return record.display_name if record != null else "Player"
|
||||
|
||||
|
||||
func get_recipient_choices() -> Array[Dictionary]:
|
||||
var choices: Array[Dictionary] = []
|
||||
var local_id := _session.get_local_peer_id()
|
||||
for peer_id: int in _session.get_authenticated_peer_ids():
|
||||
if peer_id == local_id:
|
||||
continue
|
||||
var record := _session.get_peer_record(peer_id)
|
||||
if record != null:
|
||||
choices.append({"peer_id": peer_id, "name": record.display_name})
|
||||
choices.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
return str(a["name"]).naturalnocasecmp_to(str(b["name"])) < 0
|
||||
)
|
||||
var seen: Dictionary[String, int] = {}
|
||||
for choice: Dictionary in choices:
|
||||
var name: String = choice["name"]
|
||||
seen[name] = int(seen.get(name, 0)) + 1
|
||||
if seen[name] > 1:
|
||||
choice["label"] = "%s (%d)" % [name, seen[name]]
|
||||
else:
|
||||
choice["label"] = name
|
||||
return choices
|
||||
|
||||
|
||||
func send_letter(
|
||||
recipient_peer_id: int,
|
||||
greeting_id: String,
|
||||
body: String,
|
||||
salutation_id: String,
|
||||
attachment: Dictionary,
|
||||
) -> bool:
|
||||
if (
|
||||
not _pending_local_send.is_empty()
|
||||
or not _session.is_gameplay_session_active()
|
||||
or recipient_peer_id == _session.get_local_peer_id()
|
||||
or not _session.is_authenticated_peer(recipient_peer_id)
|
||||
or (
|
||||
not _session.is_host()
|
||||
and not _session.supports_server_capability(
|
||||
NetworkMailProtocol.CAPABILITY
|
||||
)
|
||||
)
|
||||
):
|
||||
operation_finished.emit(false, "That player is no longer connected.")
|
||||
return false
|
||||
var request_id := _new_id("mail_request")
|
||||
var reservation_id := ""
|
||||
if int(attachment.get("type", 0)) != 0:
|
||||
reservation_id = _new_id("reservation")
|
||||
if not _reservations.reserve(reservation_id, attachment):
|
||||
operation_finished.emit(false, "That gift is not available.")
|
||||
return false
|
||||
var request := {
|
||||
"request_id": request_id,
|
||||
"session_id": _session.get_session_id(),
|
||||
"recipient_peer_id": recipient_peer_id,
|
||||
"greeting_id": greeting_id,
|
||||
"body": NetworkMailProtocol.sanitize_body(body),
|
||||
"salutation_id": salutation_id,
|
||||
"reservation_id": reservation_id,
|
||||
"attachment": attachment.duplicate(true),
|
||||
}
|
||||
if not NetworkMailProtocol.validate_send_request(request):
|
||||
if not reservation_id.is_empty():
|
||||
_reservations.release(reservation_id)
|
||||
operation_finished.emit(false, "Letter could not be sent.")
|
||||
return false
|
||||
_pending_local_send = request.duplicate(true)
|
||||
if _session.is_host():
|
||||
_handle_send(_session.get_local_peer_id(), request)
|
||||
else:
|
||||
submit_mail.rpc_id(1, request)
|
||||
return true
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func submit_mail(data: Dictionary) -> void:
|
||||
var sender := multiplayer.get_remote_sender_id()
|
||||
if _session.is_host() and _session.is_authenticated_peer(sender):
|
||||
_handle_send(sender, data)
|
||||
|
||||
|
||||
func _handle_send(sender: int, data: Dictionary) -> void:
|
||||
var request_id := str(data.get("request_id", ""))
|
||||
var ledger: Dictionary = _send_ledger.get(sender, {})
|
||||
if ledger.has(request_id):
|
||||
_deliver_send_result(sender, ledger[request_id])
|
||||
return
|
||||
var error := ""
|
||||
if (
|
||||
not NetworkMailProtocol.validate_send_request(data)
|
||||
or str(data.get("session_id", "")) != _session.get_session_id()
|
||||
):
|
||||
error = "Letter could not be sent."
|
||||
var recipient := int(data.get("recipient_peer_id", 0))
|
||||
if error.is_empty() and (
|
||||
recipient == sender or not _session.is_authenticated_peer(recipient)
|
||||
):
|
||||
error = "That player is no longer connected."
|
||||
if error.is_empty() and _letters.size() >= NetworkMailProtocol.MAX_SESSION_LETTERS:
|
||||
error = "The session mailbox is full."
|
||||
var recipient_count := 0
|
||||
for letter: Dictionary in _letters.values():
|
||||
if int(letter["recipient_peer_id"]) == recipient:
|
||||
recipient_count += 1
|
||||
if (
|
||||
error.is_empty()
|
||||
and recipient_count >= NetworkMailProtocol.MAX_RECIPIENT_LETTERS
|
||||
):
|
||||
error = "That inbox is full."
|
||||
var attachment: Dictionary = data.get("attachment", {})
|
||||
if error.is_empty() and not _validate_attachment_structure(attachment):
|
||||
error = "That gift is not valid."
|
||||
var result := {
|
||||
"request_id": request_id if not request_id.is_empty() else "invalid",
|
||||
"accepted": error.is_empty(),
|
||||
"message": "Letter sent." if error.is_empty() else error,
|
||||
"reservation_id": str(data.get("reservation_id", "")),
|
||||
}
|
||||
if error.is_empty():
|
||||
_sequence += 1
|
||||
var record := _session.get_peer_record(sender)
|
||||
var recipient_record := _session.get_peer_record(recipient)
|
||||
var letter := {
|
||||
"mail_id": _new_id("mail"),
|
||||
"session_id": _session.get_session_id(),
|
||||
"sequence": _sequence,
|
||||
"sender_peer_id": sender,
|
||||
"sender_display_name": record.display_name,
|
||||
"recipient_peer_id": recipient,
|
||||
"recipient_display_name": recipient_record.display_name,
|
||||
"greeting_id": str(data["greeting_id"]),
|
||||
"body": NetworkMailProtocol.sanitize_body(data["body"]),
|
||||
"salutation_id": str(data["salutation_id"]),
|
||||
"reservation_id": str(data["reservation_id"]),
|
||||
"attachment": attachment.duplicate(true),
|
||||
"state": NetworkMailProtocol.State.SENT_UNREAD,
|
||||
}
|
||||
_letters[letter["mail_id"]] = letter
|
||||
result["mail"] = letter
|
||||
_deliver_private_letter(sender, letter)
|
||||
_deliver_private_letter(recipient, letter)
|
||||
ledger[result["request_id"]] = result.duplicate(true)
|
||||
_bound(ledger)
|
||||
_send_ledger[sender] = ledger
|
||||
_deliver_send_result(sender, result)
|
||||
|
||||
|
||||
func _deliver_private_letter(peer_id: int, letter: Dictionary) -> void:
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
_receive_letter(letter)
|
||||
elif _session.is_authenticated_peer(peer_id):
|
||||
receive_private_letter.rpc_id(peer_id, letter)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func receive_private_letter(letter: Dictionary) -> void:
|
||||
_receive_letter(letter)
|
||||
|
||||
|
||||
func _receive_letter(letter: Dictionary) -> void:
|
||||
if (
|
||||
not NetworkMailProtocol.validate_mail(letter)
|
||||
or str(letter["session_id"]) != _session.get_session_id()
|
||||
or _session.get_local_peer_id() not in [
|
||||
int(letter["sender_peer_id"]), int(letter["recipient_peer_id"]),
|
||||
]
|
||||
):
|
||||
return
|
||||
_local_letters[letter["mail_id"]] = letter.duplicate(true)
|
||||
_emit_mailbox()
|
||||
|
||||
|
||||
func _deliver_send_result(peer_id: int, result: Dictionary) -> void:
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
_receive_send_result(result)
|
||||
else:
|
||||
receive_send_result.rpc_id(peer_id, result)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func receive_send_result(result: Dictionary) -> void:
|
||||
_receive_send_result(result)
|
||||
|
||||
|
||||
func _receive_send_result(result: Dictionary) -> void:
|
||||
if (
|
||||
_pending_local_send.is_empty()
|
||||
or str(result.get("request_id", ""))
|
||||
!= str(_pending_local_send.get("request_id", ""))
|
||||
):
|
||||
return
|
||||
if not bool(result.get("accepted", false)):
|
||||
var reservation_id := str(_pending_local_send.get("reservation_id", ""))
|
||||
if not reservation_id.is_empty():
|
||||
_reservations.release(reservation_id)
|
||||
_pending_local_send.clear()
|
||||
operation_finished.emit(
|
||||
bool(result.get("accepted", false)),
|
||||
str(result.get("message", "Letter could not be sent."))
|
||||
)
|
||||
|
||||
|
||||
func mark_read(mail_id: String) -> void:
|
||||
var request := _action_request(mail_id)
|
||||
if request.is_empty():
|
||||
return
|
||||
if _session.is_host():
|
||||
_handle_read(_session.get_local_peer_id(), request)
|
||||
else:
|
||||
submit_read.rpc_id(1, request)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func submit_read(data: Dictionary) -> void:
|
||||
var sender := multiplayer.get_remote_sender_id()
|
||||
if _session.is_host() and _session.is_authenticated_peer(sender):
|
||||
_handle_read(sender, data)
|
||||
|
||||
|
||||
func _handle_read(peer_id: int, data: Dictionary) -> void:
|
||||
var action_id := str(data.get("request_id", ""))
|
||||
if _action_ledger.has(action_id):
|
||||
return
|
||||
var letter: Dictionary = _letters.get(str(data.get("mail_id", "")), {})
|
||||
if (
|
||||
letter.is_empty()
|
||||
or int(letter["recipient_peer_id"]) != peer_id
|
||||
or str(data.get("session_id", "")) != _session.get_session_id()
|
||||
):
|
||||
return
|
||||
_action_ledger[action_id] = true
|
||||
if int(letter["state"]) == NetworkMailProtocol.State.SENT_UNREAD:
|
||||
letter["state"] = NetworkMailProtocol.State.READ
|
||||
_update_participants(letter)
|
||||
|
||||
|
||||
func accept_gift(mail_id: String) -> void:
|
||||
var request := _action_request(mail_id)
|
||||
if request.is_empty():
|
||||
return
|
||||
if _session.is_host():
|
||||
_handle_accept(_session.get_local_peer_id(), request)
|
||||
else:
|
||||
submit_accept.rpc_id(1, request)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func submit_accept(data: Dictionary) -> void:
|
||||
var sender := multiplayer.get_remote_sender_id()
|
||||
if _session.is_host() and _session.is_authenticated_peer(sender):
|
||||
_handle_accept(sender, data)
|
||||
|
||||
|
||||
func _handle_accept(peer_id: int, data: Dictionary) -> void:
|
||||
var action_id := str(data.get("request_id", ""))
|
||||
var letter: Dictionary = _letters.get(str(data.get("mail_id", "")), {})
|
||||
if _action_ledger.has(action_id):
|
||||
return
|
||||
if (
|
||||
letter.is_empty()
|
||||
or int(letter["recipient_peer_id"]) != peer_id
|
||||
or int(letter["state"]) not in [
|
||||
NetworkMailProtocol.State.SENT_UNREAD,
|
||||
NetworkMailProtocol.State.READ,
|
||||
]
|
||||
or int(letter["attachment"].get("type", 0)) == 0
|
||||
or not _session.is_authenticated_peer(int(letter["sender_peer_id"]))
|
||||
):
|
||||
return
|
||||
_action_ledger[action_id] = true
|
||||
var transfer_id := _new_id("mail_transfer")
|
||||
letter["state"] = NetworkMailProtocol.State.ACCEPTANCE_PENDING
|
||||
_pending_transfers[transfer_id] = {
|
||||
"letter": letter,
|
||||
"recipient_prepared": false,
|
||||
"sender_removed": false,
|
||||
}
|
||||
_update_participants(letter)
|
||||
_request_prepare(int(letter["recipient_peer_id"]), transfer_id, letter)
|
||||
|
||||
|
||||
func _request_prepare(peer_id: int, transfer_id: String, letter: Dictionary) -> void:
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
_prepare_recipient(transfer_id, letter)
|
||||
else:
|
||||
prepare_recipient.rpc_id(peer_id, transfer_id, letter)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func prepare_recipient(transfer_id: String, letter: Dictionary) -> void:
|
||||
_prepare_recipient(transfer_id, letter)
|
||||
|
||||
|
||||
func _prepare_recipient(transfer_id: String, letter: Dictionary) -> void:
|
||||
var ready := _can_receive(letter["attachment"])
|
||||
_send_phase_ack("recipient_prepared", transfer_id, ready)
|
||||
|
||||
|
||||
func _request_sender_commit(peer_id: int, transfer_id: String, letter: Dictionary) -> void:
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
_commit_sender(transfer_id, letter)
|
||||
else:
|
||||
commit_sender.rpc_id(peer_id, transfer_id, letter)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func commit_sender(transfer_id: String, letter: Dictionary) -> void:
|
||||
_commit_sender(transfer_id, letter)
|
||||
|
||||
|
||||
func _commit_sender(transfer_id: String, letter: Dictionary) -> void:
|
||||
var reservation_id: String = letter["reservation_id"]
|
||||
var snapshot := _capture_assets()
|
||||
var applied := (
|
||||
_reservations.has_reservation(reservation_id)
|
||||
and _reservations.commit_removal(reservation_id)
|
||||
and _save_manager.save_if_dirty()
|
||||
)
|
||||
if not applied:
|
||||
_restore_assets(snapshot)
|
||||
_save_manager.save_if_dirty()
|
||||
else:
|
||||
_local_removal_snapshots[transfer_id] = snapshot
|
||||
_send_phase_ack("sender_removed", transfer_id, applied)
|
||||
|
||||
|
||||
func _request_recipient_award(
|
||||
peer_id: int, transfer_id: String, letter: Dictionary
|
||||
) -> void:
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
_award_recipient(transfer_id, letter)
|
||||
else:
|
||||
award_recipient.rpc_id(peer_id, transfer_id, letter)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func award_recipient(transfer_id: String, letter: Dictionary) -> void:
|
||||
_award_recipient(transfer_id, letter)
|
||||
|
||||
|
||||
func _award_recipient(transfer_id: String, letter: Dictionary) -> void:
|
||||
if _received_awards.has(transfer_id):
|
||||
_send_phase_ack("recipient_awarded", transfer_id, true)
|
||||
return
|
||||
var snapshot := _capture_assets()
|
||||
var applied := _apply_award(letter["attachment"])
|
||||
if applied:
|
||||
applied = _save_manager.save_if_dirty()
|
||||
if not applied:
|
||||
_restore_assets(snapshot)
|
||||
_save_manager.save_if_dirty()
|
||||
else:
|
||||
_received_awards[transfer_id] = true
|
||||
_bound(_received_awards)
|
||||
_send_phase_ack("recipient_awarded", transfer_id, applied)
|
||||
|
||||
|
||||
func _send_phase_ack(phase: String, transfer_id: String, applied: bool) -> void:
|
||||
if _session.is_host():
|
||||
_handle_phase_ack(_session.get_local_peer_id(), phase, transfer_id, applied)
|
||||
else:
|
||||
acknowledge_transfer_phase.rpc_id(1, phase, transfer_id, applied)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func acknowledge_transfer_phase(
|
||||
phase: String, transfer_id: String, applied: bool
|
||||
) -> void:
|
||||
var sender := multiplayer.get_remote_sender_id()
|
||||
if _session.is_host() and _session.is_authenticated_peer(sender):
|
||||
_handle_phase_ack(sender, phase, transfer_id, applied)
|
||||
|
||||
|
||||
func _handle_phase_ack(
|
||||
peer_id: int, phase: String, transfer_id: String, applied: bool
|
||||
) -> void:
|
||||
var transfer: Dictionary = _pending_transfers.get(transfer_id, {})
|
||||
if transfer.is_empty():
|
||||
return
|
||||
var letter: Dictionary = transfer["letter"]
|
||||
if phase == "recipient_prepared":
|
||||
if peer_id != int(letter["recipient_peer_id"]):
|
||||
return
|
||||
if not applied:
|
||||
_finish_transfer(transfer_id, false, "Gift could not be transferred.")
|
||||
return
|
||||
transfer["recipient_prepared"] = true
|
||||
_request_sender_commit(int(letter["sender_peer_id"]), transfer_id, letter)
|
||||
elif phase == "sender_removed":
|
||||
if peer_id != int(letter["sender_peer_id"]):
|
||||
return
|
||||
if not applied:
|
||||
_finish_transfer(transfer_id, false, "Gift returned to sender.")
|
||||
return
|
||||
transfer["sender_removed"] = true
|
||||
_request_recipient_award(
|
||||
int(letter["recipient_peer_id"]), transfer_id, letter
|
||||
)
|
||||
elif phase == "recipient_awarded":
|
||||
if peer_id != int(letter["recipient_peer_id"]):
|
||||
return
|
||||
if applied:
|
||||
_finish_transfer(transfer_id, true, "Gift accepted.")
|
||||
else:
|
||||
_request_sender_rollback(
|
||||
int(letter["sender_peer_id"]), transfer_id
|
||||
)
|
||||
_finish_transfer(transfer_id, false, "Gift could not be transferred.")
|
||||
|
||||
|
||||
func _request_sender_rollback(peer_id: int, transfer_id: String) -> void:
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
_rollback_sender(transfer_id)
|
||||
else:
|
||||
rollback_sender.rpc_id(peer_id, transfer_id)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func rollback_sender(transfer_id: String) -> void:
|
||||
_rollback_sender(transfer_id)
|
||||
|
||||
|
||||
func _rollback_sender(transfer_id: String) -> void:
|
||||
var snapshot: Dictionary = _local_removal_snapshots.get(transfer_id, {})
|
||||
if not snapshot.is_empty():
|
||||
_restore_assets(snapshot)
|
||||
_save_manager.save_if_dirty()
|
||||
_local_removal_snapshots.erase(transfer_id)
|
||||
|
||||
|
||||
func _finish_transfer(transfer_id: String, accepted: bool, message: String) -> void:
|
||||
var transfer: Dictionary = _pending_transfers.get(transfer_id, {})
|
||||
if transfer.is_empty():
|
||||
return
|
||||
var letter: Dictionary = transfer["letter"]
|
||||
letter["state"] = (
|
||||
NetworkMailProtocol.State.ACCEPTED
|
||||
if accepted else NetworkMailProtocol.State.ATTACHMENT_RECALLED
|
||||
)
|
||||
_pending_transfers.erase(transfer_id)
|
||||
_update_participants(letter)
|
||||
operation_finished.emit(accepted, message)
|
||||
|
||||
|
||||
func decline_gift(mail_id: String) -> void:
|
||||
var request := _action_request(mail_id)
|
||||
if request.is_empty():
|
||||
return
|
||||
if _session.is_host():
|
||||
_handle_decline(_session.get_local_peer_id(), request)
|
||||
else:
|
||||
submit_decline.rpc_id(1, request)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func submit_decline(data: Dictionary) -> void:
|
||||
var sender := multiplayer.get_remote_sender_id()
|
||||
if _session.is_host() and _session.is_authenticated_peer(sender):
|
||||
_handle_decline(sender, data)
|
||||
|
||||
|
||||
func _handle_decline(peer_id: int, data: Dictionary) -> void:
|
||||
var action_id := str(data.get("request_id", ""))
|
||||
var letter: Dictionary = _letters.get(str(data.get("mail_id", "")), {})
|
||||
if _action_ledger.has(action_id):
|
||||
return
|
||||
if (
|
||||
letter.is_empty()
|
||||
or int(letter["recipient_peer_id"]) != peer_id
|
||||
or int(letter["state"]) not in [
|
||||
NetworkMailProtocol.State.SENT_UNREAD,
|
||||
NetworkMailProtocol.State.READ,
|
||||
]
|
||||
):
|
||||
return
|
||||
_action_ledger[action_id] = true
|
||||
letter["state"] = NetworkMailProtocol.State.DECLINED
|
||||
_request_release(int(letter["sender_peer_id"]), letter["reservation_id"])
|
||||
_update_participants(letter)
|
||||
|
||||
|
||||
func _request_release(peer_id: int, reservation_id: String) -> void:
|
||||
if reservation_id.is_empty():
|
||||
return
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
_reservations.release(reservation_id)
|
||||
else:
|
||||
release_reservation.rpc_id(peer_id, reservation_id)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkMailProtocol.RELIABLE_CHANNEL)
|
||||
func release_reservation(reservation_id: String) -> void:
|
||||
_reservations.release(reservation_id)
|
||||
|
||||
|
||||
func _update_participants(letter: Dictionary) -> void:
|
||||
_letters[letter["mail_id"]] = letter
|
||||
_deliver_private_letter(int(letter["sender_peer_id"]), letter)
|
||||
_deliver_private_letter(int(letter["recipient_peer_id"]), letter)
|
||||
|
||||
|
||||
func _action_request(mail_id: String) -> Dictionary:
|
||||
if mail_id.is_empty() or not _local_letters.has(mail_id):
|
||||
return {}
|
||||
return {
|
||||
"request_id": _new_id("mail_action"),
|
||||
"mail_id": mail_id,
|
||||
"session_id": _session.get_session_id(),
|
||||
}
|
||||
|
||||
|
||||
func _validate_attachment_structure(attachment: Dictionary) -> bool:
|
||||
if typeof(attachment.get("type")) != TYPE_INT:
|
||||
return false
|
||||
match int(attachment["type"]):
|
||||
PlayerAssetReservationService.AttachmentType.NONE:
|
||||
return attachment.size() == 1
|
||||
PlayerAssetReservationService.AttachmentType.FISH_COIN:
|
||||
return (
|
||||
attachment.size() == 2
|
||||
and
|
||||
typeof(attachment.get("amount")) == TYPE_INT
|
||||
and int(attachment["amount"]) >= 1
|
||||
and int(attachment["amount"]) <= 1000000000
|
||||
)
|
||||
PlayerAssetReservationService.AttachmentType.FISH:
|
||||
if not (
|
||||
attachment.size() == 3
|
||||
and
|
||||
typeof(attachment.get("catch_id")) == TYPE_STRING
|
||||
and typeof(attachment.get("catch")) == TYPE_DICTIONARY
|
||||
):
|
||||
return false
|
||||
var fish_catch := _decode_catch(attachment)
|
||||
return (
|
||||
fish_catch != null
|
||||
and str(fish_catch.catch_id) == str(attachment["catch_id"])
|
||||
and not bool(
|
||||
Dictionary(attachment["catch"]).get("is_favorited", false)
|
||||
)
|
||||
)
|
||||
PlayerAssetReservationService.AttachmentType.CONSUMABLE:
|
||||
var item := _item_catalog.get_item_by_id(
|
||||
StringName(str(attachment.get("item_id", "")))
|
||||
)
|
||||
return (
|
||||
attachment.size() == 3
|
||||
and
|
||||
item != null
|
||||
and item.category == ItemData.Category.CONSUMABLE
|
||||
and typeof(attachment.get("quantity")) == TYPE_INT
|
||||
and int(attachment["quantity"]) >= 1
|
||||
and int(attachment["quantity"]) <= item.max_stack
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
func _can_receive(attachment: Dictionary) -> bool:
|
||||
match int(attachment.get("type", 0)):
|
||||
PlayerAssetReservationService.AttachmentType.FISH_COIN:
|
||||
return _wallet.can_credit(int(attachment["amount"]))
|
||||
PlayerAssetReservationService.AttachmentType.FISH:
|
||||
return (
|
||||
_inventory.get_all_catches().size() < _cooler_capacity.get_capacity()
|
||||
and not _inventory.contains_catch_id(
|
||||
StringName(str(attachment["catch_id"]))
|
||||
)
|
||||
and _decode_catch(attachment) != null
|
||||
)
|
||||
PlayerAssetReservationService.AttachmentType.CONSUMABLE:
|
||||
return _bag.can_add_item(
|
||||
StringName(str(attachment["item_id"])),
|
||||
int(attachment["quantity"])
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
func _apply_award(attachment: Dictionary) -> bool:
|
||||
if not _can_receive(attachment):
|
||||
return false
|
||||
match int(attachment["type"]):
|
||||
PlayerAssetReservationService.AttachmentType.FISH_COIN:
|
||||
return _wallet.credit(int(attachment["amount"]))
|
||||
PlayerAssetReservationService.AttachmentType.FISH:
|
||||
var fish_catch := _decode_catch(attachment)
|
||||
_inventory.add_catch(fish_catch)
|
||||
_collection_log.mark_discovered(fish_catch.fish_id)
|
||||
return _inventory.contains_catch_id(fish_catch.catch_id)
|
||||
PlayerAssetReservationService.AttachmentType.CONSUMABLE:
|
||||
return _bag.add_item(
|
||||
StringName(str(attachment["item_id"])),
|
||||
int(attachment["quantity"])
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
func _decode_catch(attachment: Dictionary) -> FishCatch:
|
||||
var data: Dictionary = attachment.get("catch", {})
|
||||
var fish := _fish_catalog.get_fish_by_id(
|
||||
StringName(str(data.get("fish_id", "")))
|
||||
)
|
||||
return FishCatch.from_network_dict(data, fish)
|
||||
|
||||
|
||||
func _capture_assets() -> Dictionary:
|
||||
return {
|
||||
"wallet": _wallet.get_balance(),
|
||||
"bag": _bag.get_all_items(),
|
||||
"catches": _inventory.get_all_catches(),
|
||||
"next_sequence": _inventory.get_next_catch_sequence(),
|
||||
"discovered": _collection_log.get_discovered_ids(),
|
||||
}
|
||||
|
||||
|
||||
func _restore_assets(snapshot: Dictionary) -> void:
|
||||
_wallet.restore_balance(int(snapshot["wallet"]))
|
||||
_bag.replace_all_items(snapshot["bag"])
|
||||
_inventory.replace_all_catches(
|
||||
snapshot["catches"], int(snapshot["next_sequence"])
|
||||
)
|
||||
_collection_log.replace_discovered_ids(snapshot["discovered"])
|
||||
|
||||
|
||||
func _emit_mailbox() -> void:
|
||||
mailbox_changed.emit()
|
||||
unread_count_changed.emit(get_unread_count())
|
||||
|
||||
|
||||
func _on_peer_removed(peer_id: int) -> void:
|
||||
if not _session.is_host():
|
||||
_clear_local_mail_for_peer(peer_id)
|
||||
return
|
||||
for letter: Dictionary in _letters.values():
|
||||
if int(letter["recipient_peer_id"]) == peer_id:
|
||||
if int(letter["state"]) in [
|
||||
NetworkMailProtocol.State.SENT_UNREAD,
|
||||
NetworkMailProtocol.State.READ,
|
||||
]:
|
||||
letter["state"] = NetworkMailProtocol.State.CANCELLED
|
||||
_request_release(
|
||||
int(letter["sender_peer_id"]), letter["reservation_id"]
|
||||
)
|
||||
_deliver_private_letter(
|
||||
int(letter["sender_peer_id"]), letter
|
||||
)
|
||||
elif (
|
||||
int(letter["sender_peer_id"]) == peer_id
|
||||
and int(letter["state"]) in [
|
||||
NetworkMailProtocol.State.SENT_UNREAD,
|
||||
NetworkMailProtocol.State.READ,
|
||||
]
|
||||
):
|
||||
letter["state"] = NetworkMailProtocol.State.ATTACHMENT_RECALLED
|
||||
_update_participants(letter)
|
||||
_send_ledger.erase(peer_id)
|
||||
_clear_local_mail_for_peer(peer_id)
|
||||
peers_changed.emit()
|
||||
|
||||
|
||||
func _clear_local_mail_for_peer(peer_id: int) -> void:
|
||||
for mail_id: String in _local_letters.keys():
|
||||
var letter: Dictionary = _local_letters[mail_id]
|
||||
if int(letter["recipient_peer_id"]) == peer_id:
|
||||
_local_letters.erase(mail_id)
|
||||
_emit_mailbox()
|
||||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
if state in [
|
||||
NetworkSession.State.INACTIVE,
|
||||
NetworkSession.State.DISCONNECTING,
|
||||
NetworkSession.State.CONNECTION_FAILED,
|
||||
NetworkSession.State.SERVER_LOST,
|
||||
]:
|
||||
_reservations.release_all()
|
||||
_letters.clear()
|
||||
_local_letters.clear()
|
||||
_send_ledger.clear()
|
||||
_action_ledger.clear()
|
||||
_pending_local_send.clear()
|
||||
_pending_transfers.clear()
|
||||
_local_removal_snapshots.clear()
|
||||
_received_awards.clear()
|
||||
_sequence = 0
|
||||
_emit_mailbox()
|
||||
|
||||
|
||||
func _bound(values: Dictionary) -> void:
|
||||
while values.size() > MAX_LEDGER:
|
||||
values.erase(values.keys().front())
|
||||
|
||||
|
||||
func _new_id(prefix: String) -> String:
|
||||
return "%s:%s" % [
|
||||
prefix, Crypto.new().generate_random_bytes(16).hex_encode(),
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue