Add data-driven fish selection and catch showcase
This commit is contained in:
parent
ae2f724fe2
commit
bd2768b676
39 changed files with 1226 additions and 58 deletions
17
collection/collection_log.gd
Normal file
17
collection/collection_log.gd
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
class_name CollectionLog
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
signal fish_discovered(fish_id: StringName)
|
||||||
|
|
||||||
|
var _discovered: Dictionary[StringName, bool] = {}
|
||||||
|
|
||||||
|
|
||||||
|
func has_discovered(fish_id: StringName) -> bool:
|
||||||
|
return not fish_id.is_empty() and _discovered.has(fish_id)
|
||||||
|
|
||||||
|
|
||||||
|
func mark_discovered(fish_id: StringName) -> void:
|
||||||
|
if fish_id.is_empty() or has_discovered(fish_id):
|
||||||
|
return
|
||||||
|
_discovered[fish_id] = true
|
||||||
|
fish_discovered.emit(fish_id)
|
||||||
1
collection/collection_log.gd.uid
Normal file
1
collection/collection_log.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dwaig3bnt3xy3
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
[gd_resource type="Resource" load_steps=3 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
|
||||||
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_fish_data")
|
|
||||||
id = &"bluegill"
|
|
||||||
display_name = "Bluegill"
|
|
||||||
catch_profile = ExtResource("2_profile")
|
|
||||||
10
fish/default_availability.tres
Normal file
10
fish/default_availability.tres
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[gd_resource type="Resource" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="1_availability"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_availability")
|
||||||
|
allow_day = true
|
||||||
|
allow_night = true
|
||||||
|
preferred_bait_tags = Array[StringName]([&"worm"])
|
||||||
|
preferred_bait_weight_multiplier = 1.35
|
||||||
63
fish/fish_availability.gd
Normal file
63
fish/fish_availability.gd
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
class_name FishAvailability
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
const FishingContextType = preload("res://fishing/fishing_context.gd")
|
||||||
|
|
||||||
|
@export var allowed_location_tags: Array[StringName] = []
|
||||||
|
@export var required_event_tags: Array[StringName] = []
|
||||||
|
@export var forbidden_event_tags: Array[StringName] = []
|
||||||
|
@export var allow_day: bool = true
|
||||||
|
@export var allow_night: bool = true
|
||||||
|
@export var require_rain: bool = false
|
||||||
|
@export var forbid_rain: bool = false
|
||||||
|
@export var required_bait_tags: Array[StringName] = []
|
||||||
|
@export var preferred_bait_tags: Array[StringName] = []
|
||||||
|
@export_range(0.01, 10.0, 0.01) var preferred_bait_weight_multiplier: float = 1.25
|
||||||
|
|
||||||
|
|
||||||
|
func is_available(context: FishingContextType) -> bool:
|
||||||
|
if context == null or (require_rain and forbid_rain):
|
||||||
|
return false
|
||||||
|
if context.is_night and not allow_night:
|
||||||
|
return false
|
||||||
|
if not context.is_night and not allow_day:
|
||||||
|
return false
|
||||||
|
if require_rain and not context.is_raining:
|
||||||
|
return false
|
||||||
|
if forbid_rain and context.is_raining:
|
||||||
|
return false
|
||||||
|
if (
|
||||||
|
not allowed_location_tags.is_empty()
|
||||||
|
and not _has_any_match(allowed_location_tags, context.location_tags)
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
for required_tag: StringName in required_event_tags:
|
||||||
|
if required_tag not in context.active_event_tags:
|
||||||
|
return false
|
||||||
|
for forbidden_tag: StringName in forbidden_event_tags:
|
||||||
|
if forbidden_tag in context.active_event_tags:
|
||||||
|
return false
|
||||||
|
if (
|
||||||
|
not required_bait_tags.is_empty()
|
||||||
|
and not _has_any_match(required_bait_tags, context.active_bait_tags)
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func get_bait_weight_multiplier(context: FishingContextType) -> float:
|
||||||
|
if context == null:
|
||||||
|
return 1.0
|
||||||
|
if _has_any_match(preferred_bait_tags, context.active_bait_tags):
|
||||||
|
return maxf(preferred_bait_weight_multiplier, 0.01)
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
|
func _has_any_match(
|
||||||
|
required_tags: Array[StringName],
|
||||||
|
active_tags: Array[StringName],
|
||||||
|
) -> bool:
|
||||||
|
for tag: StringName in required_tags:
|
||||||
|
if tag in active_tags:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
1
fish/fish_availability.gd.uid
Normal file
1
fish/fish_availability.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bs7cqi88csolc
|
||||||
20
fish/fish_catch.gd
Normal file
20
fish/fish_catch.gd
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
class_name FishCatch
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
const FishDataType = preload("res://fish/fish_data.gd")
|
||||||
|
|
||||||
|
@export var fish: FishDataType
|
||||||
|
@export var fish_id: StringName
|
||||||
|
@export var weight_lb: float = 0.0
|
||||||
|
@export var display_scale: float = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
func is_valid() -> bool:
|
||||||
|
return (
|
||||||
|
fish != null
|
||||||
|
and not fish_id.is_empty()
|
||||||
|
and fish.id == fish_id
|
||||||
|
and weight_lb >= fish.get_minimum_weight()
|
||||||
|
and weight_lb <= fish.get_maximum_weight()
|
||||||
|
and display_scale > 0.0
|
||||||
|
)
|
||||||
1
fish/fish_catch.gd.uid
Normal file
1
fish/fish_catch.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dnocndoiq3lg4
|
||||||
|
|
@ -4,8 +4,69 @@ extends Resource
|
||||||
const CatchDifficultyProfileType = preload(
|
const CatchDifficultyProfileType = preload(
|
||||||
"res://fishing/catch_difficulty_profile.gd"
|
"res://fishing/catch_difficulty_profile.gd"
|
||||||
)
|
)
|
||||||
|
const FishAvailabilityType = preload("res://fish/fish_availability.gd")
|
||||||
|
|
||||||
|
enum Rarity {
|
||||||
|
COMMON,
|
||||||
|
UNCOMMON,
|
||||||
|
RARE,
|
||||||
|
EPIC,
|
||||||
|
LEGENDARY,
|
||||||
|
}
|
||||||
|
|
||||||
@export var id: StringName
|
@export var id: StringName
|
||||||
@export var display_name: String
|
@export var display_name: String
|
||||||
@export var icon: Texture2D
|
@export var rarity: Rarity = Rarity.COMMON
|
||||||
|
@export_range(0.0, 1000.0, 0.01) var base_catch_weight: float = 1.0
|
||||||
@export var catch_profile: CatchDifficultyProfileType
|
@export var catch_profile: CatchDifficultyProfileType
|
||||||
|
@export var availability: FishAvailabilityType
|
||||||
|
@export_range(0.01, 1000.0, 0.01) var weight_min_lb: float = 0.5
|
||||||
|
@export_range(0.01, 1000.0, 0.01) var weight_max_lb: float = 1.0
|
||||||
|
@export_range(0.01, 20.0, 0.01) var display_scale_min: float = 0.8
|
||||||
|
@export_range(0.01, 20.0, 0.01) var display_scale_max: float = 1.2
|
||||||
|
@export_range(0.01, 10.0, 0.01) var display_scale_curve: float = 1.0
|
||||||
|
@export var display_texture: Texture2D
|
||||||
|
|
||||||
|
|
||||||
|
func is_selectable() -> bool:
|
||||||
|
return (
|
||||||
|
not id.is_empty()
|
||||||
|
and base_catch_weight > 0.0
|
||||||
|
and catch_profile != null
|
||||||
|
and weight_min_lb > 0.0
|
||||||
|
and weight_max_lb >= weight_min_lb
|
||||||
|
and display_scale_min > 0.0
|
||||||
|
and display_scale_max >= display_scale_min
|
||||||
|
and display_scale_curve > 0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func get_minimum_weight() -> float:
|
||||||
|
return maxf(weight_min_lb, 0.01)
|
||||||
|
|
||||||
|
|
||||||
|
func get_maximum_weight() -> float:
|
||||||
|
return maxf(weight_max_lb, get_minimum_weight())
|
||||||
|
|
||||||
|
|
||||||
|
func get_display_scale_for_weight(weight_lb: float) -> float:
|
||||||
|
var minimum_weight: float = get_minimum_weight()
|
||||||
|
var maximum_weight: float = get_maximum_weight()
|
||||||
|
var normalized_weight: float = 0.0
|
||||||
|
if maximum_weight > minimum_weight:
|
||||||
|
normalized_weight = inverse_lerp(
|
||||||
|
minimum_weight,
|
||||||
|
maximum_weight,
|
||||||
|
weight_lb
|
||||||
|
)
|
||||||
|
normalized_weight = pow(
|
||||||
|
clampf(normalized_weight, 0.0, 1.0),
|
||||||
|
maxf(display_scale_curve, 0.01)
|
||||||
|
)
|
||||||
|
var minimum_scale: float = maxf(display_scale_min, 0.01)
|
||||||
|
var maximum_scale: float = maxf(display_scale_max, minimum_scale)
|
||||||
|
return lerpf(minimum_scale, maximum_scale, normalized_weight)
|
||||||
|
|
||||||
|
|
||||||
|
func get_rarity_name() -> String:
|
||||||
|
return Rarity.keys()[rarity].capitalize()
|
||||||
|
|
|
||||||
6
fish/fish_pool.gd
Normal file
6
fish/fish_pool.gd
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
class_name FishPool
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
const FishDataType = preload("res://fish/fish_data.gd")
|
||||||
|
|
||||||
|
@export var candidates: Array[FishDataType] = []
|
||||||
1
fish/fish_pool.gd.uid
Normal file
1
fish/fish_pool.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://qg6pu6l216ei
|
||||||
78
fish/fish_selector.gd
Normal file
78
fish/fish_selector.gd
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
class_name FishSelector
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const CollectionLogType = preload("res://collection/collection_log.gd")
|
||||||
|
const FishCatchType = preload("res://fish/fish_catch.gd")
|
||||||
|
const FishDataType = preload("res://fish/fish_data.gd")
|
||||||
|
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||||
|
const FishingContextType = preload("res://fishing/fishing_context.gd")
|
||||||
|
|
||||||
|
var undiscovered_weight_multiplier: float = 1.5
|
||||||
|
var use_deterministic_test_seed: bool = false
|
||||||
|
var deterministic_test_seed: int = 24680
|
||||||
|
var selection_seed: int = 0
|
||||||
|
|
||||||
|
var _rng := RandomNumberGenerator.new()
|
||||||
|
|
||||||
|
|
||||||
|
func begin_roll() -> void:
|
||||||
|
if use_deterministic_test_seed:
|
||||||
|
selection_seed = deterministic_test_seed
|
||||||
|
_rng.seed = deterministic_test_seed
|
||||||
|
else:
|
||||||
|
_rng.randomize()
|
||||||
|
selection_seed = _rng.seed
|
||||||
|
|
||||||
|
|
||||||
|
func select_fish(
|
||||||
|
pool: FishPoolType,
|
||||||
|
context: FishingContextType,
|
||||||
|
collection_log: CollectionLogType,
|
||||||
|
) -> FishDataType:
|
||||||
|
if pool == null or context == null or collection_log == null:
|
||||||
|
return null
|
||||||
|
|
||||||
|
var eligible_fish: Array[FishDataType] = []
|
||||||
|
var weights: Array[float] = []
|
||||||
|
var total_weight: float = 0.0
|
||||||
|
for fish: FishDataType in pool.candidates:
|
||||||
|
if fish == null or not fish.is_selectable():
|
||||||
|
continue
|
||||||
|
if fish.availability != null and not fish.availability.is_available(context):
|
||||||
|
continue
|
||||||
|
var final_weight: float = fish.base_catch_weight
|
||||||
|
if fish.availability != null:
|
||||||
|
final_weight *= fish.availability.get_bait_weight_multiplier(context)
|
||||||
|
if not collection_log.has_discovered(fish.id):
|
||||||
|
final_weight *= maxf(undiscovered_weight_multiplier, 0.0)
|
||||||
|
if final_weight <= 0.0:
|
||||||
|
continue
|
||||||
|
eligible_fish.append(fish)
|
||||||
|
weights.append(final_weight)
|
||||||
|
total_weight += final_weight
|
||||||
|
|
||||||
|
if eligible_fish.is_empty() or total_weight <= 0.0:
|
||||||
|
return null
|
||||||
|
var roll: float = _rng.randf() * total_weight
|
||||||
|
var accumulated_weight: float = 0.0
|
||||||
|
for index: int in range(eligible_fish.size()):
|
||||||
|
accumulated_weight += weights[index]
|
||||||
|
if roll <= accumulated_weight:
|
||||||
|
return eligible_fish[index]
|
||||||
|
return eligible_fish.back()
|
||||||
|
|
||||||
|
|
||||||
|
func create_catch(fish: FishDataType) -> FishCatchType:
|
||||||
|
if fish == null or not fish.is_selectable():
|
||||||
|
return null
|
||||||
|
var caught_fish := FishCatchType.new()
|
||||||
|
caught_fish.fish = fish
|
||||||
|
caught_fish.fish_id = fish.id
|
||||||
|
caught_fish.weight_lb = _rng.randf_range(
|
||||||
|
fish.get_minimum_weight(),
|
||||||
|
fish.get_maximum_weight()
|
||||||
|
)
|
||||||
|
caught_fish.display_scale = fish.get_display_scale_for_weight(
|
||||||
|
caught_fish.weight_lb
|
||||||
|
)
|
||||||
|
return caught_fish
|
||||||
1
fish/fish_selector.gd.uid
Normal file
1
fish/fish_selector.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://csooreckpm0hh
|
||||||
10
fish/pools/test_water_pool.tres
Normal file
10
fish/pools/test_water_pool.tres
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[gd_resource type="Resource" load_steps=5 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"]
|
||||||
|
[ext_resource type="Resource" path="res://fish/species/bluegill/bluegill.tres" id="2_bluegill"]
|
||||||
|
[ext_resource type="Resource" path="res://fish/species/bass/bass.tres" id="3_bass"]
|
||||||
|
[ext_resource type="Resource" path="res://fish/species/carp/carp.tres" id="4_carp"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_pool")
|
||||||
|
candidates = [ExtResource("2_bluegill"), ExtResource("3_bass"), ExtResource("4_carp")]
|
||||||
6
fish/species/bass/bass.svg
Normal file
6
fish/species/bass/bass.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="64" viewBox="0 0 128 64">
|
||||||
|
<path fill="#39713b" d="M30 32 8 12v40l22-20Z"/>
|
||||||
|
<ellipse cx="72" cy="32" rx="46" ry="24" fill="#73a94d"/>
|
||||||
|
<path fill="#2f6237" d="M35 29c24-8 48-7 74 1-27 2-51 5-74 1Z"/>
|
||||||
|
<circle cx="104" cy="26" r="3" fill="#142518"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
43
fish/species/bass/bass.svg.import
Normal file
43
fish/species/bass/bass.svg.import
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b2510mmpn6cos"
|
||||||
|
path="res://.godot/imported/bass.svg-61a5b49cdcc6e48a0677d179d664071b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://fish/species/bass/bass.svg"
|
||||||
|
dest_files=["res://.godot/imported/bass.svg-61a5b49cdcc6e48a0677d179d664071b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
28
fish/species/bass/bass.tres
Normal file
28
fish/species/bass/bass.tres
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
[gd_resource type="Resource" load_steps=5 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||||
|
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||||
|
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||||
|
[ext_resource type="Texture2D" path="res://fish/species/bass/bass.svg" id="4_texture"]
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="BassAvailability"]
|
||||||
|
script = ExtResource("3_availability_script")
|
||||||
|
allow_day = true
|
||||||
|
allow_night = true
|
||||||
|
preferred_bait_tags = Array[StringName]([&"worm", &"minnow"])
|
||||||
|
preferred_bait_weight_multiplier = 1.4
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_fish_data")
|
||||||
|
id = &"bass"
|
||||||
|
display_name = "Bass"
|
||||||
|
rarity = 1
|
||||||
|
base_catch_weight = 4.0
|
||||||
|
catch_profile = ExtResource("2_profile")
|
||||||
|
availability = SubResource("BassAvailability")
|
||||||
|
weight_min_lb = 1.5
|
||||||
|
weight_max_lb = 8.0
|
||||||
|
display_scale_min = 0.9
|
||||||
|
display_scale_max = 1.5
|
||||||
|
display_scale_curve = 0.9
|
||||||
|
display_texture = ExtResource("4_texture")
|
||||||
6
fish/species/bluegill/bluegill.svg
Normal file
6
fish/species/bluegill/bluegill.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="64" viewBox="0 0 128 64">
|
||||||
|
<path fill="#197da8" d="M30 32 8 12v40l22-20Z"/>
|
||||||
|
<ellipse cx="72" cy="32" rx="46" ry="24" fill="#35bddd"/>
|
||||||
|
<path fill="#177595" d="M45 18c16-8 38-8 56 2-20-3-38 0-56 8Z"/>
|
||||||
|
<circle cx="104" cy="26" r="3" fill="#102533"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
43
fish/species/bluegill/bluegill.svg.import
Normal file
43
fish/species/bluegill/bluegill.svg.import
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dye6e87xuabvm"
|
||||||
|
path="res://.godot/imported/bluegill.svg-e8296ec8dd9d840b3ba713e441cd7e1e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://fish/species/bluegill/bluegill.svg"
|
||||||
|
dest_files=["res://.godot/imported/bluegill.svg-e8296ec8dd9d840b3ba713e441cd7e1e.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
21
fish/species/bluegill/bluegill.tres
Normal file
21
fish/species/bluegill/bluegill.tres
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
[gd_resource type="Resource" load_steps=5 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||||
|
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
|
||||||
|
[ext_resource type="Resource" path="res://fish/default_availability.tres" id="3_availability"]
|
||||||
|
[ext_resource type="Texture2D" path="res://fish/species/bluegill/bluegill.svg" id="4_texture"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_fish_data")
|
||||||
|
id = &"bluegill"
|
||||||
|
display_name = "Bluegill"
|
||||||
|
rarity = 0
|
||||||
|
base_catch_weight = 10.0
|
||||||
|
catch_profile = ExtResource("2_profile")
|
||||||
|
availability = ExtResource("3_availability")
|
||||||
|
weight_min_lb = 0.3
|
||||||
|
weight_max_lb = 2.0
|
||||||
|
display_scale_min = 0.7
|
||||||
|
display_scale_max = 1.1
|
||||||
|
display_scale_curve = 1.0
|
||||||
|
display_texture = ExtResource("4_texture")
|
||||||
6
fish/species/carp/carp.svg
Normal file
6
fish/species/carp/carp.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="64" viewBox="0 0 128 64">
|
||||||
|
<path fill="#9b5f20" d="M30 32 8 12v40l22-20Z"/>
|
||||||
|
<ellipse cx="72" cy="32" rx="46" ry="24" fill="#d89b38"/>
|
||||||
|
<path fill="#b47728" d="M39 39c20 7 44 7 67-1-21 1-43-2-67-8Z"/>
|
||||||
|
<circle cx="104" cy="26" r="3" fill="#30200f"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
43
fish/species/carp/carp.svg.import
Normal file
43
fish/species/carp/carp.svg.import
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cv0egn63xwn3c"
|
||||||
|
path="res://.godot/imported/carp.svg-0d5425bc65e17d4f31a20a81115b2a99.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://fish/species/carp/carp.svg"
|
||||||
|
dest_files=["res://.godot/imported/carp.svg-0d5425bc65e17d4f31a20a81115b2a99.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
28
fish/species/carp/carp.tres
Normal file
28
fish/species/carp/carp.tres
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
[gd_resource type="Resource" load_steps=5 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||||
|
[ext_resource type="Resource" path="res://fishing/carp_catch_profile.tres" id="2_profile"]
|
||||||
|
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||||
|
[ext_resource type="Texture2D" path="res://fish/species/carp/carp.svg" id="4_texture"]
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="CarpAvailability"]
|
||||||
|
script = ExtResource("3_availability_script")
|
||||||
|
allow_day = true
|
||||||
|
allow_night = true
|
||||||
|
preferred_bait_tags = Array[StringName]([&"corn", &"bread"])
|
||||||
|
preferred_bait_weight_multiplier = 1.5
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_fish_data")
|
||||||
|
id = &"carp"
|
||||||
|
display_name = "Carp"
|
||||||
|
rarity = 2
|
||||||
|
base_catch_weight = 1.5
|
||||||
|
catch_profile = ExtResource("2_profile")
|
||||||
|
availability = SubResource("CarpAvailability")
|
||||||
|
weight_min_lb = 3.0
|
||||||
|
weight_max_lb = 18.0
|
||||||
|
display_scale_min = 1.0
|
||||||
|
display_scale_max = 1.8
|
||||||
|
display_scale_curve = 0.85
|
||||||
|
display_texture = ExtResource("4_texture")
|
||||||
16
fishing/bass_catch_profile.tres
Normal file
16
fishing/bass_catch_profile.tres
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[gd_resource type="Resource" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://fishing/catch_difficulty_profile.gd" id="1_profile"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_profile")
|
||||||
|
barrier_count_min = 3
|
||||||
|
barrier_count_max = 4
|
||||||
|
barrier_health_min = 4
|
||||||
|
barrier_health_max = 7
|
||||||
|
first_barrier_margin = 0.14
|
||||||
|
final_barrier_margin = 0.1
|
||||||
|
minimum_barrier_spacing = 0.14
|
||||||
|
chase_start_delay = 1.0
|
||||||
|
chase_start_offset = 0.16
|
||||||
|
chase_speed = 0.145
|
||||||
16
fishing/carp_catch_profile.tres
Normal file
16
fishing/carp_catch_profile.tres
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[gd_resource type="Resource" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://fishing/catch_difficulty_profile.gd" id="1_profile"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_profile")
|
||||||
|
barrier_count_min = 3
|
||||||
|
barrier_count_max = 4
|
||||||
|
barrier_health_min = 5
|
||||||
|
barrier_health_max = 8
|
||||||
|
first_barrier_margin = 0.13
|
||||||
|
final_barrier_margin = 0.09
|
||||||
|
minimum_barrier_spacing = 0.14
|
||||||
|
chase_start_delay = 1.1
|
||||||
|
chase_start_offset = 0.18
|
||||||
|
chase_speed = 0.15
|
||||||
8
fishing/fishing_context.gd
Normal file
8
fishing/fishing_context.gd
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
class_name FishingContext
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
var location_tags: Array[StringName] = []
|
||||||
|
var active_event_tags: Array[StringName] = []
|
||||||
|
var active_bait_tags: Array[StringName] = []
|
||||||
|
var is_night: bool = false
|
||||||
|
var is_raining: bool = false
|
||||||
1
fishing/fishing_context.gd.uid
Normal file
1
fishing/fishing_context.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://chcedxnmhuqb3
|
||||||
|
|
@ -2,6 +2,7 @@ class_name FishingPresentation
|
||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
signal cast_completed
|
signal cast_completed
|
||||||
|
signal outcome_completed(outcome: StringName)
|
||||||
|
|
||||||
enum VisualMode {
|
enum VisualMode {
|
||||||
NONE,
|
NONE,
|
||||||
|
|
@ -304,7 +305,7 @@ func play_outcome(outcome: StringName) -> void:
|
||||||
_:
|
_:
|
||||||
_active_tween.tween_property(_bobber, "scale", Vector3.ZERO, 0.18)
|
_active_tween.tween_property(_bobber, "scale", Vector3.ZERO, 0.18)
|
||||||
|
|
||||||
_active_tween.finished.connect(cleanup)
|
_active_tween.finished.connect(_on_outcome_finished.bind(outcome))
|
||||||
|
|
||||||
|
|
||||||
func cleanup() -> void:
|
func cleanup() -> void:
|
||||||
|
|
@ -323,6 +324,11 @@ func cleanup() -> void:
|
||||||
set_line_mode(LineMode.HIDDEN)
|
set_line_mode(LineMode.HIDDEN)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_outcome_finished(outcome: StringName) -> void:
|
||||||
|
cleanup()
|
||||||
|
outcome_completed.emit(outcome)
|
||||||
|
|
||||||
|
|
||||||
func _set_cast_sample(
|
func _set_cast_sample(
|
||||||
progress: float,
|
progress: float,
|
||||||
start: Vector3,
|
start: Vector3,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,17 @@ class_name FishingSpot
|
||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
const FishDataType = preload("res://fish/fish_data.gd")
|
const FishDataType = preload("res://fish/fish_data.gd")
|
||||||
|
const FishCatchType = preload("res://fish/fish_catch.gd")
|
||||||
|
const FishSelectorType = preload("res://fish/fish_selector.gd")
|
||||||
|
const CollectionLogType = preload("res://collection/collection_log.gd")
|
||||||
const CatchControllerType = preload("res://fishing/catch_controller.gd")
|
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 FishingPresentationType = preload("res://fishing/fishing_presentation.gd")
|
||||||
|
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||||
const PlayerType = preload("res://player/player.gd")
|
const PlayerType = preload("res://player/player.gd")
|
||||||
|
const FishableWaterRegionType = preload(
|
||||||
|
"res://world/fishable_water_region.gd"
|
||||||
|
)
|
||||||
|
|
||||||
signal status_changed(status: String)
|
signal status_changed(status: String)
|
||||||
signal catch_display_changed(
|
signal catch_display_changed(
|
||||||
|
|
@ -16,6 +24,12 @@ signal catch_display_changed(
|
||||||
active_barrier_index: int,
|
active_barrier_index: int,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
)
|
)
|
||||||
|
signal showcase_changed(
|
||||||
|
fish_name: String,
|
||||||
|
rarity_name: String,
|
||||||
|
weight_lb: float,
|
||||||
|
visible: bool,
|
||||||
|
)
|
||||||
|
|
||||||
enum FishingState {
|
enum FishingState {
|
||||||
READY,
|
READY,
|
||||||
|
|
@ -24,6 +38,7 @@ enum FishingState {
|
||||||
WAITING_FOR_BITE,
|
WAITING_FOR_BITE,
|
||||||
BITE_ACTIVE,
|
BITE_ACTIVE,
|
||||||
FIGHTING,
|
FIGHTING,
|
||||||
|
SHOWING_CATCH,
|
||||||
COOLDOWN,
|
COOLDOWN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,14 +64,24 @@ enum FishingState {
|
||||||
@export_range(0.1, 10.0, 0.1) var bite_window_duration: float = 1.5
|
@export_range(0.1, 10.0, 0.1) var bite_window_duration: float = 1.5
|
||||||
@export_range(0.1, 10.0, 0.1) var cooldown_duration: float = 1.0
|
@export_range(0.1, 10.0, 0.1) var cooldown_duration: float = 1.0
|
||||||
|
|
||||||
@export_category("Catch")
|
@export_category("Selection")
|
||||||
@export var catchable_fish: FishDataType
|
@export_range(0.0, 10.0, 0.05) var undiscovered_weight_multiplier: float = 1.5
|
||||||
|
@export var use_deterministic_selection_seed: bool = false
|
||||||
|
@export var deterministic_selection_seed: int = 24680
|
||||||
|
|
||||||
|
@export_category("Context Testing")
|
||||||
|
@export var context_is_night: bool = false
|
||||||
|
@export var context_is_raining: bool = false
|
||||||
|
@export var context_event_tags: Array[StringName] = []
|
||||||
|
@export var context_bait_tags: Array[StringName] = []
|
||||||
|
|
||||||
@onready var _catch_controller: CatchControllerType = %CatchController
|
@onready var _catch_controller: CatchControllerType = %CatchController
|
||||||
@onready var _presentation: FishingPresentationType = %FishingPresentation
|
@onready var _presentation: FishingPresentationType = %FishingPresentation
|
||||||
|
|
||||||
var state: FishingState = FishingState.READY
|
var state: FishingState = FishingState.READY
|
||||||
var _local_player: PlayerType
|
var _local_player: PlayerType
|
||||||
|
var _local_inventory: FishInventoryType
|
||||||
|
var _local_collection_log: CollectionLogType
|
||||||
var _active_player: PlayerType
|
var _active_player: PlayerType
|
||||||
var _state_time_remaining: float = 0.0
|
var _state_time_remaining: float = 0.0
|
||||||
var _cast_charge: float = 0.0
|
var _cast_charge: float = 0.0
|
||||||
|
|
@ -72,6 +97,14 @@ var _bobber_water_position: Vector3
|
||||||
var _fight_start_position: Vector3
|
var _fight_start_position: Vector3
|
||||||
var _cooldown_status: String = ""
|
var _cooldown_status: String = ""
|
||||||
var _fishable_query_shape: SphereShape3D = SphereShape3D.new()
|
var _fishable_query_shape: SphereShape3D = SphereShape3D.new()
|
||||||
|
var _fish_selector: FishSelectorType = FishSelectorType.new()
|
||||||
|
var _selected_water_region: FishableWaterRegionType
|
||||||
|
var _selection_context: FishingContextType
|
||||||
|
var _selected_fish: FishDataType
|
||||||
|
var _pending_catch: FishCatchType
|
||||||
|
var _showcase_ready: bool = false
|
||||||
|
var _put_away_press_armed: bool = false
|
||||||
|
var _showcase_restore_generation: int = 0
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -79,15 +112,52 @@ func _ready() -> void:
|
||||||
_catch_controller.caught.connect(_on_catch_completed)
|
_catch_controller.caught.connect(_on_catch_completed)
|
||||||
_catch_controller.escaped.connect(_on_catch_escaped)
|
_catch_controller.escaped.connect(_on_catch_escaped)
|
||||||
_presentation.cast_completed.connect(_on_cast_completed)
|
_presentation.cast_completed.connect(_on_cast_completed)
|
||||||
|
_presentation.outcome_completed.connect(_on_outcome_completed)
|
||||||
|
|
||||||
|
|
||||||
func setup(local_player: PlayerType) -> void:
|
func setup(
|
||||||
|
local_player: PlayerType,
|
||||||
|
local_inventory: FishInventoryType,
|
||||||
|
local_collection_log: CollectionLogType,
|
||||||
|
) -> void:
|
||||||
_local_player = local_player
|
_local_player = local_player
|
||||||
|
_local_inventory = local_inventory
|
||||||
|
_local_collection_log = local_collection_log
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
_showcase_restore_generation += 1
|
||||||
|
if (
|
||||||
|
state == FishingState.SHOWING_CATCH
|
||||||
|
and _pending_catch != null
|
||||||
|
and _local_inventory != null
|
||||||
|
and is_instance_valid(_local_inventory)
|
||||||
|
and _local_collection_log != null
|
||||||
|
and is_instance_valid(_local_collection_log)
|
||||||
|
):
|
||||||
|
_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)
|
||||||
|
_active_player.set_movement_enabled(true)
|
||||||
|
_active_player = null
|
||||||
|
_selected_water_region = null
|
||||||
|
_selection_context = null
|
||||||
|
_selected_fish = null
|
||||||
|
_showcase_ready = false
|
||||||
|
_put_away_press_armed = false
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if state == FishingState.READY and not Input.is_action_pressed("fish_primary"):
|
if state == FishingState.READY and not Input.is_action_pressed("fish_primary"):
|
||||||
_new_cast_press_armed = true
|
_new_cast_press_armed = true
|
||||||
|
if (
|
||||||
|
state == FishingState.SHOWING_CATCH
|
||||||
|
and _showcase_ready
|
||||||
|
and not Input.is_action_pressed("fish_primary")
|
||||||
|
):
|
||||||
|
_put_away_press_armed = true
|
||||||
|
|
||||||
match state:
|
match state:
|
||||||
FishingState.AIMING_CAST:
|
FishingState.AIMING_CAST:
|
||||||
|
|
@ -110,6 +180,13 @@ func _process(delta: float) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if (
|
||||||
|
event.is_action_pressed("ui_cancel")
|
||||||
|
and state == FishingState.SHOWING_CATCH
|
||||||
|
):
|
||||||
|
_put_away_catch()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
if (
|
if (
|
||||||
event.is_action_pressed("ui_cancel")
|
event.is_action_pressed("ui_cancel")
|
||||||
and _active_player != null
|
and _active_player != null
|
||||||
|
|
@ -148,6 +225,11 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
_presentation.set_line_mode(
|
_presentation.set_line_mode(
|
||||||
FishingPresentationType.LineMode.TAUT
|
FishingPresentationType.LineMode.TAUT
|
||||||
)
|
)
|
||||||
|
FishingState.SHOWING_CATCH:
|
||||||
|
if _showcase_ready and _put_away_press_armed:
|
||||||
|
_put_away_catch()
|
||||||
|
else:
|
||||||
|
return
|
||||||
_:
|
_:
|
||||||
return
|
return
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
|
@ -217,10 +299,24 @@ func _on_cast_completed() -> void:
|
||||||
if state != FishingState.CASTING:
|
if state != FishingState.CASTING:
|
||||||
return
|
return
|
||||||
|
|
||||||
_cast_landing_is_fishable = is_target_fishable(_cast_target)
|
_selected_water_region = get_fishable_water_region(_cast_target)
|
||||||
|
_cast_landing_is_fishable = _selected_water_region != null
|
||||||
if not _cast_landing_is_fishable:
|
if not _cast_landing_is_fishable:
|
||||||
_cleanup_attempt("Can't fish there.", &"invalid")
|
_cleanup_attempt("Can't fish there.", &"invalid")
|
||||||
return
|
return
|
||||||
|
_selection_context = _build_fishing_context(_selected_water_region)
|
||||||
|
_fish_selector.undiscovered_weight_multiplier = undiscovered_weight_multiplier
|
||||||
|
_fish_selector.use_deterministic_test_seed = use_deterministic_selection_seed
|
||||||
|
_fish_selector.deterministic_test_seed = deterministic_selection_seed
|
||||||
|
_fish_selector.begin_roll()
|
||||||
|
_selected_fish = _fish_selector.select_fish(
|
||||||
|
_selected_water_region.fish_pool,
|
||||||
|
_selection_context,
|
||||||
|
_local_collection_log
|
||||||
|
)
|
||||||
|
if _selected_fish == null:
|
||||||
|
_cleanup_attempt("Nothing is biting here.", &"invalid")
|
||||||
|
return
|
||||||
|
|
||||||
state = FishingState.WAITING_FOR_BITE
|
state = FishingState.WAITING_FOR_BITE
|
||||||
_state_time_remaining = wait_time
|
_state_time_remaining = wait_time
|
||||||
|
|
@ -350,8 +446,8 @@ func _confirm_hook() -> void:
|
||||||
if (
|
if (
|
||||||
state != FishingState.BITE_ACTIVE
|
state != FishingState.BITE_ACTIVE
|
||||||
or _active_player == null
|
or _active_player == null
|
||||||
or catchable_fish == null
|
or _selected_fish == null
|
||||||
or catchable_fish.catch_profile == null
|
or _selected_fish.catch_profile == null
|
||||||
):
|
):
|
||||||
_cancel_attempt()
|
_cancel_attempt()
|
||||||
return
|
return
|
||||||
|
|
@ -363,7 +459,7 @@ func _confirm_hook() -> void:
|
||||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||||
_presentation.begin_reeling()
|
_presentation.begin_reeling()
|
||||||
_catch_controller.start_encounter(
|
_catch_controller.start_encounter(
|
||||||
catchable_fish.catch_profile,
|
_selected_fish.catch_profile,
|
||||||
_active_player.reel_speed,
|
_active_player.reel_speed,
|
||||||
_active_player.click_power
|
_active_player.click_power
|
||||||
)
|
)
|
||||||
|
|
@ -420,12 +516,24 @@ func _on_catch_encounter_updated(
|
||||||
|
|
||||||
|
|
||||||
func _on_catch_completed() -> void:
|
func _on_catch_completed() -> void:
|
||||||
if state != FishingState.FIGHTING or _active_player == null or catchable_fish == null:
|
if (
|
||||||
|
state != FishingState.FIGHTING
|
||||||
|
or _active_player == null
|
||||||
|
or _selected_fish == null
|
||||||
|
):
|
||||||
_cancel_attempt()
|
_cancel_attempt()
|
||||||
return
|
return
|
||||||
|
|
||||||
_active_player.inventory.add_fish(catchable_fish)
|
_pending_catch = _fish_selector.create_catch(_selected_fish)
|
||||||
_cleanup_attempt("Caught %s!" % catchable_fish.display_name, &"catch")
|
if _pending_catch == null or not _pending_catch.is_valid():
|
||||||
|
_cancel_attempt()
|
||||||
|
return
|
||||||
|
state = FishingState.SHOWING_CATCH
|
||||||
|
_showcase_ready = false
|
||||||
|
_put_away_press_armed = false
|
||||||
|
_catch_controller.reset()
|
||||||
|
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||||
|
_presentation.play_outcome(&"catch")
|
||||||
|
|
||||||
|
|
||||||
func _on_catch_escaped() -> void:
|
func _on_catch_escaped() -> void:
|
||||||
|
|
@ -433,11 +541,69 @@ func _on_catch_escaped() -> void:
|
||||||
return
|
return
|
||||||
|
|
||||||
var fish_name: String = "The fish"
|
var fish_name: String = "The fish"
|
||||||
if catchable_fish != null and not catchable_fish.display_name.is_empty():
|
if _selected_fish != null and not _selected_fish.display_name.is_empty():
|
||||||
fish_name = "The %s" % catchable_fish.display_name
|
fish_name = "The %s" % _selected_fish.display_name
|
||||||
_cleanup_attempt("%s got away!" % fish_name, &"escape")
|
_cleanup_attempt("%s got away!" % fish_name, &"escape")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_outcome_completed(outcome: StringName) -> void:
|
||||||
|
if (
|
||||||
|
outcome != &"catch"
|
||||||
|
or state != FishingState.SHOWING_CATCH
|
||||||
|
or _active_player == null
|
||||||
|
or _pending_catch == null
|
||||||
|
):
|
||||||
|
return
|
||||||
|
_showcase_ready = true
|
||||||
|
_active_player.begin_catch_showcase(_pending_catch)
|
||||||
|
showcase_changed.emit(
|
||||||
|
_pending_catch.fish.display_name,
|
||||||
|
_pending_catch.fish.get_rarity_name(),
|
||||||
|
_pending_catch.weight_lb,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
status_changed.emit("Left click or Escape to put away")
|
||||||
|
|
||||||
|
|
||||||
|
func _put_away_catch() -> void:
|
||||||
|
if (
|
||||||
|
state != FishingState.SHOWING_CATCH
|
||||||
|
or _active_player == null
|
||||||
|
or _local_inventory == null
|
||||||
|
or _local_collection_log == null
|
||||||
|
or _pending_catch == null
|
||||||
|
):
|
||||||
|
return
|
||||||
|
var caught_name: String = _pending_catch.fish.display_name
|
||||||
|
_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, false)
|
||||||
|
_showcase_restore_generation += 1
|
||||||
|
var restore_generation: int = _showcase_restore_generation
|
||||||
|
_active_player.end_catch_showcase(
|
||||||
|
_finish_showcase_put_away.bind(
|
||||||
|
restore_generation,
|
||||||
|
"Caught %s!" % caught_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _finish_showcase_put_away(
|
||||||
|
restore_generation: int,
|
||||||
|
cooldown_message: String,
|
||||||
|
) -> void:
|
||||||
|
if (
|
||||||
|
restore_generation != _showcase_restore_generation
|
||||||
|
or state != FishingState.SHOWING_CATCH
|
||||||
|
or _active_player == null
|
||||||
|
):
|
||||||
|
return
|
||||||
|
_cleanup_attempt(cooldown_message)
|
||||||
|
|
||||||
|
|
||||||
func _miss_bite() -> void:
|
func _miss_bite() -> void:
|
||||||
if state != FishingState.BITE_ACTIVE:
|
if state != FishingState.BITE_ACTIVE:
|
||||||
return
|
return
|
||||||
|
|
@ -448,7 +614,9 @@ func _cleanup_attempt(
|
||||||
cooldown_message: String = "",
|
cooldown_message: String = "",
|
||||||
visual_outcome: StringName = &"",
|
visual_outcome: StringName = &"",
|
||||||
) -> void:
|
) -> void:
|
||||||
|
_showcase_restore_generation += 1
|
||||||
if _active_player != null:
|
if _active_player != null:
|
||||||
|
_active_player.end_catch_showcase()
|
||||||
_active_player.set_movement_enabled(true)
|
_active_player.set_movement_enabled(true)
|
||||||
_active_player = null
|
_active_player = null
|
||||||
_state_time_remaining = 0.0
|
_state_time_remaining = 0.0
|
||||||
|
|
@ -462,6 +630,13 @@ func _cleanup_attempt(
|
||||||
_withdrawal_input_held = false
|
_withdrawal_input_held = false
|
||||||
_bobber_water_position = Vector3.ZERO
|
_bobber_water_position = Vector3.ZERO
|
||||||
_fight_start_position = Vector3.ZERO
|
_fight_start_position = Vector3.ZERO
|
||||||
|
_selected_water_region = null
|
||||||
|
_selection_context = null
|
||||||
|
_selected_fish = null
|
||||||
|
_pending_catch = null
|
||||||
|
_showcase_ready = false
|
||||||
|
_put_away_press_armed = false
|
||||||
|
showcase_changed.emit("", "", 0.0, false)
|
||||||
_catch_controller.reset()
|
_catch_controller.reset()
|
||||||
if visual_outcome.is_empty():
|
if visual_outcome.is_empty():
|
||||||
_presentation.cleanup()
|
_presentation.cleanup()
|
||||||
|
|
@ -531,6 +706,12 @@ func _calculate_cast_target(distance: float) -> Vector3:
|
||||||
|
|
||||||
|
|
||||||
func is_target_fishable(target: Vector3) -> bool:
|
func is_target_fishable(target: Vector3) -> bool:
|
||||||
|
return get_fishable_water_region(target) != null
|
||||||
|
|
||||||
|
|
||||||
|
func get_fishable_water_region(
|
||||||
|
target: Vector3,
|
||||||
|
) -> FishableWaterRegionType:
|
||||||
_fishable_query_shape.radius = fishable_query_radius
|
_fishable_query_shape.radius = fishable_query_radius
|
||||||
var query := PhysicsShapeQueryParameters3D.new()
|
var query := PhysicsShapeQueryParameters3D.new()
|
||||||
query.shape = _fishable_query_shape
|
query.shape = _fishable_query_shape
|
||||||
|
|
@ -543,9 +724,36 @@ func is_target_fishable(target: Vector3) -> bool:
|
||||||
query.collide_with_bodies = false
|
query.collide_with_bodies = false
|
||||||
var results: Array[Dictionary] = get_world_3d().direct_space_state.intersect_shape(
|
var results: Array[Dictionary] = get_world_3d().direct_space_state.intersect_shape(
|
||||||
query,
|
query,
|
||||||
1
|
32
|
||||||
)
|
)
|
||||||
return not results.is_empty()
|
var selected_region: FishableWaterRegionType
|
||||||
|
for result: Dictionary in results:
|
||||||
|
var collider: Object = result.get("collider")
|
||||||
|
var region: FishableWaterRegionType = collider as FishableWaterRegionType
|
||||||
|
if region == null or region.fish_pool == null:
|
||||||
|
continue
|
||||||
|
if (
|
||||||
|
selected_region == null
|
||||||
|
or region.selection_priority > selected_region.selection_priority
|
||||||
|
or (
|
||||||
|
region.selection_priority == selected_region.selection_priority
|
||||||
|
and region.get_instance_id() < selected_region.get_instance_id()
|
||||||
|
)
|
||||||
|
):
|
||||||
|
selected_region = region
|
||||||
|
return selected_region
|
||||||
|
|
||||||
|
|
||||||
|
func _build_fishing_context(
|
||||||
|
region: FishableWaterRegionType,
|
||||||
|
) -> FishingContextType:
|
||||||
|
var context := FishingContextType.new()
|
||||||
|
context.location_tags = region.location_tags.duplicate()
|
||||||
|
context.active_event_tags = context_event_tags.duplicate()
|
||||||
|
context.active_bait_tags = context_bait_tags.duplicate()
|
||||||
|
context.is_night = context_is_night
|
||||||
|
context.is_raining = context_is_raining
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
func find_last_fishable_position(from: Vector3, to: Vector3) -> Vector3:
|
func find_last_fishable_position(from: Vector3, to: Vector3) -> Vector3:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
[gd_scene load_steps=12 format=3]
|
[gd_scene load_steps=11 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://fishing/fishing_spot.gd" id="1_spot"]
|
[ext_resource type="Script" path="res://fishing/fishing_spot.gd" id="1_spot"]
|
||||||
[ext_resource type="Resource" path="res://fish/bluegill.tres" id="2_bluegill"]
|
|
||||||
[ext_resource type="Script" path="res://fishing/fishing_presentation.gd" id="3_presentation"]
|
[ext_resource type="Script" path="res://fishing/fishing_presentation.gd" id="3_presentation"]
|
||||||
[ext_resource type="Script" path="res://fishing/catch_controller.gd" id="4_catch"]
|
[ext_resource type="Script" path="res://fishing/catch_controller.gd" id="4_catch"]
|
||||||
|
|
||||||
|
|
@ -41,7 +40,6 @@ size = Vector3(0.72, 0.06, 0.12)
|
||||||
|
|
||||||
[node name="FishingSpot" type="Node3D"]
|
[node name="FishingSpot" type="Node3D"]
|
||||||
script = ExtResource("1_spot")
|
script = ExtResource("1_spot")
|
||||||
catchable_fish = ExtResource("2_bluegill")
|
|
||||||
|
|
||||||
[node name="CatchController" type="Node" parent="."]
|
[node name="CatchController" type="Node" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,34 @@
|
||||||
class_name FishInventory
|
class_name FishInventory
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const FishDataType = preload("res://fish/fish_data.gd")
|
const FishCatchType = preload("res://fish/fish_catch.gd")
|
||||||
|
|
||||||
signal contents_changed(fish_id: StringName, count: int)
|
signal contents_changed(fish_id: StringName, count: int)
|
||||||
|
|
||||||
var _fish_counts: Dictionary[StringName, int] = {}
|
var _catches: Array[FishCatchType] = []
|
||||||
|
|
||||||
|
|
||||||
func add_fish(fish: FishDataType, amount: int = 1) -> void:
|
func add_catch(fish_catch: FishCatchType) -> void:
|
||||||
if fish == null or fish.id.is_empty() or amount <= 0:
|
if fish_catch == null or not fish_catch.is_valid():
|
||||||
return
|
return
|
||||||
|
_catches.append(fish_catch)
|
||||||
var new_count: int = get_count(fish.id) + amount
|
contents_changed.emit(
|
||||||
_fish_counts[fish.id] = new_count
|
fish_catch.fish_id,
|
||||||
contents_changed.emit(fish.id, new_count)
|
get_count(fish_catch.fish_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func get_count(fish_id: StringName) -> int:
|
func get_count(fish_id: StringName) -> int:
|
||||||
return _fish_counts.get(fish_id, 0)
|
return get_catches_by_fish_id(fish_id).size()
|
||||||
|
|
||||||
|
|
||||||
|
func get_all_catches() -> Array[FishCatchType]:
|
||||||
|
return _catches.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
func get_catches_by_fish_id(fish_id: StringName) -> Array[FishCatchType]:
|
||||||
|
var matching: Array[FishCatchType] = []
|
||||||
|
for fish_catch: FishCatchType in _catches:
|
||||||
|
if fish_catch.fish_id == fish_id:
|
||||||
|
matching.append(fish_catch)
|
||||||
|
return matching
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,9 @@ const PlayerType = preload("res://player/player.gd")
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_fishing_spot.setup(_player)
|
_fishing_spot.setup(
|
||||||
|
_player,
|
||||||
|
_player.inventory,
|
||||||
|
_player.collection_log
|
||||||
|
)
|
||||||
_game_ui.setup(_player.inventory, _fishing_spot)
|
_game_ui.setup(_player.inventory, _fishing_spot)
|
||||||
|
|
|
||||||
330
player/player.gd
330
player/player.gd
|
|
@ -2,6 +2,20 @@ class_name Player
|
||||||
extends CharacterBody3D
|
extends CharacterBody3D
|
||||||
|
|
||||||
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||||
|
const CollectionLogType = preload("res://collection/collection_log.gd")
|
||||||
|
const FishCatchType = preload("res://fish/fish_catch.gd")
|
||||||
|
|
||||||
|
class ShowcaseCameraSnapshot:
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
var yaw_transform: Transform3D
|
||||||
|
var yaw_rotation: Vector3
|
||||||
|
var pitch_rotation: Vector3
|
||||||
|
var spring_length: float
|
||||||
|
var target_zoom: float
|
||||||
|
var camera_input_enabled: bool
|
||||||
|
var camera_dragging: bool
|
||||||
|
|
||||||
|
|
||||||
@export_category("Movement")
|
@export_category("Movement")
|
||||||
@export var walk_speed: float = 5.0
|
@export var walk_speed: float = 5.0
|
||||||
|
|
@ -18,6 +32,15 @@ const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||||
@export_range(0.01, 2.0, 0.01) var reel_speed: float = 0.32
|
@export_range(0.01, 2.0, 0.01) var reel_speed: float = 0.32
|
||||||
@export_range(1, 100, 1) var click_power: int = 1
|
@export_range(1, 100, 1) var click_power: int = 1
|
||||||
|
|
||||||
|
@export_category("Showcase")
|
||||||
|
@export_range(0.05, 1.0, 0.01) var showcase_turn_duration: float = 0.28
|
||||||
|
@export_range(0.05, 1.0, 0.01) var showcase_restore_duration: float = 0.24
|
||||||
|
@export_range(0.05, 1.5, 0.01) var showcase_camera_transition_duration: float = 0.42
|
||||||
|
@export_range(-180.0, 180.0, 1.0) var showcase_camera_yaw_offset: float = 180.0
|
||||||
|
@export_range(-80.0, 80.0, 1.0) var showcase_camera_pitch: float = -8.0
|
||||||
|
@export_range(1.0, 10.0, 0.1) var showcase_camera_zoom_distance: float = 3.6
|
||||||
|
@export_range(0.5, 3.0, 0.05) var showcase_camera_target_height: float = 1.45
|
||||||
|
|
||||||
@export_category("Camera")
|
@export_category("Camera")
|
||||||
@export var mouse_sensitivity: float = 0.005
|
@export var mouse_sensitivity: float = 0.005
|
||||||
@export var controller_camera_speed: float = 2.5
|
@export var controller_camera_speed: float = 2.5
|
||||||
|
|
@ -35,14 +58,27 @@ const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||||
@onready var _spring_arm: SpringArm3D = %SpringArm3D
|
@onready var _spring_arm: SpringArm3D = %SpringArm3D
|
||||||
@onready var _camera: Camera3D = %Camera3D
|
@onready var _camera: Camera3D = %Camera3D
|
||||||
@onready var inventory: FishInventoryType = %Inventory
|
@onready var inventory: FishInventoryType = %Inventory
|
||||||
|
@onready var collection_log: CollectionLogType = %CollectionLog
|
||||||
@onready var _cast_origin: Marker3D = %CastOrigin
|
@onready var _cast_origin: Marker3D = %CastOrigin
|
||||||
@onready var _fishing_rod: Node3D = %FishingRod
|
@onready var _fishing_rod: Node3D = %FishingRod
|
||||||
@onready var _fishing_rod_tip: Marker3D = %FishingRodTip
|
@onready var _fishing_rod_tip: Marker3D = %FishingRodTip
|
||||||
|
@onready var _catch_display: Node3D = %CatchDisplay
|
||||||
|
@onready var _catch_sprite: Sprite3D = %CatchSprite
|
||||||
|
|
||||||
var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity"))
|
var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity"))
|
||||||
var _camera_dragging: bool = false
|
var _camera_dragging: bool = false
|
||||||
|
var _camera_input_enabled: bool = true
|
||||||
var _movement_enabled: bool = true
|
var _movement_enabled: bool = true
|
||||||
var _target_zoom: float = 5.0
|
var _target_zoom: float = 5.0
|
||||||
|
var _showcase_rod_visibility: bool = true
|
||||||
|
var _showcase_rod_state_stored: bool = false
|
||||||
|
var _showcase_visual_rotation: Vector3
|
||||||
|
var _showcase_visual_rotation_stored: bool = false
|
||||||
|
var _showcase_turn_tween: Tween
|
||||||
|
var _showcase_restore_tween: Tween
|
||||||
|
var _showcase_camera_tween: Tween
|
||||||
|
var _showcase_camera_snapshot: ShowcaseCameraSnapshot
|
||||||
|
var _showcase_restore_generation: int = 0
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -98,9 +134,14 @@ func _process(delta: float) -> void:
|
||||||
if not local_control_enabled:
|
if not local_control_enabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
if _camera_dragging and not Input.is_action_pressed("camera_drag"):
|
if (
|
||||||
|
_camera_input_enabled
|
||||||
|
and _camera_dragging
|
||||||
|
and not Input.is_action_pressed("camera_drag")
|
||||||
|
):
|
||||||
_camera_dragging = false
|
_camera_dragging = false
|
||||||
|
|
||||||
|
if _camera_input_enabled:
|
||||||
var stick: Vector2 = Vector2(
|
var stick: Vector2 = Vector2(
|
||||||
Input.get_joy_axis(0, JOY_AXIS_RIGHT_X),
|
Input.get_joy_axis(0, JOY_AXIS_RIGHT_X),
|
||||||
Input.get_joy_axis(0, JOY_AXIS_RIGHT_Y)
|
Input.get_joy_axis(0, JOY_AXIS_RIGHT_Y)
|
||||||
|
|
@ -110,14 +151,23 @@ func _process(delta: float) -> void:
|
||||||
(stick.length() - controller_camera_deadzone)
|
(stick.length() - controller_camera_deadzone)
|
||||||
/ (1.0 - controller_camera_deadzone)
|
/ (1.0 - controller_camera_deadzone)
|
||||||
)
|
)
|
||||||
_rotate_camera(stick.normalized() * adjusted_strength * controller_camera_speed * delta)
|
_rotate_camera(
|
||||||
|
stick.normalized()
|
||||||
|
* adjusted_strength
|
||||||
|
* controller_camera_speed
|
||||||
|
* delta
|
||||||
|
)
|
||||||
|
|
||||||
var zoom_weight: float = 1.0 - exp(-zoom_smoothing * delta)
|
var zoom_weight: float = 1.0 - exp(-zoom_smoothing * delta)
|
||||||
_spring_arm.spring_length = lerpf(_spring_arm.spring_length, _target_zoom, zoom_weight)
|
_spring_arm.spring_length = lerpf(
|
||||||
|
_spring_arm.spring_length,
|
||||||
|
_target_zoom,
|
||||||
|
zoom_weight
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
if not local_control_enabled:
|
if not local_control_enabled or not _camera_input_enabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
if event.is_action("camera_drag"):
|
if event.is_action("camera_drag"):
|
||||||
|
|
@ -192,6 +242,278 @@ func get_cast_origin_position() -> Vector3:
|
||||||
return _cast_origin.global_position
|
return _cast_origin.global_position
|
||||||
|
|
||||||
|
|
||||||
|
func begin_catch_showcase(fish_catch: FishCatchType) -> void:
|
||||||
|
if fish_catch == null or not fish_catch.is_valid():
|
||||||
|
return
|
||||||
|
_kill_showcase_camera_tween()
|
||||||
|
_kill_showcase_restore_tween()
|
||||||
|
_capture_showcase_camera_snapshot()
|
||||||
|
if not _showcase_visual_rotation_stored:
|
||||||
|
_showcase_visual_rotation = _visuals.rotation
|
||||||
|
_showcase_visual_rotation_stored = true
|
||||||
|
var showcase_camera_position: Vector3 = _begin_showcase_camera_transition()
|
||||||
|
_turn_showcase_toward_position(showcase_camera_position)
|
||||||
|
if not _showcase_rod_state_stored:
|
||||||
|
_showcase_rod_visibility = _fishing_rod.visible
|
||||||
|
_showcase_rod_state_stored = true
|
||||||
|
_fishing_rod.visible = false
|
||||||
|
_catch_sprite.texture = fish_catch.fish.display_texture
|
||||||
|
_catch_display.scale = Vector3.ONE * fish_catch.display_scale
|
||||||
|
_catch_display.visible = _catch_sprite.texture != null
|
||||||
|
|
||||||
|
|
||||||
|
func end_catch_showcase(
|
||||||
|
restored_callback: Callable = Callable(),
|
||||||
|
immediate: bool = false,
|
||||||
|
) -> void:
|
||||||
|
_kill_showcase_turn_tween()
|
||||||
|
_kill_showcase_camera_tween()
|
||||||
|
_kill_showcase_restore_tween()
|
||||||
|
_catch_display.visible = false
|
||||||
|
_catch_display.scale = Vector3.ONE
|
||||||
|
_catch_sprite.texture = null
|
||||||
|
if _showcase_rod_state_stored:
|
||||||
|
_fishing_rod.visible = _showcase_rod_visibility
|
||||||
|
_showcase_rod_visibility = true
|
||||||
|
_showcase_rod_state_stored = false
|
||||||
|
if (
|
||||||
|
not _showcase_visual_rotation_stored
|
||||||
|
and _showcase_camera_snapshot == null
|
||||||
|
):
|
||||||
|
if restored_callback.is_valid():
|
||||||
|
restored_callback.call()
|
||||||
|
return
|
||||||
|
if immediate:
|
||||||
|
_complete_showcase_restore(restored_callback)
|
||||||
|
return
|
||||||
|
|
||||||
|
var current_rotation: Vector3 = _visuals.rotation
|
||||||
|
var restore_target: Vector3 = _showcase_visual_rotation
|
||||||
|
restore_target.y = (
|
||||||
|
current_rotation.y
|
||||||
|
+ wrapf(
|
||||||
|
_showcase_visual_rotation.y - current_rotation.y,
|
||||||
|
-PI,
|
||||||
|
PI
|
||||||
|
)
|
||||||
|
)
|
||||||
|
var restore_generation: int = _showcase_restore_generation
|
||||||
|
_showcase_restore_tween = create_tween()
|
||||||
|
_showcase_restore_tween.set_parallel(true)
|
||||||
|
_showcase_restore_tween.set_trans(Tween.TRANS_QUAD)
|
||||||
|
_showcase_restore_tween.set_ease(Tween.EASE_IN_OUT)
|
||||||
|
if _showcase_visual_rotation_stored:
|
||||||
|
_showcase_restore_tween.tween_property(
|
||||||
|
_visuals,
|
||||||
|
"rotation",
|
||||||
|
restore_target,
|
||||||
|
maxf(showcase_restore_duration, 0.05)
|
||||||
|
)
|
||||||
|
if _showcase_camera_snapshot != null:
|
||||||
|
var camera_yaw_target: Vector3 = (
|
||||||
|
_showcase_camera_snapshot.yaw_rotation
|
||||||
|
)
|
||||||
|
camera_yaw_target.y = (
|
||||||
|
_camera_yaw.rotation.y
|
||||||
|
+ wrapf(
|
||||||
|
camera_yaw_target.y - _camera_yaw.rotation.y,
|
||||||
|
-PI,
|
||||||
|
PI
|
||||||
|
)
|
||||||
|
)
|
||||||
|
var camera_duration: float = maxf(
|
||||||
|
showcase_camera_transition_duration,
|
||||||
|
0.05
|
||||||
|
)
|
||||||
|
_showcase_restore_tween.tween_property(
|
||||||
|
_camera_yaw,
|
||||||
|
"rotation",
|
||||||
|
camera_yaw_target,
|
||||||
|
camera_duration
|
||||||
|
)
|
||||||
|
_showcase_restore_tween.tween_property(
|
||||||
|
_camera_yaw,
|
||||||
|
"position",
|
||||||
|
_showcase_camera_snapshot.yaw_transform.origin,
|
||||||
|
camera_duration
|
||||||
|
)
|
||||||
|
_showcase_restore_tween.tween_property(
|
||||||
|
_camera_pitch,
|
||||||
|
"rotation",
|
||||||
|
_showcase_camera_snapshot.pitch_rotation,
|
||||||
|
camera_duration
|
||||||
|
)
|
||||||
|
_showcase_restore_tween.tween_property(
|
||||||
|
_spring_arm,
|
||||||
|
"spring_length",
|
||||||
|
_showcase_camera_snapshot.spring_length,
|
||||||
|
camera_duration
|
||||||
|
)
|
||||||
|
_showcase_restore_tween.finished.connect(
|
||||||
|
_on_showcase_restore_finished.bind(
|
||||||
|
restore_generation,
|
||||||
|
restored_callback
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _turn_showcase_toward_position(target_position: Vector3) -> void:
|
||||||
|
_kill_showcase_turn_tween()
|
||||||
|
var camera_direction: Vector3 = (
|
||||||
|
target_position - _visuals.global_position
|
||||||
|
)
|
||||||
|
camera_direction.y = 0.0
|
||||||
|
if camera_direction.is_zero_approx():
|
||||||
|
return
|
||||||
|
camera_direction = camera_direction.normalized()
|
||||||
|
var target_world_yaw: float = atan2(
|
||||||
|
-camera_direction.x,
|
||||||
|
-camera_direction.z
|
||||||
|
)
|
||||||
|
var target_local_yaw: float = target_world_yaw - global_rotation.y
|
||||||
|
var current_yaw: float = _visuals.rotation.y
|
||||||
|
var shortest_target_yaw: float = (
|
||||||
|
current_yaw
|
||||||
|
+ wrapf(target_local_yaw - current_yaw, -PI, PI)
|
||||||
|
)
|
||||||
|
_showcase_turn_tween = create_tween()
|
||||||
|
_showcase_turn_tween.set_trans(Tween.TRANS_QUAD)
|
||||||
|
_showcase_turn_tween.set_ease(Tween.EASE_IN_OUT)
|
||||||
|
_showcase_turn_tween.tween_property(
|
||||||
|
_visuals,
|
||||||
|
"rotation:y",
|
||||||
|
shortest_target_yaw,
|
||||||
|
maxf(showcase_turn_duration, 0.05)
|
||||||
|
)
|
||||||
|
_showcase_turn_tween.finished.connect(_on_showcase_turn_finished)
|
||||||
|
|
||||||
|
|
||||||
|
func _kill_showcase_turn_tween() -> void:
|
||||||
|
if _showcase_turn_tween != null and _showcase_turn_tween.is_valid():
|
||||||
|
_showcase_turn_tween.kill()
|
||||||
|
_showcase_turn_tween = null
|
||||||
|
|
||||||
|
|
||||||
|
func _kill_showcase_restore_tween() -> void:
|
||||||
|
if _showcase_restore_tween != null and _showcase_restore_tween.is_valid():
|
||||||
|
_showcase_restore_tween.kill()
|
||||||
|
_showcase_restore_tween = null
|
||||||
|
_showcase_restore_generation += 1
|
||||||
|
|
||||||
|
|
||||||
|
func _kill_showcase_camera_tween() -> void:
|
||||||
|
if _showcase_camera_tween != null and _showcase_camera_tween.is_valid():
|
||||||
|
_showcase_camera_tween.kill()
|
||||||
|
_showcase_camera_tween = null
|
||||||
|
|
||||||
|
|
||||||
|
func _on_showcase_turn_finished() -> void:
|
||||||
|
_showcase_turn_tween = null
|
||||||
|
|
||||||
|
|
||||||
|
func _on_showcase_restore_finished(
|
||||||
|
restore_generation: int,
|
||||||
|
restored_callback: Callable,
|
||||||
|
) -> void:
|
||||||
|
if restore_generation != _showcase_restore_generation:
|
||||||
|
return
|
||||||
|
_showcase_restore_tween = null
|
||||||
|
_complete_showcase_restore(restored_callback)
|
||||||
|
|
||||||
|
|
||||||
|
func _complete_showcase_restore(
|
||||||
|
restored_callback: Callable,
|
||||||
|
) -> void:
|
||||||
|
if _showcase_visual_rotation_stored:
|
||||||
|
_visuals.rotation = _showcase_visual_rotation
|
||||||
|
_showcase_visual_rotation = Vector3.ZERO
|
||||||
|
_showcase_visual_rotation_stored = false
|
||||||
|
if _showcase_camera_snapshot != null:
|
||||||
|
_camera_yaw.transform = _showcase_camera_snapshot.yaw_transform
|
||||||
|
_camera_pitch.rotation = _showcase_camera_snapshot.pitch_rotation
|
||||||
|
_spring_arm.spring_length = _showcase_camera_snapshot.spring_length
|
||||||
|
_target_zoom = _showcase_camera_snapshot.target_zoom
|
||||||
|
_camera_input_enabled = (
|
||||||
|
_showcase_camera_snapshot.camera_input_enabled
|
||||||
|
)
|
||||||
|
_camera_dragging = _showcase_camera_snapshot.camera_dragging
|
||||||
|
_showcase_camera_snapshot = null
|
||||||
|
if restored_callback.is_valid():
|
||||||
|
restored_callback.call()
|
||||||
|
|
||||||
|
|
||||||
|
func _capture_showcase_camera_snapshot() -> void:
|
||||||
|
if _showcase_camera_snapshot != null:
|
||||||
|
return
|
||||||
|
_showcase_camera_snapshot = ShowcaseCameraSnapshot.new()
|
||||||
|
_showcase_camera_snapshot.yaw_transform = _camera_yaw.transform
|
||||||
|
_showcase_camera_snapshot.yaw_rotation = _camera_yaw.rotation
|
||||||
|
_showcase_camera_snapshot.pitch_rotation = _camera_pitch.rotation
|
||||||
|
_showcase_camera_snapshot.spring_length = _spring_arm.spring_length
|
||||||
|
_showcase_camera_snapshot.target_zoom = _target_zoom
|
||||||
|
_showcase_camera_snapshot.camera_input_enabled = _camera_input_enabled
|
||||||
|
_showcase_camera_snapshot.camera_dragging = _camera_dragging
|
||||||
|
_camera_input_enabled = false
|
||||||
|
_camera_dragging = false
|
||||||
|
|
||||||
|
|
||||||
|
func _begin_showcase_camera_transition() -> Vector3:
|
||||||
|
var target_world_yaw: float = (
|
||||||
|
_visuals.global_rotation.y
|
||||||
|
+ deg_to_rad(showcase_camera_yaw_offset)
|
||||||
|
)
|
||||||
|
var target_local_yaw: float = target_world_yaw - global_rotation.y
|
||||||
|
var shortest_target_yaw: float = (
|
||||||
|
_camera_yaw.rotation.y
|
||||||
|
+ wrapf(
|
||||||
|
target_local_yaw - _camera_yaw.rotation.y,
|
||||||
|
-PI,
|
||||||
|
PI
|
||||||
|
)
|
||||||
|
)
|
||||||
|
var target_pitch: float = deg_to_rad(showcase_camera_pitch)
|
||||||
|
var target_position: Vector3 = _camera_yaw.position
|
||||||
|
target_position.y = showcase_camera_target_height
|
||||||
|
var target_zoom: float = maxf(showcase_camera_zoom_distance, 0.1)
|
||||||
|
var camera_duration: float = maxf(
|
||||||
|
showcase_camera_transition_duration,
|
||||||
|
0.05
|
||||||
|
)
|
||||||
|
_showcase_camera_tween = create_tween()
|
||||||
|
_showcase_camera_tween.set_parallel(true)
|
||||||
|
_showcase_camera_tween.set_trans(Tween.TRANS_QUAD)
|
||||||
|
_showcase_camera_tween.set_ease(Tween.EASE_IN_OUT)
|
||||||
|
_showcase_camera_tween.tween_property(
|
||||||
|
_camera_yaw,
|
||||||
|
"rotation:y",
|
||||||
|
shortest_target_yaw,
|
||||||
|
camera_duration
|
||||||
|
)
|
||||||
|
_showcase_camera_tween.tween_property(
|
||||||
|
_camera_yaw,
|
||||||
|
"position:y",
|
||||||
|
target_position.y,
|
||||||
|
camera_duration
|
||||||
|
)
|
||||||
|
_showcase_camera_tween.tween_property(
|
||||||
|
_camera_pitch,
|
||||||
|
"rotation:x",
|
||||||
|
target_pitch,
|
||||||
|
camera_duration
|
||||||
|
)
|
||||||
|
_showcase_camera_tween.tween_property(
|
||||||
|
_spring_arm,
|
||||||
|
"spring_length",
|
||||||
|
target_zoom,
|
||||||
|
camera_duration
|
||||||
|
)
|
||||||
|
|
||||||
|
var horizontal_offset: Vector3 = (
|
||||||
|
Basis(Vector3.UP, target_world_yaw).z * target_zoom
|
||||||
|
)
|
||||||
|
return global_position + target_position + horizontal_offset
|
||||||
|
|
||||||
|
|
||||||
func get_facing_direction() -> Vector3:
|
func get_facing_direction() -> Vector3:
|
||||||
var facing: Vector3 = -_visuals.global_basis.z
|
var facing: Vector3 = -_visuals.global_basis.z
|
||||||
facing.y = 0.0
|
facing.y = 0.0
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
[gd_scene load_steps=10 format=3]
|
[gd_scene load_steps=11 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://player/player.gd" id="1_script"]
|
[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"]
|
[ext_resource type="Script" path="res://inventory/fish_inventory.gd" id="2_inventory"]
|
||||||
|
[ext_resource type="Script" path="res://collection/collection_log.gd" id="3_collection"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="PlayerShape"]
|
[sub_resource type="CapsuleShape3D" id="PlayerShape"]
|
||||||
radius = 0.45
|
radius = 0.45
|
||||||
|
|
@ -73,6 +74,26 @@ position = Vector3(0, 0.9, 0)
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
script = ExtResource("2_inventory")
|
script = ExtResource("2_inventory")
|
||||||
|
|
||||||
|
[node name="CollectionLog" type="Node" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
script = ExtResource("3_collection")
|
||||||
|
|
||||||
|
[node name="CatchDisplayAnchor" type="Marker3D" parent="Visuals"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
position = Vector3(0, 1.45, -1.15)
|
||||||
|
|
||||||
|
[node name="CatchDisplay" type="Node3D" parent="Visuals/CatchDisplayAnchor"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="CatchSprite" type="Sprite3D" parent="Visuals/CatchDisplayAnchor/CatchDisplay"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
pixel_size = 0.012
|
||||||
|
billboard = 1
|
||||||
|
shaded = false
|
||||||
|
double_sided = true
|
||||||
|
texture_filter = 0
|
||||||
|
|
||||||
[node name="CameraYaw" type="Node3D" parent="."]
|
[node name="CameraYaw" type="Node3D" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
position = Vector3(0, 1.35, 0)
|
position = Vector3(0, 1.35, 0)
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,42 @@ const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
||||||
|
|
||||||
@onready var _status_label: Label = %StatusLabel
|
@onready var _status_label: Label = %StatusLabel
|
||||||
@onready var _bluegill_count_label: Label = %BluegillCountLabel
|
@onready var _bluegill_count_label: Label = %BluegillCountLabel
|
||||||
|
@onready var _bass_count_label: Label = %BassCountLabel
|
||||||
|
@onready var _carp_count_label: Label = %CarpCountLabel
|
||||||
@onready var _catch_track: Control = %CatchTrack
|
@onready var _catch_track: Control = %CatchTrack
|
||||||
@onready var _green_catch_progress: ProgressBar = %GreenCatchProgress
|
@onready var _green_catch_progress: ProgressBar = %GreenCatchProgress
|
||||||
@onready var _red_chase_progress: ProgressBar = %RedChaseProgress
|
@onready var _red_chase_progress: ProgressBar = %RedChaseProgress
|
||||||
@onready var _barrier_markers: Control = %BarrierMarkers
|
@onready var _barrier_markers: Control = %BarrierMarkers
|
||||||
@onready var _barrier_summary: Label = %BarrierSummary
|
@onready var _barrier_summary: Label = %BarrierSummary
|
||||||
@onready var _barrier_health: Label = %BarrierHealth
|
@onready var _barrier_health: Label = %BarrierHealth
|
||||||
|
@onready var _showcase_details: Label = %ShowcaseDetails
|
||||||
|
|
||||||
|
var _showcase_active: bool = false
|
||||||
|
|
||||||
|
|
||||||
func setup(inventory: FishInventoryType, fishing_spot: FishingSpotType) -> void:
|
func setup(inventory: FishInventoryType, fishing_spot: FishingSpotType) -> void:
|
||||||
inventory.contents_changed.connect(_on_inventory_contents_changed)
|
inventory.contents_changed.connect(_on_inventory_contents_changed)
|
||||||
fishing_spot.status_changed.connect(_on_fishing_status_changed)
|
fishing_spot.status_changed.connect(_on_fishing_status_changed)
|
||||||
fishing_spot.catch_display_changed.connect(_on_catch_display_changed)
|
fishing_spot.catch_display_changed.connect(_on_catch_display_changed)
|
||||||
|
fishing_spot.showcase_changed.connect(_on_showcase_changed)
|
||||||
_update_bluegill_count(inventory.get_count(&"bluegill"))
|
_update_bluegill_count(inventory.get_count(&"bluegill"))
|
||||||
|
_bass_count_label.text = "Bass: %d" % inventory.get_count(&"bass")
|
||||||
|
_carp_count_label.text = "Carp: %d" % inventory.get_count(&"carp")
|
||||||
|
|
||||||
|
|
||||||
func _on_inventory_contents_changed(fish_id: StringName, count: int) -> void:
|
func _on_inventory_contents_changed(fish_id: StringName, count: int) -> void:
|
||||||
if fish_id == &"bluegill":
|
match fish_id:
|
||||||
|
&"bluegill":
|
||||||
_update_bluegill_count(count)
|
_update_bluegill_count(count)
|
||||||
|
&"bass":
|
||||||
|
_bass_count_label.text = "Bass: %d" % count
|
||||||
|
&"carp":
|
||||||
|
_carp_count_label.text = "Carp: %d" % count
|
||||||
|
|
||||||
|
|
||||||
func _on_fishing_status_changed(status: String) -> void:
|
func _on_fishing_status_changed(status: String) -> void:
|
||||||
|
if _showcase_active:
|
||||||
|
return
|
||||||
_status_label.text = status
|
_status_label.text = status
|
||||||
_status_label.visible = not status.is_empty()
|
_status_label.visible = not status.is_empty()
|
||||||
|
|
||||||
|
|
@ -132,5 +147,31 @@ func _clear_barrier_markers() -> void:
|
||||||
marker.queue_free()
|
marker.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_showcase_changed(
|
||||||
|
fish_name: String,
|
||||||
|
rarity_name: String,
|
||||||
|
weight_lb: float,
|
||||||
|
visible: bool,
|
||||||
|
) -> void:
|
||||||
|
_showcase_active = visible
|
||||||
|
if not visible:
|
||||||
|
_status_label.text = ""
|
||||||
|
_status_label.visible = false
|
||||||
|
_showcase_details.text = ""
|
||||||
|
_showcase_details.visible = false
|
||||||
|
return
|
||||||
|
_catch_track.visible = false
|
||||||
|
_barrier_summary.visible = false
|
||||||
|
_barrier_health.visible = false
|
||||||
|
_clear_barrier_markers()
|
||||||
|
_status_label.text = "You caught a %s!" % fish_name
|
||||||
|
_status_label.visible = true
|
||||||
|
_showcase_details.text = (
|
||||||
|
"%.1f lb • %s\nLeft click or Escape to put away"
|
||||||
|
% [weight_lb, rarity_name]
|
||||||
|
)
|
||||||
|
_showcase_details.visible = true
|
||||||
|
|
||||||
|
|
||||||
func _update_bluegill_count(count: int) -> void:
|
func _update_bluegill_count(count: int) -> void:
|
||||||
_bluegill_count_label.text = "Bluegill: %d" % count
|
_bluegill_count_label.text = "Bluegill: %d" % count
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ script = ExtResource("1_ui")
|
||||||
offset_left = 16.0
|
offset_left = 16.0
|
||||||
offset_top = 16.0
|
offset_top = 16.0
|
||||||
offset_right = 176.0
|
offset_right = 176.0
|
||||||
offset_bottom = 62.0
|
offset_bottom = 104.0
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="InventoryPanel"]
|
[node name="MarginContainer" type="MarginContainer" parent="InventoryPanel"]
|
||||||
theme_override_constants/margin_left = 12
|
theme_override_constants/margin_left = 12
|
||||||
|
|
@ -29,10 +29,20 @@ theme_override_constants/margin_top = 8
|
||||||
theme_override_constants/margin_right = 12
|
theme_override_constants/margin_right = 12
|
||||||
theme_override_constants/margin_bottom = 8
|
theme_override_constants/margin_bottom = 8
|
||||||
|
|
||||||
[node name="BluegillCountLabel" type="Label" parent="InventoryPanel/MarginContainer"]
|
[node name="VBoxContainer" type="VBoxContainer" parent="InventoryPanel/MarginContainer"]
|
||||||
|
|
||||||
|
[node name="BluegillCountLabel" type="Label" parent="InventoryPanel/MarginContainer/VBoxContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
text = "Bluegill: 0"
|
text = "Bluegill: 0"
|
||||||
|
|
||||||
|
[node name="BassCountLabel" type="Label" parent="InventoryPanel/MarginContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
text = "Bass: 0"
|
||||||
|
|
||||||
|
[node name="CarpCountLabel" type="Label" parent="InventoryPanel/MarginContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
text = "Carp: 0"
|
||||||
|
|
||||||
[node name="FishingPanel" type="PanelContainer" parent="."]
|
[node name="FishingPanel" type="PanelContainer" parent="."]
|
||||||
anchors_preset = 7
|
anchors_preset = 7
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
|
|
@ -61,6 +71,11 @@ visible = false
|
||||||
text = ""
|
text = ""
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ShowcaseDetails" type="Label" parent="FishingPanel/MarginContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="CatchTrack" type="Control" parent="FishingPanel/MarginContainer/VBoxContainer"]
|
[node name="CatchTrack" type="Control" parent="FishingPanel/MarginContainer/VBoxContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
visible = false
|
visible = false
|
||||||
|
|
|
||||||
8
world/fishable_water_region.gd
Normal file
8
world/fishable_water_region.gd
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
class_name FishableWaterRegion
|
||||||
|
extends Area3D
|
||||||
|
|
||||||
|
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||||
|
|
||||||
|
@export var location_tags: Array[StringName] = []
|
||||||
|
@export var fish_pool: FishPoolType
|
||||||
|
@export var selection_priority: int = 0
|
||||||
1
world/fishable_water_region.gd.uid
Normal file
1
world/fishable_water_region.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b6fp2obu0y2d0
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
[gd_scene load_steps=12 format=3]
|
[gd_scene load_steps=14 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://world/fishable_water_region.gd" id="1_water_region"]
|
||||||
|
[ext_resource type="Resource" path="res://fish/pools/test_water_pool.tres" id="2_fish_pool"]
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="GroundShape"]
|
[sub_resource type="BoxShape3D" id="GroundShape"]
|
||||||
size = Vector3(30, 1, 30)
|
size = Vector3(30, 1, 30)
|
||||||
|
|
@ -77,6 +80,9 @@ position = Vector3(0, 0.2, -29.75)
|
||||||
collision_layer = 4
|
collision_layer = 4
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
monitoring = false
|
monitoring = false
|
||||||
|
script = ExtResource("1_water_region")
|
||||||
|
location_tags = Array[StringName]([&"test_lake"])
|
||||||
|
fish_pool = ExtResource("2_fish_pool")
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="FishableWater"]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="FishableWater"]
|
||||||
shape = SubResource("FishableWaterShape")
|
shape = SubResource("FishableWaterShape")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue