diff --git a/collection/collection_log.gd b/collection/collection_log.gd index da36eb8..ad0ab56 100644 --- a/collection/collection_log.gd +++ b/collection/collection_log.gd @@ -2,13 +2,9 @@ class_name CollectionLog extends Node signal fish_discovered(fish_id: StringName) -signal fish_quality_discovered(fish_id: StringName, quality: int) signal collection_changed -const FishQualityType = preload("res://fish/fish_quality.gd") - var _discovered: Dictionary[StringName, bool] = {} -var _quality_masks: Dictionary[StringName, int] = {} func has_discovered(fish_id: StringName) -> bool: @@ -23,24 +19,6 @@ func mark_discovered(fish_id: StringName) -> void: collection_changed.emit() -func mark_quality_discovered(fish_id: StringName, quality: int) -> void: - if fish_id.is_empty() or not FishQualityType.is_valid(quality): - return - var species_was_discovered: bool = has_discovered(fish_id) - if not species_was_discovered: - _discovered[fish_id] = true - var previous_mask: int = _quality_masks.get(fish_id, 0) - var next_mask: int = previous_mask | FishQualityType.bit_for(quality) - if species_was_discovered and next_mask == previous_mask: - return - _quality_masks[fish_id] = next_mask - if not species_was_discovered: - fish_discovered.emit(fish_id) - if next_mask != previous_mask: - fish_quality_discovered.emit(fish_id, quality) - collection_changed.emit() - - func get_discovered_ids() -> Array[StringName]: var discovered_ids: Array[StringName] = [] for fish_id: StringName in _discovered: @@ -49,53 +27,12 @@ func get_discovered_ids() -> Array[StringName]: return discovered_ids -func has_discovered_quality(fish_id: StringName, quality: int) -> bool: - return ( - not fish_id.is_empty() - and FishQualityType.is_valid(quality) - and (_quality_masks.get(fish_id, 0) & FishQualityType.bit_for(quality)) != 0 - ) - - -func get_quality_mask(fish_id: StringName) -> int: - return _quality_masks.get(fish_id, 0) if has_discovered(fish_id) else 0 - - -func get_discovered_quality_masks() -> Dictionary[StringName, int]: - return _quality_masks.duplicate() - - -func has_mastered(fish_id: StringName) -> bool: - return get_quality_mask(fish_id) == FishQualityType.ALL_TIERS_MASK - - func replace_discovered_ids(fish_ids: Array[StringName]) -> bool: - var empty_masks: Dictionary[StringName, int] = {} - return replace_discovery_state(fish_ids, empty_masks) - - -func replace_discovery_state( - fish_ids: Array[StringName], - quality_masks: Dictionary[StringName, int], -) -> bool: var replacement: Dictionary[StringName, bool] = {} for fish_id: StringName in fish_ids: if fish_id.is_empty(): return false replacement[fish_id] = true - var replacement_masks: Dictionary[StringName, int] = {} - for fish_id: StringName in quality_masks: - var mask: int = quality_masks[fish_id] - if ( - fish_id.is_empty() - or not replacement.has(fish_id) - or mask < 0 - or (mask & ~FishQualityType.ALL_TIERS_MASK) != 0 - ): - return false - if mask != 0: - replacement_masks[fish_id] = mask _discovered = replacement - _quality_masks = replacement_masks collection_changed.emit() return true diff --git a/economy/fish_buyer_profile.gd b/economy/fish_buyer_profile.gd index 485df20..bca1cea 100644 --- a/economy/fish_buyer_profile.gd +++ b/economy/fish_buyer_profile.gd @@ -1,8 +1,6 @@ class_name FishBuyerProfile extends Resource -const FishQualityType = preload("res://fish/fish_quality.gd") - @export var id: StringName @export var display_name: String @export var animal_name_singular: String @@ -27,18 +25,6 @@ func get_offer(base_value: int) -> int: return maxi(offer, 0) -func get_quality_offer(base_value: int, quality: int) -> int: - if not FishQualityType.is_valid(quality): - return -1 - var ordinary_offer: int = get_offer(base_value) - var adjusted_offer: int = get_offer( - FishQualityType.apply_sale_value(base_value, quality) - ) - if ordinary_offer < 0 or adjusted_offer < 0: - return -1 - return maxi(adjusted_offer, ordinary_offer + quality) - - func get_sale_message( fish_name: String, payout: int, diff --git a/economy/fish_sale_service.gd b/economy/fish_sale_service.gd index 722f766..4de1faf 100644 --- a/economy/fish_sale_service.gd +++ b/economy/fish_sale_service.gd @@ -128,15 +128,7 @@ func _validate_batch( if fish_catch.sale_value < 0: result.status = FishSaleResultType.Status.INVALID_VALUE return result - 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, - ) + var offer: int = buyer.get_offer(fish_catch.sale_value) if offer < 0: result.status = FishSaleResultType.Status.INVALID_OFFER return result diff --git a/fish/fish_catch.gd b/fish/fish_catch.gd index 5edc9c4..57369ef 100644 --- a/fish/fish_catch.gd +++ b/fish/fish_catch.gd @@ -2,7 +2,6 @@ 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 @@ -15,7 +14,6 @@ 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 @@ -50,7 +48,6 @@ 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 ) @@ -63,7 +60,6 @@ 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, } @@ -75,7 +71,6 @@ 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, } @@ -93,7 +88,6 @@ static func from_save_dict( "fish_id", "weight_lb", "display_scale", - "quality", "sale_value", "is_favorited", ]: @@ -117,11 +111,6 @@ 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, @@ -139,7 +128,6 @@ 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 @@ -153,7 +141,6 @@ 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 @@ -170,7 +157,6 @@ static func from_network_dict( "fish_id", "weight_lb", "display_scale", - "quality", "sale_value", ]: if not data.has(required_key): @@ -186,11 +172,6 @@ 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 @@ -198,7 +179,6 @@ 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() @@ -208,7 +188,6 @@ 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 diff --git a/fish/fish_experience.gd b/fish/fish_experience.gd deleted file mode 100644 index 592042f..0000000 --- a/fish/fish_experience.gd +++ /dev/null @@ -1,73 +0,0 @@ -class_name FishExperience -extends RefCounted - -const FishCatchType = preload("res://fish/fish_catch.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") -const CollectionLogType = preload("res://collection/collection_log.gd") - -const RARITY_BASE_EXPERIENCE: Array[int] = [10, 18, 32, 55, 90] -const QUALITY_MULTIPLIERS: Array[float] = [1.0, 1.15, 1.4, 1.8, 2.5] -const MAXIMUM_WEIGHT_BONUS: float = 0.25 -const FIRST_SPECIES_BONUS: int = 25 -const FIRST_QUALITY_BONUS: int = 15 -const SPECIES_MASTERY_BONUS: int = 100 - - -static func calculate_for_collection( - fish_catch: FishCatchType, - collection_log: CollectionLogType, -) -> int: - if fish_catch == null or collection_log == null: - return 0 - return calculate_catch_experience( - fish_catch, - collection_log.has_discovered(fish_catch.fish_id), - collection_log.get_quality_mask(fish_catch.fish_id), - ) - - -static func calculate_catch_experience( - fish_catch: FishCatchType, - was_species_discovered: bool, - previous_quality_mask: int, -) -> int: - if fish_catch == null or not fish_catch.is_valid(): - return 0 - var rarity: int = int(fish_catch.fish.rarity) - if rarity < 0 or rarity >= RARITY_BASE_EXPERIENCE.size(): - return 0 - if not FishQualityType.is_valid(fish_catch.quality): - return 0 - var weight_percentile: float = _get_weight_percentile(fish_catch) - var weight_multiplier: float = ( - 1.0 + MAXIMUM_WEIGHT_BONUS * weight_percentile * weight_percentile - ) - var catch_experience: int = roundi( - float(RARITY_BASE_EXPERIENCE[rarity]) - * QUALITY_MULTIPLIERS[fish_catch.quality] - * weight_multiplier - ) - var quality_bit: int = FishQualityType.bit_for(fish_catch.quality) - if not was_species_discovered: - catch_experience += FIRST_SPECIES_BONUS - if (previous_quality_mask & quality_bit) == 0: - catch_experience += FIRST_QUALITY_BONUS - var next_quality_mask: int = previous_quality_mask | quality_bit - if ( - previous_quality_mask != FishQualityType.ALL_TIERS_MASK - and next_quality_mask == FishQualityType.ALL_TIERS_MASK - ): - catch_experience += SPECIES_MASTERY_BONUS - return maxi(catch_experience, 0) - - -static func _get_weight_percentile(fish_catch: FishCatchType) -> float: - var minimum_weight: float = fish_catch.fish.get_minimum_weight() - var maximum_weight: float = fish_catch.fish.get_maximum_weight() - if maximum_weight <= minimum_weight: - return 0.0 - return clampf( - inverse_lerp(minimum_weight, maximum_weight, fish_catch.weight_lb), - 0.0, - 1.0, - ) diff --git a/fish/fish_experience.gd.uid b/fish/fish_experience.gd.uid deleted file mode 100644 index aafa145..0000000 --- a/fish/fish_experience.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://d05mwat7tju1b diff --git a/fish/fish_quality.gd b/fish/fish_quality.gd deleted file mode 100644 index f851e66..0000000 --- a/fish/fish_quality.gd +++ /dev/null @@ -1,91 +0,0 @@ -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] diff --git a/fish/fish_quality.gd.uid b/fish/fish_quality.gd.uid deleted file mode 100644 index 0875763..0000000 --- a/fish/fish_quality.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://csmvl1ciuux6a diff --git a/fish/fish_selector.gd b/fish/fish_selector.gd index 8ed9a2a..35ea1d3 100644 --- a/fish/fish_selector.gd +++ b/fish/fish_selector.gd @@ -5,12 +5,10 @@ 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 @@ -83,12 +81,7 @@ func create_catch(fish: FishDataType) -> FishCatchType: caught_fish.display_scale = fish.get_display_scale_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, + caught_fish.sale_value = fish.get_sale_value_for_weight( + caught_fish.weight_lb ) return caught_fish diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index c694461..0dcaf1f 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -9,10 +9,6 @@ const CatchControllerType = preload("res://fishing/catch_controller.gd") const FishingContextType = preload("res://fishing/fishing_context.gd") const FishingPresentationType = preload("res://fishing/fishing_presentation.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") -const FishExperienceType = preload("res://fish/fish_experience.gd") -const PlayerExperienceType = preload( - "res://progression/player_experience.gd" -) const ItemCatalogType = preload("res://items/item_catalog.gd") const ItemDataType = preload("res://items/item_data.gd") const PlayerBagType = preload("res://inventory/player_bag.gd") @@ -60,7 +56,6 @@ signal showcase_changed( fish_name: String, rarity_name: String, weight_lb: float, - quality: int, visible: bool, ) signal bite_activated @@ -105,9 +100,9 @@ const BITE_QUICK_MAX_SECONDS: float = 30.0 const BITE_TYPICAL_MAX_SECONDS: float = 90.0 const BITE_LONG_MAX_SECONDS: float = 180.0 const BITE_MAX_SECONDS: float = 240.0 -const BITE_QUICK_PROBABILITY: float = 0.25 -const BITE_TYPICAL_PROBABILITY: float = 0.65 -const BITE_LONG_PROBABILITY: float = 0.08 +const BITE_QUICK_PROBABILITY: float = 0.15 +const BITE_TYPICAL_PROBABILITY: float = 0.55 +const BITE_LONG_PROBABILITY: float = 0.25 const NETWORK_INPUT_RESEND_INTERVAL_SECONDS: float = 0.1 @export_range(0.1, 10.0, 0.1) var cooldown_duration: float = 1.0 @@ -130,7 +125,6 @@ var _local_menu_input_owners: Dictionary[StringName, bool] = {} var _local_player: PlayerType var _local_inventory: FishInventoryType var _local_collection_log: CollectionLogType -var _local_experience: PlayerExperienceType var _local_bag: PlayerBagType var _local_hotbar: PlayerHotbarType var _item_catalog: ItemCatalogType @@ -191,7 +185,6 @@ func setup( local_player: PlayerType, local_inventory: FishInventoryType, local_collection_log: CollectionLogType, - local_experience: PlayerExperienceType, local_bag: PlayerBagType, local_hotbar: PlayerHotbarType, item_catalog: ItemCatalogType, @@ -207,7 +200,6 @@ func setup( _local_player = local_player _local_inventory = local_inventory _local_collection_log = local_collection_log - _local_experience = local_experience _local_bag = local_bag _local_hotbar = local_hotbar _item_catalog = item_catalog @@ -389,11 +381,12 @@ func _secure_showcase_catch_for_recovery() -> void: and _local_inventory != null and _local_collection_log != null ): - _store_catch_progression(_pending_catch) + _local_inventory.add_catch(_pending_catch) + _local_collection_log.mark_discovered(_pending_catch.fish_id) _pending_catch = null _showcase_ready = false _put_away_press_armed = false - showcase_changed.emit("", "", 0.0, 0, false) + showcase_changed.emit("", "", 0.0, false) if _active_player != null: _active_player.end_catch_showcase(Callable(), true) _cleanup_attempt() @@ -409,7 +402,8 @@ func _exit_tree() -> void: and _local_collection_log != null and is_instance_valid(_local_collection_log) ): - _store_catch_progression(_pending_catch) + _local_inventory.add_catch(_pending_catch) + _local_collection_log.mark_discovered(_pending_catch.fish_id) _pending_catch = null if _active_player != null and is_instance_valid(_active_player): _active_player.end_catch_showcase(Callable(), true) @@ -1037,7 +1031,6 @@ func _on_outcome_completed(outcome: StringName) -> void: _pending_catch.fish.display_name, _pending_catch.fish.get_rarity_name(), _pending_catch.weight_lb, - _pending_catch.quality, true ) @@ -1060,12 +1053,13 @@ func _put_away_catch() -> void: or _pending_catch == null ): return - _store_catch_progression(_pending_catch) + _local_inventory.add_catch(_pending_catch) + _local_collection_log.mark_discovered(_pending_catch.fish_id) _pending_catch = null _showcase_ready = false _showcase_outcome_completed = false _put_away_press_armed = false - showcase_changed.emit("", "", 0.0, 0, false) + showcase_changed.emit("", "", 0.0, false) _showcase_restore_generation += 1 var restore_generation: int = _showcase_restore_generation _active_player.end_catch_showcase( @@ -1076,29 +1070,6 @@ func _put_away_catch() -> void: ) -func _store_catch_progression(fish_catch: FishCatchType) -> void: - if ( - fish_catch == null - or _local_inventory == null - or _local_collection_log == null - or _local_experience == null - or _local_inventory.contains_catch_id(fish_catch.catch_id) - ): - return - var experience_award: int = ( - FishExperienceType.calculate_for_collection( - fish_catch, - _local_collection_log, - ) - ) - _local_inventory.add_catch(fish_catch) - _local_collection_log.mark_quality_discovered( - fish_catch.fish_id, - fish_catch.quality, - ) - _local_experience.award_experience(experience_award) - - func _finish_showcase_put_away( restore_generation: int, cooldown_message: String, @@ -1124,7 +1095,7 @@ func _cleanup_attempt( _showcase_ready = false _showcase_outcome_completed = false _put_away_press_armed = false - showcase_changed.emit("", "", 0.0, 0, false) + showcase_changed.emit("", "", 0.0, false) _catch_controller.reset() state = FishingState.RETURNING status_changed.emit("") @@ -1167,7 +1138,7 @@ func _finalize_attempt_cleanup(cooldown_message: String) -> void: _showcase_ready = false _showcase_outcome_completed = false _put_away_press_armed = false - showcase_changed.emit("", "", 0.0, 0, false) + showcase_changed.emit("", "", 0.0, false) _catch_controller.reset() _presentation.cleanup() _pending_cleanup_message = "" diff --git a/main/main.gd b/main/main.gd index 7a074a3..a08a53e 100644 --- a/main/main.gd +++ b/main/main.gd @@ -357,8 +357,6 @@ func _initialize_after_data_root() -> void: _player.fishing_upgrades, _player.cooler_capacity, _player.art_unlocks, - _player.experience, - _world_time, ) _save_manager.set_autosave_enabled(false) _asset_reservations.setup( @@ -413,7 +411,6 @@ func _initialize_after_data_root() -> void: _player.inventory, _player.collection_log, _player.cooler_capacity, - _player.experience, _save_manager, item_catalog, fish_catalog, @@ -454,7 +451,6 @@ func _initialize_after_data_root() -> void: _player, _player.inventory, _player.collection_log, - _player.experience, _player.bag, _player.hotbar, item_catalog, @@ -471,7 +467,6 @@ func _initialize_after_data_root() -> void: _player, _player.inventory, _player.collection_log, - _player.experience, _player.wallet, _player.fish_sale_service, pelican_buyer_profile, @@ -1159,11 +1154,6 @@ func _on_return_to_title_requested() -> void: if _quit_in_progress: return var pause_menu: PauseMenuType = _game_ui.get_pause_menu() - if not _save_manager.save_world_time_checkpoint(): - pause_menu.report_network_error( - "Could not save progression before returning to title." - ) - return pause_menu.close_for_title_transition() _network_session.disconnect_session("Returned to title.") _set_gameplay_active(false) @@ -1186,7 +1176,7 @@ func _on_title_join_game_requested(endpoint: String) -> void: func _on_pause_join_game_requested(endpoint: String) -> void: if _quit_in_progress or not _gameplay_started: return - if not _save_manager.save_world_time_checkpoint(): + if not _save_manager.save_if_dirty(): _game_ui.get_pause_menu().report_network_error( "Could not save progression before leaving this session." ) @@ -1419,7 +1409,7 @@ func _on_quit_requested() -> void: _quit_in_progress = true _settings_manager.save_if_dirty() if _gameplay_started: - _save_manager.save_world_time_checkpoint() + _save_manager.save_if_dirty() _network_session.disconnect_session("Application closing.") _title_music_requested = false _replace_title_music_transition() diff --git a/network/network_fish_showcase_protocol.gd b/network/network_fish_showcase_protocol.gd index d69e9dd..2028976 100644 --- a/network/network_fish_showcase_protocol.gd +++ b/network/network_fish_showcase_protocol.gd @@ -7,7 +7,6 @@ const MAX_SESSION_ID_LENGTH: int = 96 const MAX_FISH_ID_LENGTH: int = 96 const MAX_WEIGHT_LB: float = 1000.0 const MAX_DISPLAY_SCALE: float = 20.0 -const FishQualityType = preload("res://fish/fish_quality.gd") static func validate_state(data: Variant) -> bool: @@ -21,7 +20,6 @@ static func validate_state(data: Variant) -> bool: "fish_id", "weight_lb", "display_scale", - "quality", "revision", ]: if not value.has(key): @@ -39,8 +37,6 @@ static func validate_state(data: Variant) -> bool: or not is_finite(float(value["weight_lb"])) or typeof(value["display_scale"]) not in [TYPE_FLOAT, TYPE_INT] or not is_finite(float(value["display_scale"])) - or typeof(value["quality"]) != TYPE_INT - or not FishQualityType.is_valid(int(value["quality"])) or typeof(value["revision"]) != TYPE_INT or int(value["revision"]) < 0 ): @@ -57,5 +53,4 @@ static func validate_state(data: Variant) -> bool: str(value["fish_id"]).is_empty() and is_zero_approx(float(value["weight_lb"])) and is_zero_approx(float(value["display_scale"])) - and int(value["quality"]) == FishQualityType.Tier.BORING ) diff --git a/network/network_fish_showcase_service.gd b/network/network_fish_showcase_service.gd index 822d2e8..f4670c5 100644 --- a/network/network_fish_showcase_service.gd +++ b/network/network_fish_showcase_service.gd @@ -4,7 +4,6 @@ extends Node const FishPoolType = preload("res://fish/fish_pool.gd") const FishDataType = preload("res://fish/fish_data.gd") const FishCatchType = preload("res://fish/fish_catch.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") const PlayerHotbarType = preload("res://inventory/player_hotbar.gd") @@ -81,11 +80,6 @@ func _submit_local_state(fish_catch: FishCatchType, should_show: bool) -> void: "display_scale": ( fish_catch.display_scale if fish_catch != null else 0.0 ), - "quality": ( - fish_catch.quality - if fish_catch != null - else FishQualityType.Tier.BORING - ), "revision": _local_revision, } _local_visible = bool(data["visible"]) diff --git a/network/network_fishing_service.gd b/network/network_fishing_service.gd index be214e2..9329aea 100644 --- a/network/network_fishing_service.gd +++ b/network/network_fishing_service.gd @@ -7,10 +7,6 @@ const FishPoolType = preload("res://fish/fish_pool.gd") const FishSelectorType = preload("res://fish/fish_selector.gd") const FishingContextType = preload("res://fishing/fishing_context.gd") const CollectionLogType = preload("res://collection/collection_log.gd") -const FishExperienceType = preload("res://fish/fish_experience.gd") -const PlayerExperienceType = preload( - "res://progression/player_experience.gd" -) const RemotePresentationType = preload( "res://fishing/remote_fishing_presentation.gd" ) @@ -33,7 +29,6 @@ var _fishing_spot: FishingSpot var _local_inventory: FishInventory var _local_collection: CollectionLog var _local_capacity: PlayerCoolerCapacity -var _local_experience: PlayerExperienceType var _save_manager: PlayerSaveManager var _item_catalog: ItemCatalog var _fish_catalog: FishPoolType @@ -56,7 +51,6 @@ func setup( local_inventory: FishInventory, local_collection: CollectionLog, local_capacity: PlayerCoolerCapacity, - local_experience: PlayerExperienceType, save_manager: PlayerSaveManager, item_catalog: ItemCatalog, fish_catalog: FishPoolType, @@ -68,7 +62,6 @@ func setup( _local_inventory = local_inventory _local_collection = local_collection _local_capacity = local_capacity - _local_experience = local_experience _save_manager = save_manager _item_catalog = item_catalog _fish_catalog = fish_catalog @@ -767,18 +760,8 @@ func _apply_target_outcome(data: Dictionary) -> void: ): return if not already_owned: - var experience_award: int = ( - FishExperienceType.calculate_for_collection( - fish_catch, - _local_collection, - ) - ) _local_inventory.add_catch(fish_catch) - _local_collection.mark_quality_discovered( - fish_id, - fish_catch.quality, - ) - _local_experience.award_experience(experience_award) + _local_collection.mark_discovered(fish_id) if not _save_manager.save_if_dirty(): return _result_ledgers[result_id] = true diff --git a/network/network_mail_protocol.gd b/network/network_mail_protocol.gd index f6441c7..a3faeb2 100644 --- a/network/network_mail_protocol.gd +++ b/network/network_mail_protocol.gd @@ -144,7 +144,6 @@ static func attachment_signature_fields(value: Variant) -> Array: result.append(str(fish.get("fish_id", ""))) result.append(str(fish.get("weight_lb", ""))) result.append(str(fish.get("display_scale", ""))) - result.append(int(fish.get("quality", -1))) result.append(int(fish.get("sale_value", 0))) result.append(bool(fish.get("is_favorited", false))) 3: diff --git a/network/network_mail_service.gd b/network/network_mail_service.gd index f63c7ce..04893d1 100644 --- a/network/network_mail_service.gd +++ b/network/network_mail_service.gd @@ -7,7 +7,6 @@ signal operation_finished(success: bool, message: String) signal peers_changed const MAX_LEDGER: int = 256 -const FishQualityType = preload("res://fish/fish_quality.gd") var _session: NetworkSession var _reservations: PlayerAssetReservationService @@ -951,10 +950,7 @@ func _apply_award(attachment: Dictionary) -> bool: PlayerAssetReservationService.AttachmentType.FISH: var fish_catch := _decode_catch(attachment) _inventory.add_catch(fish_catch) - _collection_log.mark_quality_discovered( - fish_catch.fish_id, - fish_catch.quality, - ) + _collection_log.mark_discovered(fish_catch.fish_id) return _inventory.contains_catch_id(fish_catch.catch_id) PlayerAssetReservationService.AttachmentType.CONSUMABLE: return _bag.add_item( @@ -966,26 +962,10 @@ func _apply_award(attachment: Dictionary) -> bool: func _decode_catch(attachment: Dictionary) -> FishCatch: var data: Dictionary = attachment.get("catch", {}) - var fish: FishData = _fish_catalog.get_fish_by_id( + var fish := _fish_catalog.get_fish_by_id( StringName(str(data.get("fish_id", ""))) ) - var fish_catch: FishCatch = FishCatch.from_network_dict(data, fish) - if fish_catch == null or fish == null: - return null - if ( - fish_catch.weight_lb < fish.get_minimum_weight() - or fish_catch.weight_lb > fish.get_maximum_weight() - or not is_equal_approx( - fish_catch.display_scale, - fish.get_display_scale_for_weight(fish_catch.weight_lb), - ) - or fish_catch.sale_value != FishQualityType.apply_sale_value( - fish.get_sale_value_for_weight(fish_catch.weight_lb), - fish_catch.quality, - ) - ): - return null - return fish_catch + return FishCatch.from_network_dict(data, fish) func _capture_assets() -> Dictionary: @@ -995,7 +975,6 @@ func _capture_assets() -> Dictionary: "catches": _inventory.get_all_catches(), "next_sequence": _inventory.get_next_catch_sequence(), "discovered": _collection_log.get_discovered_ids(), - "quality_masks": _collection_log.get_discovered_quality_masks(), } @@ -1005,10 +984,7 @@ func _restore_assets(snapshot: Dictionary) -> void: _inventory.replace_all_catches( snapshot["catches"], int(snapshot["next_sequence"]) ) - _collection_log.replace_discovery_state( - snapshot["discovered"], - snapshot["quality_masks"], - ) + _collection_log.replace_discovered_ids(snapshot["discovered"]) func _emit_mailbox() -> void: diff --git a/network/network_protocol.gd b/network/network_protocol.gd index 94236b7..ee1c277 100644 --- a/network/network_protocol.gd +++ b/network/network_protocol.gd @@ -22,7 +22,6 @@ const SURFACE_DRAWING_CAPABILITY: String = "surface_drawing_v2" const ART_SHOP_CAPABILITY: String = "art_shop_v1" const WORLD_TIME_CAPABILITY: String = "world_time_v1" const WORLD_WEATHER_CAPABILITY: String = "world_weather_v1" -const FISH_QUALITY_CAPABILITY: String = "fish_quality_v1" enum RejectionCode { NONE, @@ -89,7 +88,6 @@ static func make_client_hello( "display_name": display_name, "client_nonce": client_nonce, "capability_flags": PackedStringArray([ - FISH_QUALITY_CAPABILITY, SURFACE_DRAWING_CAPABILITY, WORLD_TIME_CAPABILITY, WORLD_WEATHER_CAPABILITY, @@ -209,7 +207,6 @@ static func make_server_hello( "item_use_v1", "equipment_v1", "fish_showcase_v1", - FISH_QUALITY_CAPABILITY, SURFACE_DRAWING_CAPABILITY, WORLD_TIME_CAPABILITY, WORLD_WEATHER_CAPABILITY, diff --git a/network/network_sale_protocol.gd b/network/network_sale_protocol.gd index 3b80de6..ef91ccf 100644 --- a/network/network_sale_protocol.gd +++ b/network/network_sale_protocol.gd @@ -58,7 +58,7 @@ static func validate_request(data: Variant) -> String: var evidence: Dictionary = value for key: String in [ "catch_id", "fish_id", "weight_lb", "display_scale", - "quality", "sale_value", "is_favorited", + "sale_value", "is_favorited", ]: if not evidence.has(key): return "Sale could not be completed." diff --git a/network/network_sale_service.gd b/network/network_sale_service.gd index 11a499c..06eefb8 100644 --- a/network/network_sale_service.gd +++ b/network/network_sale_service.gd @@ -2,7 +2,6 @@ class_name NetworkSaleService extends Node const FishCatchType = preload("res://fish/fish_catch.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") const FishDataType = preload("res://fish/fish_data.gd") const FishPoolType = preload("res://fish/fish_pool.gd") const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd") @@ -253,10 +252,8 @@ func _build_authoritative_result( not str(decoded.catch_id).begins_with("%s:" % fish_id) or decoded.weight_lb < fish.get_minimum_weight() or decoded.weight_lb > fish.get_maximum_weight() - or decoded.sale_value != FishQualityType.apply_sale_value( - fish.get_sale_value_for_weight(decoded.weight_lb), - decoded.quality, - ) + or decoded.sale_value < fish.sell_value_min + or decoded.sale_value > fish.sell_value_max ): return _rejected_result( request_id, "Sale could not be completed." @@ -265,13 +262,7 @@ func _build_authoritative_result( return _rejected_result( request_id, "Favorite catches cannot be sold." ) - var ordinary_value: int = fish.get_sale_value_for_weight( - decoded.weight_lb - ) - var offer: int = buyer.get_quality_offer( - ordinary_value, - decoded.quality, - ) + var offer: int = buyer.get_offer(decoded.sale_value) if ( offer < 0 or base_value > 9223372036854775807 - decoded.sale_value diff --git a/network/network_session.gd b/network/network_session.gd index 8c948af..d7ecb6d 100644 --- a/network/network_session.gd +++ b/network/network_session.gd @@ -169,7 +169,6 @@ func start_private_host(port: int = DEFAULT_PORT) -> bool: _player_identity.fingerprint, _player_identity.public_pem, PackedStringArray([ - NetworkProtocol.FISH_QUALITY_CAPABILITY, NetworkProtocol.SURFACE_DRAWING_CAPABILITY, NetworkProtocol.WORLD_TIME_CAPABILITY, NetworkProtocol.WORLD_WEATHER_CAPABILITY, @@ -368,7 +367,6 @@ func supports_server_capability(capability: StringName) -> bool: "movement_v1", "fishing_v1", "sale_v1", "shop_v1", NetworkProtocol.ART_SHOP_CAPABILITY, "item_use_v1", "equipment_v1", "fish_showcase_v1", - NetworkProtocol.FISH_QUALITY_CAPABILITY, NetworkProtocol.SURFACE_DRAWING_CAPABILITY, NetworkProtocol.WORLD_TIME_CAPABILITY, NetworkProtocol.WORLD_WEATHER_CAPABILITY, @@ -917,15 +915,6 @@ func submit_client_hello(data: Dictionary) -> void: if not validation_error.is_empty(): _reject_peer(sender_id, NetworkProtocol.RejectionCode.MALFORMED_HANDSHAKE) return - var client_capabilities: PackedStringArray = _sanitized_capabilities( - data.get("capability_flags", []) - ) - if NetworkProtocol.FISH_QUALITY_CAPABILITY not in client_capabilities: - _reject_peer( - sender_id, - NetworkProtocol.RejectionCode.UNSUPPORTED_CLIENT, - ) - return var identity: Dictionary = _authenticated_identity_cache.get(sender_id, {}) if identity.is_empty(): _reject_peer(sender_id, NetworkProtocol.RejectionCode.INVALID_IDENTITY_PROOF) @@ -959,7 +948,7 @@ func submit_client_hello(data: Dictionary) -> void: NetworkProtocol.PROTOCOL_VERSION, identity["fingerprint"], identity["public_key"], - client_capabilities, + _sanitized_capabilities(data.get("capability_flags", [])), ): _reject_peer( sender_id, @@ -1082,10 +1071,6 @@ func receive_server_hello(data: Dictionary) -> void: for value: Variant in advertised_capabilities: if typeof(value) in [TYPE_STRING, TYPE_STRING_NAME]: _server_capabilities.append(str(value)) - if NetworkProtocol.FISH_QUALITY_CAPABILITY not in _server_capabilities: - _teardown_peer() - _fail("This server does not support fish quality data.") - return var local_peer_id: int = multiplayer.get_unique_id() _registry.clear() _registry.add_peer( @@ -1096,7 +1081,6 @@ func receive_server_hello(data: Dictionary) -> void: _player_identity.fingerprint, _player_identity.public_pem, PackedStringArray([ - NetworkProtocol.FISH_QUALITY_CAPABILITY, NetworkProtocol.SURFACE_DRAWING_CAPABILITY, NetworkProtocol.WORLD_TIME_CAPABILITY, NetworkProtocol.WORLD_WEATHER_CAPABILITY, diff --git a/network/network_world_time_service.gd b/network/network_world_time_service.gd index 91b4060..ad50255 100644 --- a/network/network_world_time_service.gd +++ b/network/network_world_time_service.gd @@ -39,16 +39,12 @@ func _on_session_state_changed(state: NetworkSession.State) -> void: _sequence = 0 _last_received_sequence = -1 _sync_elapsed = 0.0 - _world_time.set_persistence_tracking_enabled(true) - _world_time.begin_session( - _world_time.get_persistent_time_hours() - ) + _world_time.begin_session() return if state == NetworkSession.State.JOINED_CLIENT: _active_session_id = _session.get_session_id() _last_received_sequence = -1 _sync_elapsed = 0.0 - _world_time.set_persistence_tracking_enabled(false) if _session.supports_server_capability( NetworkProtocol.WORLD_TIME_CAPABILITY ): @@ -66,7 +62,6 @@ func _on_session_state_changed(state: NetworkSession.State) -> void: _sequence = 0 _last_received_sequence = -1 _sync_elapsed = 0.0 - _world_time.set_persistence_tracking_enabled(false) _world_time.end_session() diff --git a/player/player.gd b/player/player.gd index 2f0f134..7b8a947 100644 --- a/player/player.gd +++ b/player/player.gd @@ -21,9 +21,6 @@ const PlayerCoolerCapacityType = preload( const PlayerArtUnlocksType = preload( "res://progression/player_art_unlocks.gd" ) -const PlayerExperienceType = preload( - "res://progression/player_experience.gd" -) const FishingRodAttachmentScene = preload( "res://player/fishing_rod_attachment.tscn" ) @@ -112,7 +109,6 @@ class ShowcaseCameraSnapshot: @onready var item_effects: PlayerItemEffectsType = %ItemEffects @onready var cooler_capacity: PlayerCoolerCapacityType = %CoolerCapacity @onready var art_unlocks: PlayerArtUnlocksType = %ArtUnlocks -@onready var experience: PlayerExperienceType = %Experience @onready var _cast_origin: Marker3D = %CastOrigin @onready var _catch_display: Node3D = %CatchDisplay @onready var _catch_sprite: Sprite3D = %CatchSprite diff --git a/player/player.tscn b/player/player.tscn index 2f7c0fb..9606811 100644 --- a/player/player.tscn +++ b/player/player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=18 format=3] +[gd_scene load_steps=17 format=3] [ext_resource type="Script" path="res://player/player.gd" id="1_script"] [ext_resource type="Script" path="res://inventory/fish_inventory.gd" id="2_inventory"] @@ -14,7 +14,6 @@ [ext_resource type="Material" path="res://player/materials/player_blob_shadow.tres" id="12_blob_shadow"] [ext_resource type="PackedScene" path="res://art/exported/characters/base/netfishing_base_character.glb" id="13_character"] [ext_resource type="Script" path="res://progression/player_art_unlocks.gd" id="14_art_unlocks"] -[ext_resource type="Script" path="res://progression/player_experience.gd" id="15_experience"] [sub_resource type="CapsuleShape3D" id="PlayerShape"] radius = 0.45 @@ -105,10 +104,6 @@ script = ExtResource("10_capacity") unique_name_in_owner = true script = ExtResource("14_art_unlocks") -[node name="Experience" type="Node" parent="."] -unique_name_in_owner = true -script = ExtResource("15_experience") - [node name="CatchDisplayAnchor" type="Marker3D" parent="Visuals"] unique_name_in_owner = true position = Vector3(0, 1.45, -1.15) diff --git a/progression/player_experience.gd b/progression/player_experience.gd deleted file mode 100644 index ec5ba20..0000000 --- a/progression/player_experience.gd +++ /dev/null @@ -1,130 +0,0 @@ -class_name PlayerExperience -extends Node - -signal experience_changed(total_experience: int, level: int) -signal experience_awarded( - amount: int, - previous_total: int, - new_total: int, - previous_level: int, - new_level: int, -) - -const MAX_TOTAL_EXPERIENCE: int = 1000000000000 -const MAX_LEVEL: int = 100000 - -var _total_experience: int = 0 - - -func get_total_experience() -> int: - return _total_experience - - -func get_level() -> int: - return level_for_total_experience(_total_experience) - - -func get_experience_in_level() -> int: - return _total_experience - total_experience_for_level(get_level()) - - -func get_experience_for_next_level() -> int: - return experience_required_for_next_level(get_level()) - - -func get_level_progress() -> float: - return progress_for_total_experience(_total_experience) - - -func award_experience(amount: int) -> bool: - if amount <= 0 or _total_experience >= MAX_TOTAL_EXPERIENCE: - return false - var previous_total: int = _total_experience - var previous_level: int = level_for_total_experience(previous_total) - _total_experience = mini( - _total_experience + amount, - MAX_TOTAL_EXPERIENCE, - ) - var awarded_amount: int = _total_experience - previous_total - if awarded_amount <= 0: - return false - var new_level: int = level_for_total_experience(_total_experience) - experience_changed.emit(_total_experience, new_level) - experience_awarded.emit( - awarded_amount, - previous_total, - _total_experience, - previous_level, - new_level, - ) - return true - - -func restore_total_experience(total_experience: int) -> bool: - if total_experience < 0 or total_experience > MAX_TOTAL_EXPERIENCE: - return false - var changed: bool = _total_experience != total_experience - _total_experience = total_experience - if changed: - experience_changed.emit(_total_experience, get_level()) - return true - - -func reset_to_defaults() -> void: - restore_total_experience(0) - - -func to_save_data() -> Dictionary: - return {"total_experience": _total_experience} - - -static func experience_required_for_next_level(level: int) -> int: - var safe_level: int = maxi(level, 1) - var level_index: int = safe_level - 1 - return 100 + level_index * 25 + level_index * level_index * 5 - - -static func total_experience_for_level(level: int) -> int: - var completed_levels: int = clampi(level - 1, 0, MAX_LEVEL - 1) - if completed_levels == 0: - return 0 - var linear_sum: int = floori( - float(completed_levels) * float(completed_levels - 1) / 2.0 - ) - var square_sum: int = floori( - float(completed_levels - 1) - * float(completed_levels) - * float(2 * completed_levels - 1) - / 6.0 - ) - return completed_levels * 100 + linear_sum * 25 + square_sum * 5 - - -static func level_for_total_experience(total_experience: int) -> int: - var safe_total: int = clampi( - total_experience, - 0, - MAX_TOTAL_EXPERIENCE, - ) - var low: int = 1 - var high: int = MAX_LEVEL - while low < high: - var middle: int = low + floori(float(high - low + 1) / 2.0) - if total_experience_for_level(middle) <= safe_total: - low = middle - else: - high = middle - 1 - return low - - -static func progress_for_total_experience(total_experience: int) -> float: - var level: int = level_for_total_experience(total_experience) - var level_start: int = total_experience_for_level(level) - var required: int = experience_required_for_next_level(level) - if required <= 0: - return 0.0 - return clampf( - float(total_experience - level_start) / float(required), - 0.0, - 1.0, - ) diff --git a/progression/player_experience.gd.uid b/progression/player_experience.gd.uid deleted file mode 100644 index 9b49089..0000000 --- a/progression/player_experience.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://eml3k7a43e1d diff --git a/save/player_save_manager.gd b/save/player_save_manager.gd index 72f7676..e24cd33 100644 --- a/save/player_save_manager.gd +++ b/save/player_save_manager.gd @@ -2,7 +2,6 @@ class_name PlayerSaveManager extends Node const FishCatchType = preload("res://fish/fish_catch.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") const CollectionLogType = preload("res://collection/collection_log.gd") const PlayerWalletType = preload("res://economy/player_wallet.gd") @@ -21,12 +20,8 @@ const PlayerCoolerCapacityType = preload( const PlayerArtUnlocksType = preload( "res://progression/player_art_unlocks.gd" ) -const PlayerExperienceType = preload( - "res://progression/player_experience.gd" -) -const WorldTimeServiceType = preload("res://world/world_time_service.gd") -const SAVE_VERSION: int = 6 +const SAVE_VERSION: int = 4 const BASIC_ROD_ID: StringName = &"basic_fishing_rod" const MAX_SAFE_BALANCE: int = 1000000000000 @@ -35,7 +30,6 @@ class LoadSnapshot: var catches: Array[FishCatchType] = [] var discovered_ids: Array[StringName] = [] - var discovered_quality_masks: Dictionary[StringName, int] = {} var wallet_balance: int = 0 var next_catch_sequence: int = 1 var bag_items: Array[OwnedItemType] = [] @@ -46,8 +40,6 @@ class LoadSnapshot: var barrier_power_level: int = 0 var cooler_capacity_level: int = 0 var art_unlock_mask: int = 0 - var total_experience: int = 0 - var world_time_hours: float = WorldTimeServiceType.DEFAULT_START_HOUR @export_range(0.05, 5.0, 0.05) var autosave_delay: float = 0.5 @@ -62,8 +54,6 @@ var _item_catalog: ItemCatalogType var _fishing_upgrades: PlayerFishingUpgradesType var _cooler_capacity: PlayerCoolerCapacityType var _art_unlocks: PlayerArtUnlocksType -var _experience: PlayerExperienceType -var _world_time: WorldTimeServiceType var _autosave_timer: Timer var _is_configured: bool = false var _is_restoring: bool = false @@ -106,8 +96,6 @@ func setup( fishing_upgrades: PlayerFishingUpgradesType, cooler_capacity: PlayerCoolerCapacityType, art_unlocks: PlayerArtUnlocksType, - experience: PlayerExperienceType, - world_time: WorldTimeServiceType, ) -> void: _inventory = inventory _collection_log = collection_log @@ -119,8 +107,6 @@ func setup( _fishing_upgrades = fishing_upgrades _cooler_capacity = cooler_capacity _art_unlocks = art_unlocks - _experience = experience - _world_time = world_time _is_configured = ( _inventory != null and _collection_log != null @@ -132,18 +118,16 @@ func setup( and _fishing_upgrades != null and _cooler_capacity != null and _art_unlocks != null - and _experience != null - and _world_time != null ) if not _is_configured: push_error("PlayerSaveManager setup is missing required references.") return if not _inventory.catches_changed.is_connected(_mark_dirty): _inventory.catches_changed.connect(_mark_dirty) - if not _collection_log.collection_changed.is_connected( - _on_collection_changed + if not _collection_log.fish_discovered.is_connected( + _on_fish_discovered ): - _collection_log.collection_changed.connect(_on_collection_changed) + _collection_log.fish_discovered.connect(_on_fish_discovered) if not _wallet.balance_changed.is_connected(_on_balance_changed): _wallet.balance_changed.connect(_on_balance_changed) if not _bag.contents_changed.is_connected(_mark_dirty): @@ -168,10 +152,6 @@ func setup( ) if not _art_unlocks.unlocks_changed.is_connected(_on_art_unlocks_changed): _art_unlocks.unlocks_changed.connect(_on_art_unlocks_changed) - if not _experience.experience_changed.is_connected( - _on_experience_changed - ): - _experience.experience_changed.connect(_on_experience_changed) func load_player_data() -> bool: @@ -228,10 +208,7 @@ func load_player_data() -> bool: snapshot.next_catch_sequence ) var collection_restored: bool = ( - _collection_log.replace_discovery_state( - snapshot.discovered_ids, - snapshot.discovered_quality_masks, - ) + _collection_log.replace_discovered_ids(snapshot.discovered_ids) ) var wallet_restored: bool = _wallet.restore_balance( snapshot.wallet_balance @@ -252,12 +229,6 @@ func load_player_data() -> bool: var art_restored: bool = _art_unlocks.restore_mask( snapshot.art_unlock_mask ) - var experience_restored: bool = _experience.restore_total_experience( - snapshot.total_experience - ) - var world_time_restored: bool = ( - _world_time.restore_persistent_time_hours(snapshot.world_time_hours) - ) _is_restoring = false if ( not inventory_restored @@ -268,8 +239,6 @@ func load_player_data() -> bool: or not upgrades_restored or not cooler_restored or not art_restored - or not experience_restored - or not world_time_restored ): push_error("Validated player save could not be restored.") return false @@ -363,17 +332,6 @@ func save_if_dirty() -> bool: return not _is_dirty or save_now() -func save_world_time_checkpoint() -> bool: - if ( - not _is_configured - or _automatic_saving_blocked - or not _autosave_enabled - ): - return false - _is_dirty = true - return save_now() - - func is_dirty() -> bool: return _is_dirty @@ -443,19 +401,6 @@ func _build_save_dictionary() -> Dictionary: if fish_id.is_empty(): return {} discovered_strings.append(String(fish_id)) - var serialized_quality_masks: Dictionary = {} - var quality_masks: Dictionary[StringName, int] = ( - _collection_log.get_discovered_quality_masks() - ) - for fish_id: StringName in quality_masks: - var quality_mask: int = quality_masks[fish_id] - if ( - fish_id.is_empty() - or quality_mask <= 0 - or (quality_mask & ~FishQualityType.ALL_TIERS_MASK) != 0 - ): - return {} - serialized_quality_masks[String(fish_id)] = quality_mask var serialized_items: Array[Dictionary] = [] for owned: OwnedItemType in _bag.get_all_items(): if owned == null or not owned.is_valid(): @@ -485,7 +430,6 @@ func _build_save_dictionary() -> Dictionary: }, "collection": { "discovered_fish_ids": discovered_strings, - "discovered_quality_masks": serialized_quality_masks, }, "inventory": { "next_catch_sequence": _inventory.get_next_catch_sequence(), @@ -502,10 +446,6 @@ func _build_save_dictionary() -> Dictionary: "upgrades": _fishing_upgrades.to_save_data(), "cooler": _cooler_capacity.to_save_data(), "art": _art_unlocks.to_save_data(), - "experience": _experience.to_save_data(), - "world": { - "time_hours": _world_time.get_persistent_time_hours(), - }, } @@ -516,7 +456,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: or typeof(save_data.get("inventory")) != TYPE_DICTIONARY or typeof(save_data.get("bag")) != TYPE_DICTIONARY or typeof(save_data.get("hotbar")) != TYPE_DICTIONARY - or typeof(save_data.get("experience")) != TYPE_DICTIONARY ): return null var wallet_data: Dictionary = save_data["wallet"] @@ -524,10 +463,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: var inventory_data: Dictionary = save_data["inventory"] var bag_data: Dictionary = save_data["bag"] var hotbar_data: Dictionary = save_data["hotbar"] - var experience_data: Dictionary = save_data["experience"] - var world_data: Dictionary = {} - if typeof(save_data.get("world")) == TYPE_DICTIONARY: - world_data = save_data["world"] var upgrades_data: Dictionary = {} var cooler_data: Dictionary = {} var art_data: Dictionary = {} @@ -546,14 +481,11 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: if ( not wallet_data.has("balance") or typeof(collection_data.get("discovered_fish_ids")) != TYPE_ARRAY - or typeof(collection_data.get("discovered_quality_masks")) - != TYPE_DICTIONARY or typeof(inventory_data.get("catches")) != TYPE_ARRAY or not inventory_data.has("next_catch_sequence") or typeof(bag_data.get("items")) != TYPE_ARRAY or typeof(hotbar_data.get("slots")) != TYPE_ARRAY or not hotbar_data.has("selected_slot") - or not experience_data.has("total_experience") ): return null @@ -572,19 +504,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: var snapshot := LoadSnapshot.new() snapshot.wallet_balance = balance - snapshot.total_experience = _read_integer( - experience_data["total_experience"], - -1, - PlayerExperienceType.MAX_TOTAL_EXPERIENCE, - ) - if snapshot.total_experience < 0: - return null - if world_data.has("time_hours"): - snapshot.world_time_hours = _read_world_time_hours( - world_data["time_hours"] - ) - if snapshot.world_time_hours < 0.0: - return null var discovered_values: Array = collection_data["discovered_fish_ids"] var seen_discoveries: Dictionary[StringName, bool] = {} for value: Variant in discovered_values: @@ -595,26 +514,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: continue seen_discoveries[fish_id] = true snapshot.discovered_ids.append(fish_id) - var quality_mask_values: Dictionary = ( - collection_data["discovered_quality_masks"] - ) - for key: Variant in quality_mask_values: - if typeof(key) not in [TYPE_STRING, TYPE_STRING_NAME]: - return null - var fish_id: StringName = StringName(str(key)) - var mask: int = _read_integer( - quality_mask_values[key], - -1, - FishQualityType.ALL_TIERS_MASK, - ) - if ( - fish_id.is_empty() - or not seen_discoveries.has(fish_id) - or mask <= 0 - or (mask & ~FishQualityType.ALL_TIERS_MASK) != 0 - ): - return null - snapshot.discovered_quality_masks[fish_id] = mask var seen_ids: Dictionary[StringName, bool] = {} var seen_sequences: Dictionary[int, bool] = {} @@ -802,10 +701,6 @@ func _migrate_save( migrated = _migrate_version_2_to_3(migrated) 3: migrated = _migrate_version_3_to_4(migrated) - 4: - migrated = _migrate_version_4_to_5(migrated) - 5: - migrated = _migrate_version_5_to_6(migrated) _: return {} if migrated.is_empty(): @@ -853,68 +748,6 @@ func _migrate_version_3_to_4(data: Dictionary) -> Dictionary: return migrated -func _migrate_version_4_to_5(data: Dictionary) -> Dictionary: - var migrated: Dictionary = data.duplicate(true) - if ( - typeof(migrated.get("inventory")) != TYPE_DICTIONARY - or typeof(migrated.get("collection")) != TYPE_DICTIONARY - ): - return {} - var inventory_data: Dictionary = migrated["inventory"] - var collection_data: Dictionary = migrated["collection"] - if ( - typeof(inventory_data.get("catches")) != TYPE_ARRAY - or typeof(collection_data.get("discovered_fish_ids")) != TYPE_ARRAY - ): - return {} - var catches: Array = inventory_data["catches"] - var discovered_values: Array = collection_data["discovered_fish_ids"] - var discovered_lookup: Dictionary[String, bool] = {} - for value: Variant in discovered_values: - if typeof(value) in [TYPE_STRING, TYPE_STRING_NAME]: - var existing_id: String = str(value) - if not existing_id.is_empty(): - discovered_lookup[existing_id] = true - for index: int in catches.size(): - if typeof(catches[index]) != TYPE_DICTIONARY: - continue - var catch_data: Dictionary = catches[index] - catch_data["quality"] = FishQualityType.Tier.BORING - var catch_fish_id: String = str(catch_data.get("fish_id", "")) - if not catch_fish_id.is_empty() and not discovered_lookup.has( - catch_fish_id - ): - discovered_values.append(catch_fish_id) - discovered_lookup[catch_fish_id] = true - catches[index] = catch_data - inventory_data["catches"] = catches - migrated["inventory"] = inventory_data - var quality_masks: Dictionary = {} - for value: Variant in discovered_values: - if typeof(value) not in [TYPE_STRING, TYPE_STRING_NAME]: - continue - var fish_id: String = str(value) - if not fish_id.is_empty(): - quality_masks[fish_id] = FishQualityType.bit_for( - FishQualityType.Tier.BORING - ) - collection_data["discovered_quality_masks"] = quality_masks - collection_data["discovered_fish_ids"] = discovered_values - migrated["collection"] = collection_data - migrated["save_version"] = 5 - return migrated - - -func _migrate_version_5_to_6(data: Dictionary) -> Dictionary: - var migrated: Dictionary = data.duplicate(true) - migrated["save_version"] = 6 - migrated["experience"] = {"total_experience": 0} - migrated["world"] = { - "time_hours": WorldTimeServiceType.DEFAULT_START_HOUR, - } - return migrated - - func _mark_dirty() -> void: if ( _is_restoring @@ -927,7 +760,7 @@ func _mark_dirty() -> void: _autosave_timer.start(maxf(autosave_delay, 0.05)) -func _on_collection_changed() -> void: +func _on_fish_discovered(_fish_id: StringName) -> void: _mark_dirty() @@ -960,10 +793,6 @@ func _on_art_unlocks_changed(_unlock_mask: int) -> void: _mark_dirty() -func _on_experience_changed(_total_experience: int, _level: int) -> void: - _mark_dirty() - - func _on_autosave_timeout() -> void: if _is_dirty: save_now() @@ -1004,7 +833,6 @@ func _restore_defaults() -> void: _is_restoring = true var empty_catches: Array[FishCatchType] = [] var empty_discoveries: Array[StringName] = [] - var empty_quality_masks: Dictionary[StringName, int] = {} var default_items: Array[OwnedItemType] = [] var basic_rod := OwnedItemType.new() basic_rod.item_id = BASIC_ROD_ID @@ -1015,20 +843,13 @@ func _restore_defaults() -> void: default_slots.fill(StringName()) default_slots[0] = BASIC_ROD_ID _inventory.replace_all_catches(empty_catches, 1) - _collection_log.replace_discovery_state( - empty_discoveries, - empty_quality_masks, - ) + _collection_log.replace_discovered_ids(empty_discoveries) _wallet.restore_balance(0) _bag.replace_all_items(default_items) _hotbar.replace_state(default_slots, 0) _fishing_upgrades.reset_to_defaults() _cooler_capacity.reset_to_defaults() _art_unlocks.reset_to_defaults() - _experience.reset_to_defaults() - _world_time.restore_persistent_time_hours( - WorldTimeServiceType.DEFAULT_START_HOUR - ) _is_restoring = false _is_dirty = false @@ -1054,19 +875,6 @@ func _read_integer( return invalid_value -func _read_world_time_hours(value: Variant) -> float: - if typeof(value) not in [TYPE_FLOAT, TYPE_INT]: - return -1.0 - var time_hours: float = float(value) - if ( - not is_finite(time_hours) - or time_hours < 0.0 - or time_hours >= WorldTimeServiceType.HOURS_PER_DAY - ): - return -1.0 - return time_hours - - func _read_upgrade_level( value: Variant, maximum_level: int, diff --git a/tests/fish_hotbar_showcase_validation.gd b/tests/fish_hotbar_showcase_validation.gd index bd1df0f..81f648d 100644 --- a/tests/fish_hotbar_showcase_validation.gd +++ b/tests/fish_hotbar_showcase_validation.gd @@ -30,9 +30,6 @@ func _run() -> void: await process_frame var player := main.get("_player") as Player - var world_time := main.get_node("%WorldTimeService") as WorldTimeService - world_time.synchronize_time(19.75) - assert(player.experience.award_experience(125)) var fish_catalog := main.get("fish_catalog") as FishPool var service := main.get_node( "%NetworkFishShowcaseService" @@ -48,17 +45,11 @@ func _run() -> void: fish_catch.display_scale = fish.get_display_scale_for_weight( fish_catch.weight_lb ) - fish_catch.quality = FishQuality.Tier.EXCEPTIONAL - fish_catch.sale_value = FishQuality.apply_sale_value( - fish.get_sale_value_for_weight(fish_catch.weight_lb), - fish_catch.quality, + fish_catch.sale_value = fish.get_sale_value_for_weight( + fish_catch.weight_lb ) fish_catch.ensure_identity() player.inventory.add_catch(fish_catch) - player.collection_log.mark_quality_discovered( - fish_catch.fish_id, - fish_catch.quality, - ) var game_ui := main.get_node("%GameUI") as GameUI var hotbar_ui := game_ui.get_node("%Hotbar") as HotbarUI assert(hotbar_ui != null) @@ -103,42 +94,12 @@ func _run() -> void: var hotbar_data: Dictionary = (parsed as Dictionary)["hotbar"] assert(typeof(hotbar_data.get("fish_slots")) == TYPE_ARRAY) assert(str((hotbar_data["fish_slots"] as Array)[1]) == fish_catch.catch_id) - assert(int((parsed as Dictionary)["save_version"]) == 6) - assert( - int((parsed as Dictionary)["experience"]["total_experience"]) - == 125 - ) - assert( - is_equal_approx( - float((parsed as Dictionary)["world"]["time_hours"]), - 19.75, - ) - ) - var saved_catches: Array = (parsed as Dictionary)["inventory"]["catches"] - assert(int((saved_catches[0] as Dictionary)["quality"]) == fish_catch.quality) - var saved_masks: Dictionary = ( - (parsed as Dictionary)["collection"]["discovered_quality_masks"] - ) - assert( - int(saved_masks[String(fish.id)]) - == FishQuality.bit_for(FishQuality.Tier.EXCEPTIONAL) - ) + assert(int((parsed as Dictionary)["save_version"]) == 4) assert(player.hotbar.clear_slot(1)) - assert(player.experience.restore_total_experience(0)) - world_time.synchronize_time(8.0) assert(save_manager.load_player_data()) - assert(player.experience.get_total_experience() == 125) - assert(is_equal_approx(world_time.get_time_hours(), 19.75)) - assert(player.experience.get_level() == 2) assert(player.hotbar.get_fish_catch_id(1) == fish_catch.catch_id) assert(player.hotbar.get_selected_slot() == 1) - assert( - player.collection_log.has_discovered_quality( - fish.id, - FishQuality.Tier.EXCEPTIONAL, - ) - ) assert(not service.is_local_showcase_visible()) assert(service.toggle_selected_fish()) assert(service.is_local_showcase_visible()) @@ -157,7 +118,6 @@ func _run() -> void: "display_scale": fish.get_display_scale_for_weight( fish.get_minimum_weight() ), - "quality": FishQuality.Tier.BORING, "revision": 1, } assert(NetworkFishShowcaseProtocol.validate_state(valid_state)) @@ -166,10 +126,6 @@ func _run() -> void: assert(not NetworkFishShowcaseProtocol.validate_state(invalid_state)) assert(NetworkProtocol.PROTOCOL_VERSION == 3) assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10) - assert( - NetworkProtocol.FISH_QUALITY_CAPABILITY - == "fish_quality_v1" - ) assert( NetworkFishShowcaseProtocol.CAPABILITY == &"fish_showcase_v1" diff --git a/tests/fish_quality_validation.gd b/tests/fish_quality_validation.gd deleted file mode 100644 index e4f3288..0000000 --- a/tests/fish_quality_validation.gd +++ /dev/null @@ -1,262 +0,0 @@ -extends SceneTree - -const FishCatchType = preload("res://fish/fish_catch.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") -const FishSelectorType = preload("res://fish/fish_selector.gd") -const CollectionLogType = preload("res://collection/collection_log.gd") -const NetworkSaleServiceType = preload( - "res://network/network_sale_service.gd" -) -const Catalog: FishPool = preload("res://fish/pools/fish_catalog.tres") -const PelicanBuyer: FishBuyerProfile = preload( - "res://economy/buyers/pelicans.tres" -) - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - _validate_tiers_and_distribution() - _validate_catch_round_trip_and_sale() - _validate_mail_round_trip() - _validate_collection_mastery() - _validate_version_four_migration() - assert(NetworkProtocol.PROTOCOL_VERSION == 3) - assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10) - assert(NetworkProtocol.FISH_QUALITY_CAPABILITY == "fish_quality_v1") - print("Fish quality validation: PASS") - quit() - - -func _validate_tiers_and_distribution() -> void: - assert(FishQualityType.TIER_COUNT == 5) - assert(FishQualityType.display_name(0) == "boring") - assert(FishQualityType.display_name(4) == "shiny") - assert(UIPalette.get_quality_color(0) == UIPalette.QUALITY_BORING) - assert(UIPalette.get_quality_color(4) == UIPalette.QUALITY_SHINY) - assert( - UIPalette.get_quality_color(0) - != UIPalette.get_quality_color(1) - ) - assert(FishQualityType.apply_sale_value(3, 0) == 3) - assert(FishQualityType.apply_sale_value(3, 1) == 4) - assert(FishQualityType.apply_sale_value(3, 2) == 5) - assert(FishQualityType.apply_sale_value(3, 3) == 6) - assert(FishQualityType.apply_sale_value(3, 4) == 7) - var previous_offer: int = -1 - for quality: int in FishQualityType.TIER_COUNT: - var offer: int = PelicanBuyer.get_quality_offer(3, quality) - assert(offer > previous_offer) - previous_offer = offer - assert( - FishQualityType.qualified_name_with_article("bluegill", 0) - == "a boring bluegill" - ) - assert( - FishQualityType.qualified_name_with_article("bluegill", 1) - == "an average bluegill" - ) - var rng := RandomNumberGenerator.new() - rng.seed = 727272 - var counts: Array[int] = [0, 0, 0, 0, 0] - const SAMPLE_COUNT: int = 50000 - for _sample: int in SAMPLE_COUNT: - counts[FishQualityType.roll(rng)] += 1 - for quality: int in FishQualityType.TIER_COUNT: - var observed: float = float(counts[quality]) / float(SAMPLE_COUNT) - var expected: float = ( - FishQualityType.BASE_ROLL_WEIGHTS[quality] / 100.0 - ) - assert(absf(observed - expected) < 0.015) - - -func _validate_catch_round_trip_and_sale() -> void: - var fish: FishData = Catalog.get_fish_by_id(&"bluegill") - assert(fish != null) - var selector := FishSelectorType.new() - selector.use_deterministic_test_seed = true - selector.deterministic_test_seed = 9911 - selector.quality_weight_multipliers = [0.0, 0.0, 0.0, 0.0, 1.0] - selector.begin_roll() - var fish_catch: FishCatch = selector.create_catch(fish) - assert(fish_catch != null) - assert(fish_catch.quality == FishQualityType.Tier.SHINY) - assert( - fish_catch.sale_value - == FishQualityType.apply_sale_value( - fish.get_sale_value_for_weight(fish_catch.weight_lb), - FishQualityType.Tier.SHINY, - ) - ) - var save_data: Dictionary = fish_catch.to_save_dict() - fish_catch.catch_sequence = 1 - save_data = fish_catch.to_save_dict() - var restored: FishCatch = FishCatchType.from_save_dict(save_data, fish) - assert(restored != null and restored.quality == fish_catch.quality) - var network_data: Dictionary = fish_catch.to_network_dict() - var replicated: FishCatch = FishCatchType.from_network_dict( - network_data, - fish, - ) - assert(replicated != null and replicated.quality == fish_catch.quality) - var missing_quality: Dictionary = network_data.duplicate(true) - missing_quality.erase("quality") - assert(FishCatchType.from_network_dict(missing_quality, fish) == null) - - var session := NetworkSession.new() - var sale_service := NetworkSaleServiceType.new() - root.add_child(session) - root.add_child(sale_service) - sale_service.set("_session", session) - sale_service.set("_fish_catalog", Catalog) - var accepted: Dictionary = sale_service.call( - "_build_authoritative_result", - 1, - "quality_sale", - [network_data], - PelicanBuyer, - ) - assert(bool(accepted.get("accepted", false))) - assert( - int(accepted["payout"]) - == PelicanBuyer.get_quality_offer( - fish.get_sale_value_for_weight(fish_catch.weight_lb), - fish_catch.quality, - ) - ) - var inventory := FishInventory.new() - var wallet := PlayerWallet.new() - var local_sale := FishSaleService.new() - root.add_child(inventory) - root.add_child(wallet) - root.add_child(local_sale) - local_sale.setup(inventory, wallet) - inventory.add_catch(fish_catch) - var local_preview: FishSaleResult = local_sale.preview_batch( - [fish_catch.catch_id], - PelicanBuyer, - ) - assert(local_preview.is_success()) - assert(local_preview.payout == int(accepted["payout"])) - var forged: Dictionary = network_data.duplicate(true) - forged["quality"] = FishQualityType.Tier.BORING - var rejected: Dictionary = sale_service.call( - "_build_authoritative_result", - 1, - "quality_sale_forged", - [forged], - PelicanBuyer, - ) - assert(not bool(rejected.get("accepted", false))) - local_sale.queue_free() - wallet.queue_free() - inventory.queue_free() - sale_service.queue_free() - session.queue_free() - - -func _validate_mail_round_trip() -> void: - var fish: FishData = Catalog.get_fish_by_id(&"bluegill") - var fish_catch := FishCatchType.new() - fish_catch.fish = fish - fish_catch.fish_id = fish.id - fish_catch.weight_lb = fish.get_minimum_weight() - fish_catch.display_scale = fish.get_display_scale_for_weight( - fish_catch.weight_lb - ) - fish_catch.quality = FishQualityType.Tier.IMPRESSIVE - fish_catch.sale_value = FishQualityType.apply_sale_value( - fish.get_sale_value_for_weight(fish_catch.weight_lb), - fish_catch.quality, - ) - fish_catch.ensure_identity() - var attachment: Dictionary = { - "type": PlayerAssetReservationService.AttachmentType.FISH, - "catch_id": String(fish_catch.catch_id), - "catch": fish_catch.to_network_dict(), - } - var service := NetworkMailService.new() - root.add_child(service) - service.set("_fish_catalog", Catalog) - var restored: FishCatch = service.call("_decode_catch", attachment) - assert(restored != null) - assert(restored.quality == FishQualityType.Tier.IMPRESSIVE) - var signature: Array = NetworkMailProtocol.attachment_signature_fields( - attachment - ) - assert(int(signature[5]) == FishQualityType.Tier.IMPRESSIVE) - var altered: Dictionary = attachment.duplicate(true) - (altered["catch"] as Dictionary)["quality"] = ( - FishQualityType.Tier.BORING - ) - assert( - NetworkMailProtocol.attachment_signature_fields(altered) - != signature - ) - service.queue_free() - - -func _validate_collection_mastery() -> void: - var collection := CollectionLogType.new() - root.add_child(collection) - for quality: int in FishQualityType.TIER_COUNT: - collection.mark_quality_discovered(&"bluegill", quality) - assert(collection.has_discovered_quality(&"bluegill", quality)) - assert(collection.has_discovered(&"bluegill")) - assert(collection.has_mastered(&"bluegill")) - assert( - collection.get_quality_mask(&"bluegill") - == FishQualityType.ALL_TIERS_MASK - ) - collection.queue_free() - - -func _validate_version_four_migration() -> void: - var manager := PlayerSaveManager.new() - root.add_child(manager) - var version_four: Dictionary = { - "save_version": 4, - "wallet": {"balance": 12}, - "collection": {"discovered_fish_ids": ["bluegill"]}, - "inventory": { - "next_catch_sequence": 2, - "catches": [{ - "catch_id": "carp:legacy", - "catch_sequence": 1, - "fish_id": "carp", - "weight_lb": 2.0, - "display_scale": 1.0, - "sale_value": 4, - "is_favorited": false, - }], - }, - "bag": {"items": []}, - "hotbar": {"selected_slot": 0, "slots": []}, - "upgrades": {"reel_speed_level": 0, "barrier_power_level": 0}, - "cooler": {"capacity_level": 0}, - } - var migrated: Dictionary = manager.call( - "_migrate_save", - version_four, - 4, - ) - assert(int(migrated.get("save_version", -1)) == 6) - assert(int((migrated["experience"] as Dictionary)["total_experience"]) == 0) - assert( - is_equal_approx( - float((migrated["world"] as Dictionary)["time_hours"]), - 8.0, - ) - ) - var catches: Array = migrated["inventory"]["catches"] - assert(int((catches[0] as Dictionary)["quality"]) == 0) - var collection: Dictionary = migrated["collection"] - var ids: Array = collection["discovered_fish_ids"] - assert("bluegill" in ids and "carp" in ids) - var masks: Dictionary = collection["discovered_quality_masks"] - var boring_bit: int = FishQualityType.bit_for(FishQualityType.Tier.BORING) - assert(int(masks["bluegill"]) == boring_bit) - assert(int(masks["carp"]) == boring_bit) - manager.queue_free() diff --git a/tests/fish_quality_validation.gd.uid b/tests/fish_quality_validation.gd.uid deleted file mode 100644 index e129478..0000000 --- a/tests/fish_quality_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bffg3t4du01cu diff --git a/tests/fishing_surface_validation.gd b/tests/fishing_surface_validation.gd index c178aac..3c0ad87 100644 --- a/tests/fishing_surface_validation.gd +++ b/tests/fishing_surface_validation.gd @@ -239,29 +239,18 @@ func _validate_bite_wait_distribution() -> void: var fishing_spot := FishingSpotScene.instantiate() as FishingSpotType root.add_child(fishing_spot) await process_frame - var bite_rng := fishing_spot.get("_bite_rng") as RandomNumberGenerator - bite_rng.seed = 84219 var quick_count: int = 0 var typical_or_long_count: int = 0 - var very_long_count: int = 0 - var total_wait_seconds: float = 0.0 for _sample_index: int in 10000: var wait_seconds: float = fishing_spot.roll_bite_wait_time() assert(wait_seconds >= 10.0) assert(wait_seconds <= 240.0) - total_wait_seconds += wait_seconds if wait_seconds < 30.0: quick_count += 1 else: typical_or_long_count += 1 - if wait_seconds >= 180.0: - very_long_count += 1 - var average_wait_seconds: float = total_wait_seconds / 10000.0 - assert(quick_count >= 2300 and quick_count <= 2700) + assert(quick_count > 0) assert(typical_or_long_count > quick_count) - assert(very_long_count >= 100 and very_long_count <= 300) - assert(average_wait_seconds >= 57.0) - assert(average_wait_seconds <= 61.0) fishing_spot.queue_free() await process_frame diff --git a/tests/logbook_validation.gd b/tests/logbook_validation.gd index 4c25e35..c584f0e 100644 --- a/tests/logbook_validation.gd +++ b/tests/logbook_validation.gd @@ -274,8 +274,6 @@ func _validate_handwritten_logbook_font(page: LogbookPage) -> void: var detail_body := page.get("_detail_body") as VBoxContainer for node: Node in detail_body.find_children("*", "Label", true, false): var label := node as Label - if label.text.begins_with("quality collection"): - continue if label.text in [ "fish facts", "catalog number", @@ -309,9 +307,6 @@ func _validate_detail_field_fonts(page: LogbookPage) -> void: ] for node: Node in detail_body.find_children("*", "Label", true, false): var label := node as Label - if label.text.begins_with("quality collection"): - assert(label.get_theme_font("font") == UtilityPageStyle.TuffyFont) - continue if label.text not in field_names: continue assert(label.get_theme_font("font") == UtilityPageStyle.TuffyFont) diff --git a/tests/player_experience_ui_validation.gd b/tests/player_experience_ui_validation.gd deleted file mode 100644 index 93c23eb..0000000 --- a/tests/player_experience_ui_validation.gd +++ /dev/null @@ -1,47 +0,0 @@ -extends SceneTree - -const GameUIScene: PackedScene = preload("res://ui/game_ui.tscn") - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - var game_ui := GameUIScene.instantiate() as GameUI - root.add_child(game_ui) - await process_frame - game_ui.set("_gameplay_ui_enabled", true) - game_ui.call( - "_on_experience_awarded", - 50, - 0, - 50, - 1, - 1, - ) - var panel := game_ui.get_node("%ExperienceProgressPanel") as PanelContainer - var bubble := game_ui.get_node("%ExperienceBubble") as PanelContainer - var bubble_label := game_ui.get_node("%ExperienceBubbleLabel") as Label - var award_label := game_ui.get_node("%ExperienceAwardLabel") as Label - assert(panel != null and bubble != null) - assert(not panel.visible and not bubble.visible) - game_ui.call("_on_showcase_changed", "bluegill", "common", 1.0, 0, true) - await process_frame - assert(not panel.visible and not bubble.visible) - game_ui.call("_on_showcase_changed", "", "", 0.0, 0, false) - await process_frame - await process_frame - assert(panel.visible and bubble.visible) - assert(award_label.text == "+50 xp") - assert(bubble_label.text == "+50 xp!") - await create_timer(1.6).timeout - var progress := game_ui.get_node("%ExperienceProgress") as ProgressBar - var level_label := game_ui.get_node("%ExperienceLevelLabel") as Label - assert(is_equal_approx(progress.value, 50.0)) - assert(level_label.text == "level 1") - await create_timer(1.2).timeout - assert(not panel.visible and not bubble.visible) - game_ui.queue_free() - print("Player experience UI validation: PASS") - quit() diff --git a/tests/player_experience_ui_validation.gd.uid b/tests/player_experience_ui_validation.gd.uid deleted file mode 100644 index b2f976b..0000000 --- a/tests/player_experience_ui_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c03tvq1ydhdqj diff --git a/tests/player_experience_validation.gd b/tests/player_experience_validation.gd deleted file mode 100644 index e7b8c18..0000000 --- a/tests/player_experience_validation.gd +++ /dev/null @@ -1,178 +0,0 @@ -extends SceneTree - -const FishCatchType = preload("res://fish/fish_catch.gd") -const FishExperienceType = preload("res://fish/fish_experience.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") -const PlayerExperienceType = preload( - "res://progression/player_experience.gd" -) -const Catalog: FishPool = preload("res://fish/pools/fish_catalog.tres") - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - _validate_level_curve() - _validate_catch_awards() - _validate_player_experience_state() - _validate_save_migration() - print("Player experience validation: PASS") - quit() - - -func _validate_level_curve() -> void: - assert(PlayerExperienceType.experience_required_for_next_level(1) == 100) - assert(PlayerExperienceType.experience_required_for_next_level(2) == 130) - assert(PlayerExperienceType.experience_required_for_next_level(3) == 170) - assert(PlayerExperienceType.total_experience_for_level(1) == 0) - assert(PlayerExperienceType.total_experience_for_level(2) == 100) - assert(PlayerExperienceType.total_experience_for_level(3) == 230) - assert(PlayerExperienceType.total_experience_for_level(4) == 400) - assert(PlayerExperienceType.level_for_total_experience(99) == 1) - assert(PlayerExperienceType.level_for_total_experience(100) == 2) - assert(PlayerExperienceType.level_for_total_experience(399) == 3) - assert(PlayerExperienceType.level_for_total_experience(400) == 4) - assert( - is_equal_approx( - PlayerExperienceType.progress_for_total_experience(50), - 0.5, - ) - ) - - -func _validate_catch_awards() -> void: - var minimum_boring: FishCatch = _make_catch( - FishQualityType.Tier.BORING, - false, - ) - assert( - FishExperienceType.calculate_catch_experience( - minimum_boring, - false, - 0, - ) == 50 - ) - var maximum_shiny: FishCatch = _make_catch( - FishQualityType.Tier.SHINY, - true, - ) - var boring_mask: int = FishQualityType.bit_for( - FishQualityType.Tier.BORING - ) - assert( - FishExperienceType.calculate_catch_experience( - maximum_shiny, - true, - boring_mask, - ) == 46 - ) - var almost_mastered: int = ( - FishQualityType.ALL_TIERS_MASK - & ~FishQualityType.bit_for(FishQualityType.Tier.SHINY) - ) - assert( - FishExperienceType.calculate_catch_experience( - maximum_shiny, - true, - almost_mastered, - ) == 146 - ) - assert( - FishExperienceType.calculate_catch_experience( - maximum_shiny, - true, - FishQualityType.ALL_TIERS_MASK, - ) == 31 - ) - - -func _validate_player_experience_state() -> void: - var experience := PlayerExperienceType.new() - root.add_child(experience) - var awards: Array[Array] = [] - experience.experience_awarded.connect( - func( - amount: int, - previous_total: int, - new_total: int, - previous_level: int, - new_level: int, - ) -> void: - awards.append([ - amount, - previous_total, - new_total, - previous_level, - new_level, - ]) - ) - assert(experience.get_level() == 1) - assert(experience.award_experience(125)) - assert(experience.get_total_experience() == 125) - assert(experience.get_level() == 2) - assert(experience.get_experience_in_level() == 25) - assert(awards.size() == 1) - assert(awards[0] == [125, 0, 125, 1, 2]) - assert(experience.restore_total_experience(400)) - assert(experience.get_level() == 4) - assert(not experience.restore_total_experience(-1)) - experience.queue_free() - - -func _validate_save_migration() -> void: - var manager := PlayerSaveManager.new() - root.add_child(manager) - var version_five: Dictionary = { - "save_version": 5, - "wallet": {"balance": 0}, - "collection": { - "discovered_fish_ids": [], - "discovered_quality_masks": {}, - }, - "inventory": {"next_catch_sequence": 1, "catches": []}, - "bag": {"items": []}, - "hotbar": {"selected_slot": 0, "slots": []}, - "upgrades": {"reel_speed_level": 0, "barrier_power_level": 0}, - "cooler": {"capacity_level": 0}, - "art": {"unlock_mask": 0}, - } - var migrated: Dictionary = manager.call( - "_migrate_save", - version_five, - 5, - ) - assert(int(migrated.get("save_version", -1)) == 6) - var experience_data: Dictionary = migrated.get("experience", {}) - assert(int(experience_data.get("total_experience", -1)) == 0) - var world_data: Dictionary = migrated.get("world", {}) - assert(is_equal_approx(float(world_data.get("time_hours", -1.0)), 8.0)) - manager.queue_free() - - -func _make_catch(quality: int, maximum_weight: bool) -> FishCatch: - var fish: FishData = Catalog.get_fish_by_id(&"bluegill") - assert(fish != null) - var fish_catch := FishCatchType.new() - fish_catch.fish = fish - fish_catch.fish_id = fish.id - fish_catch.catch_id = StringName("bluegill:xp-%d-%s" % [ - quality, - "max" if maximum_weight else "min", - ]) - fish_catch.catch_sequence = 1 - fish_catch.weight_lb = ( - fish.get_maximum_weight() - if maximum_weight - else fish.get_minimum_weight() - ) - fish_catch.display_scale = fish.get_display_scale_for_weight( - fish_catch.weight_lb - ) - fish_catch.quality = quality - fish_catch.sale_value = FishQualityType.apply_sale_value( - fish.get_sale_value_for_weight(fish_catch.weight_lb), - quality, - ) - return fish_catch diff --git a/tests/player_experience_validation.gd.uid b/tests/player_experience_validation.gd.uid deleted file mode 100644 index 1afe50c..0000000 --- a/tests/player_experience_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://djkr2iaa5taks diff --git a/tests/world_time_multiplayer_validation.gd b/tests/world_time_multiplayer_validation.gd index 6aa89aa..66f7dad 100644 --- a/tests/world_time_multiplayer_validation.gd +++ b/tests/world_time_multiplayer_validation.gd @@ -32,9 +32,6 @@ func _run_host() -> void: ) assert(session.start_private_host(TEST_PORT)) world_time.synchronize_time(INITIAL_HOST_TIME) - assert(is_equal_approx( - world_time.get_persistent_time_hours(), INITIAL_HOST_TIME - )) world_weather.apply_authoritative_snapshot( WorldWeatherService.Weather.RAINY, 300.0 ) @@ -59,9 +56,6 @@ func _run_host() -> void: await create_timer(1.0).timeout world_time.synchronize_time(UPDATED_HOST_TIME) - assert(is_equal_approx( - world_time.get_persistent_time_hours(), UPDATED_HOST_TIME - )) world_weather.apply_authoritative_snapshot( WorldWeatherService.Weather.FOGGY, 300.0 ) @@ -104,7 +98,6 @@ func _run_client() -> void: assert(session.supports_server_capability( NetworkProtocol.WORLD_WEATHER_CAPABILITY )) - assert(world_time.restore_persistent_time_hours(15.25)) var initial_deadline: int = Time.get_ticks_msec() + 8000 while ( @@ -152,7 +145,6 @@ func _run_client() -> void: world_time.get_time_hours(), UPDATED_HOST_TIME ) <= TIME_TOLERANCE_HOURS) assert(world_time.get_phase() == WorldTimeService.Phase.NIGHT) - assert(is_equal_approx(world_time.get_persistent_time_hours(), 15.25)) assert(clock_label.text == world_time.get_clock_text()) var fog_deadline: int = Time.get_ticks_msec() + 8000 while ( diff --git a/tests/world_time_validation.gd b/tests/world_time_validation.gd index 1908951..666831e 100644 --- a/tests/world_time_validation.gd +++ b/tests/world_time_validation.gd @@ -25,7 +25,6 @@ func _initialize() -> void: func _run() -> void: _validate_clock_boundaries_and_duration() - _validate_persistent_host_clock() _validate_fishing_availability() _validate_fishing_spot_context() _validate_network_snapshot_bounds() @@ -79,24 +78,6 @@ func _validate_clock_boundaries_and_duration() -> void: clock.queue_free() -func _validate_persistent_host_clock() -> void: - var clock := WorldTimeServiceType.new() - root.add_child(clock) - assert(clock.restore_persistent_time_hours(18.75)) - clock.set_persistence_tracking_enabled(true) - clock.begin_session(clock.get_persistent_time_hours()) - assert(is_equal_approx(clock.get_time_hours(), 18.75)) - clock.synchronize_time(19.25) - assert(is_equal_approx(clock.get_persistent_time_hours(), 19.25)) - clock.set_persistence_tracking_enabled(false) - clock.synchronize_time(6.5) - assert(is_equal_approx(clock.get_time_hours(), 6.5)) - assert(is_equal_approx(clock.get_persistent_time_hours(), 19.25)) - assert(not clock.restore_persistent_time_hours(-1.0)) - assert(not clock.restore_persistent_time_hours(24.0)) - clock.queue_free() - - func _validate_fishing_availability() -> void: var day_context := FishingContextType.new() day_context.location_tags = [&"starter_pond"] diff --git a/ui/components/bubble_hotbar/bubble_hotbar_slot.gd b/ui/components/bubble_hotbar/bubble_hotbar_slot.gd index 11a7db3..6b9e9c2 100644 --- a/ui/components/bubble_hotbar/bubble_hotbar_slot.gd +++ b/ui/components/bubble_hotbar/bubble_hotbar_slot.gd @@ -12,7 +12,6 @@ const PlayerBagType = preload("res://inventory/player_bag.gd") const PlayerHotbarType = preload("res://inventory/player_hotbar.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") const FishCatchType = preload("res://fish/fish_catch.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") const SELECTED_SCALE: float = 1.12 const HOVER_SCALE: float = 1.025 @@ -180,10 +179,7 @@ func refresh() -> void: _quantity_label.visible = not quantity_text.is_empty() tooltip_text = ( "%s · %.1f lb" % [ - FishQualityType.qualified_name( - fish_catch.fish.display_name, - fish_catch.quality, - ), + fish_catch.fish.display_name, fish_catch.weight_lb, ] if fish_catch != null diff --git a/ui/components/bubble_menu/cooler_fish_sprite.gd b/ui/components/bubble_menu/cooler_fish_sprite.gd index d491969..0e7384f 100644 --- a/ui/components/bubble_menu/cooler_fish_sprite.gd +++ b/ui/components/bubble_menu/cooler_fish_sprite.gd @@ -15,7 +15,7 @@ var _depth_scale: float = 1.0 var _batch_selected: bool = false var _focused_catch: bool = false var _hovered: bool = false -var _quality_color := Color.WHITE +var _rarity_color := Color.WHITE func _ready() -> void: @@ -40,7 +40,7 @@ func configure( texture: Texture2D, phase: float, depth_scale: float, - quality_color: Color, + rarity_color: Color, ) -> void: catch_id = identity _display_name = display_name @@ -48,7 +48,7 @@ func configure( _fish_shadow.texture = texture _motion_phase = phase _depth_scale = depth_scale - _quality_color = quality_color + _rarity_color = rarity_color _refresh_style() tooltip_text = "%s · drag to a hotbar slot" % _display_name @@ -136,16 +136,14 @@ func _update_visual_pivot() -> void: func _refresh_style() -> void: var idle := StyleBoxEmpty.new() - var normal := _make_quality_style(_quality_color, 2, 0.25) - var hover := _make_quality_style( - _quality_color.lightened(0.08), 4, 0.34 - ) + var normal := _make_rarity_style(_rarity_color, 2, 0.25) + var hover := _make_rarity_style(_rarity_color.lightened(0.08), 4, 0.34) var selected_fill: Color = ( - _quality_color.lightened(0.12) + _rarity_color.lightened(0.12) if _batch_selected - else _quality_color + else _rarity_color ) - var selected := _make_quality_style( + var selected := _make_rarity_style( selected_fill, 6 if _batch_selected else 4, 0.46 if _batch_selected else 0.34, @@ -171,7 +169,7 @@ func _refresh_style() -> void: add_theme_stylebox_override("pressed", selected) -func _make_quality_style( +func _make_rarity_style( fill: Color, shadow_size: int, shadow_alpha: float, diff --git a/ui/game_ui.gd b/ui/game_ui.gd index 0bcaa53..f7e2d09 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -5,7 +5,6 @@ const CollectionLogType = preload("res://collection/collection_log.gd") const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd") const FishSaleServiceType = preload("res://economy/fish_sale_service.gd") const FishPoolType = preload("res://fish/fish_pool.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") const FishingSpotType = preload("res://fishing/fishing_spot.gd") const PlayerMenuType = preload("res://ui/player_menu.gd") @@ -44,12 +43,6 @@ const WorldTimeServiceType = preload("res://world/world_time_service.gd") const WorldWeatherServiceType = preload( "res://world/world_weather_service.gd" ) -const PlayerExperienceType = preload( - "res://progression/player_experience.gd" -) -const UIReferencePresentationType = preload( - "res://ui/ui_reference_presentation.gd" -) signal pixelation_settings_visibility_changed(is_visible: bool) signal crisp_reset_focus_requested @@ -69,13 +62,6 @@ signal shop_backdrop_visibility_changed(is_visible: bool) @onready var _barrier_health: Label = %BarrierHealth @onready var _showcase_details: Label = %ShowcaseDetails @onready var _fishing_panel: PanelContainer = %FishingPanel -@onready var _experience_panel: PanelContainer = %ExperienceProgressPanel -@onready var _experience_level_label: Label = %ExperienceLevelLabel -@onready var _experience_award_label: Label = %ExperienceAwardLabel -@onready var _experience_progress: ProgressBar = %ExperienceProgress -@onready var _experience_bubble: PanelContainer = %ExperienceBubble -@onready var _experience_bubble_label: Label = %ExperienceBubbleLabel -@onready var _canonical_stage: Control = %CanonicalStage @onready var _player_menu: PlayerMenuType = %PlayerMenu @onready var _screen_fade: ScreenFade = %ScreenFade @onready var _title_screen: TitleScreenType = %TitleScreen @@ -109,11 +95,6 @@ var _item_effects: PlayerItemEffectsType var _main_shop_buyer: FishBuyerProfileType var _shop_interaction: ShopInteractionType var _surface_drawing: NetworkSurfaceDrawingService -var _experience: PlayerExperienceType -var _experience_award_queue: Array[Dictionary] = [] -var _experience_animation_active: bool = false -var _experience_animation_generation: int = 0 -var _experience_panel_rest_y: float = 18.0 func _ready() -> void: @@ -142,7 +123,6 @@ func setup( player: PlayerType, inventory: FishInventoryType, collection_log: CollectionLogType, - experience: PlayerExperienceType, wallet: PlayerWalletType, sale_service: FishSaleServiceType, default_buyer: FishBuyerProfileType, @@ -175,14 +155,6 @@ func setup( _player = player _fishing_spot = fishing_spot _item_effects = item_effects - _experience = experience - if ( - _experience != null - and not _experience.experience_awarded.is_connected( - _on_experience_awarded - ) - ): - _experience.experience_awarded.connect(_on_experience_awarded) _chat_ui.setup( network_chat_service, network_session, spawn_service, player, fishing_spot, settings_manager, world_time, world_weather @@ -331,7 +303,6 @@ func setup_data_and_identity( func _process(_delta: float) -> void: - _update_experience_bubble_position() if _item_effects == null or not _gameplay_ui_enabled: _effect_status.hide() return @@ -400,7 +371,6 @@ func set_gameplay_ui_enabled(enabled: bool) -> void: _refresh_hotbar_visibility() _hotbar_ui.set_gameplay_input_enabled(true) _refresh_fishing_panel_visibility() - call_deferred("_start_next_experience_animation") func set_system_menu_open(is_open: bool) -> void: @@ -599,7 +569,6 @@ func _on_showcase_changed( fish_name: String, rarity_name: String, weight_lb: float, - quality: int, visible: bool, ) -> void: _showcase_active = visible @@ -607,7 +576,6 @@ func _on_showcase_changed( _showcase_details.text = "" _showcase_details.visible = false _set_fishing_status("") - call_deferred("_start_next_experience_animation") return _catch_track.visible = false _barrier_prompt_panel.visible = false @@ -615,208 +583,15 @@ func _on_showcase_changed( _barrier_health.visible = false _clear_barrier_markers() _showcase_details.text = ( - "%s • %.1f lb" - % [rarity_name.to_lower(), weight_lb] + "%.1f lb • %s" + % [weight_lb, rarity_name] ) _showcase_details.visible = true - _status_label.text = "You caught %s!" % ( - FishQualityType.qualified_name_with_article(fish_name, quality) - ) + _status_label.text = "You caught a %s!" % fish_name _status_label.visible = true _refresh_fishing_panel_visibility() -func _on_experience_awarded( - amount: int, - previous_total: int, - new_total: int, - previous_level: int, - new_level: int, -) -> void: - if amount <= 0: - return - _experience_award_queue.append({ - "amount": amount, - "previous_total": previous_total, - "new_total": new_total, - "previous_level": previous_level, - "new_level": new_level, - }) - - -func _start_next_experience_animation() -> void: - if ( - _experience_animation_active - or _showcase_active - or _experience_award_queue.is_empty() - or not _gameplay_ui_enabled - ): - return - var award: Dictionary = _experience_award_queue.pop_front() - _experience_animation_active = true - _experience_animation_generation += 1 - var generation: int = _experience_animation_generation - _play_experience_animation(award, generation) - - -func _play_experience_animation( - award: Dictionary, - generation: int, -) -> void: - var amount: int = int(award.get("amount", 0)) - var previous_total: int = int(award.get("previous_total", 0)) - var new_total: int = int(award.get("new_total", previous_total)) - _experience_award_label.text = "+%d xp" % amount - _experience_bubble_label.text = "+%d xp!" % amount - _update_experience_progress(previous_total) - _experience_panel.position.y = -_experience_panel.size.y - 8.0 - _experience_panel.modulate.a = 0.0 - _experience_panel.show() - _experience_bubble.modulate.a = 0.0 - _experience_bubble.scale = Vector2(0.72, 0.72) - _experience_bubble.pivot_offset = _experience_bubble.size * 0.5 - _experience_bubble.show() - - var entry_tween: Tween = create_tween() - entry_tween.set_parallel(true) - entry_tween.tween_property( - _experience_panel, - "position:y", - _experience_panel_rest_y, - 0.26, - ).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) - entry_tween.tween_property( - _experience_panel, - "modulate:a", - 1.0, - 0.18, - ) - entry_tween.tween_property( - _experience_bubble, - "modulate:a", - 1.0, - 0.15, - ) - entry_tween.tween_property( - _experience_bubble, - "scale", - Vector2.ONE, - 0.28, - ).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) - await entry_tween.finished - if generation != _experience_animation_generation: - return - - var fill_duration: float = clampf( - 0.8 + float(amount) * 0.006, - 0.9, - 1.65, - ) - var fill_tween: Tween = create_tween() - fill_tween.tween_method( - Callable(self, "_set_experience_animation_progress").bind( - previous_total, - new_total, - ), - 0.0, - 1.0, - fill_duration, - ).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) - await fill_tween.finished - if generation != _experience_animation_generation: - return - await get_tree().create_timer(0.75).timeout - if generation != _experience_animation_generation: - return - - var exit_tween: Tween = create_tween() - exit_tween.set_parallel(true) - exit_tween.tween_property( - _experience_panel, - "modulate:a", - 0.0, - 0.24, - ) - exit_tween.tween_property( - _experience_bubble, - "modulate:a", - 0.0, - 0.2, - ) - exit_tween.tween_property( - _experience_bubble, - "scale", - Vector2(0.82, 0.82), - 0.24, - ).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) - await exit_tween.finished - if generation != _experience_animation_generation: - return - _experience_panel.hide() - _experience_bubble.hide() - _experience_animation_active = false - call_deferred("_start_next_experience_animation") - - -func _set_experience_animation_progress( - progress: float, - previous_total: int, - new_total: int, -) -> void: - var displayed_total: int = roundi(lerpf( - float(previous_total), - float(new_total), - clampf(progress, 0.0, 1.0), - )) - _update_experience_progress(displayed_total) - - -func _update_experience_progress(total_experience: int) -> void: - var level: int = PlayerExperienceType.level_for_total_experience( - total_experience - ) - _experience_level_label.text = "level %d" % level - _experience_progress.value = ( - PlayerExperienceType.progress_for_total_experience(total_experience) - * 100.0 - ) - - -func _update_experience_bubble_position() -> void: - if not _experience_animation_active or _player == null: - return - var camera: Camera3D = _player.get_gameplay_camera() - var anchor_position: Vector3 = _player.get_chat_anchor_position() - if camera == null or camera.is_position_behind(anchor_position): - _experience_bubble.hide() - return - _experience_bubble.show() - var window_size := Vector2(get_window().size) - var output_scale: float = UIReferencePresentationType.get_scale( - window_size - ) - var stage_position: Vector2 = ( - camera.unproject_position(anchor_position) / output_scale - - _canonical_stage.position - ) - var desired: Vector2 = stage_position - Vector2( - _experience_bubble.size.x * 0.5, - _experience_bubble.size.y + 10.0, - ) - _experience_bubble.position = Vector2( - clampf( - desired.x, - 8.0, - _canonical_stage.size.x - _experience_bubble.size.x - 8.0, - ), - clampf( - desired.y, - 8.0, - _canonical_stage.size.y - _experience_bubble.size.y - 8.0, - ), - ) - - func _on_player_menu_visibility_changed(is_open: bool) -> void: _player_menu_open = is_open if is_open and _surface_drawing != null: diff --git a/ui/game_ui.tscn b/ui/game_ui.tscn index 3d59a55..4289771 100644 --- a/ui/game_ui.tscn +++ b/ui/game_ui.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=19 format=3] +[gd_scene load_steps=16 format=3] [ext_resource type="Script" path="res://ui/game_ui.gd" id="1_ui"] [ext_resource type="PackedScene" path="res://ui/player_menu.tscn" id="2_menu"] @@ -35,27 +35,6 @@ corner_radius_bottom_left = 12 [sub_resource type="StyleBoxFlat" id="StyleBox_transparent"] bg_color = Color(0, 0, 0, 0) -[sub_resource type="StyleBoxFlat" id="StyleBox_experience_panel"] -bg_color = Color(0.051, 0.173, 0.227, 0.97) -corner_radius_top_left = 14 -corner_radius_top_right = 14 -corner_radius_bottom_right = 14 -corner_radius_bottom_left = 14 - -[sub_resource type="StyleBoxFlat" id="StyleBox_experience_background"] -bg_color = Color(0.025, 0.102, 0.137, 1) -corner_radius_top_left = 7 -corner_radius_top_right = 7 -corner_radius_bottom_right = 7 -corner_radius_bottom_left = 7 - -[sub_resource type="StyleBoxFlat" id="StyleBox_experience_fill"] -bg_color = Color(1, 0.82, 0.4, 1) -corner_radius_top_left = 7 -corner_radius_top_right = 7 -corner_radius_bottom_right = 7 -corner_radius_bottom_left = 7 - [node name="GameUI" type="CanvasLayer"] script = ExtResource("1_ui") @@ -80,91 +59,6 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -[node name="ExperienceProgressPanel" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"] -unique_name_in_owner = true -visible = false -z_index = 63 -anchors_preset = 10 -anchor_left = 0.5 -anchor_right = 0.5 -offset_left = -240.0 -offset_top = 18.0 -offset_right = 240.0 -offset_bottom = 82.0 -grow_horizontal = 2 -mouse_filter = 2 -theme = ExtResource("3_theme") -theme_override_styles/panel = SubResource("StyleBox_experience_panel") - -[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel"] -layout_mode = 2 -theme_override_constants/margin_left = 14 -theme_override_constants/margin_top = 7 -theme_override_constants/margin_right = 14 -theme_override_constants/margin_bottom = 9 - -[node name="Layout" type="VBoxContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin"] -layout_mode = 2 -theme_override_constants/separation = 4 - -[node name="Header" type="HBoxContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout"] -layout_mode = 2 - -[node name="ExperienceLevelLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout/Header"] -unique_name_in_owner = true -layout_mode = 2 -text = "level 1" -theme_override_colors/font_color = Color(0.95, 0.98, 1, 1) -theme_override_font_sizes/font_size = 16 - -[node name="Spacer" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout/Header"] -layout_mode = 2 -size_flags_horizontal = 3 -mouse_filter = 2 - -[node name="ExperienceAwardLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout/Header"] -unique_name_in_owner = true -layout_mode = 2 -text = "+0 xp" -theme_override_colors/font_color = Color(1, 0.82, 0.4, 1) -theme_override_font_sizes/font_size = 16 - -[node name="ExperienceProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout"] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 20) -layout_mode = 2 -mouse_filter = 2 -theme_override_styles/background = SubResource("StyleBox_experience_background") -theme_override_styles/fill = SubResource("StyleBox_experience_fill") -value = 0.0 -show_percentage = false - -[node name="ExperienceBubble" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"] -unique_name_in_owner = true -visible = false -z_index = 63 -offset_right = 122.0 -offset_bottom = 46.0 -mouse_filter = 2 -theme = ExtResource("3_theme") -theme_override_styles/panel = SubResource("StyleBox_experience_panel") - -[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceBubble"] -layout_mode = 2 -theme_override_constants/margin_left = 10 -theme_override_constants/margin_top = 5 -theme_override_constants/margin_right = 10 -theme_override_constants/margin_bottom = 5 - -[node name="ExperienceBubbleLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceBubble/Margin"] -unique_name_in_owner = true -layout_mode = 2 -text = "+0 xp!" -horizontal_alignment = 1 -vertical_alignment = 1 -theme_override_colors/font_color = Color(1, 0.82, 0.4, 1) -theme_override_font_sizes/font_size = 22 - [node name="FishingPanel" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"] unique_name_in_owner = true visible = false diff --git a/ui/hotbar.gd b/ui/hotbar.gd index 6466c34..a1bbf6e 100644 --- a/ui/hotbar.gd +++ b/ui/hotbar.gd @@ -8,7 +8,6 @@ const PlayerBagType = preload("res://inventory/player_bag.gd") const PlayerHotbarType = preload("res://inventory/player_hotbar.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") const FishCatchType = preload("res://fish/fish_catch.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") const BubbleHotbarSlotType = preload( "res://ui/components/bubble_hotbar/bubble_hotbar_slot.gd" ) @@ -322,10 +321,7 @@ func _show_assignment_name(slot_index: int, identity: StringName) -> void: if not catch_id.is_empty() and _fish_inventory != null: var fish_catch: FishCatchType = _fish_inventory.get_catch_by_id(catch_id) if fish_catch != null: - _selected_item_label.text = FishQualityType.qualified_name( - fish_catch.fish.display_name, - fish_catch.quality, - ) + _selected_item_label.text = fish_catch.fish.display_name _selected_item_label.visible = true return var item = ( diff --git a/ui/logbook_page.gd b/ui/logbook_page.gd index 115b791..aa56cbf 100644 --- a/ui/logbook_page.gd +++ b/ui/logbook_page.gd @@ -3,7 +3,6 @@ extends Control const CollectionLogType = preload("res://collection/collection_log.gd") const FishDataType = preload("res://fish/fish_data.gd") -const FishQualityType = preload("res://fish/fish_quality.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") const FishPoolType = preload("res://fish/fish_pool.gd") const SILHOUETTE_SHADER: Shader = preload( @@ -560,7 +559,6 @@ func _build_known_details(fish: FishDataType) -> void: facts.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART facts.size_flags_horizontal = Control.SIZE_EXPAND_FILL facts_column.add_child(facts) - _detail_body.add_child(_build_quality_progress(fish.id)) var stats_columns := HBoxContainer.new() stats_columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL @@ -596,14 +594,8 @@ func _build_known_details(fish: FishDataType) -> void: right_stats, "value range", "%d–%d fish coin" % [ - FishQualityType.apply_sale_value( - fish.sell_value_min, - FishQualityType.Tier.BORING, - ), - FishQualityType.apply_sale_value( - fish.sell_value_max, - FishQualityType.Tier.SHINY, - ), + fish.sell_value_min, + fish.sell_value_max, ], ) _add_detail_row( @@ -613,56 +605,6 @@ func _build_known_details(fish: FishDataType) -> void: ) -func _build_quality_progress(fish_id: StringName) -> VBoxContainer: - var quality_section := VBoxContainer.new() - quality_section.add_theme_constant_override("separation", 3) - var discovered_count: int = 0 - for quality: int in FishQualityType.TIER_COUNT: - if _collection_log.has_discovered_quality(fish_id, quality): - discovered_count += 1 - var heading := _field_label( - "quality collection • %d / %d" % [ - discovered_count, - FishQualityType.TIER_COUNT, - ], - 14, - ) - heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - heading.add_theme_color_override("font_color", MUTED_INK) - quality_section.add_child(heading) - var tiers := HBoxContainer.new() - tiers.alignment = BoxContainer.ALIGNMENT_CENTER - tiers.add_theme_constant_override("separation", 10) - quality_section.add_child(tiers) - for quality: int in FishQualityType.TIER_COUNT: - var discovered: bool = _collection_log.has_discovered_quality( - fish_id, - quality, - ) - var tier := HBoxContainer.new() - tier.alignment = BoxContainer.ALIGNMENT_CENTER - tier.add_theme_constant_override("separation", 3) - var quality_color: Color = UIPalette.get_quality_color(quality) - var dot := _label("●" if discovered else "○", 13) - dot.add_theme_color_override("font_color", quality_color) - dot.modulate.a = 1.0 if discovered else 0.48 - tier.add_child(dot) - var tier_label := _label(FishQualityType.display_name(quality), 13) - tier_label.add_theme_color_override( - "font_color", - INK if discovered else MUTED_INK, - ) - tier_label.modulate.a = 1.0 if discovered else 0.58 - tier.tooltip_text = ( - "%s quality collected" - if discovered - else "%s quality not yet collected" - ) % FishQualityType.display_name(quality) - tier.add_child(tier_label) - tiers.add_child(tier) - return quality_section - - func _show_no_selection() -> void: _clear_details() var instruction := _label("Select an entry to read its catch record.", 21) diff --git a/ui/mail_page.gd b/ui/mail_page.gd index 746835a..1f5e607 100644 --- a/ui/mail_page.gd +++ b/ui/mail_page.gd @@ -16,7 +16,6 @@ const SALUTATION_LABELS := { "salutations": "Salutations", "good_luck_have_fun": "Good Luck Have Fun", } -const FishQualityType = preload("res://fish/fish_quality.gd") var _service: NetworkMailService var _reservations: PlayerAssetReservationService @@ -508,11 +507,7 @@ func _refresh_attachment_choices(_index: int) -> void: ): _attachment_choice.add_item( "%s — %.1f lb" % [ - FishQualityType.qualified_name( - fish_catch.fish.display_name, - fish_catch.quality, - ), - fish_catch.weight_lb, + fish_catch.fish.display_name, fish_catch.weight_lb, ] ) _attachment_choice.set_item_metadata( @@ -724,18 +719,11 @@ func _attachment_text(attachment: Dictionary) -> String: ) if fish_catch != null: return "%s — %.1f lb" % [ - FishQualityType.qualified_name( - fish_catch.fish.display_name, - fish_catch.quality, - ), - fish_catch.weight_lb, + fish_catch.fish.display_name, fish_catch.weight_lb, ] var data: Dictionary = attachment.get("catch", {}) return "%s — %.1f lb" % [ - FishQualityType.qualified_name( - str(data.get("fish_id", "fish")).capitalize(), - int(data.get("quality", FishQualityType.Tier.BORING)), - ), + str(data.get("fish_id", "fish")).capitalize(), float(data.get("weight_lb", 0.0)), ] PlayerAssetReservationService.AttachmentType.CONSUMABLE: diff --git a/ui/player_menu.gd b/ui/player_menu.gd index dd13792..54cd109 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -5,7 +5,6 @@ const INPUT_OWNER: StringName = &"player_menu" 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 FishQualityType = preload("res://fish/fish_quality.gd") const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd") const FishSaleResultType = preload("res://economy/fish_sale_result.gd") const FishSaleServiceType = preload("res://economy/fish_sale_service.gd") @@ -75,6 +74,11 @@ const NAVIGATION_PRESENTATION_SCALE: float = 0.60 const NAVIGATION_CANONICAL_POSITION := Vector2(424.0, 44.0) const NAVIGATION_SELECTED_SCALE: float = 1.02 const INVENTORY_TAB_LEFT_INSET: float = 96.0 +const COOLER_RARITY_COMMON := Color("e8eef0") +const COOLER_RARITY_UNCOMMON := Color("64c87c") +const COOLER_RARITY_RARE := Color("6098dd") +const COOLER_RARITY_EPIC := Color("a979cf") +const COOLER_RARITY_LEGENDARY := Color("db78a7") const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop" signal menu_visibility_changed(is_open: bool) @@ -2605,14 +2609,11 @@ func _sync_cooler_fish_nodes(catches: Array[FishCatchType]) -> void: var identity_hash: int = absi(String(fish_catch.catch_id).hash()) fish_node.configure( fish_catch.catch_id, - FishQualityType.qualified_name( - fish_catch.fish.display_name, - fish_catch.quality, - ), + fish_catch.fish.display_name, fish_catch.fish.display_texture, float(identity_hash % 628) / 100.0, 0.96 + float(identity_hash % 9) * 0.01, - UIPalette.get_quality_color(fish_catch.quality), + _get_cooler_rarity_color(fish_catch.fish.rarity), ) fish_node.set_item_state( _fish_selection.is_selected(fish_catch.catch_id), @@ -2630,6 +2631,20 @@ func _sync_cooler_fish_nodes(catches: Array[FishCatchType]) -> void: _configure_cooler_fish_focus() +func _get_cooler_rarity_color(rarity: int) -> Color: + match rarity: + FishDataType.Rarity.UNCOMMON: + return COOLER_RARITY_UNCOMMON + FishDataType.Rarity.RARE: + return COOLER_RARITY_RARE + FishDataType.Rarity.EPIC: + return COOLER_RARITY_EPIC + FishDataType.Rarity.LEGENDARY: + return COOLER_RARITY_LEGENDARY + _: + return COOLER_RARITY_COMMON + + func _layout_cooler_fish(animate: bool = true) -> void: if not is_node_ready(): return @@ -2911,10 +2926,7 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void: and _reservations.is_fish_reserved(fish_catch.catch_id) ): _cooler_detail_name.text += " • reserved in mail" - _cooler_weight_unit.text = "lb • %s • %s" % [ - FishQualityType.display_name(fish_catch.quality), - fish_catch.fish.get_rarity_name(), - ] + _cooler_weight_unit.text = "lb • %s" % fish_catch.fish.get_rarity_name() if buyer_offer >= 0 and active_buyer != null: _cooler_offer_label.text = _get_offer_label(active_buyer) _cooler_offer_value.text = "$%d" % buyer_offer diff --git a/ui/ui_palette.gd b/ui/ui_palette.gd index dec14ce..e2e7604 100644 --- a/ui/ui_palette.gd +++ b/ui/ui_palette.gd @@ -18,12 +18,6 @@ const RARITY_RARE := Color("5596f6") const RARITY_EPIC := Color("b176e8") const RARITY_LEGENDARY := Color("f276b3") -const QUALITY_BORING := Color("e8eef0") -const QUALITY_AVERAGE := Color("64c87c") -const QUALITY_IMPRESSIVE := Color("6098dd") -const QUALITY_EXCEPTIONAL := Color("a979cf") -const QUALITY_SHINY := Color("db78a7") - static func get_rarity_color(rarity: int) -> Color: match rarity: @@ -37,17 +31,3 @@ static func get_rarity_color(rarity: int) -> Color: return RARITY_LEGENDARY _: return RARITY_COMMON - - -static func get_quality_color(quality: int) -> Color: - match quality: - 1: - return QUALITY_AVERAGE - 2: - return QUALITY_IMPRESSIVE - 3: - return QUALITY_EXCEPTIONAL - 4: - return QUALITY_SHINY - _: - return QUALITY_BORING diff --git a/world/world_time_service.gd b/world/world_time_service.gd index 378fcd1..aef3054 100644 --- a/world/world_time_service.gd +++ b/world/world_time_service.gd @@ -24,8 +24,6 @@ const DUSK_END_HOUR: float = NIGHT_START_HOUR + TRANSITION_HALF_HOURS const DEFAULT_START_HOUR: float = DAY_START_HOUR var _time_hours: float = DEFAULT_START_HOUR -var _persistent_time_hours: float = DEFAULT_START_HOUR -var _persistence_tracking_enabled: bool = false var _running: bool = false var _phase: Phase = Phase.DAWN @@ -65,31 +63,6 @@ func synchronize_time(authoritative_time_hours: float) -> void: _set_time_hours(authoritative_time_hours, true) -func set_persistence_tracking_enabled(enabled: bool) -> void: - if _persistence_tracking_enabled == enabled: - return - if _persistence_tracking_enabled: - _persistent_time_hours = _time_hours - _persistence_tracking_enabled = enabled - - -func restore_persistent_time_hours(time_hours: float) -> bool: - if ( - not is_finite(time_hours) - or time_hours < 0.0 - or time_hours >= HOURS_PER_DAY - ): - return false - _persistent_time_hours = time_hours - if _persistence_tracking_enabled: - _set_time_hours(_persistent_time_hours, true) - return true - - -func get_persistent_time_hours() -> float: - return _persistent_time_hours - - func get_time_hours() -> float: return _time_hours @@ -143,8 +116,6 @@ func _set_time_hours(time_hours: float, force_emit: bool) -> void: var time_was_changed: bool = not is_equal_approx(normalized, _time_hours) _time_hours = normalized _phase = next_phase - if _persistence_tracking_enabled: - _persistent_time_hours = normalized if phase_was_changed: phase_changed.emit(_phase) if force_emit or time_was_changed or phase_was_changed: