class_name FishSelector extends RefCounted const CollectionLogType = preload("res://collection/collection_log.gd") const FishCatchType = preload("res://fish/fish_catch.gd") const FishDataType = preload("res://fish/fish_data.gd") const FishPoolType = preload("res://fish/fish_pool.gd") const FishQualityType = preload("res://fish/fish_quality.gd") const FishingContextType = preload("res://fishing/fishing_context.gd") var undiscovered_weight_multiplier: float = 1.5 var rarity_weight_multipliers: Array[float] = [] var quality_weight_multipliers: Array[float] = [] var use_deterministic_test_seed: bool = false var deterministic_test_seed: int = 24680 var selection_seed: int = 0 var _active_bait_tags: Array[StringName] = [] var _rng := RandomNumberGenerator.new() func begin_roll() -> void: if use_deterministic_test_seed: selection_seed = deterministic_test_seed _rng.seed = deterministic_test_seed else: _rng.randomize() selection_seed = _rng.seed func select_fish( pool: FishPoolType, context: FishingContextType, collection_log: CollectionLogType, ) -> FishDataType: if pool == null or context == null or collection_log == null: return null _active_bait_tags = context.active_bait_tags.duplicate() var all_fish: Array[FishDataType] = [] var all_weights: Array[float] = [] var rarity_fish: Array[FishDataType] = [] var rarity_weights: Array[float] = [] var selected_rarity: int = _roll_rarity(context) var weights: Array[float] = [] var total_weight: float = 0.0 for fish: FishDataType in pool.candidates: if fish == null or not fish.is_selectable(): continue if not fish.is_allowed_in_water(context.water_type): continue if fish.availability != null and not fish.availability.is_available(context): continue var final_weight: float = fish.base_catch_weight # Species-specific preferred bait is deprecated. Bait now controls # quality and rarity bands globally; required tags remain authoritative. if not collection_log.has_discovered(fish.id): final_weight *= maxf(undiscovered_weight_multiplier, 0.0) if fish.rarity >= 0 and fish.rarity < rarity_weight_multipliers.size(): final_weight *= maxf(rarity_weight_multipliers[fish.rarity], 0.0) if final_weight <= 0.0: continue all_fish.append(fish) all_weights.append(final_weight) if int(fish.rarity) == selected_rarity: rarity_fish.append(fish) rarity_weights.append(final_weight) var eligible_fish: Array[FishDataType] = rarity_fish if not rarity_fish.is_empty() else all_fish weights = rarity_weights if not rarity_weights.is_empty() else all_weights for weight: float in weights: total_weight += weight if eligible_fish.is_empty() or total_weight <= 0.0: return null var roll: float = _rng.randf() * total_weight var accumulated_weight: float = 0.0 for index: int in range(eligible_fish.size()): accumulated_weight += weights[index] if roll <= accumulated_weight: return eligible_fish[index] return eligible_fish.back() func _roll_rarity(context: FishingContextType) -> int: var weights: Array[float] = FishQualityType.rarity_weights_for_bait( context.active_bait_tags ) for index: int in range(weights.size()): if index < rarity_weight_multipliers.size(): weights[index] *= maxf(rarity_weight_multipliers[index], 0.0) var total: float = 0.0 for weight: float in weights: total += weight if total <= 0.0: return FishDataType.Rarity.COMMON var roll: float = _rng.randf() * total for index: int in range(weights.size()): roll -= weights[index] if roll <= 0.0: return index return FishDataType.Rarity.LEGENDARY func create_catch(fish: FishDataType, bait_tags: Array[StringName] = []) -> FishCatchType: if fish == null or not fish.is_selectable(): return null var caught_fish := FishCatchType.new() caught_fish.fish = fish caught_fish.fish_id = fish.id caught_fish.ensure_identity() caught_fish.weight_lb = _rng.randf_range( fish.get_minimum_weight(), fish.get_maximum_weight() ) caught_fish.display_scale = fish.get_display_scale_for_weight( caught_fish.weight_lb ) var effective_bait: Array[StringName] = bait_tags if not bait_tags.is_empty() else _active_bait_tags var quality_multipliers: Array[float] = FishQualityType.roll_weights_for_bait(effective_bait) for quality: int in range(mini(quality_multipliers.size(), quality_weight_multipliers.size())): quality_multipliers[quality] *= maxf(quality_weight_multipliers[quality], 0.0) caught_fish.quality = FishQualityType.roll( _rng, quality_multipliers, ) caught_fish.sale_value = FishQualityType.apply_sale_value( fish.get_sale_value_for_weight(caught_fish.weight_lb), caught_fish.quality, ) return caught_fish