2 class_name CharacterModel
3 ## The base model class for any input-driven character
5 ## Drives all the logical features of a character, including the states
9 @onready var player: Player = $".."
10 @onready var skeleton: AnimatedSkeleton3D = $Skeleton3D
11 @onready var animator: AnimationPlayer = $AnimationPlayer
13 # TODO: these should be discovered dynamically, and their attack states
14 # should be discovered at the same time
15 @onready var weapon_r: Weapon = $RightHand/hand_socket_R/Sword
16 @onready var weapon_l: Weapon = $LeftHand/hand_socket_L/Gun
17 @onready var hitbox: Hitbox = $Root/Hitbox
18 @onready var shield: Node3D = $Shield
21 var states: Dictionary = {}
22 var current_state: State
25 func _ready() -> void:
26 assemble_character_states()
27 set_weapon_collision()
28 set_hitbox_collision()
29 assign_current_state("Idle")
32 func set_hitbox_collision():
33 hitbox.collision_layer = player.collision_layer
34 hitbox.collision_mask = player.collision_mask
37 func set_weapon_collision():
38 # make sure hurtbox collision layers and mask are same as player's
39 for w in [weapon_r, weapon_l]:
41 w.equipped_by = player
42 w.collision_layer = player.collision_layer
43 w.collision_mask = player.collision_mask
46 func assemble_character_states():
47 for node in $States.get_children():
49 states[node.name] = node
52 print("Found states for " + player.name + ": " + str(states.keys()))
55 func translate_combat_actions_to_states(input: InputPacket):
56 for w in [weapon_r, weapon_l]:
58 input.player_actions.append_array(w.translate_combat_actions_to_states(input))
61 func update(input: InputPacket, delta: float):
62 if not player.is_on_floor():
63 input.player_actions.append("Fall")
65 # translate weapon layer into action states
66 translate_combat_actions_to_states(input)
67 var new_state_name := current_state.should_enter(input)
69 # check combos for states to queue
70 if current_state.ok_to_queue_next_state():
71 current_state.check_combos(input)
73 # a queued state will always override an action list state
74 if current_state.has_queued_state and current_state.ok_to_transition_to_queued_state():
75 current_state.has_queued_state = false
76 new_state_name = current_state.queued_state_name
78 # switch to appropriate state
79 if states.has(new_state_name):
80 # TODO: transition to same state!
81 if states[new_state_name] != current_state:
82 if states[new_state_name].get_remaining_cooldown_time() == 0:
83 if player.stamina_points > 0:
84 player.stamina_points -= states[new_state_name].stamina_toll
85 switch_to(new_state_name)
86 current_state.update(input, delta)
88 print_debug("Requested input state not found: " + new_state_name)
91 func switch_to(state_name: String):
92 animator.animation_started.disconnect(current_state._when_animation_started)
93 animator.animation_finished.disconnect(current_state._when_animation_finished)
94 current_state.on_exit_state()
95 current_state.mark_exit_state()
96 assign_current_state(state_name)
99 func assign_current_state(state_name: String):
100 current_state = states[state_name]
101 current_state.mark_enter_state()
102 current_state.on_enter_state()
103 player.state_name = current_state.name
105 animator.animation_started.connect(current_state._when_animation_started)
106 animator.animation_finished.connect(current_state._when_animation_finished)
107 animator.play_section(current_state.animation_name, 0.0, -1, -1, current_state.animation_speed_scale)