added the game

This commit is contained in:
JHDev2006
2025-09-13 16:30:32 +01:00
parent 5ef689109b
commit 3773bdaf64
3616 changed files with 263702 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
class_name AchievementContainer
extends HBoxContainer
var achievement_id := 0
var selected := false
var unlocked := false
const ICON_RECTS := [
Vector2i(0, 0), Vector2i(1, 0), Vector2i(2, 0), Vector2i(3, 0),
Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1),
Vector2i(0, 2), Vector2i(1, 2), Vector2i(2, 2),
Vector2i(0, 3), Vector2i(1, 3), Vector2i(2, 3),
Vector2i(0, 4), Vector2i(1, 4), Vector2i(2, 4),
Vector2i(0, 5), Vector2i(1, 5), Vector2i(2, 5),
Vector2i(0, 6), Vector2i(1, 6), Vector2i(2, 6),
Vector2i(0, 7), Vector2i(1, 7), Vector2i(2, 7),
Vector2i(3, 1), Vector2i(3, 2), Vector2i(3, 4)
]
const ACHIEVEMENT_NAMES := [
"TITLE_SMB1_CLEAR", "TITLE_SMBLL_CLEAR", "TITLE_SMBS_CLEAR", "TITLE_SMBANN_CLEAR",
"TITLE_SMB1_CHALLENGE", "TITLE_SMBLL_CHALLENGE", "TITLE_SMBS_CHALLENGE",
"TITLE_SMB1_BOO", "TITLE_SMBLL_BOO", "TITLE_SMBS_BOO",
"TITLE_SMB1_GOLD_BOO", "TITLE_SMBLL_GOLD_BOO", "TITLE_SMBS_GOLD_BOO",
"TITLE_SMB1_BRONZE", "TITLE_SMBLL_BRONZE", "TITLE_SMBS_BRONZE",
"TITLE_SMB1_SILVER", "TITLE_SMBLL_SILVER", "TITLE_SMBS_SILVER",
"TITLE_SMB1_GOLD", "TITLE_SMBLL_GOLD", "TITLE_SMBS_GOLD",
"TITLE_SMB1_RUN", "TITLE_SMBLL_RUN", "TITLE_SMBS_RUN",
"TITLE_ANN_PRANK", "TITLE_SMBLL_WORLD9", "TITLE_COMPLETION"
]
const ACHIEVEMENT_DESCS := [
"DESC_SMB1_CLEAR", "DESC_SMBLL_CLEAR", "DESC_SMBS_CLEAR", "DESC_SMBANN_CLEAR",
"DESC_SMB1_CHALLENGE", "DESC_SMBLL_CHALLENGE", "DESC_SMBS_CHALLENGE",
"DESC_SMB1_BOO", "DESC_SMBLL_BOO", "DESC_SMBS_BOO",
"DESC_SMB1_GOLD_BOO", "DESC_SMBLL_GOLD_BOO", "DESC_SMBS_GOLD_BOO",
"DESC_SMB1_BRONZE", "DESC_SMBLL_BRONZE", "DESC_SMBS_BRONZE",
"DESC_SMB1_SILVER", "DESC_SMBLL_SILVER", "DESC_SMBS_SILVER",
"DESC_SMB1_GOLD", "DESC_SMBLL_GOLD", "DESC_SMBS_GOLD",
"DESC_SMB1_RUN", "DESC_SMBLL_RUN", "DESC_SMBS_RUN",
"DESC_ANN_PRANK", "DESC_SMBLL_WORLD9", "DESC_COMPLETION"
]
var progress := 0
var total_needed := 0
func _ready() -> void:
setup_visuals()
set_active(false)
func setup_visuals() -> void:
var achievement_name = "TITLE_LOCKED_ACHIEVEMENT"
var rect = Vector2i(3, 3)
if unlocked:
achievement_name = ACHIEVEMENT_NAMES[achievement_id]
rect = ICON_RECTS[achievement_id]
$PanelContainer.modulate = Color.WHITE
%Title.text = achievement_name
%Description.text = ACHIEVEMENT_DESCS[achievement_id]
%Icon.region_rect = Rect2(rect * 32, Vector2(32, 32))
%Progress.visible = not unlocked and total_needed > 0
%ProgressBar.max_value = total_needed
%ProgressBar.value = progress
%TotalGot.text = str(progress)
%TotalNeeded.text = "/" + str(total_needed)
func set_active(active := false) -> void:
$Cursor.modulate.a = int(active)
$PanelContainer/MarginContainer/HBoxContainer/VBoxContainer/AutoScrollContainer.is_active = active
$PanelContainer/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/AutoScrollContainer2.is_active = active

View File

@@ -0,0 +1 @@
uid://cfoivmjplms0g

View File

@@ -0,0 +1,43 @@
class_name AchievementMenu
extends Node
const ACHIEVEMENT_CONTAINER = ("uid://8wnmuhtwu8ib")
var total_unlocked := 0
static var unlocked_achievements := "0000000000000000000000000000"
func _ready() -> void:
unlocked_achievements = Global.achievements
spawn_achievement_containers()
$BG/Border/MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer.get_child(0).grab_focus()
Global.get_node("GameHUD").hide()
var percent = int((float(total_unlocked) / Global.achievements.length()) * 100)
%Progress.text = str(percent) + "% "
if percent == 100:
%Progress.modulate = Color("FFB259")
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_back"):
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
func spawn_achievement_containers() -> void:
var idx := 0
for i in Global.achievements:
if Global.HIDDEN_ACHIEVEMENTS.has(idx) and Global.achievements[idx] == "0":
idx += 1
continue
var container = load(ACHIEVEMENT_CONTAINER).instantiate()
container.achievement_id = idx
container.unlocked = i == "1" or Global.debug_mode
if i == "1":
total_unlocked += 1
else:
if $ProgressCalculators.has_node(str(idx)):
container.total_needed = $ProgressCalculators.get_node(str(idx)).target_number
container.progress = $ProgressCalculators.get_node(str(idx)).get_progress()
$BG/Border/MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer.add_child(container)
idx += 1
func _exit_tree() -> void:
Global.get_node("GameHUD").show()

View File

@@ -0,0 +1 @@
uid://d3jebf1trkaor

View File

@@ -0,0 +1,25 @@
extends Control
signal finished
func show_popup(achievements: Array) -> void:
var idx := 0
$Control/Panel/MarginContainer/VBoxContainer/Label.text = "NEW ACHIEVEMENT!" if achievements.size() == 1 else "NEW ACHIEVEMENTS!"
for i in [%Icon, %Icon2, %Icon3, %Icon4]:
i.hide()
i.visible = achievements.size() > idx
if idx == 3 and achievements.size() > 4:
i.hide()
%Extra.show()
%Extra.text = "+" + str(achievements.size() - 3)
if i.visible:
i.region_rect = Rect2(AchievementContainer.ICON_RECTS[achievements[idx]] * 32, Vector2(32, 32))
idx += 1
%AchievementName.visible = achievements.size() == 1
%AchievementName.text = AchievementContainer.ACHIEVEMENT_NAMES[achievements[0]]
if %AchievementName.text.length() > 16:
$AnimationPlayer.play("AppearLong")
else:
$AnimationPlayer.play("Appear")
await $AnimationPlayer.animation_finished
finished.emit()

View File

@@ -0,0 +1 @@
uid://dmp6qqm3q16pc

View File

@@ -0,0 +1,40 @@
extends Node
func master_changed(new_value := 0) -> void:
AudioServer.set_bus_volume_linear(0, float(new_value) / 10)
Settings.file.audio.master = new_value
func music_changed(new_value := 0) -> void:
AudioServer.set_bus_volume_linear(1, float(new_value) / 10)
Settings.file.audio.music = new_value
func sfx_changed(new_value := 0) -> void:
AudioServer.set_bus_volume_linear(2, float(new_value) / 10)
Settings.file.audio.sfx = new_value
func athletic_changed(new_value := 0) -> void:
Settings.file.audio.extra_bgm = new_value
func skid_changed(new_value := 0) -> void:
Settings.file.audio.skid_sfx = new_value
func extra_sfx_changed(new_value := 0) -> void:
Settings.file.audio.extra_sfx = new_value
func menu_bgm_changed(new_value := 0) -> void:
Settings.file.audio.menu_bgm = new_value
func blank(_hello := 0) -> void:
pass
func set_value(value_name := "", value := 0) -> void:
{
"master": master_changed,
"music": music_changed,
"sfx": sfx_changed,
"athletic_bgm": blank,
"extra_bgm": athletic_changed,
"skid_sfx": skid_changed,
"extra_sfx": extra_sfx_changed,
"menu_bgm": menu_bgm_changed
}[value_name].call(value)

View File

@@ -0,0 +1 @@
uid://bdgvsycico544

View File

@@ -0,0 +1,65 @@
@tool
class_name AutoScrollContainer
extends ScrollContainer
var is_focused := false
@export_enum("Wave", "Endless") var mode := 0
@export_enum("Horizontal", "Vertical") var direction := 0
var scroll_direction := "scroll_vertical"
var scroll := 0.0
@export var is_active := false
@export var auto_connect_focus := true
@export var auto_minimum_resize := false
func _ready() -> void:
scroll_direction = "scroll_horizontal" if direction == 0 else "scroll_vertical"
set_focused(is_active)
if auto_connect_focus:
owner.focus_entered.connect(set_focused.bind(true))
owner.focus_exited.connect(set_focused.bind(false))
if auto_minimum_resize:
get_child(0).resized.connect(update_sizing)
func set_focused(enabled := false) -> void:
is_focused = enabled
func _physics_process(delta: float) -> void:
wave(delta)
func update_sizing() -> void:
custom_minimum_size.x = clamp(get_child(0).size.x, 0, 100)
var scroll_pos := 0.0
var scroll_speed := 16.0 # pixels per second
var move_direction := 1
func wave(delta: float) -> void:
if not is_focused:
scroll_pos = 0
set_deferred(scroll_direction, -1)
var total_range := 0.0
if direction == 0:
total_range = get_child(0).size.x - size.x
else:
total_range = (get_child(0).size.y) - (size.y + 8)
if total_range <= 0:
return
if scroll_pos > total_range + 16 or scroll_pos <= -16:
move_direction *= -1
scroll_pos += scroll_speed * move_direction * delta
if direction == 0:
scroll_horizontal = scroll_pos
else:
scroll_vertical = scroll_pos
func endless(delta: float) -> void:
scroll = wrap(scroll - delta, 0, 1)
var amount = lerpf(0.0, get_child(0).size.x - size.x, scroll)
scroll_horizontal = amount

View File

@@ -0,0 +1 @@
uid://d63p6qr5a748

120
Scripts/UI/BooRaceMenu.gd Normal file
View File

@@ -0,0 +1,120 @@
extends Node
static var selected_index := 0
var active := true
var boo_index := 0
const levels := {
"SMB1": SMB1_LEVELS,
"SMBLL": SMBLL_LEVELS,
"SMBS": SMBS_LEVELS
}
const SMB1_LEVELS := [
"res://Scenes/Levels/SMB1/YouVSBoo/Boo1-1.tscn",
"res://Scenes/Levels/SMB1/YouVSBoo/Boo1-2.tscn",
"res://Scenes/Levels/SMB1/YouVSBoo/Boo1-3.tscn",
"res://Scenes/Levels/SMB1/YouVSBoo/Boo1-4.tscn",
"res://Scenes/Levels/SMB1/YouVSBoo/Boo2-1.tscn",
"res://Scenes/Levels/SMB1/YouVSBoo/Boo2-2.tscn",
"res://Scenes/Levels/SMB1/YouVSBoo/Boo2-3.tscn",
"res://Scenes/Levels/SMB1/YouVSBoo/Boo2-4.tscn"
]
const SMBLL_LEVELS := [
"res://Scenes/Levels/SMBLL/YouVSBoo/Boo1-1.tscn",
"res://Scenes/Levels/SMBLL/YouVSBoo/Boo1-2.tscn",
"res://Scenes/Levels/SMBLL/YouVSBoo/Boo1-3.tscn",
"res://Scenes/Levels/SMBLL/YouVSBoo/Boo1-4.tscn",
"res://Scenes/Levels/SMBLL/YouVSBoo/Boo2-1.tscn",
"res://Scenes/Levels/SMBLL/YouVSBoo/Boo2-2.tscn",
"res://Scenes/Levels/SMBLL/YouVSBoo/Boo2-3.tscn",
"res://Scenes/Levels/SMBLL/YouVSBoo/Boo2-4.tscn",
]
const SMBS_LEVELS := [
"res://Scenes/Levels/SMBS/YouVsBoo/Boo1-1.tscn",
"res://Scenes/Levels/SMBS/YouVsBoo/Boo1-2.tscn",
"res://Scenes/Levels/SMBS/YouVsBoo/Boo1-3.tscn",
"res://Scenes/Levels/SMBS/YouVsBoo/Boo1-4.tscn",
"res://Scenes/Levels/SMBS/YouVsBoo/Boo2-1.tscn",
"res://Scenes/Levels/SMBS/YouVsBoo/Boo2-2.tscn",
"res://Scenes/Levels/SMBS/YouVsBoo/Boo2-3.tscn",
"res://Scenes/Levels/SMBS/YouVsBoo/Boo2-4.tscn"
]
func _ready() -> void:
AudioManager.stop_all_music()
Global.player_power_states = "0000"
Global.get_node("GameHUD").hide()
boo_index = BooRaceHandler.boo_colour
Global.current_game_mode = Global.GameMode.BOO_RACE
Global.reset_values()
LevelPersistance.reset_states()
Level.first_load = true
Level.can_set_time = true
setup_visuals()
%LevelLabels.get_child(BooRaceHandler.current_level_id).grab_focus()
func _exit_tree() -> void:
Global.get_node("GameHUD").show()
func setup_visuals() -> void:
for i in %LevelLabels.get_child_count():
if i >= 1:
var level_unlocked = int(BooRaceHandler.cleared_boo_levels[i - 1]) > 0
%LevelLabels.get_child(i).modulate = Color.WHITE if level_unlocked else Color.DIM_GRAY
%LevelLabels.get_child(i).get_node("Control/Sprite2D").visible = level_unlocked
if int(BooRaceHandler.cleared_boo_levels[i]) > 0:
%LevelLabels.get_child(i).get_node("Control/Sprite2D").frame = clamp(int(BooRaceHandler.cleared_boo_levels[i]), 0, 4)
%LevelLabels.get_child(i).get_node("Control/Sprite2D").modulate = Color.DIM_GRAY if int(BooRaceHandler.cleared_boo_levels[i]) >= 5 else Color.WHITE
for i in %Boos.get_children():
if i is Node2D:
i.visible = $BooSelect.selected_boo == int(i.name)
i.modulate = Color.BLACK if int(BooRaceHandler.cleared_boo_levels[selected_index]) < int(i.name) else Color.WHITE
if int(BooRaceHandler.cleared_boo_levels[selected_index]) > int(i.name):
i.modulate = Color.DIM_GRAY
i.play("Lose" if int(BooRaceHandler.cleared_boo_levels[selected_index]) > int(i.name) else "Idle")
func _process(_delta: float) -> void:
handle_input()
$BooSelect.lvl_idx = selected_index
func open() -> void:
active = true
func set_current_level_idx(new_idx := 0) -> void:
selected_index = new_idx
update_pb()
func update_pb() -> void:
var pb_string := "--:--:--"
if BooRaceHandler.best_times[selected_index] >= 0:
pb_string = SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(BooRaceHandler.best_times[selected_index]))
%PB.text = "PB: " + pb_string
func handle_input() -> void:
if active == false:
return
if Input.is_action_just_pressed("ui_back"):
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
if Input.is_action_just_pressed("ui_accept"):
level_selected()
func regrab_focus() -> void:
%LevelLabels.get_child(selected_index).grab_focus()
func level_selected() -> void:
if selected_index > 0:
if int(BooRaceHandler.cleared_boo_levels[selected_index - 1]) <= 0 and not Global.debug_mode:
AudioManager.play_global_sfx("bump")
return
active = false
Global.reset_values()
Global.clear_saved_values()
ResourceSetter.cache.clear()
ResourceSetterNew.cache.clear()
$BooSelect.open()
await $CharacterSelect.selected
Global.transition_to_scene(levels[Global.current_campaign][selected_index])

