Add collectible fish quality tiers
This commit is contained in:
parent
7661b057bc
commit
35d00e8035
30 changed files with 834 additions and 73 deletions
|
|
@ -2,6 +2,7 @@ class_name FishCatch
|
|||
extends Resource
|
||||
|
||||
const FishDataType = preload("res://fish/fish_data.gd")
|
||||
const FishQualityType = preload("res://fish/fish_quality.gd")
|
||||
|
||||
const MAX_SAFE_WEIGHT_LB: float = 1000000.0
|
||||
const MAX_SAFE_DISPLAY_SCALE: float = 10000.0
|
||||
|
|
@ -14,6 +15,7 @@ const MAX_SAFE_SEQUENCE: int = 2147483647
|
|||
@export var catch_sequence: int = 0
|
||||
@export var weight_lb: float = 0.0
|
||||
@export var display_scale: float = 1.0
|
||||
@export_range(0, 4, 1) var quality: int = FishQualityType.Tier.BORING
|
||||
@export var sale_value: int = 0
|
||||
@export var is_favorited: bool = false
|
||||
|
||||
|
|
@ -48,6 +50,7 @@ func is_valid() -> bool:
|
|||
and is_finite(display_scale)
|
||||
and display_scale > 0.0
|
||||
and display_scale <= MAX_SAFE_DISPLAY_SCALE
|
||||
and FishQualityType.is_valid(quality)
|
||||
and sale_value >= 0
|
||||
and sale_value <= MAX_SAFE_SALE_VALUE
|
||||
)
|
||||
|
|
@ -60,6 +63,7 @@ func to_save_dict() -> Dictionary:
|
|||
"fish_id": String(fish_id),
|
||||
"weight_lb": weight_lb,
|
||||
"display_scale": display_scale,
|
||||
"quality": quality,
|
||||
"sale_value": sale_value,
|
||||
"is_favorited": is_favorited,
|
||||
}
|
||||
|
|
@ -71,6 +75,7 @@ func to_network_dict() -> Dictionary:
|
|||
"fish_id": String(fish_id),
|
||||
"weight_lb": weight_lb,
|
||||
"display_scale": display_scale,
|
||||
"quality": quality,
|
||||
"sale_value": sale_value,
|
||||
"is_favorited": is_favorited,
|
||||
}
|
||||
|
|
@ -88,6 +93,7 @@ static func from_save_dict(
|
|||
"fish_id",
|
||||
"weight_lb",
|
||||
"display_scale",
|
||||
"quality",
|
||||
"sale_value",
|
||||
"is_favorited",
|
||||
]:
|
||||
|
|
@ -111,6 +117,11 @@ static func from_save_dict(
|
|||
-1,
|
||||
MAX_SAFE_SALE_VALUE
|
||||
)
|
||||
var loaded_quality: int = _read_safe_integer(
|
||||
data["quality"],
|
||||
-1,
|
||||
FishQualityType.TIER_COUNT - 1,
|
||||
)
|
||||
var loaded_weight: float = _read_safe_float(
|
||||
data["weight_lb"],
|
||||
0.0,
|
||||
|
|
@ -128,6 +139,7 @@ static func from_save_dict(
|
|||
or loaded_sequence <= 0
|
||||
or loaded_sequence >= MAX_SAFE_SEQUENCE
|
||||
or loaded_sale_value < 0
|
||||
or not FishQualityType.is_valid(loaded_quality)
|
||||
or loaded_weight <= 0.0
|
||||
or loaded_scale <= 0.0
|
||||
or typeof(loaded_favorite) != TYPE_BOOL
|
||||
|
|
@ -141,6 +153,7 @@ static func from_save_dict(
|
|||
fish_catch.catch_sequence = loaded_sequence
|
||||
fish_catch.weight_lb = loaded_weight
|
||||
fish_catch.display_scale = loaded_scale
|
||||
fish_catch.quality = loaded_quality
|
||||
fish_catch.sale_value = loaded_sale_value
|
||||
fish_catch.is_favorited = bool(loaded_favorite)
|
||||
return fish_catch if fish_catch.is_valid() else null
|
||||
|
|
@ -157,6 +170,7 @@ static func from_network_dict(
|
|||
"fish_id",
|
||||
"weight_lb",
|
||||
"display_scale",
|
||||
"quality",
|
||||
"sale_value",
|
||||
]:
|
||||
if not data.has(required_key):
|
||||
|
|
@ -172,6 +186,11 @@ static func from_network_dict(
|
|||
var loaded_value: int = _read_safe_integer(
|
||||
data.get("sale_value"), -1, MAX_SAFE_SALE_VALUE
|
||||
)
|
||||
var loaded_quality: int = _read_safe_integer(
|
||||
data.get("quality"),
|
||||
-1,
|
||||
FishQualityType.TIER_COUNT - 1,
|
||||
)
|
||||
if (
|
||||
loaded_id.is_empty()
|
||||
or loaded_id.length() > 160
|
||||
|
|
@ -179,6 +198,7 @@ static func from_network_dict(
|
|||
or loaded_weight <= 0.0
|
||||
or loaded_scale <= 0.0
|
||||
or loaded_value < 0
|
||||
or not FishQualityType.is_valid(loaded_quality)
|
||||
):
|
||||
return null
|
||||
var fish_catch := FishCatch.new()
|
||||
|
|
@ -188,6 +208,7 @@ static func from_network_dict(
|
|||
fish_catch.catch_sequence = 0
|
||||
fish_catch.weight_lb = loaded_weight
|
||||
fish_catch.display_scale = loaded_scale
|
||||
fish_catch.quality = loaded_quality
|
||||
fish_catch.sale_value = loaded_value
|
||||
fish_catch.is_favorited = false
|
||||
return fish_catch if fish_catch.is_valid() else null
|
||||
|
|
|
|||
91
fish/fish_quality.gd
Normal file
91
fish/fish_quality.gd
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
class_name FishQuality
|
||||
extends RefCounted
|
||||
|
||||
enum Tier {
|
||||
BORING,
|
||||
AVERAGE,
|
||||
IMPRESSIVE,
|
||||
EXCEPTIONAL,
|
||||
SHINY,
|
||||
}
|
||||
|
||||
const TIER_COUNT: int = 5
|
||||
const ALL_TIERS_MASK: int = (1 << TIER_COUNT) - 1
|
||||
|
||||
# Starting distribution. Equipment and tackle can supply per-tier
|
||||
# multipliers later without changing catch serialization or tier identity.
|
||||
const BASE_ROLL_WEIGHTS: Array[float] = [40.0, 32.0, 18.0, 8.0, 2.0]
|
||||
const SALE_MULTIPLIERS: Array[float] = [1.0, 1.1, 1.25, 1.5, 2.0]
|
||||
const DISPLAY_NAMES: PackedStringArray = [
|
||||
"boring",
|
||||
"average",
|
||||
"impressive",
|
||||
"exceptional",
|
||||
"shiny",
|
||||
]
|
||||
|
||||
|
||||
static func is_valid(quality: int) -> bool:
|
||||
return quality >= 0 and quality < TIER_COUNT
|
||||
|
||||
|
||||
static func display_name(quality: int) -> String:
|
||||
return DISPLAY_NAMES[quality] if is_valid(quality) else "unknown"
|
||||
|
||||
|
||||
static func bit_for(quality: int) -> int:
|
||||
return 1 << quality if is_valid(quality) else 0
|
||||
|
||||
|
||||
static func sale_multiplier(quality: int) -> float:
|
||||
return SALE_MULTIPLIERS[quality] if is_valid(quality) else 1.0
|
||||
|
||||
|
||||
static func apply_sale_value(base_value: int, quality: int) -> int:
|
||||
if base_value <= 0 or not is_valid(quality):
|
||||
return maxi(base_value, 0)
|
||||
if quality == Tier.BORING:
|
||||
return base_value
|
||||
return maxi(
|
||||
base_value + quality,
|
||||
ceili(float(base_value) * sale_multiplier(quality)),
|
||||
)
|
||||
|
||||
|
||||
static func roll(
|
||||
rng: RandomNumberGenerator,
|
||||
weight_multipliers: Array[float] = [],
|
||||
) -> int:
|
||||
if rng == null:
|
||||
return Tier.BORING
|
||||
var weights: Array[float] = []
|
||||
var total_weight: float = 0.0
|
||||
for quality: int in TIER_COUNT:
|
||||
var multiplier: float = 1.0
|
||||
if quality < weight_multipliers.size():
|
||||
multiplier = maxf(weight_multipliers[quality], 0.0)
|
||||
var weight: float = BASE_ROLL_WEIGHTS[quality] * multiplier
|
||||
weights.append(weight)
|
||||
total_weight += weight
|
||||
if total_weight <= 0.0:
|
||||
return Tier.BORING
|
||||
var rolled_weight: float = rng.randf() * total_weight
|
||||
var accumulated_weight: float = 0.0
|
||||
for quality: int in TIER_COUNT:
|
||||
accumulated_weight += weights[quality]
|
||||
if rolled_weight <= accumulated_weight:
|
||||
return quality
|
||||
return Tier.SHINY
|
||||
|
||||
|
||||
static func qualified_name(fish_name: String, quality: int) -> String:
|
||||
return "%s %s" % [display_name(quality), fish_name]
|
||||
|
||||
|
||||
static func qualified_name_with_article(
|
||||
fish_name: String,
|
||||
quality: int,
|
||||
) -> String:
|
||||
var quality_name: String = display_name(quality)
|
||||
var article: String = "an" if quality_name[0] in ["a", "e", "i", "o", "u"] else "a"
|
||||
return "%s %s %s" % [article, quality_name, fish_name]
|
||||
1
fish/fish_quality.gd.uid
Normal file
1
fish/fish_quality.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://csmvl1ciuux6a
|
||||
|
|
@ -5,10 +5,12 @@ 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
|
||||
|
|
@ -81,7 +83,12 @@ func create_catch(fish: FishDataType) -> FishCatchType:
|
|||
caught_fish.display_scale = fish.get_display_scale_for_weight(
|
||||
caught_fish.weight_lb
|
||||
)
|
||||
caught_fish.sale_value = fish.get_sale_value_for_weight(
|
||||
caught_fish.weight_lb
|
||||
caught_fish.quality = FishQualityType.roll(
|
||||
_rng,
|
||||
quality_weight_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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue