extends Node3D class_name CharacterModel ## The base model class for any input-driven character ## ## Drives all the logical features of a character, including the states ## @onready var player: Player = $".." @onready var skeleton: AnimatedSkeleton3D = $Skeleton3D @onready var animator: AnimationPlayer = $AnimationPlayer # TODO: these should be discovered dynamically, and their attack states # should be discovered at the same time @onready var weapon_r: Weapon = $RightHand/hand_socket_R/Sword @onready var weapon_l: Weapon = $LeftHand/hand_socket_L/Gun @onready var hitbox: Hitbox = $Root/Hitbox @onready var shield: Node3D = $Shield var states: Dictionary = {} var current_state: State func _ready() -> void: assemble_character_states() set_weapon_collision() set_hitbox_collision() assign_current_state("Idle") func set_hitbox_collision(): hitbox.collision_layer = player.collision_layer hitbox.collision_mask = player.collision_mask func set_weapon_collision(): # make sure hurtbox collision layers and mask are same as player's for w in [weapon_r, weapon_l]: if w is Weapon: w.equipped_by = player w.collision_layer = player.collision_layer w.collision_mask = player.collision_mask func assemble_character_states(): for node in $States.get_children(): if node is State: states[node.name] = node node.player = player node.states = states print("Found states for " + player.name + ": " + str(states.keys())) func translate_combat_actions_to_states(input: InputPacket): for w in [weapon_r, weapon_l]: if w is Weapon: input.player_actions.append_array(w.translate_combat_actions_to_states(input)) func update(input: InputPacket, delta: float): if not player.is_on_floor(): input.player_actions.append("Fall") # translate weapon layer into action states translate_combat_actions_to_states(input) var new_state_name := current_state.should_enter(input) # check combos for states to queue if current_state.ok_to_queue_next_state(): current_state.check_combos(input) # a queued state will always override an action list state if current_state.has_queued_state and current_state.ok_to_transition_to_queued_state(): current_state.has_queued_state = false new_state_name = current_state.queued_state_name # switch to appropriate state if states.has(new_state_name): # TODO: transition to same state! if states[new_state_name] != current_state: if states[new_state_name].get_remaining_cooldown_time() == 0: if player.stamina_points > 0: player.stamina_points -= states[new_state_name].stamina_toll switch_to(new_state_name) current_state.update(input, delta) else: print_debug("Requested input state not found: " + new_state_name) func switch_to(state_name: String): animator.animation_started.disconnect(current_state._when_animation_started) animator.animation_finished.disconnect(current_state._when_animation_finished) current_state.on_exit_state() current_state.mark_exit_state() assign_current_state(state_name) func assign_current_state(state_name: String): current_state = states[state_name] current_state.mark_enter_state() current_state.on_enter_state() player.state_name = current_state.name animator.animation_started.connect(current_state._when_animation_started) animator.animation_finished.connect(current_state._when_animation_finished) animator.play_section(current_state.animation_name, 0.0, -1, -1, current_state.animation_speed_scale)