1
Scripts/UI/BooRaceMenu.gd.uid Executable file
View File

@@ -0,0 +1 @@
uid://b3ol0iyjcb5n1

64
Scripts/UI/BooSelect.gd Normal file
View File

@@ -0,0 +1,64 @@
extends Control
@onready var cursor: Label = %Cursor
var selected_boo := 0
var active := false
var lvl_idx := 0
signal boo_selected
signal cancelled
signal boo_changed
@onready var boos := [%Boo1, %Boo2, %Boo3, %Boo4, %Boo5]
func _process(_delta: float) -> void:
if active:
handle_input()
BooRaceHandler.boo_colour = selected_boo
for i in boos:
i.get_node("Cursor").visible = selected_boo == i.get_index()
func open() -> void:
grab_focus()
selected_boo = int(BooRaceHandler.cleared_boo_levels[lvl_idx])
update_visuals()
show()
await get_tree().process_frame
active = true
func update_visuals() -> void:
var idx := 0
for i in boos:
i.modulate = Color.WHITE if (int(BooRaceHandler.cleared_boo_levels[lvl_idx]) >= idx or Global.debug_mode) else Color.DIM_GRAY
idx += 1
func handle_input() -> void:
var old_colour = selected_boo
if Input.is_action_just_pressed("ui_left"):
selected_boo -= 1
if Input.is_action_just_pressed("ui_right"):
selected_boo += 1
selected_boo = clamp(selected_boo, 0, 4)
BooRaceHandler.boo_colour = selected_boo
if old_colour != selected_boo:
print(selected_boo)
boo_changed.emit()
if Input.is_action_just_pressed("ui_back"):
cancelled.emit()
close()
if Input.is_action_just_pressed("ui_accept"):
if int(BooRaceHandler.cleared_boo_levels[lvl_idx]) < selected_boo and not Global.debug_mode:
AudioManager.play_sfx("bump")
else:
select_world()
func select_world() -> void:
boo_selected.emit()
close()
func close() -> void:
active = false
hide()

1
Scripts/UI/BooSelect.gd.uid Executable file
View File

@@ -0,0 +1 @@
uid://di5w6db0ha2oa

View File

@@ -0,0 +1,101 @@
extends Control
var selected_index := 0
signal selected
signal cancelled
var active := false
@export var campaign_icons: Array[Texture2D] = []
var old_campaign := ""
@export var campaign := ["SMB1", "SMBLL", "SMBS", "SMBANN", "Custom"]
func _ready() -> void:
get_starting_position()
handle_visuals()
func _process(_delta: float) -> void:
if active:
handle_input()
handle_visuals()
func handle_visuals() -> void:
%Left.texture = campaign_icons[wrap(selected_index - 1, 0, campaign_icons.size())]
%Right.texture = campaign_icons[wrap(selected_index + 1, 0, campaign_icons.size())]
%Middle.texture = campaign_icons[selected_index]
%BarLabel.text = generate_text()
for i in %CampaignNames.get_child_count():
%CampaignNames.get_child(i).visible = selected_index == i
func generate_text() -> String:
var string := ""
string += ""
for i in 5:
if i == selected_index:
string += ""
else:
string += "-"
string += ""
return string
func open() -> void:
old_campaign = Global.current_campaign
Global.current_game_mode = Global.GameMode.NONE
get_starting_position()
handle_visuals()
show()
await get_tree().process_frame
active = true
await selected
hide()
func get_starting_position() -> void:
if CustomLevelMenu.has_entered or selected_index == 4:
selected_index = 4
else:
selected_index = campaign.find(Global.current_campaign)
func handle_input() -> void:
if Input.is_action_just_pressed("ui_left"):
selected_index -= 1
if Input.is_action_just_pressed("ui_right"):
selected_index += 1
selected_index = wrap(selected_index, 0, campaign.size())
Global.current_campaign = campaign[selected_index]
if Input.is_action_just_pressed("ui_accept"):
select()
elif Input.is_action_just_pressed("ui_back"):
close()
Global.current_campaign = old_campaign
cancelled.emit()
return
func select() -> void:
CustomLevelMenu.has_entered = false
if selected_index == 4:
Global.current_campaign = "SMB1"
Global.transition_to_scene("res://Scenes/Levels/CustomLevelMenu.tscn")
return
active = false
Settings.file.game.campaign = Global.current_campaign
SaveManager.apply_save(SaveManager.load_save(campaign[selected_index]))
if Global.current_campaign != "SMBANN":
SpeedrunHandler.load_best_times()
Settings.save_settings()
selected.emit()
hide()
if old_campaign != Global.current_campaign:
Global.freeze_screen()
ResourceSetter.cache.clear()
ResourceSetterNew.cache.clear()
Global.level_theme_changed.emit()
for i in 2:
await get_tree().process_frame
Global.close_freeze()
func close() -> void:
CustomLevelMenu.has_entered = false
active = false
hide()

View File

@@ -0,0 +1 @@
uid://cae4v54jq1gs3

View File

@@ -0,0 +1,118 @@
extends Node
@export var can_exit := false
func _enter_tree() -> void:
Global.get_node("GameHUD").hide()
var coin_medal := true
var score_medal := false
var yoshi_medal := false
var exiting := false
func _ready() -> void:
var your_results = tr("CHALLENGE_DIALOGUE_RESULTS").split(" ")
$SpeechBubble/Your.text = your_results[0]
$SpeechBubble/Your/Results.text = your_results[1]
coin_medal = int(ChallengeModeHandler.red_coins_collected[Global.world_num - 1][Global.level_num - 1]) & 0b011111 == 0b011111
score_medal = ChallengeModeHandler.top_challenge_scores[Global.world_num -1][Global.level_num - 1] >= ChallengeModeHandler.CHALLENGE_TARGETS[Global.current_campaign][Global.world_num -1][Global.level_num -1]
yoshi_medal = ChallengeModeHandler.is_coin_collected(ChallengeModeHandler.CoinValues.YOSHI_EGG, ChallengeModeHandler.red_coins_collected[Global.world_num - 1][Global.level_num - 1])
setup_results()
func _process(_delta: float) -> void:
if can_exit and Input.is_action_just_pressed("jump_0"):
can_exit = false
exiting = true
save_results()
$Music.stop()
$Music.stream = preload("res://Assets/Audio/BGM/ChallengeEnd.mp3")
$Music.play()
await $Music.finished
open_menu()
Engine.time_scale = 5 if Input.is_action_pressed("jump_0") and can_exit == false and exiting == false else 1
func open_menu() -> void:
$CanvasLayer/PauseMenu.open()
func save_results() -> void:
var index := 0
ChallengeModeHandler.red_coins_collected[Global.world_num - 1][Global.level_num - 1] = int(ChallengeModeHandler.red_coins_collected[Global.world_num - 1][Global.level_num - 1]) | ChallengeModeHandler.current_run_red_coins_collected
if Global.score >= ChallengeModeHandler.top_challenge_scores[Global.world_num - 1][Global.level_num - 1]:
ChallengeModeHandler.top_challenge_scores[Global.world_num - 1][Global.level_num - 1] = Global.score
ChallengeModeHandler.new().check_for_achievement()
SaveManager.write_save()
func retry_level() -> void:
Global.player_power_states = "0000"
ChallengeModeHandler.current_run_red_coins_collected = ChallengeModeHandler.red_coins_collected[Global.world_num - 1][Global.level_num - 1]
Global.score = 0
LevelTransition.level_to_transition_to = Level.get_scene_string(Global.world_num, Global.level_num)
Global.transition_to_scene("res://Scenes/Levels/LevelTransition.tscn")
func go_to_title_screen() -> void:
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
func _exit_tree() -> void:
Global.get_node("GameHUD").show()
Engine.time_scale = 1
func setup_results() -> void:
$Sprite2D3/RedCoins.visible = coin_medal
$Sprite2D3/Score.visible = score_medal
$Sprite2D3/YoshiEgg.visible = yoshi_medal
$SpeechBubble/Score/ScoreLabel.text = str(Global.score)
var idx = 0
for i in $Sprite2D/Sprite2D3/Coins.get_children():
if ChallengeModeHandler.is_coin_collected(idx, ChallengeModeHandler.red_coins_collected[Global.world_num - 1][Global.level_num - 1]):
i.frame = 1
else:
i.frame = 0
idx += 1
idx = 0
for i in $SpeechBubble/Coins/Node2D.get_children():
i.frame = int(ChallengeModeHandler.is_coin_collected(idx))
idx += 1
$Sprite2D/Sprite2D3/ScoreText/Target.text = "/ " + str(ChallengeModeHandler.CHALLENGE_TARGETS[Global.current_campaign][Global.world_num - 1][Global.level_num - 1])
$WorldLevel.text = str(Global.world_num) + "-" + str(Global.level_num)
$Yoshi.play(["Green", "Yellow", "Red", "Blue"][Global.level_num - 1])
func update_coins_display() -> void:
var idx := 0
for i in $Sprite2D/Sprite2D3/Coins.get_children():
if ChallengeModeHandler.is_coin_collected(idx):
i.frame = 1
idx += 1
func update_score() -> void:
$Sprite2D/Sprite2D3/ScoreText.text = str(Global.score)
func give_red_coin_medal() -> void:
const mask = (1 << ChallengeModeHandler.CoinValues.R_COIN_1) | (1 << ChallengeModeHandler.CoinValues.R_COIN_2) | (1 << ChallengeModeHandler.CoinValues.R_COIN_3) | (1 << ChallengeModeHandler.CoinValues.R_COIN_4) | (1 << ChallengeModeHandler.CoinValues.R_COIN_5)
var valid := (ChallengeModeHandler.current_run_red_coins_collected & mask) == mask
if valid and not coin_medal:
do_medal_give_animation($Sprite2D3/RedCoins)
func give_score_medal() -> void:
if Global.score >= ChallengeModeHandler.CHALLENGE_TARGETS[Global.current_campaign][Global.world_num - 1][Global.level_num - 1] and not score_medal:
do_medal_give_animation($Sprite2D3/Score)
func give_yoshi_medal() -> void:
if ChallengeModeHandler.is_coin_collected(ChallengeModeHandler.CoinValues.YOSHI_EGG):
$SmokeParticle.play()
$Yoshi/AudioStreamPlayer2D.play()
$Yoshi.show()
if yoshi_medal == false:
await get_tree().create_timer(0.5, false).timeout
do_medal_give_animation($Sprite2D3/YoshiEgg)
func do_medal_give_animation(medal: Node) -> void:
$AudioStreamPlayer2.play()
get_tree().paused = true
for i in 4:
medal.hide()
await get_tree().create_timer(0.1, true).timeout
medal.show()
await get_tree().create_timer(0.1, true).timeout
get_tree().paused = false

View File

@@ -0,0 +1 @@
uid://bqhrq7sabq811

View File

