Add expanded fishing gear and presentation

This commit is contained in:
Alexander Sellite 2026-08-08 22:39:50 -04:00
parent 551db3ccc6
commit 72d3a303f5
29 changed files with 783 additions and 86 deletions

View file

@ -22,8 +22,10 @@ var target: Vector3
var bobber_position: Vector3
var fish_id: StringName
var bait_tags: Array[StringName] = []
var lure_effects: Array[StringName] = []
var encounter_seed: int = 0
var bite_time_remaining: float = 0.0
var bite_confirmation_pending: bool = false
var withdrawal_progress: float = 0.0
var reel_speed: float = 0.0
var barrier_damage: int = 1

View file

@ -55,6 +55,8 @@ static func validate_cast_request(data: Variant) -> String:
return "Malformed fishing request."
if payload.has("bait_id") and typeof(payload["bait_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]:
return "Malformed fishing request."
if payload.has("lure_id") and typeof(payload["lure_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]:
return "Malformed fishing request."
var request_id: String = payload["request_id"]
var session_id: String = payload["session_id"]
var origin: Array = payload["origin"]
@ -89,6 +91,7 @@ static func validate_cast_request(data: Variant) -> String:
or discovered.size() > MAX_EVIDENCE_FISH_IDS
or str(payload["rod_id"]).is_empty()
or str(payload["rod_id"]).length() > 96
or str(payload.get("lure_id", "")).length() > 96
):
return "Fishing request values are outside allowed limits."
for value: Variant in rarity:

View file

@ -24,6 +24,7 @@ const MIN_CAST_INTERVAL: float = 0.25
signal local_cast_accepted(attempt_id: String, target: Vector3)
signal local_cast_rejected(message: String)
signal local_bite_started(attempt_id: String)
signal local_bite_pending(attempt_id: String)
signal local_snapshot_received(snapshot: Dictionary)
signal local_catch_received(fish_catch: FishCatch)
signal local_attempt_ended(outcome: StringName, message: String)
@ -111,6 +112,7 @@ func request_local_cast(
"discovered_fish_ids": evidence.get("discovered_fish_ids", []),
"capacity_available": bool(evidence.get("capacity_available", false)),
"bait_id": str(evidence.get("bait_id", "")),
"lure_id": str(evidence.get("lure_id", "")),
}
if _session.is_host():
_handle_cast_request(_session.get_local_peer_id(), data)
@ -176,12 +178,17 @@ func _process(delta: float) -> void:
continue
match attempt.phase:
NetworkFishingAttempt.Phase.WAITING_FOR_BITE:
if attempt.bite_confirmation_pending:
continue
_update_waiting_attempt(attempt, delta)
if not _attempts.has(peer_id):
continue
attempt.bite_time_remaining -= delta
if attempt.bite_time_remaining <= 0.0:
_start_bite(attempt)
if &"deferred_fight" in attempt.lure_effects:
_set_bite_pending(attempt)
else:
_start_bite(attempt)
NetworkFishingAttempt.Phase.PENDING_CAPACITY:
if now >= attempt.capacity_deadline:
_cancel_attempt(peer_id, "Fishing attempt ended.")
@ -296,6 +303,23 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void:
return
var bait_id: StringName = StringName(str(data.get("bait_id", "")))
var avatar_bag: PlayerBag = avatar.bag
var lure_id: StringName = StringName(str(data.get("lure_id", "")))
var lure: ItemDataType = (
_item_catalog.get_item_by_id(lure_id)
if not lure_id.is_empty() and _item_catalog != null
else null
)
if (
not lure_id.is_empty()
and (
lure == null
or not lure.is_lure()
or avatar_bag == null
or not avatar_bag.owns_item(lure_id)
)
):
_record_and_reject(peer_id, request_id, "Lure is unavailable.")
return
if not bait_id.is_empty() and (avatar_bag == null or not avatar_bag.remove_item(bait_id, 1)):
_record_and_reject(peer_id, request_id, "No bait available.")
return
@ -310,6 +334,10 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void:
attempt.bobber_position = authoritative_target
attempt.fish_id = selected_fish.id
attempt.bait_tags = _bait_tags_for_request(data)
attempt.lure_effects.clear()
if lure != null:
for effect_id: StringName in lure.lure_effects:
attempt.lure_effects.append(effect_id)
attempt.reel_speed = float(data["reel_speed"]) * (
effects.get_reel_multiplier() if effects != null else 1.0
)
@ -437,6 +465,23 @@ func _bait_tags_for_request(data: Dictionary) -> Array[StringName]:
return bait.bait_tags.duplicate() if bait != null and bait.is_bait() else []
func _set_bite_pending(attempt: NetworkFishingAttempt) -> void:
if (
attempt.phase != NetworkFishingAttempt.Phase.WAITING_FOR_BITE
or attempt.bite_confirmation_pending
):
return
attempt.bite_confirmation_pending = true
attempt.bite_time_remaining = 0.0
attempt.input_held = false
var data: Dictionary = {
"attempt_id": attempt.attempt_id,
"owner_peer_id": attempt.owner_peer_id,
}
_apply_bite_pending(data)
receive_bite_pending.rpc(data)
func _start_bite(attempt: NetworkFishingAttempt) -> void:
if attempt.phase != NetworkFishingAttempt.Phase.WAITING_FOR_BITE:
return
@ -444,6 +489,7 @@ func _start_bite(attempt: NetworkFishingAttempt) -> void:
if fish == null or fish.catch_profile == null:
_cancel_attempt(attempt.owner_peer_id, "Fishing attempt ended.")
return
attempt.bite_confirmation_pending = false
attempt.phase = NetworkFishingAttempt.Phase.FIGHTING
attempt.encounter_seed = _new_seed()
var selector := FishSelectorType.new()
@ -496,6 +542,14 @@ func _handle_fishing_input(peer_id: int, data: Dictionary) -> void:
return
attempt.last_input_sequence = int(data["sequence"])
attempt.input_held = bool(data["held"])
if (
attempt.phase == NetworkFishingAttempt.Phase.WAITING_FOR_BITE
and attempt.bite_confirmation_pending
):
if bool(data["pressed"]):
attempt.input_held = false
_start_bite(attempt)
return
if attempt.phase != NetworkFishingAttempt.Phase.FIGHTING:
return
attempt.controller.set_reel_input(attempt.input_held)
@ -978,6 +1032,21 @@ func _apply_cast_accepted(data: Dictionary) -> void:
presentation.show_cast(origin, target)
@rpc("authority", "call_remote", "reliable", 0)
func receive_bite_pending(data: Dictionary) -> void:
_apply_bite_pending(data)
func _apply_bite_pending(data: Dictionary) -> void:
if (
typeof(data.get("attempt_id")) != TYPE_STRING
or typeof(data.get("owner_peer_id")) != TYPE_INT
):
return
if int(data["owner_peer_id"]) == _session.get_local_peer_id():
local_bite_pending.emit(str(data["attempt_id"]))
@rpc("authority", "call_remote", "reliable", 0)
func receive_bite_started(data: Dictionary) -> void:
_apply_bite_started(data)

View file

@ -50,9 +50,19 @@ func request_use(item_id: StringName) -> String:
if not _pending_local.is_empty():
local_item_use_finished.emit(false, "An item use is already pending.")
return ""
var consumed_item_id: StringName = _consumed_item_id(item_id)
if item_id == PlayerItemEffects.FISH_FINDER_ID and (
_local_bag == null
or not _local_bag.owns_item(item_id)
or _local_bag.get_quantity(consumed_item_id) < 1
):
local_item_use_finished.emit(
false, PlayerItemEffects.FISH_FINDER_DEAD_MESSAGE
)
return ""
if (
_reservations != null
and _reservations.get_available_item_quantity(item_id) < 1
and _reservations.get_available_item_quantity(consumed_item_id) < 1
):
local_item_use_finished.emit(false, "Reserved in a letter.")
return ""
@ -73,7 +83,7 @@ func request_use(item_id: StringName) -> String:
"request_id": request_id,
"session_id": _session.get_session_id(),
"item_id": str(item_id),
"quantity": _local_bag.get_quantity(item_id),
"quantity": _local_bag.get_quantity(consumed_item_id),
}
_pending_local = data.duplicate(true)
local_item_use_pending.emit(request_id)
@ -109,14 +119,28 @@ func _handle_use_request(peer_id: int, data: Dictionary) -> void:
var item: ItemData = _catalog.get_item_by_id(item_id)
var avatar: Player = _spawn_service.get_avatar(peer_id)
var duration: float = 0.0
var is_fish_finder: bool = (
item_id == PlayerItemEffects.FISH_FINDER_ID
and item != null
and item.category == ItemData.Category.TOOL
and item.usable
and item.equippable
)
if error.is_empty() and (
item == null
or item.category != ItemData.Category.CONSUMABLE
or (
item.category != ItemData.Category.CONSUMABLE
and not is_fish_finder
)
or not item.usable
):
error = "That item cannot be used now."
if error.is_empty() and int(data["quantity"]) < 1:
error = "You do not have that item."
error = (
PlayerItemEffects.FISH_FINDER_DEAD_MESSAGE
if is_fish_finder
else "You do not have that item."
)
if error.is_empty() and (
avatar == null
or avatar.is_water_recovery_active()
@ -180,9 +204,10 @@ func _apply_result(data: Dictionary) -> void:
_acknowledge(data, false, str(data["message"]))
return
var item_id := StringName(str(data["item_id"]))
var consumed_item_id: StringName = _consumed_item_id(item_id)
if (
_reservations != null
and _reservations.get_available_item_quantity(item_id) < 1
and _reservations.get_available_item_quantity(consumed_item_id) < 1
):
_pending_local.clear()
local_item_use_finished.emit(false, "Reserved in a letter.")
@ -190,8 +215,8 @@ func _apply_result(data: Dictionary) -> void:
return
var bag_snapshot := _local_bag.get_all_items()
if (
_local_bag.get_quantity(item_id) < 1
or not _local_bag.remove_item(item_id, 1)
_local_bag.get_quantity(consumed_item_id) < 1
or not _local_bag.remove_item(consumed_item_id, 1)
or not _save_manager.save_if_dirty()
):
_local_bag.replace_all_items(bag_snapshot)
@ -409,3 +434,11 @@ func _new_id(prefix: String) -> String:
return "%s:%s" % [
prefix, Crypto.new().generate_random_bytes(16).hex_encode(),
]
func _consumed_item_id(item_id: StringName) -> StringName:
return (
PlayerItemEffects.BATTERIES_ID
if item_id == PlayerItemEffects.FISH_FINDER_ID
else item_id
)

View file

@ -335,15 +335,29 @@ func _build_authoritative_result(
var bait_topoff: bool = (
FishingShopStockType.is_bait_topoff(product_id)
)
var permanent_unlock: bool = (
FishingShopStockType.is_permanent_unlock(product_id, item)
)
if (
item == null
or not item.is_valid()
or product_id not in (
FishingShopStockType.get_stock_item_ids()
)
or (item.category != ItemDataType.Category.CONSUMABLE and not bait_topoff)
or not item.stackable
or (not item.usable and not bait_topoff)
or (
item.category != ItemDataType.Category.CONSUMABLE
and not bait_topoff
and not permanent_unlock
)
or (not item.stackable and not permanent_unlock)
or (
not item.usable
and not bait_topoff
and not permanent_unlock
and not FishingShopStockType.is_passive_supply(
product_id
)
)
or current_state >= item.max_stack
or current_state + quantity > item.max_stack
):