class_name Hero extends CharacterBody3D signal ready_state_changed(state: bool) signal spawned signal died @export var hero_class: HeroClass @export var camera: Camera3D @export var gun_camera: Camera3D @export var left_hand_sprite: Sprite3D @export var card_sprites: Array[CardInHand] @export var left_hand: Node3D @export var right_hand: Node3D @export var right_hand_animator: AnimationPlayer @export var edit_tool: EditTool @export var gauntlet_sprite: Sprite3D @export var sprite: EightDirectionSprite3D @export var hand_sprite: Sprite2D @export var interaction_raycast: RayCast3D @export var inventory: Inventory @export var gauntlet_card_1: CardInHand @export var gauntlet_card_2: CardInHand @export var pause_menu_scene: PackedScene @export var hud: HUD @export var movement: PlayerMovement @export var sprint_zoom_speed: float = 0.2 @export var player_name_tag: Label @export var weapon_swap_timer: Timer @export var ears: AudioListener3D @export var place_card_audio: AudioStreamPlayer @export var swap_card_audio: AudioStreamPlayer @export var ready_audio: AudioStreamPlayer @export var unready_audio: AudioStreamPlayer @export var fullpower_audio: AudioStreamPlayer @export var zeropower_audio: AudioStreamPlayer @export var swap_off_audio: AudioStreamPlayer @export var swap_on_audio: AudioStreamPlayer var weapons_spawn_count: int = 0 #Used to prevent node name collisions for multiplayer var inventory_selected_index: int = 0 var equipped_card: Card var offhand_card: Card var weapon: Weapon var offhand_weapon: Weapon var weapons_active: bool = false var paused: bool = false var editing_mode: bool = true var profile: PlayerProfile var ready_state: bool = false : set(value): ready_state = value ready_state_changed.emit(value) var currency: int = 0 : set(value): currency = value hud.set_currency_count(value) get: return currency func set_zoom_factor(value: float) -> void: movement.zoom_factor = value func _ready() -> void: if is_multiplayer_authority(): right_hand_animator.play("weapon_sway") right_hand_animator.speed_scale = 0 ears.make_current() camera.make_current() sprite.queue_free() hand_sprite.texture = hero_class.hand_texture player_name_tag.queue_free() for card: Card in hero_class.deck: inventory.add(card) else: camera.set_visible(false) gun_camera.set_visible(false) hud.set_visible(false) if weapon != null: weapon.set_raycast_origin(camera) sprite.texture.atlas = hero_class.texture check_left_hand_valid() Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func _physics_process(_delta: float) -> void: if !is_multiplayer_authority() or paused: return if movement.input_vector == Vector2.ZERO: right_hand_animator.speed_scale = 0 elif movement.sprinting: right_hand_animator.speed_scale = 1 else: right_hand_animator.speed_scale = 0.6 func _process(delta: float) -> void: if !is_multiplayer_authority() or paused: return if !movement.sprinting: movement.zoom_factor += sprint_zoom_speed * 2.0 * delta if movement.zoom_factor > 1.0: movement.zoom_factor = 1.0 if editing_mode: if interaction_raycast.is_colliding() and interaction_raycast.get_collider() is InteractButton: hud.set_hover_text(interaction_raycast.get_collider().hover_text) else: hud.unset_hover_text() if edit_tool.is_looking_at_tower_base: card_sprites[0].view_tower() else: card_sprites[0].view_weapon() if Input.is_action_just_pressed("Interact"): edit_tool.interact() if interaction_raycast.get_collider() is InteractButton: var button: InteractButton = interaction_raycast.get_collider() as InteractButton if currency >= button.press_cost: button.press(self) currency -= button.press_cost if interaction_raycast.get_collider() is ItemCard: add_card(interaction_raycast.get_collider().pick_up()) if Input.is_action_just_pressed("Equip In Gauntlet"): equip_weapon() if Input.is_action_just_pressed("Secondary Fire"): if equipped_card or offhand_card: swap_weapons() if Input.is_action_just_pressed("Select Next Card") and inventory.contents.size() > 1: increment_selected() swap_card_audio.play() if Input.is_action_just_pressed("Select Previous Card") and inventory.contents.size() > 1: decrement_selected() swap_card_audio.play() if Input.is_action_just_pressed("Primary Fire"): edit_tool.interact_key_held = true if Input.is_action_just_released("Primary Fire"): edit_tool.interact_key_held = false if weapon != null: weapon.release_trigger() weapon.release_second_trigger() else: if weapon and weapons_active: if Input.is_action_just_pressed("Primary Fire"): weapon.hold_trigger() if Input.is_action_just_released("Primary Fire"): weapon.release_trigger() if Input.is_action_pressed("Secondary Fire"): weapon.hold_second_trigger() if Input.is_action_just_released("Secondary Fire"): weapon.release_second_trigger() if Input.is_action_pressed("Primary Fire"): movement.can_sprint = false if Input.is_action_pressed("Secondary Fire"): movement.can_sprint = false if Input.is_action_just_pressed("Equip In Gauntlet"): if weapon and offhand_weapon: swap_weapons() if movement.sprinting: movement.zoom_factor -= sprint_zoom_speed * delta if movement.zoom_factor <= 1.0 - movement.sprint_zoom_factor: movement.zoom_factor = 1.0 - movement.sprint_zoom_factor camera.fov = Data.graphics.hfov * (1.0 / movement.zoom_factor) if Input.is_action_just_pressed("View Map"): hud.maximise_minimap(Game.level) #Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if Input.is_action_just_released("View Map"): hud.minimize_minimap(self) #Input.mouse_mode = Input.MOUSE_MODE_CAPTURED check_left_hand_valid() func increment_selected() -> void: inventory_selected_index += 1 if inventory_selected_index >= inventory.contents.keys().size(): inventory_selected_index = 0 func decrement_selected() -> void: inventory_selected_index -= 1 if inventory_selected_index < 0: inventory_selected_index = max(inventory.contents.keys().size() - 1, 0) func _unhandled_input(event: InputEvent) -> void: if !is_multiplayer_authority() or paused: return if editing_mode and event.is_action_pressed("Ready"): if ready_state: unready_self() else: ready_self() if event.is_action_pressed("Pause"): var menu: PauseMenu = pause_menu_scene.instantiate() as PauseMenu pause() menu.closed.connect(unpause) hud.add_child(menu) func ready_self() -> void: edit_tool.interact_key_held = false if !ready_state: ready_state = true hud.place_icon.set_visible(false) hud.swap_icon.set_visible(false) hud.shrink_wave_start_label() ready_audio.play() networked_set_ready_state.rpc(ready_state) func unready_self() -> void: if ready_state: ready_state = false if !equipped_card: hud.place_icon.set_visible(true) if !offhand_card: hud.swap_icon.set_visible(true) hud.grow_wave_start_label() unready_audio.play() networked_set_ready_state(ready_state) func add_card(new_card: Card) -> void: inventory.add(new_card) hud.pickup(new_card) place_card_audio.play() func unpause() -> void: paused = false movement.paused = false Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func pause() -> void: paused = true movement.paused = true Input.mouse_mode = Input.MOUSE_MODE_VISIBLE func enter_editing_mode(value: int) -> void: gauntlet_sprite.set_visible(true) weapons_active = false hud.set_wave_count(value + 1) hud.set_energy_visible(false) hud.set_offhand_energy_visible(false) hud.grow_wave_start_label() editing_mode = true edit_tool.enabled = true left_hand.set_visible(true) if weapon: weapon.release_trigger() weapon.set_visible(false) func exit_editing_mode(value: int) -> void: gauntlet_sprite.set_visible(false) weapons_active = false hud.set_wave_count(value) if !weapon and offhand_weapon: swap_weapons() if weapon: hud.set_energy_visible(true) #weapon.set_visible(false) weapon.current_energy = weapon.max_energy weapon.energy_changed.emit(weapon.current_energy) if offhand_weapon: hud.set_offhand_energy_visible(true) offhand_weapon.current_energy = offhand_weapon.max_energy offhand_weapon.energy_changed.emit(offhand_weapon.current_energy) edit_tool.enabled = false edit_tool.delete_tower_preview() left_hand.set_visible(false) editing_mode = false weapon_swap_timer.start() func check_left_hand_valid() -> void: if !editing_mode: return if inventory.size == 0: left_hand_sprite.set_visible(false) #gauntlet.texture.region = Rect2(64, 0, 64, 64) else: left_hand_sprite.set_visible(true) #gauntlet.texture.region = Rect2(0, 0, 64, 64) var selected_card: Card = inventory.contents.keys()[inventory_selected_index] for index: int in card_sprites.size(): if index < inventory.contents[selected_card]: card_sprites[index].set_visible(true) card_sprites[index].set_card(selected_card) #card_sprites[index].view_weapon() else: card_sprites[index].set_visible(false) func equip_weapon() -> void: if weapon != null: unequip_weapon() return if inventory.size > 0: place_card_audio.play() equipped_card = inventory.remove_at(inventory_selected_index) if !inventory.contents.has(equipped_card): decrement_selected() weapon = equipped_card.weapon_scene.instantiate() weapon.name = str(weapons_spawn_count) networked_equip_weapon.rpc(Data.cards.find(equipped_card), 0, weapons_spawn_count) weapons_spawn_count += 1 weapon.energy_changed.connect(hud.set_weapon_energy) weapon.set_multiplayer_authority(multiplayer.get_unique_id()) gauntlet_card_1.set_card(equipped_card) hud.place_icon.set_visible(false) gauntlet_card_1.view_weapon() gauntlet_card_1.set_visible(true) weapon.set_hero(self) weapon.set_visible(false) right_hand.add_child(weapon) check_left_hand_valid() func swap_weapons() -> void: if !editing_mode: weapons_active = false if weapon or offhand_weapon: if editing_mode: swap_card_audio.play() else: swap_off_audio.play() hud.audio_guard = true var temp: Weapon = offhand_weapon var temp_card: Card = offhand_card if weapon: offhand_weapon = weapon offhand_card = equipped_card offhand_weapon.set_visible(false) offhand_weapon.energy_changed.disconnect(hud.set_weapon_energy) offhand_weapon.energy_changed.connect(hud.set_offhand_energy) offhand_weapon.energy_changed.emit(offhand_weapon.current_energy) offhand_weapon.release_trigger() offhand_weapon.release_second_trigger() gauntlet_card_2.set_card(offhand_card) gauntlet_card_2.view_weapon() gauntlet_card_2.set_visible(true) hud.swap_icon.set_visible(false) else: offhand_weapon = null offhand_card = null gauntlet_card_2.set_visible(false) hud.swap_icon.set_visible(true) if temp: weapon = temp equipped_card = temp_card weapon.energy_changed.disconnect(hud.set_offhand_energy) weapon.energy_changed.connect(hud.set_weapon_energy) weapon.energy_changed.emit(weapon.current_energy) weapon.release_trigger() weapon.release_second_trigger() gauntlet_card_1.set_card(equipped_card) gauntlet_card_1.view_weapon() gauntlet_card_1.set_visible(true) hud.place_icon.set_visible(false) else: weapon = null equipped_card = null gauntlet_card_1.set_visible(false) hud.place_icon.set_visible(true) if !editing_mode: weapon_swap_timer.start() func _on_timer_timeout() -> void: weapons_active = true if weapon: swap_on_audio.play() weapon.set_visible(true) func unequip_weapon() -> void: networked_unequip_weapon.rpc(0) gauntlet_card_1.set_visible(false) hud.place_icon.set_visible(true) #gauntlet_sprite.set_visible(true) weapon.queue_free() weapon = null inventory.add(equipped_card) equipped_card = null place_card_audio.play() check_left_hand_valid() #MULTIPLAYER NETWORKED FUNCTIONS @rpc("reliable") func networked_set_ready_state(state: bool) -> void: ready_state = state @rpc("reliable") func networked_swap_weapon() -> void: var temp: Weapon = offhand_weapon var temp_card: Card = offhand_card if weapon: offhand_weapon = weapon offhand_card = equipped_card else: offhand_weapon = null offhand_card = null if temp: weapon = temp equipped_card = temp_card else: weapon = null equipped_card = null @rpc("reliable") func networked_equip_weapon(card_index: int, slot: int, id: int) -> void: var new_card: Card = Data.cards[card_index] var new_weapon: Weapon = new_card.weapon_scene.instantiate() new_weapon.set_multiplayer_authority(multiplayer.get_remote_sender_id()) new_weapon.name = str(id) new_weapon.set_hero(self) right_hand.add_child(new_weapon) match slot: 0: equipped_card = new_card weapon = new_weapon 1: offhand_card = new_card offhand_weapon = new_weapon @rpc("reliable") func networked_unequip_weapon(slot: int) -> void: match slot: 0: weapon.queue_free() weapon = null equipped_card = null 1: offhand_weapon.queue_free() offhand_weapon = null offhand_card = null