@@ -0,0 +1,108 @@
extends Control
@onready var cursor: TextureRect = %Cursor
var selected_index := 0
signal selected
signal cancelled
var active := false
var player_id := 0
var character_sprite_jsons := [
"res://Assets/Sprites/Players/Mario/Small.json",
"res://Assets/Sprites/Players/Luigi/Small.json",
"res://Assets/Sprites/Players/Toad/Small.json",
"res://Assets/Sprites/Players/Toadette/Small.json"
]
func _process(_delta: float) -> void:
if active:
handle_input()
func _ready() -> void:
update_sprites()
func get_custom_characters() -> void:
Player.CHARACTERS = ["Mario", "Luigi", "Toad", "Toadette"]
Player.CHARACTER_NAMES = ["CHAR_MARIO", "CHAR_LUIGI", "CHAR_TOAD", "CHAR_TOADETTE"]
AudioManager.character_sfx_map.clear()
var idx := 0
for i in Player.CHARACTERS:
var path = ResourceSetter.get_pure_resource_path("res://Assets/Sprites/Players/" + i + "/CharacterInfo.json")
print(path)
if FileAccess.file_exists(path):
var json = JSON.parse_string(FileAccess.open(path, FileAccess.READ).get_as_text())
Player.CHARACTER_NAMES[idx] = json.name
path = ResourceSetter.get_pure_resource_path("res://Assets/Sprites/Players/" + i + "/CharacterColour.json")
if FileAccess.file_exists(path):
Player.CHARACTER_COLOURS[idx] = load(path)
idx += 1
print(Player.CHARACTER_NAMES)
DirAccess.make_dir_recursive_absolute("user://custom_characters")
for i in DirAccess.get_directories_at("user://custom_characters"):
if FileAccess.file_exists("user://custom_characters/" + i + "/CharacterInfo.json"):
var char_path = "user://custom_characters/" + i + "/"
var json = JSON.parse_string(FileAccess.open(char_path + "CharacterInfo.json", FileAccess.READ).get_as_text())
Player.CHARACTERS.append(i)
Player.CHARACTER_NAMES.append(json.name)
if FileAccess.file_exists(char_path + "CharacterColour.json"):
Player.CHARACTER_COLOURS.append(load(char_path + "CharacterColour.json"))
if FileAccess.file_exists(char_path + "LifeIcon.json"):
GameHUD.character_icons.append(load(char_path + "LifeIcon.json"))
if FileAccess.file_exists(char_path + "ColourPalette.json"):
Player.CHARACTER_PALETTES.append(load(char_path + "ColourPalette.json"))
if FileAccess.file_exists(char_path + "SFX.json"):
AudioManager.character_sfx_map[i] = JSON.parse_string(FileAccess.open(char_path + "SFX.json", FileAccess.READ).get_as_text())
func open() -> void:
get_custom_characters()
show()
grab_focus()
selected_index = int(Global.player_characters[player_id])
update_sprites()
await get_tree().physics_frame
active = true
func handle_input() -> void:
if Input.is_action_just_pressed("ui_left"):
selected_index = wrap(selected_index - 1, 0, Player.CHARACTERS.size())
update_sprites()
elif Input.is_action_just_pressed("ui_right"):
selected_index = wrap(selected_index + 1, 0, Player.CHARACTERS.size())
update_sprites()
if Input.is_action_just_pressed("ui_accept"):
Global.player_characters[player_id] = str(selected_index)
var characters := Global.player_characters
for i in characters:
if int(i) > 3:
characters = "0000"
Settings.file.game.characters = characters
Settings.save_settings()
selected.emit()
close()
elif Input.is_action_just_pressed("ui_back"):
close()
cancelled.emit()
func update_sprites() -> void:
%Left.force_character = Player.CHARACTERS[wrap(selected_index - 1, 0, Player.CHARACTERS.size())]
%Selected.force_character = Player.CHARACTERS[wrap(selected_index, 0, Player.CHARACTERS.size())]
%Right.force_character = Player.CHARACTERS[wrap(selected_index + 1, 0, Player.CHARACTERS.size())]
for i in [%Left, %Selected, %Right]:
i.update()
i.play("Pose" if i == %Selected else "FaceForward")
%PlayerColourTexture.resource_json = Player.CHARACTER_COLOURS[selected_index]
%CharacterName.text = tr(Player.CHARACTER_NAMES[selected_index])
$Panel/MarginContainer/VBoxContainer/CharacterName/TextShadowColourChanger/ColourPaletteSampler.texture = %ColourPaletteSampler.texture
$Panel/MarginContainer/VBoxContainer/CharacterName/TextShadowColourChanger.handle_shadow_colours()
func select() -> void:
selected.emit()
hide()
active = false
func close() -> void:
active = false
hide()

View File

@@ -0,0 +1 @@
uid://d0f4edmfxs2pu

50
Scripts/UI/Credits.gd Normal file
View File

@@ -0,0 +1,50 @@
class_name CreditsLevel
extends Level
func _enter_tree() -> void:
pass
static var go_to_title_screen := true
func _ready() -> void:
for i in $Labels.get_children():
i.hide()
AudioManager.stop_all_music()
Global.get_node("GameHUD").hide()
await get_tree().create_timer(1, false).timeout
do_sequence()
func _exit_tree() -> void:
Global.get_node("GameHUD").show()
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_accept"):
if $Skip.visible:
exit()
else:
$Skip.show()
await get_tree().create_timer(2, false).timeout
$Skip.hide()
func exit() -> void:
if go_to_title_screen:
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
else:
LevelTransition.level_to_transition_to = Level.get_scene_string(9, 1)
Global.world_num = 8
Global.world_num = 4
update_next_level_info()
transition_to_next_level()
func do_sequence() -> void:
$Music.play()
for i in $Labels.get_children():
i.show()
if i.has_meta("time"):
await get_tree().create_timer(i.get_meta("time"), false).timeout
else:
await get_tree().create_timer(4, false).timeout
i.hide()
await get_tree().create_timer(0.5, false).timeout
await get_tree().create_timer(5, false).timeout
exit()

View File

@@ -0,0 +1 @@
uid://b3stoe2kp1nv1

View File

@@ -0,0 +1,72 @@
class_name CustomLevelContainer
extends Control
signal selected(this: CustomLevelContainer)
var level_name := ""
var level_author := ""
var level_desc := ""
var level_theme := "Overworld"
var level_time := 0
var game_style := "SMBLL"
var difficulty := 0
var file_path := ""
var idx := 0
const CAMPAIGN_RECTS := {
"SMB1": Rect2(0, 0, 42, 16),
"SMBLL": Rect2(0, 16, 42, 16),
"SMBS": Rect2(0, 32, 42, 16),
"SMBANN": Rect2(0, 0, 42, 16)
}
const ICON_TEXTURES := [
preload("uid://chtjq1vr0rpso"),
preload("uid://cn8bcncfmdikq")
]
const THEME_RECTS := {
"Overworld": Rect2(0, 0, 32, 32),
"Underground": Rect2(32, 0, 32, 32),
"Desert": Rect2(64, 0, 32, 32),
"Snow": Rect2(96, 0, 32, 32),
"Jungle": Rect2(128, 0, 32, 32),
"Beach": Rect2(0, 32, 32, 32),
"Garden": Rect2(32, 32, 32, 32),
"Mountain": Rect2(64, 32, 32, 32),
"Skyland": Rect2(96, 32, 32, 32),
"Autumn": Rect2(128, 32, 32, 32),
"Pipeland": Rect2(0, 64, 32, 32),
"Space": Rect2(32, 64, 32, 32),
"Underwater": Rect2(64, 64, 32, 32),
"Volcano": Rect2(96, 64, 32, 32),
"GhostHouse": Rect2(128, 64, 32, 32),
"Castle": Rect2(0, 96, 32, 32),
"CastleWater": Rect2(32, 96, 32, 32),
"Mystery": Rect2(96, 96, 32, 32),
"Airship": Rect2(128, 96, 32, 32),
"Bonus": Rect2(0, 128, 32, 32)
}
func _ready() -> void:
set_process(false)
update_visuals()
func update_visuals() -> void:
%LevelIcon.texture = ResourceSetter.get_resource(ICON_TEXTURES[level_time])
%LevelIcon.region_rect = THEME_RECTS[level_theme]
%LevelName.text = level_name if level_name != "" else "(Unnamed Level)"
%LevelAuthor.text = "By " + (level_author if level_author != "" else "Player")
%CampaignIcon.region_rect = CAMPAIGN_RECTS[game_style]
var idx := 0
for i in %DifficultyStars.get_children():
i.region_rect.position.x = 24 if idx > difficulty else [0, 0, 8, 8, 16][difficulty]
idx += 1
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_accept") and visible:
selected.emit(self)

View File

@@ -0,0 +1 @@
uid://0pxa4836hn6c

View File

@@ -0,0 +1,78 @@
extends VBoxContainer
signal level_selected(container: CustomLevelContainer)
const CUSTOM_LEVEL_CONTAINER = preload("uid://dt20tjug8m6oh")
const CUSTOM_LEVEL_PATH := "user://custom_levels/"
const base64_charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
signal closed
var containers := []
var selected_lvl_idx := -1
func open(refresh_list := true) -> void:
show()
if refresh_list:
refresh()
if selected_lvl_idx >= 0:
%LevelContainers.get_child(selected_lvl_idx).grab_focus()
else:
$TopBit/Button.grab_focus()
await get_tree().process_frame
set_process(true)
func open_folder() -> void:
OS.shell_show_in_file_manager(ProjectSettings.globalize_path(CUSTOM_LEVEL_PATH))
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_back"):
closed.emit()
func close() -> void:
hide()
set_process(false)
func refresh() -> void:
%LevelContainers.get_node("Label").show()
for i in %LevelContainers.get_children():
if i is CustomLevelContainer:
i.queue_free()
containers.clear()
get_levels("user://custom_levels")
get_levels("user://custom_levels/downloaded")
func get_levels(path := "user://custom_levels") -> void:
DirAccess.make_dir_recursive_absolute(path)
var idx := 0
for i in DirAccess.get_files_at(path):
if i.contains(".lvl") == false:
continue
%LevelContainers.get_node("Label").hide()
var container = CUSTOM_LEVEL_CONTAINER.instantiate()
var file = FileAccess.open(path + "/" + i, FileAccess.READ)
var json = JSON.parse_string(file.get_as_text())
file.close()
var data = json["Levels"][0]["Data"].split("=")
var info = json["Info"]
container.level_name = info["Name"]
container.level_author = info["Author"]
container.level_desc = info["Description"]
container.idx = idx
container.file_path = path + "/" + i
container.level_theme = Level.THEME_IDXS[base64_charset.find(data[0])]
container.level_time = base64_charset.find(data[1])
container.game_style = Global.CAMPAIGNS[base64_charset.find(data[3])]
container.selected.connect(container_selected)
containers.append(container)
print(data)
if info.has("Difficulty"):
container.difficulty = info["Difficulty"]
%LevelContainers.add_child(container)
idx += 1
func container_selected(container: CustomLevelContainer) -> void:
level_selected.emit(container)
selected_lvl_idx = container.get_index()

View File

@@ -0,0 +1 @@
uid://cv5avutyesjxt

View File

@@ -0,0 +1,86 @@
class_name CustomLevelMenu
extends Node
static var current_level_file := ""
static var has_entered := false
var selected_lvl_idx := 0
const CUSTOM_LEVEL_PATH := "user://custom_levels/"
const base64_charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
func _ready() -> void:
has_entered = true
ResourceSetterNew.cache.clear()
ResourceSetter.cache.clear()
Global.get_node("GameHUD").hide()
Checkpoint.passed = false
Global.world_num = 1
Global.level_num = 1
Checkpoint.sublevel_id = 0
Global.current_campaign = "SMB1"
AudioManager.stop_all_music()
Global.second_quest = false
%LevelList.open(true)
func _exit_tree() -> void:
Global.get_node("GameHUD").show()
func new_level() -> void:
Global.current_game_mode = Global.GameMode.LEVEL_EDITOR
LevelEditor.load_play = false
LevelEditor.level_name = ""
LevelEditor.level_author = ""
LevelEditor.level_desc = ""
LevelEditor.difficulty = 0
LevelEditor.level_file = LevelEditor.BLANK_FILE.duplicate(true)
Global.transition_to_scene("res://Scenes/Levels/LevelEditor.tscn")
func back_to_title_screen() -> void:
if Global.transitioning_scene:
await Global.transition_finished
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
func edit_level() -> void:
Global.current_game_mode = Global.GameMode.LEVEL_EDITOR
LevelEditor.load_play = false
Global.transition_to_scene("res://Scenes/Levels/LevelEditor.tscn")
func play_level() -> void:
Global.current_game_mode = Global.GameMode.CUSTOM_LEVEL
Settings.file.difficulty.inf_lives = 1
LevelEditor.load_play = true
$CharacterSelect.open()
await $CharacterSelect.selected
LevelTransition.level_to_transition_to = ("res://Scenes/Levels/LevelEditor.tscn")
Global.transition_to_scene("res://Scenes/Levels/LevelTransition.tscn")
func delete_level() -> void:
DirAccess.remove_absolute(current_level_file)
go_back_to_list()
%LevelList.refresh()
if %LevelList.containers.is_empty() == false:
%LevelList.containers[0].grab_focus()
else:
$BG/Border/Levels/VBoxContainer/LevelList/TopBit/Button.grab_focus()
func go_back_to_list() -> void:
$BG/Border/Levels/VBoxContainer/LevelList.show()
%LevelInfo.hide()
func open_lss_browser() -> void:
$BG/Border/Levels/VBoxContainer/LevelList.hide()
%LSSBrowser.open()
func show_lss_level_info(container: OnlineLevelContainer) -> void:
for i in ["level_name", "level_author", "level_theme", "level_id", "thumbnail_url"]:
%SelectedOnlineLevel.set(i, container.get(i))
%SelectedOnlineLevel.setup_visuals()
LevelEditor.level_name = container.level_name
LevelEditor.level_author = container.level_author
%LSSDescription.text = "Fetching Description..."
$BG/Border/Levels/VBoxContainer/LSSBrowser.hide()
%LSSLevelInfo.show()
await get_tree().physics_frame
%Download.grab_focus()

View File

@@ -0,0 +1 @@
uid://bxu6kcun4m6uo

View File

@@ -0,0 +1,68 @@
extends HBoxContainer
@export var title := ""
@export var selected := false
@export var campaigns: Array[String]
@export var extra_confirm := false
signal deleted(campaign: String)
var confirming := false
var confirm_2 := false
var selected_index := 0:
set(value):
selected_index = value
func _process(_delta: float) -> void:
if selected:
handle_inputs()
else:
confirm_2 = false
confirming = false
$Cursor.modulate.a = int(selected)
for i in [$AutoScrollContainer, %AutoScrollContainer2]:
i.is_focused = selected
%Title.text = tr(title) + ":"
if not confirming:
%Value.modulate = Color.WHITE
%Value.text = tr(str(campaigns[selected_index]))
else:
if confirm_2:
%Value.text = tr("DELETION_CONFIRM_2")
else:
%Value.text = tr("DELETION_CONFIRM")
%Value.modulate = Color.RED
%LeftArrow.modulate.a = int(selected and selected_index > 0)
%RightArrow.modulate.a = int(selected and selected_index < campaigns.size() - 1)
func set_selected(active := false) -> void:
selected = active
func handle_inputs() -> void:
if Input.is_action_just_pressed("ui_left"):
confirming = false
confirm_2 = false
selected_index -= 1
if Settings.file.audio.extra_sfx == 1:
AudioManager.play_global_sfx("menu_move")
if Input.is_action_just_pressed("ui_right"):
confirming = false
confirm_2 = false
selected_index += 1
if Settings.file.audio.extra_sfx == 1:
AudioManager.play_global_sfx("menu_move")
if Input.is_action_just_pressed("ui_accept"):
if confirming or confirm_2:
if extra_confirm and confirm_2 == false:
confirm_2 = true
else:
AudioManager.play_global_sfx("cannon")
confirm_2 = false
confirming = false
deleted.emit(campaigns[selected_index])
else:
confirm_2 = false
confirming = true
selected_index = clamp(selected_index, 0, campaigns.size() - 1)

