]> purplebirdman git - frog-ninja.git/blob - asset/character/model/character_model.gd
Squashed commit of the following:
[frog-ninja.git] / asset / character / model / character_model.gd
1 extends Node3D
2 class_name CharacterModel
3 ## The base model class for any input-driven character
4 ##
5 ## Drives all the logical features of a character, including the states
6 ##
7
8
9 @onready var player: Player = $".."
10 @onready var skeleton: AnimatedSkeleton3D = $Skeleton3D
11 @onready var animator: AnimationPlayer = $AnimationPlayer
12
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
19
20
21 var states: Dictionary = {}
22 var current_state: State
23
24
25 func _ready() -> void:
26         assemble_character_states()
27         set_weapon_collision()
28         set_hitbox_collision()
29         assign_current_state("Idle")
30
31
32 func set_hitbox_collision():
33         hitbox.collision_layer = player.collision_layer
34         hitbox.collision_mask = player.collision_mask
35
36
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]:
40                 if w is Weapon:
41                         w.equipped_by = player
42                         w.collision_layer = player.collision_layer
43                         w.collision_mask = player.collision_mask
44
45
46 func set_hitbox_monitoring(b: bool):
47         hitbox.monitoring = b
48
49
50 func assemble_character_states():
51         for node in $States.get_children():
52                 if node is State:
53                         states[node.name] = node
54                         node.player = player
55                         node.states = states
56         print("Found states for " + player.name + ": " + str(states.keys()))
57
58
59 func translate_combat_actions_to_states(input: InputPacket):
60         for w in [weapon_r, weapon_l]:
61                 if w is Weapon:
62                         input.player_actions.append_array(w.translate_combat_actions_to_states(input))
63
64
65 func update(input: InputPacket, delta: float):
66         if not player.is_on_floor():
67                 input.player_actions.append("Fall")
68
69         # translate weapon layer into action states
70         translate_combat_actions_to_states(input)
71         var new_state_name := current_state.should_enter(input)
72         
73         # check combos for states to queue
74         if current_state.ok_to_queue_next_state():
75                 current_state.check_combos(input)
76
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
81         
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)
91         else:
92                 print_debug("Requested input state not found: " + new_state_name)
93
94
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)
101         
102         
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
108         
109         # reset all stateful information
110         animator.play(&"RESET")
111         animator.advance(0)
112         
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)