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 set_hitbox_monitoring(b: bool):
50 func assemble_character_states():
51 for node in $States.get_children():
53 states[node.name] = node
56 print("Found states for " + player.name + ": " + str(states.keys()))
59 func translate_combat_actions_to_states(input: InputPacket):
60 for w in [weapon_r, weapon_l]:
62 input.player_actions.append_array(w.translate_combat_actions_to_states(input))
65 func update(input: InputPacket, delta: float):
66 if not player.is_on_floor():
67 input.player_actions.append("Fall")
69 # translate weapon layer into action states
70 translate_combat_actions_to_states(input)
71 var new_state_name := current_state.should_enter(input)
73 # check combos for states to queue
74 if current_state.ok_to_queue_next_state():
75 current_state.check_combos(input)
77 # a queued state will always override an action list state
78 if current_state.has_queued_state and current_state.ok_to_transition_to_queued_state():
79 current_state.has_queued_state = false
80 new_state_name = current_state.queued_state_name
82 # switch to appropriate state
83 if states.has(new_state_name):
84 # TODO: transition to same state!
85 if states[new_state_name] != current_state:
86 if states[new_state_name].get_remaining_cooldown_time() == 0:
87 if player.stamina_points > 0:
88 player.stamina_points -= states[new_state_name].stamina_toll
89 switch_to(new_state_name)
90 current_state.update(input, delta)
92 print_debug("Requested input state not found: " + new_state_name)
95 func switch_to(state_name: String):
96 animator.animation_started.disconnect(current_state._when_animation_started)
97 animator.animation_finished.disconnect(current_state._when_animation_finished)
98 current_state.on_exit_state()
99 current_state.mark_exit_state()
100 assign_current_state(state_name)
103 func assign_current_state(state_name: String):
104 current_state = states[state_name]
105 current_state.mark_enter_state()
106 current_state.on_enter_state()
107 player.state_name = current_state.name
109 # reset all stateful information
110 animator.play(&"RESET")
113 # connect signals and play state's animation
114 animator.animation_started.connect(current_state._when_animation_started)
115 animator.animation_finished.connect(current_state._when_animation_finished)
116 animator.play_section(current_state.animation_name, 0.0, -1, -1, current_state.animation_speed_scale)