View File

@@ -0,0 +1 @@
uid://cmxqrlwdt6tst

View File

@@ -0,0 +1,48 @@
extends Control
const RANK_MESSAGES = ["F_RANK_MESSAGE", "D_RANK_MESSAGE", "C_RANK_MESSAGE", "B_RANK_MESSAGE", "A_RANK_MESSAGE", "S_RANK_MESSAGE", "P_RANK_MESSAGE"]
var selected_index := 0
func _ready() -> void:
pass
func open() -> void:
setup_visuals()
show()
set_focus(true)
await get_tree().physics_frame
[%Continue, %Retry, %LevelSelect, %ReturnMenu][selected_index].grab_focus()
func setup_visuals() -> void:
%Score.text = str(Global.score)
var rank_idx = DiscoLevel.RANK_IDs.find(DiscoLevel.current_rank)
%Medal.region_rect.position.x = 16 * (rank_idx + 1)
%RankMessage.text = RANK_MESSAGES[rank_idx]
%RankMessage.modulate = GameHUD.RANK_COLOURS[DiscoLevel.current_rank]
func close() -> void:
hide()
func set_focus(enabled := false) -> void:
for i in [%Continue, %Retry, %LevelSelect, %ReturnMenu]:
i.focus_mode = 0 if enabled == false else 2
func continue_to_next_level() -> void:
Global.current_level.transition_to_next_level()
Global.disco_level_continued.emit()
close()
func set_index(idx := 0) -> void:
selected_index = idx
func restart_level() -> void:
LevelTransition.level_to_transition_to = Level.get_scene_string(Global.world_num, Global.level_num)
Global.reset_values()
DiscoLevel.reset_values()
Global.transition_to_scene("res://Scenes/Levels/LevelTransition.tscn")
close()
func go_to_menu() -> void:
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
close()

View File

@@ -0,0 +1 @@
uid://b5u7ht2124lfs

View File

@@ -0,0 +1,24 @@
extends Control
var active := false
signal closed
func _ready() -> void:
set_process(false)
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_cancel") or Input.is_action_just_pressed("editor_open_menu"):
close()
func open() -> void:
set_process(true)
show()
active = true
func close() -> void:
set_process(false)
hide()
active = false
await get_tree().create_timer(0.1).timeout
closed.emit()

View File

@@ -0,0 +1 @@
uid://d1gpliy41d31n

View File

@@ -0,0 +1,4 @@
extends Control
func _ready() -> void:
pass

View File

@@ -0,0 +1 @@
uid://b32rbwuvfqn6x

42
Scripts/UI/GhostSelect.gd Normal file
View File

@@ -0,0 +1,42 @@
extends Control
var selected_index := 0
var active := false
signal selected
signal cancelled
func _ready() -> void:
pass
func open() -> void:
show()
await get_tree().physics_frame
active = true
func _process(_delta: float) -> void:
if active == false: return
if Input.is_action_just_pressed("ui_down"):
selected_index += 1
elif Input.is_action_just_pressed("ui_up"):
selected_index -= 1
selected_index = clamp(selected_index, 0, 1)
if Input.is_action_just_pressed("ui_accept"):
selected.emit()
SpeedrunHandler.ghost_enabled = bool(selected_index)
close()
elif Input.is_action_just_pressed("ui_back"):
close()
cancelled.emit()
var idx := 0
for i in [%NoGhost, %Ghost]:
i.get_node("Cursor").modulate.a = int(selected_index == idx)
idx += 1
func load_ghost() -> void:
SpeedrunHandler.load_best_marathon()
func close() -> void :
hide()
active = false

View File

@@ -0,0 +1 @@
uid://btp7od8thj3mk

View File

@@ -0,0 +1,25 @@
extends MarginContainer
static var current_tab = null
@export var icon: Texture = null
@export var title := ""
@export var linked_control: Control = null
@export var first_pick := false
func _ready() -> void:
if first_pick:
tab_clicked()
$HBoxContainer/Label.text = title
$HBoxContainer/TextureRect.texture = icon
update()
func update() -> void:
print(current_tab == self)
$HBoxContainer/Label.visible = current_tab == self
$Selected.visible = current_tab == self
linked_control.visible = current_tab == self
func tab_clicked() -> void:
current_tab = self
get_tree().call_group("EditorTabs", "update")

View File

@@ -0,0 +1 @@
uid://kvlxhhkgtuk2

55
Scripts/UI/LevelInfo.gd Normal file
View File

@@ -0,0 +1,55 @@
extends VBoxContainer
signal closed
var file_path := ""
var active := false
func _ready() -> void:
set_process(false)
signal level_play
signal level_edit
func open(container: CustomLevelContainer = null) -> void:
if container != null:
for i in ["level_name", "level_author", "level_theme", "game_style", "level_time", "difficulty"]:
%SelectedLevel.set(i, container.get(i))
%SelectedLevel.update_visuals()
LevelEditor.level_name = container.level_name
CustomLevelMenu.current_level_file = container.file_path
LevelEditor.level_author = container.level_author
file_path = container.file_path
LevelEditor.level_desc = container.level_desc
%Description.text = container.level_desc
show()
await get_tree().physics_frame
active = true
set_process(true)
%Play.grab_focus()
func reopen() -> void:
show()
await get_tree().physics_frame
active = true
set_process(true)
%Play.grab_focus()
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_back") and active:
closed.emit()
close()
func level_selected() -> void:
LevelEditor.level_file = JSON.parse_string(FileAccess.open(file_path, FileAccess.READ).get_as_text())
level_play.emit()
active = false
func level_edited() -> void:
LevelEditor.level_file = JSON.parse_string(FileAccess.open(file_path, FileAccess.READ).get_as_text())
level_edit.emit()
func close() -> void:
hide()
set_process(false)

View File

@@ -0,0 +1 @@
uid://bsmrnnpjkhidw

189
Scripts/UI/LevelSelect.gd Normal file
View File

@@ -0,0 +1,189 @@
extends Control
var selected_level := 0
signal level_selected
signal cancelled
var active := false
var starting_value := -1
@export var has_speedrun_stuff := false
@export var has_disco_stuff := false
const LEVEL_ICONS := {
"SMB1": SMB1_ICONS,
"SMBLL": SMBLL_ICONS,
"SMBS": SMBS_ICONS,
"SMBANN": SMB1_ICONS
}
const SMB1_ICONS := [
"0123",
"0453",
"0023",
"0163",
"8893",
"8893",
"8AB3",
"8883"
]
const SMBLL_ICONS := [
"0123",
"0053",
"0423",
"0023",
"8193",
"8AB3",
"8993",
"88D3",
"8888",
"0123",
"0423",
"0523",
"0003"
]
const SMBS_ICONS := [
"0123",
"0453",
"0023",
"0163",
"8893",
"8893",
"8AB3",
"CA13"
]
const NUMBER_Y := [
"Overworld",
"Underground",
"Castle",
"Snow",
"Space",
"Volcano"
]
func _ready() -> void:
for i in %SlotContainer.get_children():
i.focus_entered.connect(slot_selected.bind(i.get_index()))
for i in [$Panel/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/SlotContainer/Slot1/Icon/RankMedal/SRankParticles, $Panel/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/SlotContainer/Slot1/Icon/RankMedal/PRankParticles, $Panel/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/SlotContainer/Slot2/Icon/RankMedal/SRankParticles, $Panel/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/SlotContainer/Slot2/Icon/RankMedal/PRankParticles, $Panel/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/SlotContainer/Slot3/Icon/RankMedal/SRankParticles, $Panel/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/SlotContainer/Slot3/Icon/RankMedal/PRankParticles, $Panel/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/SlotContainer/Slot4/Icon/RankMedal/SRankParticles, $Panel/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/SlotContainer/Slot4/Icon/RankMedal/PRankParticles]:
start_particle(i)
func start_particle(particle: GPUParticles2D) -> void:
await get_tree().create_timer(randf_range(0, 5)).timeout
particle.emitting = true
func _process(_delta: float) -> void:
if active:
handle_input()
Global.level_num = selected_level + 1
func open() -> void:
if starting_value == -1:
starting_value = Global.level_num
print([Global.level_num, starting_value])
selected_level = Global.level_num - 1
setup_visuals()
update_pb()
show()
$%SlotContainer.get_child(selected_level).grab_focus()
await get_tree().create_timer(0.1).timeout
active = true
const CHARSET := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var visited_levels := "0000"
func setup_visuals() -> void:
%MarathonBits.visible = Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE
var idx := 0
for i in %SlotContainer.get_children():
if i.visible == false:
continue
var level_theme = Global.LEVEL_THEMES[Global.current_campaign][Global.world_num - 1]
visited_levels = (SaveManager.visited_levels.substr((Global.world_num - 1) * 4, 4))
var level_visited = SaveManager.visited_levels[SaveManager.get_level_idx(Global.world_num, idx + 1)] != "0" or Global.debug_mode
var num = CHARSET.find(LEVEL_ICONS[Global.current_campaign][Global.world_num - 1][idx])
if level_visited == false:
num = 7
i.get_node("ChallengeModeBits").visible = Global.current_game_mode == Global.GameMode.CHALLENGE
if Global.current_game_mode == Global.GameMode.CHALLENGE:
setup_challenge_mode_bits(i.get_node("ChallengeModeBits"), idx + 1)
i.get_node("Icon").region_rect = Rect2((num % 4) * 56, (num / 4) * 32, 56, 32)
i.get_node("Icon/Number").region_rect.position.y = clamp(NUMBER_Y.find(level_theme) * 12, 0, 9999)
i.get_node("Icon/Number").region_rect.position.x = (idx) * 12
i.get_node("Icon/RankMedal").visible = Global.current_campaign == "SMBANN"
if Global.current_campaign == "SMBANN":
i.get_node("Icon/RankMedal").frame = "ZFDCBASP".find(DiscoLevel.level_ranks[SaveManager.get_level_idx(Global.world_num, idx + 1)])
i.get_node("Icon/RankMedal/SRankParticles").visible = i.get_node("Icon/RankMedal").frame == 6
i.get_node("Icon/RankMedal/PRankParticles").visible = i.get_node("Icon/RankMedal").frame == 7
idx += 1
func setup_challenge_mode_bits(container: HBoxContainer, level_num := 1) -> void:
for i in [container.get_node("1"), container.get_node("2"), container.get_node("3"), container.get_node("4"), container.get_node("5"), container.get_node("6")]:
var collected = ChallengeModeHandler.is_coin_collected(int(i.name) - 1, ChallengeModeHandler.red_coins_collected[Global.world_num - 1][level_num - 1])
i.get_node("Full").visible = collected
container.get_node("Score/Full").visible = ChallengeModeHandler.top_challenge_scores[Global.world_num - 1][level_num - 1] >= ChallengeModeHandler.CHALLENGE_TARGETS[Global.current_campaign][Global.world_num - 1][level_num - 1]
func update_pb() -> void:
if has_speedrun_stuff == false: return
var best_warpless_time = SpeedrunHandler.best_level_warpless_times[Global.world_num - 1][selected_level]
print(SpeedrunHandler.best_level_warpless_times)
var best_any_time = SpeedrunHandler.best_level_any_times.get(str(Global.world_num) + "-" + str(selected_level + 1), -1)
%FullRunPB.text = "--:--:--" if best_warpless_time == -1 else SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(best_warpless_time))
%WarpRunPB.text = "--:--:--" if best_any_time == -1 else SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(best_any_time))
%Flag.visible = selected_level < 3
%Axe.visible = selected_level >= 3
$Panel/MarginContainer/VBoxContainer/MarathonBits/VBoxContainer/Warp.modulate = Color.WHITE if SpeedrunHandler.WARP_LEVELS[Global.current_campaign].has(str(Global.world_num) + "-" + str(selected_level + 1)) else Color(0.25, 0.25, 0.25)
var gold_warpless_time = SpeedrunHandler.LEVEL_GOLD_WARPLESS_TIMES[Global.current_campaign][Global.world_num - 1][selected_level]
var gold_any_time := -1.0
if SpeedrunHandler.LEVEL_GOLD_ANY_TIMES[Global.current_campaign].has(str(Global.world_num) + "-" + str(selected_level + 1)):
gold_any_time = SpeedrunHandler.LEVEL_GOLD_ANY_TIMES[Global.current_campaign][str(Global.world_num) + "-" + str(selected_level + 1)]
for i in %FullRunMedals.get_children():
var target_time = gold_warpless_time * SpeedrunHandler.MEDAL_CONVERSIONS[i.get_index()]
i.get_node("Full").visible = best_warpless_time <= target_time and best_warpless_time > 0
if gold_any_time != -1:
for i in %WarpRunMedals.get_children():
var target_time = gold_any_time * SpeedrunHandler.MEDAL_CONVERSIONS[i.get_index()]
i.get_node("Full").visible = best_any_time <= target_time and best_any_time > 0
else:
for i in %WarpRunMedals.get_children():
i.get_node("Full").hide()
func handle_input() -> void:
selected_level = clamp(selected_level, 0, 3)
if Input.is_action_just_pressed("ui_accept"):
if visited_levels[selected_level] == "0" and selected_level != 0 and not Global.debug_mode:
AudioManager.play_sfx("bump")
else:
select_world()
elif Input.is_action_just_pressed("ui_back"):
close()
cleanup()
cancelled.emit()
return
func select_world() -> void:
if owner is Level:
owner.level_id = selected_level + 1
Global.level_num = selected_level + 1
level_selected.emit()
close()
func slot_selected(idx := 0) -> void:
selected_level = idx
update_pb()
func cleanup() -> void:
await get_tree().process_frame
Global.level_num = starting_value
starting_value = -1
Global.level_num = clamp(Global.level_num, 1, 4)
if owner is Level:
owner.level_id = clamp(owner.level_id, 1, 8)
func close() -> void:
active = false
hide()

1
Scripts/UI/LevelSelect.gd.uid Executable file
View File

@@ -0,0 +1 @@
uid://bupc8brk48aqi

102
Scripts/UI/LssBrowser.gd Normal file
View File

@@ -0,0 +1,102 @@
extends VBoxContainer
var page_number := 1
@onready var http_request: HTTPRequest = $HTTPRequest
const LSS_URL := "https://levelsharesquare.com"
signal closed
signal level_selected(container: OnlineLevelContainer)
var list := {}
const ONLINE_LEVEL_CONTAINER = preload("uid://cr2pku7fjkgpo")
var filter = 0
var selected_lvl_idx := -1
var sort := -1
func _ready() -> void:
set_process(false)
func open(refresh_list := true) -> void:
show()
if refresh_list:
grab_levels()
await get_tree().physics_frame
if selected_lvl_idx >= 0:
%OnlineLevelList.get_child(selected_lvl_idx).grab_focus()
else:
%RefreshList.grab_focus()
set_process(true)
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_back"):
closed.emit()
close()
func close() -> void:
set_process(false)
hide()
func grab_levels() -> void:
selected_lvl_idx = -1
%OverloadMSG.hide()
%ErrorMSG.hide()
http_request.cancel_request()
for i in %OnlineLevelList.get_children():
i.queue_free()
$LoadingMSG.show()
var filter_str = ["", "", "&sort=plays", "&sort=rating"][filter]
var get_type = ["featured?", "get?", "get?", "get?"][filter]
var page_str = "&page=" + str(page_number)
var url = LSS_URL + "/api/levels/filter/" + get_type + "game=" + str(Global.LSS_GAME_ID) + "&authors=1" + filter_str + page_str + "&sortType=" + str(sort)
http_request.request(url, [], HTTPClient.METHOD_GET)
func level_list_retrieved(result := 0, response_code := 0, headers: PackedStringArray = [], body: PackedByteArray = []) -> void:
$LoadingMSG.hide()
var string = body.get_string_from_utf8()
if response_code != HTTPClient.RESPONSE_OK:
%ErrorMSG.show()
return
if string == "Too many requests, please slow down!":
%OverloadMSG.show()
return
var json = JSON.parse_string(string)
list = json
print(list)
spawn_containers()
%Page.values.clear()
for i in json.numberOfPages:
%Page.values.append(str(int(i + 1)))
func spawn_containers() -> void:
$HSeparator.show()
for i in list.levels:
var container = ONLINE_LEVEL_CONTAINER.instantiate()
container.level_name = i.name
if i.has("status"):
container.featured = i.status == "Featured"
container.level_author = i.author.username
container.difficulty = i.difficulty
container.level_id = i._id
container.level_selected.connect(show_info)
if i.has("thumbnail"):
if i.thumbnail != null:
container.thumbnail_url = i.thumbnail
%OnlineLevelList.add_child(container)
func show_info(container: OnlineLevelContainer) -> void:
selected_lvl_idx = container.get_index()
level_selected.emit(container)
func set_filter(filter_idx := 0) -> void:
filter = filter_idx
grab_levels()
func set_page(page_idx := 0) -> void:
page_number = page_idx + 1
grab_levels()
func set_order(order_idx := 0) -> void:
sort = [-1, 1][order_idx]
grab_levels()

View File

@@ -0,0 +1 @@
uid://crmjepbqu408q

View File

@@ -0,0 +1,89 @@
extends VBoxContainer
signal closed
const LEVEL_INFO_URL := "https://levelsharesquare.com/api/levels/"
var level_id := ""
var has_downloaded := false
signal level_play
func _ready() -> void:
set_process(false)
func open(container: OnlineLevelContainer) -> void:
has_downloaded = FileAccess.file_exists("user://custom_levels/downloaded/" + container.level_id + ".lvl")
show()
%Download.text = "DOWNLOAD"
if has_downloaded:
%OnlinePlay.grab_focus()
else:
%Download.grab_focus()
setup_visuals(container)
level_id = container.level_id
await get_tree().physics_frame
set_process(true)
func setup_visuals(container: OnlineLevelContainer) -> void:
$Panel/AutoScrollContainer.scroll_pos = 0
$Panel/AutoScrollContainer.move_direction = -1
%LSSDescription.text = "Fetching Description..."
%SelectedOnlineLevel.level_name = container.level_name
%SelectedOnlineLevel.level_author = container.level_author
%SelectedOnlineLevel.level_id = container.level_id
%SelectedOnlineLevel.thumbnail_url = container.thumbnail_url
%SelectedOnlineLevel.level_thumbnail = container.level_thumbnail
%SelectedOnlineLevel.difficulty = container.difficulty
%SelectedOnlineLevel.setup_visuals()
$Description.request(LEVEL_INFO_URL + container.level_id)
%Download.visible = not has_downloaded
%OnlinePlay.visible = has_downloaded
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_back"):
close()
func close() -> void:
hide()
closed.emit()
set_process(false)
func download_level() -> void:
DirAccess.make_dir_recursive_absolute("user://custom_levels/downloaded")
var url = "https://levelsharesquare.com/api/levels/" + level_id + "/code"
print(url)
$DownloadLevel.request(url, [], HTTPClient.METHOD_GET)
%Download.text = "DOWNLOADING..."
func open_lss() -> void:
OS.shell_open("https://levelsharesquare.com/levels/" + str(level_id))
func on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
var string = body.get_string_from_utf8()
var json = JSON.parse_string(string)
%LSSDescription.text = Global.sanitize_string(json["level"]["description"])
func level_downloaded(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
var string = body.get_string_from_utf8()
var json = JSON.parse_string(string)
var file = FileAccess.open("user://custom_levels/downloaded/" + level_id + ".lvl", FileAccess.WRITE)
var data = null
if json.levelData is Array:
data = get_json_from_bytes(json.levelData)
else:
data = json.levelData
file.store_string(JSON.stringify(data))
file.close()
%Download.hide()
%OnlinePlay.show()
%OnlinePlay.grab_focus()
func play_level() -> void:
var file_path := "user://custom_levels/downloaded/" + level_id + ".lvl"
LevelEditor.level_file = JSON.parse_string(FileAccess.open(file_path, FileAccess.READ).get_as_text())
level_play.emit()
func get_json_from_bytes(json := []) -> String:
return PackedByteArray(json).get_string_from_ascii()

View File

@@ -0,0 +1 @@
uid://c4x7i885h4si0

View File

@@ -0,0 +1,13 @@
extends Node
func restart_level() -> void:
Global.checkpoint_passed = false
Level.first_load = true
Global.speed_run_timer = 0
Global.speed_run_timer_active = false
Global.reset_values()
AudioManager.main_level_music.stop()
Global.death_load = true
Global.current_level.reload_level()
func quit_to_menu() -> void:
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")

View File

@@ -0,0 +1 @@
uid://c6k7fcjod18yi

View File

@@ -0,0 +1,90 @@
extends Control
var selected_index := 0
func setup_visuals() -> void:
%Time.text = SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(SpeedrunHandler.timer))
var best_time = SpeedrunHandler.best_time
if best_time <= 0 or SpeedrunHandler.best_time > SpeedrunHandler.timer:
best_time = SpeedrunHandler.timer
AudioManager.stop_all_music()
$PBSfx.play()
%PB.text = SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(best_time))
%NewPB.visible = SpeedrunHandler.timer < SpeedrunHandler.best_time or SpeedrunHandler.best_time <= 0
var target_time = -1
%LevelSelect.visible = Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE
if Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
if SpeedrunHandler.is_warp_run:
target_time = SpeedrunHandler.LEVEL_GOLD_ANY_TIMES[Global.current_campaign][str(Global.world_num) + "-" + str(Global.level_num)]
else:
target_time = SpeedrunHandler.LEVEL_GOLD_WARPLESS_TIMES[Global.current_campaign][Global.world_num - 1][Global.level_num - 1]
else:
if SpeedrunHandler.is_warp_run:
target_time = SpeedrunHandler.GOLD_ANY_TIMES[Global.current_campaign]
else:
target_time = SpeedrunHandler.GOLD_WARPLESS_TIMES[Global.current_campaign]
%Target.text = SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(target_time))
var medal_index := -1
if SpeedrunHandler.timer < target_time:
medal_index = 2
elif SpeedrunHandler.timer < target_time * SpeedrunHandler.MEDAL_CONVERSIONS[1]:
medal_index = 1
elif SpeedrunHandler.timer < target_time * SpeedrunHandler.MEDAL_CONVERSIONS[0]:
medal_index = 0
%Medal.get_node("Full").visible = medal_index >= 0
%Medal.get_node("Full").region_rect.position = Vector2(8 * medal_index, 0)
if medal_index >= 0:
%Time.modulate = [Color("C6691D"), Color("BCBCBC"), Color("FFB259")][medal_index]
else:
%Time.modulate = Color.WHITE
func open() -> void:
set_focus(true)
setup_visuals()
show()
return_focus()
func return_focus() -> void:
await get_tree().physics_frame
[%Restart, %LevelSelect, %Return][selected_index].grab_focus()
func check_for_warp() -> void:
SpeedrunHandler.is_warp_run = false
if SpeedrunHandler.WARP_LEVELS[Global.current_campaign].has(str(Global.world_num) + "-" + str(Global.level_num)) or Global.current_game_mode == Global.GameMode.MARATHON:
$SpeedrunTypeSelect.open()
else:
restart_level()
func set_focus(enabled := false) -> void:
for i in [%Restart, %LevelSelect, %Return]:
i.focus_mode = 0 if enabled == false else 2
func check_for_warp_level_select_edition() -> void:
SpeedrunHandler.is_warp_run = false
if SpeedrunHandler.WARP_LEVELS[Global.current_campaign].has(str(Global.world_num) + "-" + str(Global.level_num)):
$SpeedrunTypeSelectLevelSelect.open()
else:
restart_level()
func restart_level() -> void:
var path := ""
SpeedrunHandler.timer = 0
Global.reset_values()
Global.clear_saved_values()
if Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
path = Level.get_scene_string(Global.world_num, Global.level_num)
else:
Global.world_num = 1
Global.level_num = 1
path = Level.get_scene_string(1, 1)
SpeedrunHandler.best_time = SpeedrunHandler.get_best_time()
Level.start_level_path = path
LevelTransition.level_to_transition_to = path
Global.transition_to_scene("res://Scenes/Levels/LevelTransition.tscn")
close()
func go_to_menu() -> void:
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
func close() -> void:
hide()

View File

@@ -0,0 +1 @@
uid://djuvlj1dt0aay

View File

@@ -0,0 +1,54 @@
extends Control
signal full_run_selected
signal level_run_selected
signal cancelled
var selected := 0
var active := false
func open() -> void:
setup_visuals()
[$PanelContainer/VBoxContainer/HBoxContainer/Full, $PanelContainer/VBoxContainer/HBoxContainer/Level][selected].grab_focus()
show()
await get_tree().process_frame
active = true
func close() -> void:
hide()
active = false
func _process(_delta: float) -> void:
if active:
if Input.is_action_just_pressed("ui_accept"):
[full_run_selected, level_run_selected][selected].emit()
Global.current_game_mode = [Global.GameMode.MARATHON, Global.GameMode.MARATHON_PRACTICE][selected]
close()
elif Input.is_action_just_pressed("ui_back"):
close()
cancelled.emit()
if Input.is_action_just_pressed("ui_left"):
selected -= 1
if Input.is_action_just_pressed("ui_right"):
selected += 1
selected = clamp(selected, 0, 1)
%MarathonName.text = ["MARATHON_FULL", "MARATHON_LEVEL"][selected]
for i in [$PanelContainer/VBoxContainer/HBoxContainer/Full, $PanelContainer/VBoxContainer/HBoxContainer/Level]:
i.get_node("Label").visible = selected == i.get_index()
func setup_visuals() -> void:
if SpeedrunHandler.marathon_best_warpless_time <= 0:
%FullRunPB.text = "--:--:--"
else:
%FullRunPB.text = SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(SpeedrunHandler.marathon_best_warpless_time))
if SpeedrunHandler.marathon_best_any_time <= 0:
%WarpedRunPB.text = "--:--:--"
else:
%WarpedRunPB.text = SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(SpeedrunHandler.marathon_best_any_time))
for i in %FullMedals.get_children():
i.get_node("Full").visible = SpeedrunHandler.marathon_best_warpless_time <= SpeedrunHandler.GOLD_WARPLESS_TIMES[Global.current_campaign] * SpeedrunHandler.MEDAL_CONVERSIONS[i.get_index()] and SpeedrunHandler.marathon_best_warpless_time > 0
for i in %WarpedMedals.get_children():
i.get_node("Full").visible = SpeedrunHandler.marathon_best_any_time <= SpeedrunHandler.GOLD_ANY_TIMES[Global.current_campaign] * SpeedrunHandler.MEDAL_CONVERSIONS[i.get_index()] and SpeedrunHandler.marathon_best_any_time > 0

View File

@@ -0,0 +1 @@
uid://bpqtkfuv6a2sx

View File

@@ -0,0 +1,65 @@
class_name OnlineLevelContainer
extends Button
var level_name := ""
var level_author := ""
var level_thumbnail = null
var level_id := ""
var thumbnail_url := ""
var difficulty := "Easy"
var featured = false
signal level_selected(container: OnlineLevelContainer)
const DIFFICULTY_TO_STAR_TRANSLATION := {
"Easy": 0,
"Medium": 2,
"Hard": 3,
"Extreme": 4
}
static var cached_thumbnails := {}
func _ready() -> void:
set_process(false)
setup_visuals()
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_accept") and visible:
level_selected.emit(self)
func setup_visuals() -> void:
%LevelName.text = Global.sanitize_string(level_name)
%LevelAuthor.text = level_author
if featured:
self_modulate = Color.YELLOW
var idx := 0
print(difficulty)
var difficulty_int = DIFFICULTY_TO_STAR_TRANSLATION[difficulty]
for i in %DifficultyStars.get_children():
i.region_rect.position.x = 24 if idx > difficulty_int else [0, 0, 8, 8, 16][difficulty_int]
idx += 1
get_thumbnail()
func get_thumbnail() -> void:
if cached_thumbnails.has(level_id):
%LevelIcon.texture = cached_thumbnails[level_id]
$MarginContainer/HBoxContainer/HSplitContainer/LeftHalf/LevelIcon/Error.hide()
return
if thumbnail_url == "":
$MarginContainer/HBoxContainer/HSplitContainer/LeftHalf/LevelIcon/Label.hide()
$MarginContainer/HBoxContainer/HSplitContainer/LeftHalf/LevelIcon/Error.show()
return
$ThumbnailDownloader.request(thumbnail_url, [], HTTPClient.METHOD_GET)
func on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
var image = Image.new()
if thumbnail_url.contains(".webp"):
image.load_webp_from_buffer(body)
elif thumbnail_url.contains(".jpeg") or thumbnail_url.contains(".jpg"):
image.load_jpg_from_buffer(body)
else:
image.load_png_from_buffer(body)
%LevelIcon.texture = ImageTexture.create_from_image(image)
cached_thumbnails[level_id] = %LevelIcon.texture

