class_name HostileCombatant extends Node3D 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 func _ready() -> void: spawn_scale = Vector3.ONE 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) func bind(combatant_id_value: StringName, manager: Node) -> void: combatant_id = combatant_id_value simulation_manager = manager combatant = manager.get_combatant(combatant_id) as CombatantStateRecord if combatant == null: return global_position = combatant.get_position() _build_body(combatant.get_kind()) combatant.changed.connect(_on_combatant_changed) 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() 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) func _on_combatant_changed(_state: CombatantStateRecord) -> void: if combatant == null or combatant.get_health() >= combatant.get_max_health(): return _flash_hit() 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) 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) func stop_travel_visual() -> void: pass func _build_body(kind: StringName) -> void: if kind == SimulationIds.COMBATANT_KIND_WOLF: _build_wolf() else: _build_raider() func _build_raider() -> void: var tunic := StandardMaterial3D.new() tunic.albedo_color = Color(0.4, 0.13, 0.1, 1.0) 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.roughness = 0.35 _add_mesh("Body", CylinderMesh.new(), Vector3(0.0, 1.1, 0.0), tunic) _add_mesh("Head", SphereMesh.new(), Vector3(0.0, 1.7, 0.0), skin) var blade := BoxMesh.new() blade.size = Vector3(0.06, 0.72, 0.14) _add_mesh("Sword", blade, Vector3(0.42, 1.25, 0.0), metal, Vector3(0.0, 0.0, -0.2)) var shield := CylinderMesh.new() shield.top_radius = 0.3 shield.bottom_radius = 0.3 shield.height = 0.12 _add_mesh( "Shield", shield, Vector3(-0.42, 1.15, 0.0), metal, Vector3.ZERO, Vector3(1.57, 0.0, 0.0) ) func _build_wolf() -> void: var fur := StandardMaterial3D.new() fur.albedo_color = Color(0.32, 0.32, 0.34, 1.0) fur.roughness = 0.95 var eye := StandardMaterial3D.new() eye.albedo_color = Color(0.95, 0.5, 0.12, 1.0) eye.roughness = 0.3 var body := SphereMesh.new() body.radius = 0.34 body.height = 0.68 _add_mesh("Body", body, Vector3(0.0, 0.55, 0.0), fur, Vector3.ZERO, Vector3(1.57, 0.0, 0.0)) var head := SphereMesh.new() head.radius = 0.2 head.height = 0.4 _add_mesh("Head", head, Vector3(0.0, 0.62, -0.42), fur, Vector3.ZERO, Vector3(1.57, 0.0, 0.0)) var tail := BoxMesh.new() tail.size = Vector3(0.1, 0.1, 0.5) _add_mesh("Tail", tail, Vector3(0.0, 0.72, 0.42), fur, Vector3(0.6, 0.0, 0.0)) var leg_mesh := CylinderMesh.new() leg_mesh.top_radius = 0.06 leg_mesh.bottom_radius = 0.06 leg_mesh.height = 0.42 for leg_x in [-0.18, 0.18]: for leg_z in [-0.2, 0.2]: _add_mesh("Leg", leg_mesh, Vector3(leg_x, 0.21, leg_z), fur) func _add_mesh( node_name: String, mesh: PrimitiveMesh, local_position: Vector3, material: Material, rotation_offset: Vector3 = Vector3.ZERO, mesh_rotation: Vector3 = Vector3.ZERO ) -> void: var mesh_instance := MeshInstance3D.new() mesh_instance.name = node_name mesh_instance.mesh = mesh mesh_instance.material_override = material mesh_instance.position = local_position if not rotation_offset.is_zero_approx(): mesh_instance.rotation = rotation_offset if not mesh_rotation.is_zero_approx(): mesh_instance.rotation += mesh_rotation add_child(mesh_instance)