feat: shared creature root, enemy definitions, defend duty, and player downing
This commit is contained in:
@@ -48,7 +48,12 @@ func get_interaction_position() -> Vector3:
|
||||
|
||||
|
||||
func supports_action(candidate_action_id: StringName) -> bool:
|
||||
return action_id == candidate_action_id
|
||||
if action_id == candidate_action_id:
|
||||
return true
|
||||
return (
|
||||
action_id == SimulationIds.ACTION_PATROL
|
||||
and candidate_action_id == SimulationIds.ACTION_DEFEND
|
||||
)
|
||||
|
||||
|
||||
func _update_presentation() -> void:
|
||||
|
||||
@@ -1,48 +1,39 @@
|
||||
class_name HostileCombatant
|
||||
extends Node3D
|
||||
extends CreatureVisual
|
||||
|
||||
const FOLLOW_SPEED := 6.0
|
||||
const ARRIVE_DISTANCE := 0.5
|
||||
|
||||
var combatant_id: StringName
|
||||
var combatant: CombatantStateRecord
|
||||
var simulation_manager: Node
|
||||
var is_dead_visual := false
|
||||
var flash_tween: Tween
|
||||
var spawn_scale := Vector3.ONE
|
||||
var _flash_tween: Tween
|
||||
var _spawn_tween: Tween
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
spawn_scale = Vector3.ONE
|
||||
add_to_group("grass_interactors")
|
||||
scale = Vector3.ONE * 0.001
|
||||
var tween := create_tween()
|
||||
tween.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
||||
tween.tween_property(self, "scale", Vector3.ONE, 0.28)
|
||||
_spawn_tween = create_tween()
|
||||
_spawn_tween.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
||||
_spawn_tween.tween_property(self, "scale", Vector3.ONE, 0.28)
|
||||
|
||||
|
||||
func bind(combatant_id_value: StringName, manager: Node) -> void:
|
||||
combatant_id = combatant_id_value
|
||||
creature_id = combatant_id_value
|
||||
simulation_manager = manager
|
||||
combatant = manager.get_combatant(combatant_id) as CombatantStateRecord
|
||||
combatant = manager.get_combatant(creature_id) as CombatantStateRecord
|
||||
if combatant == null:
|
||||
return
|
||||
global_position = combatant.get_position()
|
||||
_build_body(combatant.get_kind())
|
||||
sim_position = combatant.get_position()
|
||||
_build_from_definition()
|
||||
combatant.changed.connect(_on_combatant_changed)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
func _process(_delta: float) -> void:
|
||||
if is_dead_visual or combatant == null or not is_instance_valid(combatant):
|
||||
return
|
||||
if not combatant.is_alive():
|
||||
_play_death()
|
||||
play_death()
|
||||
return
|
||||
var target_position := combatant.get_position()
|
||||
var to_target := target_position - global_position
|
||||
if to_target.length() <= ARRIVE_DISTANCE:
|
||||
return
|
||||
global_position += to_target.normalized() * minf(FOLLOW_SPEED * delta, to_target.length())
|
||||
rotation.y = lerp_angle(rotation.y, atan2(to_target.x, to_target.z), delta * 8.0)
|
||||
set_sim_position(combatant.get_position())
|
||||
|
||||
|
||||
func _on_combatant_changed(_state: CombatantStateRecord) -> void:
|
||||
@@ -52,42 +43,54 @@ func _on_combatant_changed(_state: CombatantStateRecord) -> void:
|
||||
|
||||
|
||||
func _flash_hit() -> void:
|
||||
if flash_tween != null and flash_tween.is_valid():
|
||||
flash_tween.kill()
|
||||
flash_tween = create_tween()
|
||||
flash_tween.tween_property(self, "scale", Vector3.ONE * 0.85, 0.06)
|
||||
flash_tween.tween_property(self, "scale", Vector3.ONE, 0.12)
|
||||
if _flash_tween != null and _flash_tween.is_valid():
|
||||
_flash_tween.kill()
|
||||
_flash_tween = create_tween()
|
||||
_flash_tween.tween_property(self, "scale", _definition_scale() * 0.85, 0.06)
|
||||
_flash_tween.tween_property(self, "scale", _definition_scale(), 0.12)
|
||||
|
||||
|
||||
func _play_death() -> void:
|
||||
func play_death() -> void:
|
||||
is_dead_visual = true
|
||||
stop_travel_visual()
|
||||
var tween := create_tween()
|
||||
tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
||||
tween.tween_property(self, "scale", Vector3(0.001, 0.001, 0.001), 0.45)
|
||||
tween.tween_callback(queue_free)
|
||||
super.play_death()
|
||||
|
||||
|
||||
func stop_travel_visual() -> void:
|
||||
pass
|
||||
func _definition_scale() -> Vector3:
|
||||
var definition := _enemy_definition()
|
||||
return definition.visual_scale if definition != null else Vector3.ONE
|
||||
|
||||
|
||||
func _build_body(kind: StringName) -> void:
|
||||
if kind == SimulationIds.COMBATANT_KIND_WOLF:
|
||||
_build_wolf()
|
||||
else:
|
||||
_build_raider()
|
||||
func _build_from_definition() -> void:
|
||||
var definition := _enemy_definition()
|
||||
if definition != null:
|
||||
move_speed = definition.move_speed
|
||||
scale = definition.visual_scale
|
||||
if definition.kind == SimulationIds.COMBATANT_KIND_WOLF:
|
||||
_build_wolf(definition)
|
||||
else:
|
||||
_build_raider(definition)
|
||||
|
||||
|
||||
func _build_raider() -> void:
|
||||
func _enemy_definition() -> EnemyDefinition:
|
||||
if combatant == null:
|
||||
return null
|
||||
var enemy_id: StringName = (
|
||||
&"enemy_wolf"
|
||||
if combatant.get_kind() == SimulationIds.COMBATANT_KIND_WOLF
|
||||
else &"enemy_raider"
|
||||
)
|
||||
return SimulationEnemies.get_enemy(enemy_id)
|
||||
|
||||
|
||||
func _build_raider(definition: EnemyDefinition) -> void:
|
||||
var tunic := StandardMaterial3D.new()
|
||||
tunic.albedo_color = Color(0.4, 0.13, 0.1, 1.0)
|
||||
tunic.albedo_color = definition.body_color
|
||||
tunic.roughness = 0.85
|
||||
var skin := StandardMaterial3D.new()
|
||||
skin.albedo_color = Color(0.8, 0.58, 0.36, 1.0)
|
||||
skin.roughness = 0.9
|
||||
var metal := StandardMaterial3D.new()
|
||||
metal.albedo_color = Color(0.55, 0.55, 0.58, 1.0)
|
||||
metal.albedo_color = definition.accent_color
|
||||
metal.roughness = 0.35
|
||||
|
||||
_add_mesh("Body", CylinderMesh.new(), Vector3(0.0, 1.1, 0.0), tunic)
|
||||
@@ -104,12 +107,12 @@ func _build_raider() -> void:
|
||||
)
|
||||
|
||||
|
||||
func _build_wolf() -> void:
|
||||
func _build_wolf(definition: EnemyDefinition) -> void:
|
||||
var fur := StandardMaterial3D.new()
|
||||
fur.albedo_color = Color(0.32, 0.32, 0.34, 1.0)
|
||||
fur.albedo_color = definition.body_color
|
||||
fur.roughness = 0.95
|
||||
var eye := StandardMaterial3D.new()
|
||||
eye.albedo_color = Color(0.95, 0.5, 0.12, 1.0)
|
||||
eye.albedo_color = definition.accent_color
|
||||
eye.roughness = 0.3
|
||||
|
||||
var body := SphereMesh.new()
|
||||
|
||||
@@ -66,7 +66,7 @@ func _on_combatant_spawned(combatant_id: StringName) -> void:
|
||||
func _on_combatant_died(combatant_id: StringName) -> void:
|
||||
var scene := hostiles.get(combatant_id) as HostileCombatant
|
||||
if scene != null:
|
||||
scene._play_death()
|
||||
scene.play_death()
|
||||
hostiles.erase(combatant_id)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
class_name CreatureVisual
|
||||
extends Node3D
|
||||
|
||||
signal position_changed(creature_id: StringName, active_position: Vector3)
|
||||
signal arrived_at_sim_position(creature_id: StringName)
|
||||
|
||||
const ARRIVAL_DISTANCE := 0.5
|
||||
const PATH_RETARGET_DISTANCE := 0.8
|
||||
|
||||
@export_range(0.5, 12.0, 0.1) var move_speed := 3.5
|
||||
@export_range(0.05, 1.0, 0.05) var waypoint_reached_distance := 0.3
|
||||
@export_range(1.0, 20.0, 0.5) var rotation_speed := 8.0
|
||||
|
||||
var creature_id: StringName
|
||||
var sim_position := Vector3.INF
|
||||
var is_dead_visual := false
|
||||
|
||||
var current_path := PackedVector3Array()
|
||||
var path_index := 0
|
||||
var path_target := Vector3.INF
|
||||
var path_request_id := 0
|
||||
var path_pending := false
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if is_dead_visual:
|
||||
return
|
||||
if not sim_position.is_finite():
|
||||
return
|
||||
if _horizontal_distance_to(sim_position) <= ARRIVAL_DISTANCE:
|
||||
_stop_moving()
|
||||
return
|
||||
_ensure_path()
|
||||
if path_pending:
|
||||
return
|
||||
if current_path.is_empty() or path_index >= current_path.size():
|
||||
_request_path()
|
||||
return
|
||||
var next_position := current_path[path_index]
|
||||
var offset := next_position - global_position
|
||||
if offset.length() <= waypoint_reached_distance:
|
||||
path_index += 1
|
||||
return
|
||||
var direction := offset.normalized()
|
||||
global_position = global_position.move_toward(next_position, move_speed * delta)
|
||||
position_changed.emit(creature_id, global_position)
|
||||
var target_angle := atan2(direction.x, direction.z)
|
||||
rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta)
|
||||
|
||||
|
||||
func set_sim_position(position: Vector3) -> void:
|
||||
if not position.is_finite():
|
||||
return
|
||||
var moved := (
|
||||
not path_target.is_finite()
|
||||
or _horizontal_distance_to_position(sim_position, position) > PATH_RETARGET_DISTANCE
|
||||
)
|
||||
sim_position = position
|
||||
if moved and not is_dead_visual:
|
||||
_request_path()
|
||||
|
||||
|
||||
func play_death() -> void:
|
||||
is_dead_visual = true
|
||||
_stop_moving()
|
||||
var tween := create_tween()
|
||||
tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
||||
tween.tween_property(self, "scale", Vector3(0.001, 0.001, 0.001), 0.45)
|
||||
tween.tween_callback(queue_free)
|
||||
|
||||
|
||||
func build_visual() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _ensure_path() -> void:
|
||||
if (
|
||||
path_pending
|
||||
or (
|
||||
path_target.is_finite()
|
||||
and (
|
||||
_horizontal_distance_to_position(path_target, sim_position)
|
||||
<= PATH_RETARGET_DISTANCE
|
||||
)
|
||||
)
|
||||
):
|
||||
return
|
||||
_request_path()
|
||||
|
||||
|
||||
func _request_path() -> void:
|
||||
path_request_id += 1
|
||||
var request_id := path_request_id
|
||||
path_target = sim_position
|
||||
current_path = PackedVector3Array()
|
||||
path_index = 0
|
||||
path_pending = true
|
||||
await get_tree().physics_frame
|
||||
if request_id != path_request_id or is_dead_visual:
|
||||
return
|
||||
if _horizontal_distance_to(sim_position) <= ARRIVAL_DISTANCE:
|
||||
path_pending = false
|
||||
return
|
||||
current_path = NavigationServer3D.map_get_path(
|
||||
get_world_3d().navigation_map, global_position, sim_position, true
|
||||
)
|
||||
path_pending = false
|
||||
if current_path.is_empty():
|
||||
_stop_moving()
|
||||
|
||||
|
||||
func _stop_moving() -> void:
|
||||
path_request_id += 1
|
||||
current_path = PackedVector3Array()
|
||||
path_index = 0
|
||||
path_pending = false
|
||||
path_target = Vector3.INF
|
||||
|
||||
|
||||
func _horizontal_distance_to(target_position: Vector3) -> float:
|
||||
return _horizontal_distance_to_position(global_position, target_position)
|
||||
|
||||
|
||||
static func _horizontal_distance_to_position(from_position: Vector3, to_position: Vector3) -> float:
|
||||
return Vector2(from_position.x, from_position.z).distance_to(
|
||||
Vector2(to_position.x, to_position.z)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://6r66un51bane
|
||||
Reference in New Issue
Block a user