View File

@@ -0,0 +1 @@
uid://d486p6delid2

39
Scripts/UI/QuestSelect.gd Normal file
View File

@@ -0,0 +1,39 @@
extends Control
signal selected
signal cancelled
var active := false
var selected_index := 0
func _process(_delta: float) -> void:
if active:
handle_input()
func open() -> void:
show()
await get_tree().process_frame
[%FirstQuest, %SecondQuest][int(Global.second_quest)].grab_focus()
active = true
func handle_input() -> void:
if Input.is_action_just_pressed("ui_accept"):
select()
close()
elif Input.is_action_just_pressed("ui_back"):
Global.second_quest = false
close()
cancelled.emit()
return
func set_index(idx := false) -> void:
selected_index = int(idx)
func select() -> void:
Global.second_quest = bool(selected_index)
selected.emit()
close()
func close() -> void:
active = false
hide()

View File

@@ -0,0 +1 @@
uid://dwiic1tdol4wh

17
Scripts/UI/QuitDialog.gd Normal file
View File

@@ -0,0 +1,17 @@
extends Control
signal cancelled
var is_active := false
func _physics_process(delta: float) -> void:
modulate.a = lerpf(modulate.a, int(is_active), delta * 15)
if Input.is_action_just_released("ui_back") and is_active:
cancelled.emit()
is_active = false
$AnimationPlayer.stop()
func start() -> void:
$AnimationPlayer.play("Animation")
is_active = true
await $AnimationPlayer.animation_finished
get_tree().quit()

View File

@@ -0,0 +1 @@
uid://fwgm5xy4jvwf

View File

@@ -0,0 +1,67 @@
extends Control
var config_json := {}
const RESOURCE_PACK_CONFIG_OPTION_NODE = preload("uid://c5ea03ob6ncq7")
signal closed
var selected_index := 0
var active := false
var json_path := ""
func open() -> void:
if active: return
clear_options()
spawn_options()
show()
await get_tree().process_frame
%Options.active = true
active = true
func clear_options() -> void:
for i in %Options.options:
i.queue_free()
%Options.options.clear()
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_back") and active:
close()
func spawn_options() -> void:
for i in config_json.options:
var node = RESOURCE_PACK_CONFIG_OPTION_NODE.instantiate()
node.config_name = i
if config_json.options[i] is bool:
node.values = ["SETTING_OFF", "SETTING_ON"]
node.selected_index = int(config_json.options[i])
node.is_bool = true
else:
node.values = config_json.value_keys[i]
node.selected_index = config_json.value_keys[i].find(config_json.options[i])
%Options.add_child(node)
node.value_changed.connect(value_changed)
%Options.options.append(node)
func value_changed(option: PackConfigOption) -> void:
if option.is_bool:
config_json.options[option.config_name] = bool(option.selected_index)
else:
config_json.options[option.config_name] = option.values[option.selected_index]
update_json()
func update_json() -> void:
var file = FileAccess.open(json_path, FileAccess.WRITE)
file.store_string(JSON.stringify(config_json, "\t", false))
file.close()
func close() -> void:
ResourceSetter.cache.clear()
ResourceSetterNew.cache.clear()
Global.level_theme_changed.emit()
closed.emit()
clear_options()
hide()
%Options.active = false
active = false

View File

@@ -0,0 +1 @@
uid://le6t1opa0sr0

View File

@@ -0,0 +1,32 @@
class_name PackConfigOption
extends HBoxContainer
@export var selected := false
var values := []
signal value_changed(this: PackConfigOption)
var config_name := ""
var selected_index := 0
var is_bool := false
func _process(_delta: float) -> void:
if selected:
handle_inputs()
$Cursor.modulate.a = int(selected)
$Title.text = tr(config_name) + ":"
$Value.text = ("" if selected_index > 0 and selected else " ") + tr(str(values[selected_index])) + ("" if selected_index < values.size() - 1 and selected else " ")
func handle_inputs() -> void:
var old := selected_index
if Input.is_action_just_pressed("ui_left"):
selected_index -= 1
if Input.is_action_just_pressed("ui_right"):
selected_index += 1
selected_index = clamp(selected_index, 0, values.size() - 1)
if old != selected_index:
value_changed.emit(self)

View File

@@ -0,0 +1 @@
uid://cu43fwblwup82

View File

@@ -0,0 +1,72 @@
class_name ResourcePackContainer
extends HBoxContainer
const RESOURCE_PACK_CONFIG_MENU = preload("uid://bom2rstlk8fws")
var pack_json := {"name": "Hello",
"description": "Hi :"}
var icon: Texture = null
var pack_name := ""
var loaded := false
var selected := false
var load_order := 0
var config := {}
var config_path := ""
signal resource_pack_selected()
signal open_config(pack: ResourcePackContainer)
func _ready() -> void:
setup_visuals()
func setup_visuals() -> void:
%Title.text = pack_json.name.to_upper()
%Description.text = pack_json.description.to_upper()
%Icon.texture = icon
%LoadedOrder.text = str(load_order)
func _process(_delta: float) -> void:
loaded = Settings.file.visuals.resource_packs.has(pack_name)
%Cursor.modulate.a = int(selected)
%LoadedOrder.visible = loaded
%LoadedOrder.text = str(load_order + 1)
load_order = Settings.file.visuals.resource_packs.find(pack_name)
$ResourcePackContainer.self_modulate = Color.GREEN if loaded else Color.WHITE
$Edit/EditLabel.visible = selected and config != {}
for i in [%TitleScroll, %DescScroll]:
i.is_focused = selected
if selected:
focus_mode = Control.FOCUS_ALL
grab_focus()
else:
focus_mode = Control.FOCUS_NONE
if Input.is_action_just_pressed("jump_0") and selected and visible:
select()
elif Input.is_action_just_pressed("ui_right") and selected and visible and config != {}:
open_config_menu()
func open_config_menu() -> void:
open_config.emit(self)
func select() -> void:
print(ResourceSetter.cache)
ResourceSetter.cache.clear()
print(ResourceSetter.cache)
ResourceSetterNew.cache.clear()
ResourceGetter.cache.clear()
AudioManager.current_level_theme = ""
loaded = not loaded
if loaded and Settings.file.visuals.resource_packs.has(pack_name) == false:
Settings.file.visuals.resource_packs.push_front(pack_name)
if config != {}:
ResourceSetterNew.pack_configs[pack_name] = config
else:
ResourceSetterNew.pack_configs.erase(pack_name)
Settings.file.visuals.resource_packs.erase(pack_name)
Global.level_theme_changed.emit()
if loaded:
AudioManager.play_global_sfx("coin")
else:
AudioManager.play_global_sfx("bump")

View File

@@ -0,0 +1 @@
uid://cbc02k2pmw13p

View File

@@ -0,0 +1,307 @@
class_name AssetEditor
extends AssetRipper
const CUR_SPRITE_TEXT: String = "Current Sprite:\n%s"
var list_index: int = 0
var sprite_list: Array
var cached_sprites: Dictionary[String, Dictionary]
var tile_atlas: Texture
var rom_provided: bool
var source_path: String
var tile_index: int = 0
var columns: int = 4
var sheet_size := Vector2i(16, 16)
var palette_base: String = "Tile"
var palettes: Dictionary
var tiles: Dictionary
@onready var rom_required: Label = %RomRequired
@onready var file_dialog: FileDialog = %FileDialog
@onready var image_preview: TextureRect = %ImagePreview
@onready var green_preview: TextureRect = %GreenPreview
@onready var tiles_preview: TextureRect = %TilesPreview
@onready var cur_sprite: Label = %CurSprite
@onready var preview: Sprite2D = %Preview
@onready var buttons: HBoxContainer = %Buttons
@onready var palette_override: LineEdit = %PaletteOverride
@onready var scroll_container: ScrollContainer = %ScrollContainer
@onready var json_container: VBoxContainer = %JSONContainer
@onready var json_edit: TextEdit = %JSONEdit
func _ready() -> void:
Global.get_node("GameHUD").hide()
if Global.rom_path != "":
on_file_selected(Global.rom_path)
func _exit_tree() -> void:
Global.get_node("GameHUD").show()
func _unhandled_input(event: InputEvent) -> void:
if not rom_provided:
return
if event is InputEventMouseMotion:
var is_snapped = not Input.is_action_pressed("editor_cam_fast")
var mouse_pos: Vector2 = event.position + Vector2(
scroll_container.scroll_horizontal,
scroll_container.scroll_vertical
) + Vector2(-4, -8)
preview.position = Vector2i(mouse_pos).snappedi(8 if is_snapped else 1)
preview.visible = true
if preview.position.x + 8 > sheet_size.x:
preview.visible = false
if preview.position.y + 8 > sheet_size.y:
preview.visible = false
if not preview.is_visible_in_tree(): return
var direction: int = 0
if event.is_action_pressed("editor_cam_left"): direction = -1
if event.is_action_pressed("editor_cam_right"): direction = 1
if event.is_action_pressed("pick_tile"):
if tiles.has(preview.position):
tile_index = tiles[preview.position].index
preview.flip_h = tiles[preview.position].flip_h
preview.flip_v = tiles[preview.position].flip_v
if Input.is_action_pressed("editor_select"):
if tiles[preview.position].has("palette"):
palette_override.text = tiles[preview.position]["palette"]
else:
palette_override.text = ""
preview.region_rect = Rect2i(index_to_coords(tile_index, 32) * 8, Vector2i(8, 8))
if direction != 0:
var multiply = 1 if not Input.is_action_pressed("editor_cam_fast") else 8
tile_index = wrapi(tile_index + direction * multiply, 0, 512)
preview.region_rect = Rect2i(index_to_coords(tile_index, 32) * 8, Vector2i(8, 8))
if event.is_action_pressed("jump_0"): preview.flip_h = not preview.flip_h
if event.is_action_pressed("run_0"): preview.flip_v = not preview.flip_v
var left_click: bool = event.is_action_pressed("mb_left")
var right_click: bool = event.is_action_pressed("mb_right")
if left_click or right_click:
if left_click:
tiles[preview.position] = {
"index": tile_index,
"flip_h": preview.flip_h,
"flip_v": preview.flip_v
}
if not palette_override.text.is_empty():
tiles[preview.position]["palette"] = palette_override.text
else:
tiles.erase(preview.position)
update_edited_image()
func _process(_delta: float) -> void:
if Input.is_action_pressed("editor_select") == false:
return
var left_click: bool = Input.is_action_pressed("mb_left")
var right_click: bool = Input.is_action_pressed("mb_right")
if left_click or right_click:
if left_click:
tiles[preview.position] = {
"index": tile_index,
"flip_h": preview.flip_h,
"flip_v": preview.flip_v
}
if not palette_override.text.is_empty():
tiles[preview.position]["palette"] = palette_override.text
else:
tiles.erase(preview.position)
update_edited_image()
func update_edited_image() -> void:
var tiles_images: Array[Image] = get_tile_images(image_preview.texture.get_image().get_size(), tiles, palettes)
tiles_preview.texture = ImageTexture.create_from_image(tiles_images[0])
green_preview.texture = ImageTexture.create_from_image(tiles_images[1])
func get_tile_images(
img_size: Vector2i, tile_list: Dictionary, palette_lists: Dictionary
) -> Array[Image]:
var image := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8)
var green_image := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8)
for palette_name in palette_lists.keys():
var cur_column: int = 0
var offset := Vector2.ZERO
var pal_json: String = FileAccess.get_file_as_string(
PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_name])
var pal_dict: Dictionary = JSON.parse_string(pal_json).palettes
for palette_id: String in palette_lists[palette_name]:
var palette: Array = pal_dict.get(palette_id, PREVIEW_PALETTE)
for tile_pos: Vector2 in tile_list:
var tile_dict: Dictionary = tile_list[tile_pos]
var tile_palette: String = tile_dict.get("palette", palette_base)
if tile_palette == palette_name:
var destination: Vector2 = tile_pos + offset
if destination.x < img_size.x and destination.y < img_size.y:
draw_green(green_image, destination)
draw_tile(
false,
image,
tile_dict.get("index", 0),
destination,
palette,
tile_dict.get("flip_h", false),
tile_dict.get("flip_v", false)
)
cur_column += 1
if cur_column >= columns:
cur_column = 0
offset.x = 0
offset.y += sheet_size.y
else:
offset.x += sheet_size.x
return [image, green_image]
func on_file_selected(path: String) -> void:
rom = FileAccess.get_file_as_bytes(path)
prg_rom_size = rom[4] * 16384
chr_rom = rom.slice(16 + prg_rom_size)
rom_provided = true
rom_required.hide()
buttons.show()
# setup sprite atlas for placing tiles
var atlas := Image.create(256, 256, false, Image.FORMAT_RGBA8)
for index in range(512):
var pos: Vector2i = index_to_coords(index, 32) * 8
draw_tile(false, atlas, index, pos, PREVIEW_PALETTE)
preview.texture = ImageTexture.create_from_image(atlas)
#
var list_json: String = FileAccess.get_file_as_string(SPRITE_LIST_PATH)
var list_dict: Dictionary = JSON.parse_string(list_json)
sprite_list = list_dict.get("sprites", [])
cycle_list(0)
func load_sprite(sprite_dict: Dictionary) -> void:
source_path = sprite_dict.source_path
if source_path.begins_with("res://"):
image_preview.texture = load(source_path)
else:
var image := Image.load_from_file(source_path)
image_preview.texture = ImageTexture.create_from_image(image)
cur_sprite.text = CUR_SPRITE_TEXT % source_path.get_file()
columns = str_to_var(sprite_dict.get("columns", "4"))
sheet_size = str_to_var(sprite_dict.get("sheet_size", "Vector2i(16, 16)"))
palette_base = sprite_dict.get("palette_base", "Tile")
var palettes_var: Variant = str_to_var(sprite_dict.get("palettes", var_to_str(get_default_palettes())))
if typeof(palettes_var) == TYPE_ARRAY:
palettes = {}
palettes[palette_base] = palettes_var
elif typeof(palettes_var) == TYPE_DICTIONARY:
palettes = palettes_var
tiles = str_to_var(sprite_dict.get("tiles", "{}"))
update_palettes()
update_edited_image()
func save_sprite() -> void:
var sprite_dict: Dictionary = get_as_dict()
var destination_path: String = png_path_to_json(sprite_dict.source_path)
var json_string: String = JSON.stringify(sprite_dict)
DirAccess.make_dir_recursive_absolute(destination_path.get_base_dir())
var file: FileAccess = FileAccess.open(destination_path, FileAccess.WRITE)
file.store_line(json_string)
file.close()
# save green over original image
var base_image: Image = image_preview.texture.get_image()
var green_image: Image = green_preview.texture.get_image()
for y in range(green_image.get_size().y):
for x in range(green_image.get_size().x):
var found_color: Color = green_image.get_pixel(x, y)
if found_color.a > 0:
base_image.set_pixel(x, y, found_color)
base_image.save_png(sprite_dict.source_path)
func cycle_list(add_index: int) -> void:
if add_index != 0:
cached_sprites[sprite_list[list_index]] = get_as_dict()
list_index = wrapi(list_index + add_index, 0, sprite_list.size())
if sprite_list[list_index] in cached_sprites:
load_sprite(cached_sprites[sprite_list[list_index]])
else:
var json_path: String = sprite_list[list_index].replace(
"res://Assets/Sprites/", "res://Resources/AssetRipper/Sprites/"
).replace(".png", ".json")
if FileAccess.file_exists(json_path):
var json_string: String = FileAccess.get_file_as_string(json_path)
load_sprite(JSON.parse_string(json_string))
else:
load_sprite({"source_path": sprite_list[list_index]})
func get_as_dict() -> Dictionary:
return {
"source_path": source_path,
"columns": var_to_str(columns),
"sheet_size": var_to_str(sheet_size),
"palette_base": palette_base,
"palettes": var_to_str(palettes),
"tiles": var_to_str(tiles)
}
func get_default_palettes() -> Dictionary:
var pal_json: String = FileAccess.get_file_as_string(
PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_base])
var pal_dict: Dictionary = JSON.parse_string(pal_json)
var default_palettes: Array = pal_dict.get("palettes", []).keys()
return {palette_base: default_palettes}
func update_palettes(load_textedit: bool = false) -> void:
if load_textedit:
var dict: Dictionary = JSON.parse_string(json_edit.text)
columns = dict.get("columns", 1)
var size_array: Array = dict.get("sheet_size", [16, 16])
sheet_size = Vector2i(size_array[0], size_array[1])
palette_base = dict.get("palette_base", "Tile")
var pal_var: Variant = dict.get("palettes", get_default_palettes())
if typeof(pal_var) == TYPE_ARRAY:
palettes = {}
palettes[palette_base] = pal_var
elif typeof(pal_var) == TYPE_DICTIONARY:
palettes = pal_var
update_edited_image()
json_edit.text = JSON.stringify(
{
"columns": columns,
"sheet_size": [sheet_size.x, sheet_size.y],
"palette_base": palette_base,
"palettes": palettes
}, "\t", false)
func toggle_palettes_view(toggled_on: bool) -> void:
json_container.visible = toggled_on
scroll_container.visible = not toggled_on
func draw_green(
image: Image,
pos: Vector2i
) -> void:
for y in range(8):
for x in range(8):
image.set_pixelv(Vector2i(x, y) + pos, Color.GREEN)

