Make crab behavior reflect catch quality
This commit is contained in:
parent
abedd64782
commit
13fc64b80c
4 changed files with 161 additions and 11 deletions
|
|
@ -2,6 +2,7 @@ class_name GatherableData
|
|||
extends Resource
|
||||
|
||||
const FishDataType = preload("res://fish/fish_data.gd")
|
||||
const FishQualityType = preload("res://fish/fish_quality.gd")
|
||||
|
||||
@export var type_id: StringName
|
||||
@export var catch_data: FishDataType
|
||||
|
|
@ -12,6 +13,20 @@ const FishDataType = preload("res://fish/fish_data.gd")
|
|||
@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
|
||||
|
|
@ -37,6 +52,12 @@ func is_valid() -> bool:
|
|||
and movement_speed >= 0.0
|
||||
and roam_radius > 0.0
|
||||
and scare_radius > 0.0
|
||||
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
|
||||
|
|
@ -46,6 +67,20 @@ func is_valid() -> bool:
|
|||
)
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -59,3 +94,23 @@ func get_respawn_delay(reason: StringName, rng: RandomNumberGenerator) -> float:
|
|||
|
||||
func is_available() -> bool:
|
||||
return catch_data != null and catch_data.active 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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const FishCatchType = preload("res://fish/fish_catch.gd")
|
|||
const FishDataType = preload("res://fish/fish_data.gd")
|
||||
const FishExperienceType = preload("res://fish/fish_experience.gd")
|
||||
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||
const FishQualityType = preload("res://fish/fish_quality.gd")
|
||||
const FishSelectorType = preload("res://fish/fish_selector.gd")
|
||||
const GatherableCatalogType = preload("res://gathering/gatherable_catalog.gd")
|
||||
const GatherableDataType = preload("res://gathering/gatherable_data.gd")
|
||||
|
|
@ -278,6 +279,7 @@ func _spawn_entity(entry: GatherableDataType) -> void:
|
|||
var position: Vector3 = _sample_surface_position(entry)
|
||||
if not position.is_finite():
|
||||
return
|
||||
var quality: int = FishQualityType.roll(_rng)
|
||||
var entity_id: String = _new_id("world")
|
||||
var state: Dictionary = {
|
||||
"entity_id": entity_id,
|
||||
|
|
@ -286,6 +288,7 @@ func _spawn_entity(entry: GatherableDataType) -> void:
|
|||
"position": position,
|
||||
"target": _sample_surface_position(entry, position, entry.roam_radius),
|
||||
"yaw": _rng.randf_range(-PI, PI),
|
||||
"quality": quality,
|
||||
"revision": 1,
|
||||
"locked": false,
|
||||
}
|
||||
|
|
@ -308,6 +311,7 @@ func _update_host_entities(delta: float) -> void:
|
|||
var entry := state.get("data") as GatherableDataType
|
||||
if entry == null:
|
||||
continue
|
||||
var quality: int = _get_state_quality(state)
|
||||
var position: Vector3 = state["position"]
|
||||
var target: Vector3 = state["target"]
|
||||
var horizontal_delta := Vector3(
|
||||
|
|
@ -331,7 +335,7 @@ func _update_host_entities(delta: float) -> void:
|
|||
)
|
||||
if not horizontal_delta.is_zero_approx():
|
||||
var step: float = minf(
|
||||
entry.movement_speed * delta,
|
||||
entry.get_movement_speed_for_quality(quality) * delta,
|
||||
horizontal_delta.length(),
|
||||
)
|
||||
var direction: Vector3 = horizontal_delta.normalized()
|
||||
|
|
@ -345,12 +349,17 @@ func _update_host_entities(delta: float) -> void:
|
|||
state["yaw"] = atan2(-direction.x, -direction.z)
|
||||
state["position"] = position
|
||||
_entities[entity_id] = state
|
||||
if _should_scare(entry, position):
|
||||
if _should_scare(entry, position, quality):
|
||||
_despawn_entity(entity_id, &"scared", true, true)
|
||||
|
||||
|
||||
func _should_scare(entry: GatherableDataType, position: Vector3) -> bool:
|
||||
var radius_squared: float = entry.scare_radius * entry.scare_radius
|
||||
func _should_scare(
|
||||
entry: GatherableDataType,
|
||||
position: Vector3,
|
||||
quality: int,
|
||||
) -> bool:
|
||||
var scare_radius: float = entry.get_scare_radius_for_quality(quality)
|
||||
var radius_squared: float = scare_radius * scare_radius
|
||||
for peer_id: int in _spawn_service.get_peer_ids():
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
if (
|
||||
|
|
@ -571,9 +580,7 @@ func _handle_interaction_finish(peer_id: int, data: Dictionary) -> void:
|
|||
if not error.is_empty():
|
||||
_send_interaction_result(peer_id, request_id, false, error)
|
||||
return
|
||||
var selector := FishSelectorType.new()
|
||||
selector.begin_roll()
|
||||
var fish_catch: FishCatch = selector.create_catch(entry.catch_data)
|
||||
var fish_catch: FishCatch = _create_catch_for_state(entry, state)
|
||||
if fish_catch == null or not fish_catch.is_valid():
|
||||
_send_interaction_result(peer_id, request_id, false, "The catch could not be recorded.")
|
||||
return
|
||||
|
|
@ -600,6 +607,35 @@ func _handle_interaction_finish(peer_id: int, data: Dictionary) -> void:
|
|||
receive_capacity_probe.rpc_id(peer_id, probe)
|
||||
|
||||
|
||||
func _create_catch_for_state(
|
||||
entry: GatherableDataType,
|
||||
state: Dictionary,
|
||||
) -> FishCatch:
|
||||
var selector := FishSelectorType.new()
|
||||
selector.begin_roll()
|
||||
var fish_catch: FishCatch = selector.create_catch(entry.catch_data)
|
||||
if fish_catch == null:
|
||||
return null
|
||||
var quality: int = _get_state_quality(state)
|
||||
fish_catch.quality = quality
|
||||
fish_catch.sale_value = FishQualityType.apply_sale_value(
|
||||
entry.catch_data.get_sale_value_for_weight(fish_catch.weight_lb),
|
||||
quality,
|
||||
)
|
||||
return fish_catch
|
||||
|
||||
|
||||
func _get_state_quality(state: Dictionary) -> int:
|
||||
var quality: int = int(
|
||||
state.get("quality", FishQualityType.Tier.BORING)
|
||||
)
|
||||
return (
|
||||
quality
|
||||
if FishQualityType.is_valid(quality)
|
||||
else FishQualityType.Tier.BORING
|
||||
)
|
||||
|
||||
|
||||
@rpc(
|
||||
"any_peer",
|
||||
"call_remote",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func _run_host() -> void:
|
|||
assert(session.start_dedicated_host(TEST_PORT, 8, "127.0.0.1"))
|
||||
assert(session.set_host_open(true))
|
||||
await _wait_for_population(service)
|
||||
_validate_population(service)
|
||||
_validate_population(service, true)
|
||||
|
||||
var remote_peer_id: int = 0
|
||||
var join_deadline: int = Time.get_ticks_msec() + 20000
|
||||
|
|
@ -51,7 +51,7 @@ func _run_host() -> void:
|
|||
):
|
||||
await process_frame
|
||||
assert(not session.is_authenticated_peer(remote_peer_id))
|
||||
_validate_population(service)
|
||||
_validate_population(service, true)
|
||||
_validate_respawn_budget(service)
|
||||
print("World spawn multiplayer host validation: PASS")
|
||||
session.disconnect_session("")
|
||||
|
|
@ -82,7 +82,7 @@ func _run_client() -> void:
|
|||
NetworkProtocol.WORLD_SPAWN_CAPABILITY
|
||||
))
|
||||
await _wait_for_population(service)
|
||||
_validate_population(service)
|
||||
_validate_population(service, false)
|
||||
print("World spawn multiplayer client validation: PASS")
|
||||
session.disconnect_session("")
|
||||
main.queue_free()
|
||||
|
|
@ -101,7 +101,7 @@ func _wait_for_population(service: Node) -> void:
|
|||
assert(false, "Timed out waiting for the world spawn population.")
|
||||
|
||||
|
||||
func _validate_population(service: Node) -> void:
|
||||
func _validate_population(service: Node, expect_authoritative_state: bool) -> void:
|
||||
var entities: Dictionary = service.get("_entities")
|
||||
var presentations: Dictionary = service.get("_presentations")
|
||||
assert(entities.size() == EXPECTED_POPULATION)
|
||||
|
|
@ -112,6 +112,13 @@ func _validate_population(service: Node) -> void:
|
|||
assert(typeof(position) == TYPE_VECTOR3)
|
||||
assert((position as Vector3).is_finite())
|
||||
assert((position as Vector3).y > 0.08)
|
||||
if expect_authoritative_state:
|
||||
var quality: int = int(state.get("quality", -1))
|
||||
assert(FishQuality.is_valid(quality))
|
||||
var entry := state.get("data") as GatherableData
|
||||
assert(entry != null)
|
||||
assert(entry.get_movement_speed_for_quality(quality) > 0.0)
|
||||
assert(entry.get_scare_radius_for_quality(quality) > 0.0)
|
||||
|
||||
|
||||
func _validate_respawn_budget(service: Node) -> void:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ func _validate_catalog_statuses() -> void:
|
|||
assert(brown.catch_data.logbook_section == FishData.LogbookSection.SHELLFISH)
|
||||
assert(brown.population == 2)
|
||||
assert(is_equal_approx(brown.charge_duration, 2.0))
|
||||
_validate_quality_behavior(brown)
|
||||
assert(is_equal_approx(brown.capture_respawn_min_seconds, 480.0))
|
||||
assert(is_equal_approx(brown.capture_respawn_max_seconds, 720.0))
|
||||
assert(is_equal_approx(brown.scare_respawn_min_seconds, 45.0))
|
||||
|
|
@ -63,6 +64,57 @@ func _validate_catalog_statuses() -> void:
|
|||
assert(scared_delay >= 45.0 and scared_delay <= 90.0)
|
||||
|
||||
|
||||
func _validate_quality_behavior(entry: GatherableData) -> void:
|
||||
var prior_speed: float = -1.0
|
||||
var prior_scare_radius: float = -1.0
|
||||
for quality: int in FishQuality.TIER_COUNT:
|
||||
var movement_speed: float = entry.get_movement_speed_for_quality(
|
||||
quality
|
||||
)
|
||||
var scare_radius: float = entry.get_scare_radius_for_quality(quality)
|
||||
assert(movement_speed > prior_speed)
|
||||
assert(scare_radius > prior_scare_radius)
|
||||
prior_speed = movement_speed
|
||||
prior_scare_radius = scare_radius
|
||||
assert(
|
||||
is_equal_approx(
|
||||
entry.get_movement_speed_for_quality(FishQuality.Tier.BORING),
|
||||
entry.movement_speed,
|
||||
)
|
||||
)
|
||||
assert(
|
||||
is_equal_approx(
|
||||
entry.get_movement_speed_for_quality(FishQuality.Tier.SHINY),
|
||||
entry.movement_speed * 2.0,
|
||||
)
|
||||
)
|
||||
assert(
|
||||
is_equal_approx(
|
||||
entry.get_scare_radius_for_quality(FishQuality.Tier.SHINY),
|
||||
entry.scare_radius * 1.7,
|
||||
)
|
||||
)
|
||||
var service := NetworkWorldSpawnService.new()
|
||||
var spawned_catch := service.call(
|
||||
"_create_catch_for_state",
|
||||
entry,
|
||||
{"quality": FishQuality.Tier.EXCEPTIONAL},
|
||||
) as FishCatch
|
||||
assert(spawned_catch != null and spawned_catch.is_valid())
|
||||
assert(spawned_catch.quality == FishQuality.Tier.EXCEPTIONAL)
|
||||
assert(
|
||||
spawned_catch.sale_value
|
||||
== FishQuality.apply_sale_value(
|
||||
entry.catch_data.get_sale_value_for_weight(
|
||||
spawned_catch.weight_lb
|
||||
),
|
||||
FishQuality.Tier.EXCEPTIONAL,
|
||||
)
|
||||
)
|
||||
spawned_catch = null
|
||||
service.free()
|
||||
|
||||
|
||||
func _validate_envelopes() -> void:
|
||||
var entity: Dictionary = {
|
||||
"entity_id": "world:sample",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue