221 lines
6.8 KiB
GDScript
221 lines
6.8 KiB
GDScript
class_name GatherableData
|
|
extends Resource
|
|
|
|
const FishDataType = preload("res://fish/fish_data.gd")
|
|
const FishQualityType = preload("res://fish/fish_quality.gd")
|
|
const CalendarSeasonType = preload("res://world/calendar_season.gd")
|
|
|
|
enum PresentationMode {
|
|
VISIBLE_CREATURE,
|
|
WATER_SPURT,
|
|
}
|
|
|
|
enum SurfaceWaterRelation {
|
|
ANY,
|
|
ABOVE_SALT_WATER,
|
|
BELOW_SALT_WATER,
|
|
}
|
|
|
|
@export var type_id: StringName
|
|
@export var catch_data: FishDataType
|
|
@export var required_tool_id: StringName
|
|
@export var surface_materials: Array[StringName] = []
|
|
@export var diggable_area_id: StringName
|
|
@export var spawn_anchor_set_id: StringName
|
|
@export_range(-100.0, 100.0, 0.01) var minimum_surface_y: float = 0.08
|
|
@export_range(-100.0, 100.0, 0.01) var maximum_surface_y: float = 100.0
|
|
@export var surface_water_relation := SurfaceWaterRelation.ANY
|
|
@export_range(0.0, 2.0, 0.01) var waterline_margin: float = 0.01
|
|
@export_range(0, 64, 1) var population: int = 0
|
|
## Anchored gatherables can scale with authored/generated attachment geometry.
|
|
## Zero preserves the fixed population above.
|
|
@export_range(0.0, 1.0, 0.01) var spawn_anchor_occupancy_ratio := 0.0
|
|
## Zero leaves the anchor count as the only ceiling.
|
|
@export_range(0, 256, 1) var maximum_anchor_population := 0
|
|
## Optional density scaling for large generated surfaces. The authored
|
|
## population remains the minimum while the maximum keeps large maps bounded.
|
|
@export_range(0.0, 8.0, 0.01)
|
|
var surface_population_per_100_square_meters := 0.0
|
|
@export_range(0, 256, 1) var maximum_surface_population := 0
|
|
@export var presentation_mode: PresentationMode = PresentationMode.VISIBLE_CREATURE
|
|
@export var requires_sneaking: bool = true
|
|
@export_range(0.0, 120.0, 0.1) var active_lifetime_seconds: float = 0.0
|
|
@export_range(0.0, 10.0, 0.05) var movement_speed: float = 0.35
|
|
@export_range(0.1, 20.0, 0.1) var roam_radius: float = 3.5
|
|
@export_range(0.1, 20.0, 0.1) var scare_radius: float = 2.8
|
|
@export var quality_movement_speed_multipliers: Array[float] = [
|
|
1.0,
|
|
1.15,
|
|
1.35,
|
|
1.6,
|
|
2.0,
|
|
]
|
|
@export var quality_scare_radius_multipliers: Array[float] = [
|
|
1.0,
|
|
1.1,
|
|
1.25,
|
|
1.45,
|
|
1.7,
|
|
]
|
|
@export_range(0.1, 5.0, 0.05) var capture_radius: float = 0.7
|
|
@export_range(0.1, 10.0, 0.05) var interaction_range: float = 2.6
|
|
@export_range(0.1, 10.0, 0.05) var charge_duration: float = 2.0
|
|
@export_range(0.0001, 0.01, 0.00005) var sprite_pixel_size: float = 0.001
|
|
@export_range(-90.0, 90.0, 1.0) var sprite_tilt_degrees: float = -45.0
|
|
@export_category("Respawn Budget")
|
|
@export_range(0.0, 3600.0, 1.0) var capture_respawn_min_seconds: float = 480.0
|
|
@export_range(0.0, 3600.0, 1.0) var capture_respawn_max_seconds: float = 720.0
|
|
@export_range(0.0, 3600.0, 1.0) var scare_respawn_min_seconds: float = 45.0
|
|
@export_range(0.0, 3600.0, 1.0) var scare_respawn_max_seconds: float = 90.0
|
|
@export_range(0.0, 3600.0, 1.0) var minimum_respawn_spacing_seconds: float = 180.0
|
|
|
|
|
|
func is_valid() -> bool:
|
|
var uses_diggable_area: bool = not diggable_area_id.is_empty()
|
|
var uses_spawn_anchors: bool = not spawn_anchor_set_id.is_empty()
|
|
var movement_parameters_valid: bool = (
|
|
movement_speed >= 0.0
|
|
and (
|
|
presentation_mode == PresentationMode.WATER_SPURT
|
|
or (roam_radius > 0.0 and scare_radius > 0.0)
|
|
)
|
|
)
|
|
return (
|
|
not type_id.is_empty()
|
|
and catch_data != null
|
|
and catch_data.is_valid_catalog_entry()
|
|
and catch_data.collection_method != FishDataType.CollectionMethod.FISHING
|
|
and not required_tool_id.is_empty()
|
|
and (
|
|
uses_diggable_area
|
|
or uses_spawn_anchors
|
|
or not surface_materials.is_empty()
|
|
)
|
|
and maximum_surface_y >= minimum_surface_y
|
|
and (
|
|
not uses_diggable_area
|
|
or catch_data.collection_method
|
|
== FishDataType.CollectionMethod.DIGGING
|
|
)
|
|
and population > 0
|
|
and (
|
|
maximum_anchor_population == 0
|
|
or maximum_anchor_population >= population
|
|
)
|
|
and (
|
|
maximum_surface_population == 0
|
|
or maximum_surface_population >= population
|
|
)
|
|
and movement_parameters_valid
|
|
and _quality_multipliers_are_valid(
|
|
quality_movement_speed_multipliers
|
|
)
|
|
and _quality_multipliers_are_valid(
|
|
quality_scare_radius_multipliers
|
|
)
|
|
and capture_radius > 0.0
|
|
and interaction_range > 0.0
|
|
and charge_duration > 0.0
|
|
and sprite_pixel_size > 0.0
|
|
and capture_respawn_max_seconds >= capture_respawn_min_seconds
|
|
and scare_respawn_max_seconds >= scare_respawn_min_seconds
|
|
)
|
|
|
|
|
|
func is_stationary_hotspot() -> bool:
|
|
return presentation_mode == PresentationMode.WATER_SPURT
|
|
|
|
|
|
func is_stationary_spawn() -> bool:
|
|
return is_stationary_hotspot() or not spawn_anchor_set_id.is_empty()
|
|
|
|
|
|
func target_population_for_anchor_count(anchor_count: int) -> int:
|
|
if spawn_anchor_set_id.is_empty() or spawn_anchor_occupancy_ratio <= 0.0:
|
|
return population
|
|
if anchor_count <= 0:
|
|
return 0
|
|
var target := maxi(
|
|
population,
|
|
ceili(float(anchor_count) * spawn_anchor_occupancy_ratio),
|
|
)
|
|
target = mini(target, anchor_count)
|
|
if maximum_anchor_population > 0:
|
|
target = mini(target, maximum_anchor_population)
|
|
return target
|
|
|
|
|
|
func target_population_for_surface_area(surface_area: float) -> int:
|
|
if surface_population_per_100_square_meters <= 0.0:
|
|
return population
|
|
if surface_area <= 0.0:
|
|
return 0
|
|
var target := maxi(
|
|
population,
|
|
ceili(
|
|
surface_area
|
|
* surface_population_per_100_square_meters
|
|
/ 100.0
|
|
),
|
|
)
|
|
if maximum_surface_population > 0:
|
|
target = mini(target, maximum_surface_population)
|
|
return target
|
|
|
|
|
|
func can_be_scared() -> bool:
|
|
return not is_stationary_spawn() and scare_radius > 0.0
|
|
|
|
|
|
func get_movement_speed_for_quality(quality: int) -> float:
|
|
return movement_speed * _quality_multiplier(
|
|
quality_movement_speed_multipliers,
|
|
quality,
|
|
)
|
|
|
|
|
|
func get_scare_radius_for_quality(quality: int) -> float:
|
|
return scare_radius * _quality_multiplier(
|
|
quality_scare_radius_multipliers,
|
|
quality,
|
|
)
|
|
|
|
|
|
func get_respawn_delay(reason: StringName, rng: RandomNumberGenerator) -> float:
|
|
var minimum_seconds: float = capture_respawn_min_seconds
|
|
var maximum_seconds: float = capture_respawn_max_seconds
|
|
if reason == &"scared":
|
|
minimum_seconds = scare_respawn_min_seconds
|
|
maximum_seconds = scare_respawn_max_seconds
|
|
if rng == null or maximum_seconds <= minimum_seconds:
|
|
return minimum_seconds
|
|
return rng.randf_range(minimum_seconds, maximum_seconds)
|
|
|
|
|
|
func is_available(season: int = CalendarSeasonType.UNKNOWN) -> bool:
|
|
return (
|
|
catch_data != null
|
|
and catch_data.active
|
|
and catch_data.is_available_in_season(season)
|
|
and is_valid()
|
|
)
|
|
|
|
|
|
func _quality_multiplier(values: Array[float], quality: int) -> float:
|
|
if values.size() != FishQualityType.TIER_COUNT:
|
|
return 1.0
|
|
var safe_quality: int = (
|
|
quality
|
|
if FishQualityType.is_valid(quality)
|
|
else FishQualityType.Tier.BORING
|
|
)
|
|
return maxf(values[safe_quality], 0.0)
|
|
|
|
|
|
func _quality_multipliers_are_valid(values: Array[float]) -> bool:
|
|
if values.size() != FishQualityType.TIER_COUNT:
|
|
return false
|
|
for multiplier: float in values:
|
|
if not is_finite(multiplier) or multiplier <= 0.0:
|
|
return false
|
|
return true
|