View File

@@ -0,0 +1 @@
uid://bhplwsmbalqng

View File

@@ -0,0 +1,129 @@
class_name ResourceGenerator
extends AssetRipper
@onready var progress_bar: ProgressBar = %ProgressBar
@onready var error: Label = %Error
func _ready() -> void:
Global.get_node("GameHUD").hide()
rom = FileAccess.get_file_as_bytes(Global.rom_path)
prg_rom_size = rom[4] * 16384
chr_rom = rom.slice(16 + prg_rom_size)
await get_tree().create_timer(1, false).timeout
generate_resource_pack()
func _exit_tree() -> void:
Global.get_node("GameHUD").show()
func done() -> void:
if not Settings.file.visuals.resource_packs.has(Global.ROM_PACK_NAME):
Settings.file.visuals.resource_packs.insert(0, Global.ROM_PACK_NAME)
await get_tree().create_timer(0.5).timeout
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
func generate_resource_pack() -> void:
DirAccess.make_dir_recursive_absolute(Global.ROM_ASSETS_PATH)
var pack_json: String = FileAccess.get_file_as_string("res://Resources/AssetRipper/ResourcePack/pack_info.json")
var pack_dict: Dictionary = JSON.parse_string(pack_json)
pack_dict.set("version", Global.ROM_ASSETS_VERSION)
var pack_file := FileAccess.open(Global.ROM_ASSETS_PATH + "/pack_info.json", FileAccess.WRITE)
pack_file.store_line(JSON.stringify(pack_dict))
pack_file.close()
var list_json: String = FileAccess.get_file_as_string(SPRITE_LIST_PATH)
var list_dict: Dictionary = JSON.parse_string(list_json)
var sprite_list: Array = list_dict.get("sprites", [])
progress_bar.max_value = sprite_list.size()
var sprites_handled: int = 0
for sprite_path in sprite_list:
var json_path: String = png_path_to_json(sprite_path)
var sprite_image: Image
print("Running:" + sprite_path)
if sprite_path.begins_with("res://"):
sprite_image = load(sprite_path).get_image()
else:
sprite_image = Image.load_from_file(sprite_path)
sprite_image.convert(Image.FORMAT_RGBA8)
if FileAccess.file_exists(json_path):
var json_string: String = FileAccess.get_file_as_string(json_path)
var json_dict: Dictionary = JSON.parse_string(json_string)
paste_sprite(sprite_image, json_dict)
var destination_path: String = get_destination_path(sprite_path)
if not DirAccess.dir_exists_absolute(destination_path.get_base_dir()):
DirAccess.make_dir_recursive_absolute(destination_path.get_base_dir())
sprite_image.save_png(destination_path)
sprites_handled += 1
progress_bar.value = sprites_handled
await get_tree().process_frame
if sprites_handled < sprite_list.size():
error.show()
## uncomment this once the initial jsons are fully setup so that the game won't
## boot if the resource pack loading is borked
OS.move_to_trash(Global.ROM_ASSETS_PATH)
else:
done()
func paste_sprite(sprite_image: Image, json_dict: Dictionary):
var columns: int = str_to_var(json_dict.get("columns", "4"))
var sheet_size: Vector2i = str_to_var(json_dict.get("sheet_size", "Vector2i(16, 16)"))
var palette_base: String = json_dict.get("palette_base", "Tile")
var palette_var: Variant = str_to_var(json_dict.get("palettes", "{}"))
var palette_lists: Dictionary
if typeof(palette_var) == TYPE_ARRAY:
palette_lists[palette_base] = palette_var
elif typeof(palette_var) == TYPE_DICTIONARY:
palette_lists = palette_var
var tile_list: Dictionary = str_to_var(json_dict.get("tiles", "{}"))
var img_size: Vector2i = sprite_image.get_size()
for palette_name in palette_lists.keys():
var cur_column: int = 0
var offset := Vector2.ZERO
var pal_json: String = FileAccess.get_file_as_string(
PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_name])
var pal_dict: Dictionary = JSON.parse_string(pal_json).palettes
for palette_id: String in palette_lists[palette_name]:
var palette: Array = pal_dict.get(palette_id, PREVIEW_PALETTE)
for tile_pos: Vector2 in tile_list:
var tile_dict: Dictionary = tile_list[tile_pos]
var tile_palette: String = tile_dict.get("palette", palette_base)
if tile_palette == palette_name:
var destination: Vector2 = tile_pos + offset
if destination.x < img_size.x and destination.y < img_size.y:
draw_tile(
true,
sprite_image,
tile_dict.get("index", 0),
destination,
palette,
tile_dict.get("flip_h", false),
tile_dict.get("flip_v", false)
)
cur_column += 1
if cur_column >= columns:
cur_column = 0
offset.x = 0
offset.y += sheet_size.y
else:
offset.x += sheet_size.x
func get_destination_path(sprite_path: String) -> String:
return sprite_path.replace("res://Assets/", Global.ROM_ASSETS_PATH + "/")

View File

@@ -0,0 +1 @@
uid://bwlrdsclxd15j

55
Scripts/UI/RomVerifier.gd Normal file
View File

@@ -0,0 +1,55 @@
class_name ROMVerifier
extends Node
const VALID_HASH := "c9b34443c0414f3b91ef496d8cfee9fdd72405d673985afa11fb56732c96152b"
func _ready() -> void:
Global.get_node("GameHUD").hide()
get_window().files_dropped.connect(on_file_dropped)
await get_tree().physics_frame
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false)
func on_file_dropped(files: PackedStringArray) -> void:
for i in files:
if is_valid_rom(i):
Global.rom_path = i
verified()
copy_rom(i)
return
error()
func copy_rom(file_path := "") -> void:
DirAccess.copy_absolute(file_path, Global.ROM_PATH)
static func get_hash(file_path := "") -> String:
var file_bytes = FileAccess.open(file_path, FileAccess.READ).get_buffer(40976)
var data = file_bytes.slice(16)
return Marshalls.raw_to_base64(data).sha256_text()
static func is_valid_rom(rom_path := "") -> bool:
return get_hash(rom_path) == VALID_HASH
func error() -> void:
%Error.show()
$ErrorSFX.play()
func verified() -> void:
$BGM.queue_free()
%DefaultText.queue_free()
%SuccessMSG.show()
$SuccessSFX.play()
await get_tree().create_timer(3, false).timeout
if not Global.rom_assets_exist:
Global.transition_to_scene("res://Scenes/Levels/RomResourceGenerator.tscn")
else:
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
func _exit_tree() -> void:
Global.get_node("GameHUD").show()
func create_file_pointer(file_path := "") -> void:
var pointer = FileAccess.open(Global.ROM_POINTER_PATH, FileAccess.WRITE)
pointer.store_string(file_path)
pointer.close()

View File

@@ -0,0 +1 @@
uid://dhxt5av5njyiv

View File

@@ -0,0 +1,45 @@
extends Control
@onready var cursor: TextureRect = $Cursor
var selected_index := 0
signal selected
signal cancelled
var active := false
@onready var choices := [$PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/Yes, $PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/No]
func _process(_delta: float) -> void:
if active:
handle_input()
cursor.global_position.x = choices[selected_index].global_position.x - 6
func open() -> void:
show()
AudioManager.play_global_sfx("pause")
selected_index = 1
await get_tree().process_frame
active = true
await selected
hide()
func handle_input() -> void:
if Input.is_action_just_pressed("ui_left"):
selected_index -= 1
if Input.is_action_just_pressed("ui_right"):
selected_index += 1
if Input.is_action_just_pressed("ui_back"):
close()
cancelled.emit()
selected_index = clamp(selected_index, 0, 1)
if Input.is_action_just_pressed("ui_accept"):
select()
func select() -> void:
selected.emit()
close()
func close() -> void:
active = false
hide()

View File

@@ -0,0 +1 @@
uid://cqjtevrvgmcl0

View File

@@ -0,0 +1,147 @@
class_name SelectableInputOption
extends HBoxContainer
@export var settings_category := "video"
@export var selected := false
@export var action_name := ""
@export var title := ""
@export_enum("Keyboard", "Controller") var type := 0
@export var player_idx := 0
signal input_changed(action_name: String, input_event: InputEvent)
var awaiting_input := false
static var rebinding_input := false
var event_name := ""
var can_remap := true
var current_device_brand := 0
var input_event: InputEvent = null
const button_id_translation := [
["A", "B", "~"],
["B", "A", "&"],
["X", "Y", "%"],
["Y", "X", "{"],
["Select", "-", "Share"],
"Home",
["Start", "+", "Options"],
["LS Push", "LS Push", "L3"],
["RS Push", "RS Push", "R3"],
["LB", "L", "L1"],
["RB", "R", "R1"],
"DPad U",
"DPad D",
"DPad L",
"DPad R"
]
func _process(_delta: float) -> void:
if selected:
handle_inputs()
$Cursor.modulate.a = int(selected)
$Title.text = tr(title) + ":"
$Value.text = get_event_string(input_event) if not awaiting_input else "Press Any..."
func handle_inputs() -> void:
if selected and can_remap:
if Input.is_action_just_pressed("ui_accept"):
begin_remap()
func begin_remap() -> void:
$Timer.stop()
$Timer.start()
rebinding_input = true
can_remap = false
get_parent().can_input = false
await get_tree().create_timer(0.1).timeout
awaiting_input = true
func _input(event: InputEvent) -> void:
if awaiting_input == false: return
if event.is_pressed() == false:
return
if event is InputEventKey:
if event.as_text_physical_keycode() == "Escape":
cancel_remap()
return
if type == 0 and event is InputEventKey:
map_event_to_action(event)
elif type == 1 and (event is InputEventJoypadButton or event is InputEventJoypadMotion):
if event is InputEventJoypadMotion:
event.axis_value = sign(event.axis_value)
map_event_to_action(event)
func map_event_to_action(event) -> void:
var action = action_name + "_" + str(player_idx)
var events = InputMap.action_get_events(action).duplicate()
events[type] = event
InputMap.action_erase_events(action)
for i in events:
InputMap.action_add_event(action, i)
input_changed.emit(action, event)
input_event = event
awaiting_input = false
await get_tree().create_timer(0.1).timeout
rebinding_input = false
get_parent().can_input = true
can_remap = true
func get_event_string(event: InputEvent) -> String:
var event_string := ""
if event is InputEventKey:
event_string = OS.get_keycode_string(event.keycode)
elif event is InputEventJoypadButton:
var translation = button_id_translation[event.button_index]
if translation is Array:
translation = translation[current_device_brand]
event_string = translation
elif event is InputEventJoypadMotion:
var stick = "LS"
var direction = "Left"
if event.axis == JOY_AXIS_TRIGGER_LEFT:
return ["LT", "ZL", "L2"][current_device_brand]
elif event.axis == JOY_AXIS_TRIGGER_RIGHT:
return ["RT", "ZR", "R2"][current_device_brand]
if event.axis == JOY_AXIS_RIGHT_X or event.axis == JOY_AXIS_RIGHT_Y:
stick = "RS"
if (event.axis == JOY_AXIS_LEFT_X or event.axis == JOY_AXIS_RIGHT_X):
if event.axis_value < 0:
direction = "Left"
else:
direction = "Right"
elif (event.axis == JOY_AXIS_LEFT_Y or event.axis == JOY_AXIS_RIGHT_Y):
if event.axis_value < 0:
direction = "Up"
else:
direction = "Down"
event_string = stick + " " + direction
return event_string
func _unhandled_input(event: InputEvent) -> void:
if event is not InputEventJoypadButton and event is not InputEventJoypadMotion:
return
var device_name = Input.get_joy_name(event.device)
if device_name.to_upper().contains("NINTENDO") or device_name.to_upper().contains("SWITCH") or device_name.to_upper().contains("WII"):
current_device_brand = 1
elif device_name.to_upper().contains("PS") or device_name.to_upper().contains("PLAYSTATION"):
current_device_brand = 2
else:
current_device_brand = 0
func cancel_remap() -> void:
awaiting_input = false
await get_tree().create_timer(0.1).timeout
rebinding_input = false
get_parent().can_input = true
can_remap = true

