class_name MainMenu_CharacterSelect extends Control const IDLE_ANIMS: Array[StringName] = [ &'run', &'run', &'walk', &'walk' ] const CHOICE_ANIMS_1: Array[StringName] = [ &'spin', &'binky', &'run', &'attack' ] const CHOICE_ANIMS_2: Array[StringName] = [ &'wave', &'swim', &'caw', &'bow' ] const CHOICE_AUDIO_1: Array[StringName] = [ "spin", "hop", "low_whoosh", "violent_whoosh" ] @onready var models: Array[Node3D] = [ $Node3D/Stick, $Node3D/Lorna, $Node3D/Blujai, $Node3D/Gibbo ] @onready var animplayers: Array[AnimationPlayer] = [ $Node3D/Stick/AnimationPlayer, $Node3D/Lorna/AnimationPlayer, $Node3D/Blujai/AnimationPlayer, $Node3D/Gibbo/AnimationPlayer ] @onready var profiles: Array[CharacterProfile] = [ load("res://characters/playable/Stick/StickProfile.tres"), load("res://characters/playable/Lorna/LornaProfile.tres"), load("res://characters/playable/Blujai/BlujaiProfile.tres"), load("res://characters/playable/Gibbo/GibboProfile.tres") ] @onready var pivot := $Node3D/Pivot as Node3D @onready var prev_button := ( $PanelContainer/HBoxContainer/Control/PrevButton ) as BaseButton @onready var next_button := ( $PanelContainer/HBoxContainer/Control/NextButton ) as BaseButton @onready var name_label := ( $PanelContainer/HBoxContainer/VBoxContainer/Name ) as Label @onready var description_label := ( $PanelContainer/HBoxContainer/VBoxContainer/ScrollContainer/Description ) as Label @onready var back_button := ( $PanelContainer/HBoxContainer/Control/HBoxContainer/BackButton ) as Button @onready var go_button := ( $PanelContainer/HBoxContainer/Control/HBoxContainer/GoButton ) as Button @onready var audio_player := $AudioStreamPlayer as AudioStreamPlayer @onready var panelcontainer := $PanelContainer as PanelContainer var selected: int = 0 var mode: StringName func _ready() -> void: mode = UI.Args(self).get(&'mode', &'story') for i in animplayers.size(): animplayers[i].play(IDLE_ANIMS[i]) _update_info() _adjust_description_width.call_deferred() prev_button.pressed.connect(_select_prev) next_button.pressed.connect(_select_next) back_button.pressed.connect(_back) go_button.pressed.connect(_go) match mode: &'story': panelcontainer.add_theme_stylebox_override(&'panel', load( "res://ui/MainMenu/StoryMenuStyleBox.tres" )) &'mission': panelcontainer.add_theme_stylebox_override(&'panel', load( "res://ui/MainMenu/MissionMenuStyleBox.tres" )) &'freerun': panelcontainer.add_theme_stylebox_override(&'panel', load( "res://ui/MainMenu/FreerunMenuStyleBox.tres" )) func _process(delta: float) -> void: for model in models: model.rotation.y += delta pivot.basis = pivot.basis.slerp( Basis.looking_at(( models[selected].global_position - pivot.global_position ).slide(Vector3.UP)), 3.0*delta ) func _select_next() -> void: selected = (selected + 1)%profiles.size() _update_info() func _select_prev() -> void: selected = (selected + profiles.size() - 1)%profiles.size() _update_info() func _update_info() -> void: var profile := profiles[selected] name_label.text = profile.nickname name_label.modulate = profile.talk_color.lerp(Color.WHITE, 0.125) var pronouns := profile.pronouns.split("/") if Storyboard.character_unlocked(profile.id): go_button.disabled = false go_button.text = "Go!" description_label.text = (( "%s (%s)\n" + "\n" + "SPEED: %s\n" + "TRACTION: %s\n" + "AIR: %s\n" + "COMBAT: %s\n" + "ABILITY: %s\n" + "\n" + "%s\n" + "\n" + "BIO: %s\n" ) % [ profile.full_name, profile.pronouns, _rating_string(profile.speed_rating), _rating_string(profile.traction_rating), _rating_string(profile.air_rating), _rating_string(profile.combat_rating), profile.ability, profile.playstyle, profile.story ]) else: go_button.disabled = true go_button.text = "Not unlocked" description_label.text = (( "This character is not unlocked yet. " + "Meet %s in another character's story to unlock %s." ) % [pronouns[1], pronouns[1]]) func _rating_string(rating: CharacterProfile.StatRating) -> String: match rating: CharacterProfile.StatRating.LOW: return "Low" CharacterProfile.StatRating.MEDIUM: return "Medium" CharacterProfile.StatRating.HIGH: return "High" CharacterProfile.StatRating.STELLAR: return "STELLAR!" _: return "I don't know" func _adjust_description_width() -> void: description_label.custom_minimum_size.x = ( description_label.get_parent().size.x ) func _input(event: InputEvent) -> void: if ( event.is_action_pressed(&'move_left') or event.is_action_pressed(&'look_left') or event.is_action_pressed(&'ui_left') ): get_viewport().set_input_as_handled() UI._play_sound_confirm() _select_prev() elif ( event.is_action_pressed(&'move_right') or event.is_action_pressed(&'look_right') or event.is_action_pressed(&'ui_right') ): get_viewport().set_input_as_handled() UI._play_sound_confirm() _select_next() elif event.is_action_pressed(&'confirm'): get_viewport().set_input_as_handled() if not go_button.disabled: _go() func _back() -> void: UI.Return(self) func _go() -> void: process_mode = PROCESS_MODE_DISABLED UI._audio_player.stop() audio_player.stream = load("res://audio/choice.ogg") audio_player.play() var model := models[selected] var animplayer := animplayers[selected] var profile := profiles[selected] var snd1 := load("res://audio/%s.ogg" % CHOICE_AUDIO_1[selected]) var snd2 := profile.talk_blip var anim1 := CHOICE_ANIMS_1[selected] var anim2 := CHOICE_ANIMS_2[selected] var target_basis := Basis.looking_at(( FX.get_camera().global_position - model.global_position ).slide(Vector3.UP), Vector3.UP, true) var target_pivot_basis := Basis.looking_at(( models[selected].global_position - pivot.global_position ).slide(Vector3.UP)) while ( (pivot.basis.z - target_pivot_basis.z).length() > 0.02 or (model.basis.z - target_basis.z).length() > 0.02 ): var tick := await Wait.tick() pivot.basis = pivot.basis.slerp(target_pivot_basis, 10.0*tick) model.basis = model.basis.slerp(target_basis, 10.0*tick) audio_player.stream = snd1 audio_player.play() animplayer.play(anim1, Runner.ANIMATION_BLEND_TIME) await Wait.seconds(0.5*(3.0 - profile.speed_rating)) audio_player.stream = snd2 audio_player.play() animplayer.play(anim2, Runner.ANIMATION_BLEND_TIME) await Wait.seconds(1.0) FX.stop_bgm() await FX.fade(Color.BLACK) process_mode = PROCESS_MODE_ALWAYS match mode: &'story': UI.Return(self, _prepare_game_start_params()) _: var params = await UI.Call(load( "res://ui/MainMenu/MapSelect/MainMenu_MapSelect.tscn" ), { &'mode': mode, &'character': profile.id }) UI.Return(self, params) func _prepare_game_start_params() -> GameStartParameters: var params := GameStartParameters.new() params.character = profiles[selected].id var story_state := Storyboard.save_data.find_character_story_state( params.character ) as CharacterStoryState params.game_mode = story_state.checkpoint.game_mode params.level = story_state.checkpoint.location.map.level params.mission = story_state.checkpoint.location.map.mission params.loading_zone = story_state.checkpoint.location.loading_zone params.show_level_card = bool( params.game_mode != LevelDescriptor.GameMode.HUB ) return params