View File

@@ -0,0 +1 @@
uid://w6t6q5vfow5p

View File

@@ -0,0 +1,10 @@
extends Label
signal pressed
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_accept"):
pressed.emit()
func toggle_process(enabled := false) -> void:
set_process(enabled)

View File

@@ -0,0 +1 @@
uid://c8xv30jxw6uum

View File

@@ -0,0 +1,22 @@
extends HBoxContainer
@export var title := ""
@export var selected := false
signal button_pressed
var selected_index := 0
@export var press_sfx := "beep"
func _process(_delta: float) -> void:
if selected:
handle_inputs()
$Cursor.modulate.a = int(selected)
$Title.text = tr(title)
func handle_inputs() -> void:
if Input.is_action_just_pressed("ui_accept"):
button_pressed.emit()
if press_sfx != "":
AudioManager.play_global_sfx(press_sfx)

View File

@@ -0,0 +1 @@
uid://b8vx7oox0ca8p

View File

@@ -0,0 +1,30 @@
extends HBoxContainer
var selected := false
@export var title := ""
var selected_index := 0
@export var values: Array[String] = []
@export var add_colon := true
signal value_changed(new_index: int)
func _process(_delta: float) -> void:
if selected:
handle_inputs()
$Title/Cursor.visible = (selected)
$Title.text = tr(title) + (":" if add_colon else "")
$Value.text = ("" if selected_index > 0 and selected else " ") + tr(str(values[selected_index])) + ("" if selected_index < values.size() - 1 and selected else " ")
func set_selected(active := false) -> void:
selected = active
func handle_inputs() -> void:
var old := selected_index
if Input.is_action_just_pressed("ui_left"):
selected_index -= 1
if Input.is_action_just_pressed("ui_right"):
selected_index += 1
selected_index = clamp(selected_index, 0, values.size() - 1)
if old != selected_index:
value_changed.emit(selected_index)

View File

@@ -0,0 +1 @@
uid://d0tcqr0x101j

View File

@@ -0,0 +1,55 @@
extends HBoxContainer
@export var option_key := ""
@export var title := ""
@export var value_descs: Array[String] = []
@export var values := []
@export var settings_category := "video"
@export var selected := false
signal value_changed(new_value)
var selected_index := 0:
set(value):
selected_index = value
func _ready() -> void:
await get_tree().process_frame
update_starting_values()
func update_starting_values() -> void:
if Settings.file.has(settings_category):
if Settings.file[settings_category].has(option_key):
if Settings.file[settings_category][option_key] is String:
selected_index = values.find(Settings.file[settings_category][option_key])
else:
selected_index = Settings.file[settings_category][option_key]
func _process(_delta: float) -> void:
if selected:
handle_inputs()
$Cursor.modulate.a = int(selected)
for i in [$AutoScrollContainer, %AutoScrollContainer2]:
i.is_focused = selected
%Title.text = tr(title) + ":"
%Value.text = tr(str(values[selected_index]))
%LeftArrow.modulate.a = int(selected and selected_index > 0)
%RightArrow.modulate.a = int(selected and selected_index < values.size() - 1)
func set_selected(active := false) -> void:
selected = active
func handle_inputs() -> void:
var old := selected_index
if Input.is_action_just_pressed("ui_left"):
selected_index -= 1
if Settings.file.audio.extra_sfx == 1:
AudioManager.play_global_sfx("menu_move")
if Input.is_action_just_pressed("ui_right"):
selected_index += 1
if Settings.file.audio.extra_sfx == 1:
AudioManager.play_global_sfx("menu_move")
selected_index = clamp(selected_index, 0, values.size() - 1)
if old != selected_index:
value_changed.emit(selected_index)

View File

@@ -0,0 +1 @@
uid://vm4py6u87p6d

View File

@@ -0,0 +1,52 @@
extends HBoxContainer
@export var option_key := ""
@export var title := ""
@export var selected := false
signal value_changed(new_value: int)
var selected_index := 5
var value := 0.5
@onready var sfx: AudioStreamPlayer = $SFX
func _ready() -> void:
update_starting_values()
func update_starting_values() -> void:
selected_index = Settings.file.audio[option_key]
func _process(_delta: float) -> void:
if selected:
handle_inputs()
$Cursor.modulate.a = int(selected)
$Value.text = generate_text()
%Title.text = tr(title) + ":"
$AutoScrollContainer.is_focused = selected
func generate_text() -> String:
var string := ""
string += "" if selected and selected_index > 0 else " "
string += ""
for i in 11:
if i == selected_index:
string += ""
else:
string += "-"
string += ""
string += "" if selected and selected_index < 10 else " "
return string
func handle_inputs() -> void:
var old := selected_index
if Input.is_action_just_pressed("ui_left"):
selected_index -= 1
sfx.play()
if Input.is_action_just_pressed("ui_right"):
selected_index += 1
sfx.play()
selected_index = clamp(selected_index, 0, 10)
if old != selected_index:
value_changed.emit(selected_index)

View File

@@ -0,0 +1 @@
uid://cufb1mxff7b8f

View File

@@ -0,0 +1,50 @@
extends VBoxContainer
@export var category_name := "Hi"
@export var options: Array[Control] = []
var selected_index := -1
@export var minimum_idx := -1
@export var active := true
@export var description_node: Control = null
@export var scroll_container: ScrollContainer = null
@export var scroll_step := 8
var can_input := true
func _process(_delta: float) -> void:
visible = active
if active and can_input:
handle_input()
var idx := 0
for i in options:
if i != null:
i.selected = selected_index == idx and active and can_input
idx += 1
if description_node != null and selected_index >= 0 and options[selected_index] != null:
description_node.text = options[selected_index].value_descs[options[selected_index].selected_index]
if not active:
selected_index = minimum_idx
func handle_input() -> void:
if Input.is_action_just_pressed("ui_down"):
selected_index += 1
if Settings.file.audio.extra_sfx == 1:
AudioManager.play_global_sfx("menu_move")
if Input.is_action_just_pressed("ui_up"):
selected_index -= 1
if Settings.file.audio.extra_sfx == 1:
AudioManager.play_global_sfx("menu_move")
if scroll_container != null:
scroll_container.scroll_vertical = float(lerpf(0.0, scroll_container.get_v_scroll_bar().max_value, inverse_lerp(0.0, options.size() - 1, selected_index - 2)))
selected_index = clamp(selected_index, minimum_idx, options.size() - 1)
func auto_get_options() -> void:
options.clear()
selected_index = 0
for i in get_children():
if i is HBoxContainer:
options.append(i)

View File

@@ -0,0 +1 @@
uid://dcmjifllvi3qd

View File

@@ -0,0 +1,87 @@
extends Control
@onready var current_container: Control = $PanelContainer/MarginContainer/VBoxContainer/Video
@export var containers: Array[Control]
@export var disabled_containers: Array[Control]
var category_select_active := false
var category_index := 0
signal closed
var can_move := true
var active = false
signal opened
func _process(_delta: float) -> void:
category_select_active = current_container.selected_index == -1 and active
%Category.text = tr(current_container.category_name)
%Icon.region_rect.position.x = category_index * 24
for i in [%LeftArrow, %RightArrow]:
i.modulate.a = int(current_container.selected_index == -1)
for i in containers.size():
containers[i].active = category_index == i and active
if SelectableInputOption.rebinding_input == false:
containers[i].can_input = can_move
for i in disabled_containers:
i.active = false
if category_select_active and active and can_move:
handle_inputs()
if Input.is_action_just_pressed("ui_back") and active and current_container.can_input and can_move:
close()
func handle_inputs() -> void:
var direction := 0
if Input.is_action_just_pressed("ui_left"):
category_index -= 1
direction = -1
if Settings.file.audio.extra_sfx == 1:
AudioManager.play_global_sfx("menu_move")
if Input.is_action_just_pressed("ui_right"):
category_index += 1
direction += 1
if Settings.file.audio.extra_sfx == 1:
AudioManager.play_global_sfx("menu_move")
category_index = wrap(category_index, 0, containers.size())
current_container = containers[category_index]
if disabled_containers.has(current_container):
category_index = wrap(category_index + direction, 0, containers.size())
func open_pack_config_menu(pack: ResourcePackContainer) -> void:
$ResourcePackConfigMenu.config_json = pack.config
$ResourcePackConfigMenu.json_path = pack.config_path
$ResourcePackConfigMenu.open()
can_move = false
await $ResourcePackConfigMenu.closed
can_move = true
func open() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
opened.emit()
update_all_starting()
$PanelContainer/MarginContainer/VBoxContainer/KeyboardControls.selected_index = -1
$PanelContainer/MarginContainer/VBoxContainer/Controller.selected_index = -1
show()
update_minimum_size()
current_container.show()
current_container.active = true
await get_tree().process_frame
active = true
func update_all_starting() -> void:
get_tree().call_group("Options", "update_starting_values")
%Flag.region_rect.position.x = Global.lang_codes.find(TranslationServer.get_locale()) * 16
$PanelContainer/MarginContainer/VBoxContainer/Video/Language.selected_index = Global.lang_codes.find(Settings.file.game.lang)
func close() -> void:
hide()
active = false
closed.emit()
await get_tree().process_frame
Settings.save_settings()
process_mode = Node.PROCESS_MODE_DISABLED

1
Scripts/UI/SettingsMenu.gd.uid Executable file
View File

@@ -0,0 +1 @@
uid://cj6858gmexp11

View File

@@ -0,0 +1,41 @@
extends Control
signal selected
signal cancelled
var selected_index := 0
var active := false
func _ready() -> void:
pass
func _physics_process(delta: float) -> void:
if active:
if Input.is_action_just_pressed("ui_down"):
selected_index += 1
elif Input.is_action_just_pressed("ui_up"):
selected_index -= 1
selected_index = clamp(selected_index, 0, 1)
if Input.is_action_just_pressed("ui_accept"):
SpeedrunHandler.is_warp_run = bool(selected_index)
selected.emit()
close()
elif Input.is_action_just_pressed("ui_back"):
cancelled.emit()
close()
var idx := 0
for i in [$Panel/VBoxContainer/Warpless/Cursor, $Panel/VBoxContainer/Any/Cursor]:
i.modulate.a = int(selected_index == idx)
idx += 1
func open() -> void:
show()
$Panel/VBoxContainer/Warpless.grab_focus()
await get_tree().create_timer(0.1, false).timeout
active = true
grab_focus()
func close() -> void:
active = false
hide()

View File

@@ -0,0 +1 @@
uid://b8nv6ojpgvo0r

View File

@@ -0,0 +1,4 @@
extends SpinBox
func _ready() -> void:
get_line_edit().context_menu_enabled = false

View File

@@ -0,0 +1 @@
uid://1m5otytedbp4

59
Scripts/UI/StoryPause.gd Normal file
View File

@@ -0,0 +1,59 @@
extends Control
var selected_index := 0
@export var options: Array[Label]
@onready var cursor: TextureRect = $Control/Cursor
var active := false
@export var is_pause := true
signal option_1_selected
signal option_2_selected
signal option_3_selected
signal option_4_selected
func _process(_delta: float) -> void:
if active:
handle_inputs()
cursor.global_position.y = options[selected_index].global_position.y + 4
cursor.global_position.x = options[selected_index].global_position.x - 10
func handle_inputs() -> void:
if Input.is_action_just_pressed("ui_down"):
selected_index += 1
if Input.is_action_just_pressed("ui_up"):
selected_index -= 1
selected_index = clamp(selected_index, 0, options.size() - 1)
if Input.is_action_just_pressed("ui_accept"):
option_selected()
if Input.is_action_just_pressed("pause"):
close()
func option_selected() -> void:
emit_signal("option_" + str(selected_index + 1) + "_selected")
func open_settings() -> void:
active = false
$SettingsMenu.open()
await $SettingsMenu.closed
active = true
func open() -> void:
if is_pause:
Global.game_paused = true
AudioManager.play_global_sfx("pause")
get_tree().paused = true
show()
await get_tree().physics_frame
active = true
func close() -> void:
active = false
selected_index = 0
hide()
for i in 2:
await get_tree().physics_frame
Global.game_paused = false
get_tree().paused = false

1
Scripts/UI/StoryPause.gd.uid Executable file
View File

@@ -0,0 +1 @@
uid://by48a8oa5hefr

View File

@@ -0,0 +1,19 @@
extends Node
func restart_level() -> void:
Global.player_power_states = "0000"
Global.checkpoint_passed = false
Level.first_load = true
Global.speed_run_timer = 0
Global.marathon_mode = false
Global.marathon_practice = false
Global.speed_run_timer_enabled = false
Global.boo_race = false
Global.speed_run_timer_active = false
Global.reset_values()
AudioManager.main_level_music.stop()
Global.death_load = true
Global.current_level.reload_level()
func quit_to_menu() -> void:
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")

View File

@@ -0,0 +1 @@
uid://ju8kkqfm3qb6

View File

@@ -0,0 +1,18 @@
class_name SwapContainer
extends BoxContainer
@export var test_node: Control = null
@export var dummy_node: Control = null
func _ready() -> void:
resized.connect(check)
func check() -> void:
print(size.x >= test_node.size.x)
print([size.x, test_node.size.x])
if size.x > test_node.size.x:
test_node.show()
dummy_node.hide()
else:
test_node.hide()
dummy_node.show()

View File

@@ -0,0 +1 @@
uid://bb8ldpxo5h71i

Some files were not shown because too many files have changed in this diff Show More