Super-Mario-Bros-Remastered.../Scripts/Classes/Singletons/Global.gd
SkyanUltra becdf9ba77
Expanded world/level icons (#763)
* Expanded Level Icons

Level icons are now defined in their own custom JSON file (LevelIcons.json) which determines which icons will be shown, along with an area where the user can input the size of their icons for custom sizing (larger sizes will likely be a bit odd in behavior, smaller should work fine though)

Along with that, icons have been greatly expanded, with 32 new level icons available for levels to use now, (a total of 45!) along with various older icons recieving slight touch-ups. Various levels now use these new icons to better represent major recognizable elements from those levels, and to give them a bit more variation between each other.

If there are any additional level icons that would make sense for implementation, let me know. I think this should cover most of the important ones, though.

* Delete LevelIcons.png.import

* Marathon + ANN Medal Icons on World Select

Marathon mode and ANN now show the highest ranking achieved on all levels when applicable. So if you manage to get a gold medal on every level, but get a bronze on one, then it'll display a bronze medal for your world completion.

Side note: Why the fuck was the only solution to the GPU particle emitting behavior to make a massive array of node paths. I hate this! This sucks! Joe, why did you do that? And why was it the only thing I could find that worked?

* Optimized particle emitting for world/level select

* ANN now has its own menu + bugfixes

This gives ANN its own dedicated menu rather than throwing you directly into the world selection menu, which additionally fixes an issue with rendering medal icons when selecting the campaign, and a few other fixes like the DiscoResults menu not using the ANN visual settings.

* Challenge Hunt Icons + menu bugfixes

Challenge Hunt icons are now implemented, so you can see all of your red coins, eggs and score requirements if you've met them. Along with that, fixed a bug where you could enter Worlds 9-D in marathon when you aren't supposed to by selecting a story mode option and then entering marathon.

* New icons + layout change for Challenge Hunt icons

By recommendation by Vanny, Challenge Hunt icons have been changed. Along with this, all icons for progress tracking are now in their own relegated image file for separate modification, and have been updated to use white outlines similar to the world icons themselves.

* Update ChallengeModeResults.tscn

Forgot to update this to use challenge icons for the world select screen.

* Eggs cycle through color again

Accidentally got rid of this, but didn't want to update every node. It simply does it based on the world number now rather than needing to be manually set.
2025-12-02 08:30:32 -05:00

517 lines
15 KiB
GDScript

extends Node
var level_theme := "Overworld":
set(value):
level_theme = value
level_theme_changed.emit()
var theme_time := "Day":
set(value):
theme_time = value
level_time_changed.emit()
signal level_theme_changed
signal level_time_changed
const BASE64_CHARSET := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
const VERSION_CHECK_URL := "https://raw.githubusercontent.com/JHDev2006/smb1r-version/refs/heads/main/version.txt"
var entity_gravity := 10.0
var entity_max_fall_speed := 280
var level_editor: LevelEditor = null
var current_level: Level = null
var second_quest := false
var extra_worlds_win := false
const lang_codes := ["en", "fr", "es", "de", "it", "pt", "pl", "tr", "ru", "jp", "fil", "id", "ga"]
var config_path : String = get_config_path()
var rom_path := ""
var rom_assets_exist := false
var ROM_POINTER_PATH = config_path.path_join("rom_pointer.smb")
var ROM_PATH = config_path.path_join("baserom.nes")
var ROM_ASSETS_PATH = config_path.path_join("resource_packs/BaseAssets")
const ROM_PACK_NAME := "BaseAssets"
const ROM_ASSETS_VERSION := 2
var server_version := -1
var current_version := -1
var version_number := ""
var is_snapshot := true
const LEVEL_THEMES := {
"SMB1": SMB1_LEVEL_THEMES,
"SMBLL": SMB1_LEVEL_THEMES,
"SMBANN": SMB1_LEVEL_THEMES,
"SMBS": SMBS_LEVEL_THEMES
}
const SMB1_LEVEL_THEMES := ["Overworld", "Desert", "Snow", "Jungle", "Desert", "Snow", "Jungle", "Overworld", "Space", "Autumn", "Pipeland", "Skyland", "Volcano"]
const SMBS_LEVEL_THEMES := ["Overworld", "Garden", "Beach", "Mountain", "Garden", "Beach", "Mountain", "Overworld", "Autumn", "Pipeland", "Skyland", "Volcano", "Fuck"]
const FORCE_NIGHT_THEMES := ["Space"]
const FORCE_DAY_THEMES := []
signal text_shadow_changed
@onready var player_ghost: PlayerGhost = $PlayerGhost
var debugged_in := true
var score_tween = null
var time_tween = null
var total_deaths := 0
var score := 0:
set(value):
if disco_mode == true:
if value > score:
var diff = value - score
score = score + (diff * 1)
else:
score = value
else:
score = value
var coins := 0:
set(value):
coins = value
if coins >= 100:#
if Settings.file.difficulty.inf_lives == 0 and (Global.current_game_mode != Global.GameMode.CHALLENGE and Global.current_campaign != "SMBANN"):
lives += floor(coins / 100.0)
AudioManager.play_sfx("1_up", get_viewport().get_camera_2d().get_screen_center_position())
coins = coins % 100
var time := 300
var lives := 3
var world_num := 1
var level_num := 1
var disco_mode := false
enum Room{MAIN_ROOM, BONUS_ROOM, COIN_HEAVEN, PIPE_CUTSCENE, TITLE_SCREEN}
const room_strings := ["MainRoom", "BonusRoom", "CoinHeaven", "PipeCutscene", "TitleScreen"]
var current_room: Room = Room.MAIN_ROOM
signal transition_finished
var transitioning_scene := false
var awaiting_transition := false
signal level_complete_begin
signal score_tally_finished
var achievements := "0000000000000000000000000000"
const LSS_GAME_ID := 5
enum AchievementID{
SMB1_CLEAR, SMBLL_CLEAR, SMBS_CLEAR, SMBANN_CLEAR,
SMB1_CHALLENGE, SMBLL_CHALLENGE, SMBS_CHALLENGE,
SMB1_BOO, SMBLL_BOO, SMBS_BOO,
SMB1_GOLD_BOO, SMBLL_GOLD_BOO, SMBS_GOLD_BOO,
SMB1_BRONZE, SMBLL_BRONZE, SMBS_BRONZE,
SMB1_SILVER, SMBLL_SILVER, SMBS_SILVER,
SMB1_GOLD, SMBLL_GOLD, SMBS_GOLD,
SMB1_RUN, SMBLL_RUN, SMBS_RUN,
ANN_PRANK, SMBLL_WORLD9,
COMPLETIONIST
}
const HIDDEN_ACHIEVEMENTS := [AchievementID.COMPLETIONIST]
var can_time_tick := true:
set(value):
can_time_tick = value
if value == false:
pass
var player_power_states := "0000"
var connected_players := 1
const CAMPAIGNS := ["SMB1", "SMBLL", "SMBS", "SMBANN"]
var player_characters := [0, 0, 0, 0]:
set(value):
player_characters = value
player_characters_changed.emit()
signal player_characters_changed
signal disco_level_continued
signal frame_rule
var hard_mode := false
var current_campaign := "SMB1"
var death_load := false
var tallying_score := false
var in_title_screen := false
var game_paused := false
var can_pause := true
var fade_transition := true
enum GameMode{NONE, CAMPAIGN, BOO_RACE, CHALLENGE, MARATHON, MARATHON_PRACTICE, LEVEL_EDITOR, CUSTOM_LEVEL, DISCO}
const game_mode_strings := ["Default", "Campaign", "BooRace", "Challenge", "Marathon", "MarathonPractice", "LevelEditor", "CustomLevel", "Disco"]
var current_game_mode: GameMode = GameMode.NONE
var high_score := 0
var game_beaten := false
signal p_switch_toggle
var p_switch_active := false
var p_switch_timer := 0.0
var p_switch_timer_paused := false
var debug_mode := false
func _ready() -> void:
if is_snapshot: get_build_time()
if OS.is_debug_build(): debug_mode = false
current_version = get_version_number()
get_server_version()
setup_config_dirs()
check_for_rom()
func setup_config_dirs() -> void:
var dirs = [
"custom_characters",
"custom_levels",
"logs",
"marathon_recordings",
"resource_packs",
"saves",
"screenshots"
]
for d in dirs:
var full_path = Global.config_path.path_join(d)
if not DirAccess.dir_exists_absolute(full_path):
DirAccess.make_dir_recursive_absolute(full_path)
func get_config_path() -> String:
var exe_path := OS.get_executable_path()
var exe_dir := exe_path.get_base_dir()
var portable_flag := exe_dir.path_join("portable.txt")
# Test that exe dir is writeable, if not fallback to user://
if FileAccess.file_exists(portable_flag):
var test_file = exe_dir.path_join("test.txt")
var f = FileAccess.open(test_file, FileAccess.WRITE)
if f:
f.close()
var dir = DirAccess.open(exe_dir)
if dir:
dir.remove(test_file.get_file())
var local_dir = exe_dir.path_join("config")
if not DirAccess.dir_exists_absolute(local_dir):
DirAccess.make_dir_recursive_absolute(local_dir)
return local_dir
else:
push_warning("Portable flag found but exe directory is not writeable. Falling back to user://")
return "user://"
func check_for_rom() -> void:
rom_path = ""
rom_assets_exist = false
if FileAccess.file_exists(Global.ROM_PATH) == false:
return
var path = Global.ROM_PATH
if FileAccess.file_exists(path):
if ROMVerifier.is_valid_rom(path):
rom_path = path
if DirAccess.dir_exists_absolute(ROM_ASSETS_PATH):
var pack_json: String = FileAccess.get_file_as_string(ROM_ASSETS_PATH + "/pack_info.json")
var pack_dict: Dictionary = JSON.parse_string(pack_json)
if pack_dict.get("version", -1) == ROM_ASSETS_VERSION:
rom_assets_exist = true
else:
ResourceGenerator.updating = true
OS.move_to_trash(ROM_ASSETS_PATH)
func _process(delta: float) -> void:
if Input.is_action_just_pressed("debug_reload"):
ResourceSetter.cache.clear()
ResourceSetterNew.cache.clear()
ResourceGetter.cache.clear()
AudioManager.current_level_theme = ""
level_theme_changed.emit()
log_comment("Reloaded resource packs!")
if Input.is_action_just_pressed("toggle_fps_count"):
%FPSCount.visible = !%FPSCount.visible
%FPSCount.text = str(int(Engine.get_frames_per_second())) + " FPS"
handle_p_switch(delta)
if Input.is_key_label_pressed(KEY_F11) and debug_mode == false and OS.is_debug_build():
AudioManager.play_global_sfx("switch")
debug_mode = true
log_comment("Debug Mode enabled! some bugs may occur!")
if Input.is_action_just_pressed("ui_screenshot"):
take_screenshot()
func take_screenshot() -> void:
var img: Image = get_viewport().get_texture().get_image()
var filename = Global.config_path.path_join("screenshots/screenshot_" + str(int(Time.get_unix_time_from_system())) + ".png")
var err = img.save_png(filename)
if !err:
log_comment("Screenshot Saved!")
else:
log_error(error_string(err))
func handle_p_switch(delta: float) -> void:
if p_switch_active and get_tree().paused == false:
if p_switch_timer_paused == false:
p_switch_timer -= delta
if p_switch_timer <= 0:
p_switch_active = false
p_switch_toggle.emit()
AudioManager.stop_music_override(AudioManager.MUSIC_OVERRIDES.PSWITCH)
func get_build_time() -> void:
# SkyanUltra: Slightly expanded function to make it easier to get snapshot build numbers.
var date = Time.get_date_dict_from_system()
var year_last_two = date.year % 100
var now = Time.get_unix_time_from_system()
print("[b][color=cyan]Current unix time:[/color][/b] ", int(now))
var start_of_year = Time.get_unix_time_from_datetime_dict({
"year": date.year,
"month": 1,
"day": 1,
"hour": 0,
"minute": 0,
"second": 0
})
var days_since_year_start = int((now - start_of_year) / 86400)
@warning_ignore("integer_division")
var week = int(days_since_year_start / 7) + 1
var build_date = "%02dw%02d" % [year_last_two, week]
print_rich("[b][color=cyan]Partial snapshot build ID:[/color][/b] ", build_date)
func get_version_number() -> int:
var number = (FileAccess.open("res://version.txt", FileAccess.READ).get_as_text())
version_number = str(number)
return int(number)
func player_action_pressed(action := "", player_id := 0) -> bool:
return Input.is_action_pressed(action + "_" + str(player_id))
func player_action_just_pressed(action := "", player_id := 0) -> bool:
return Input.is_action_just_pressed(action + "_" + str(player_id))
func player_action_just_released(action := "", player_id := 0) -> bool:
return Input.is_action_just_released(action + "_" + str(player_id))
func tally_time() -> void:
if tallying_score:
return
$ScoreTally.play()
tallying_score = true
var target_score = score + (time * 50)
score_tween = create_tween()
time_tween = create_tween()
var duration = float(time) / 120
score_tween.tween_property(self, "score", target_score, duration)
time_tween.tween_property(self, "time", 0, duration)
await score_tween.finished
tallying_score = false
$ScoreTally.stop()
$ScoreTallyEnd.play()
score_tally_finished.emit()
func cancel_score_tally() -> void:
if score_tween != null:
score_tween.kill()
if time_tween != null:
time_tween.kill()
tallying_score = false
$ScoreTally.stop()
func activate_p_switch() -> void:
if p_switch_active == false:
p_switch_toggle.emit()
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.PSWITCH, 99, false)
p_switch_timer = 10
p_switch_active = true
func reset_values() -> void:
PlayerGhost.idx = 0
Checkpoint.passed_checkpoints.clear()
Checkpoint.sublevel_id = 0
Global.total_deaths = 0
Door.unlocked_doors = []
Checkpoint.unlocked_doors = []
KeyItem.total_collected = 0
Checkpoint.keys_collected = 0
Level.start_level_path = Level.get_scene_string(Global.world_num, Global.level_num)
LevelPersistance.reset_states()
Level.first_load = true
Level.can_set_time = true
Level.in_vine_level = false
Level.vine_return_level = ""
Level.vine_warp_level = ""
p_switch_active = false
p_switch_timer = 0.0
func clear_saved_values() -> void:
coins = 0
score = 0
lives = 3
player_power_states = "0000"
func transition_to_scene(scene_path := "") -> void:
Global.fade_transition = bool(Settings.file.visuals.transition_animation)
if transitioning_scene:
return
transitioning_scene = true
if fade_transition:
$Transition/AnimationPlayer.play("FadeIn")
await $Transition/AnimationPlayer.animation_finished
await get_tree().create_timer(0.1, true).timeout
else:
%TransitionBlock.modulate.a = 1
$Transition.show()
await get_tree().create_timer(0.1, true).timeout
get_tree().change_scene_to_file(scene_path)
await get_tree().scene_changed
await get_tree().create_timer(0.15, true).timeout
if fade_transition:
$Transition/AnimationPlayer.play_backwards("FadeIn")
else:
$Transition/AnimationPlayer.play("RESET")
$Transition.hide()
transitioning_scene = false
transition_finished.emit()
func do_fake_transition(duration := 0.2) -> void:
if fade_transition:
$Transition/AnimationPlayer.play("FadeIn")
await $Transition/AnimationPlayer.animation_finished
await get_tree().create_timer(duration, false).timeout
$Transition/AnimationPlayer.play_backwards("FadeIn")
else:
%TransitionBlock.modulate.a = 1
$Transition.show()
await get_tree().create_timer(duration + 0.05, false).timeout
$Transition.hide()
func freeze_screen() -> void:
if Settings.file.video.visuals == 1:
return
$Transition.show()
$Transition/Freeze.show()
$Transition/Freeze.texture = ImageTexture.create_from_image(get_viewport().get_texture().get_image())
func close_freeze() -> void:
$Transition/Freeze.hide()
$Transition.hide()
var recording_dir = config_path.path_join("marathon_recordings")
func update_game_status() -> void:
var lives_str := str(Global.lives)
if Settings.file.difficulty.inf_lives == 1:
lives_str = ""
var string := "Coins = " + str(Global.coins) + " Lives = " + lives_str
func open_marathon_results() -> void:
get_node("GameHUD/MarathonResults").open()
func open_disco_results() -> void:
get_node("GameHUD/DiscoResults").open()
func on_score_sfx_finished() -> void:
if tallying_score:
$ScoreTally.play()
func get_server_version() -> void:
var http = HTTPRequest.new()
add_child(http)
http.request_completed.connect(version_got)
http.request(VERSION_CHECK_URL, [], HTTPClient.METHOD_GET)
func version_got(_result, response_code, _headers, body) -> void:
if response_code == 200:
server_version = int(body.get_string_from_utf8())
else:
server_version = -2
func log_error(msg := "") -> void:
var error_message = $CanvasLayer/VBoxContainer/ErrorMessage.duplicate()
error_message.text = "Error - " + msg
error_message.visible = true
$CanvasLayer/VBoxContainer.add_child(error_message)
await get_tree().create_timer(10, false).timeout
error_message.queue_free()
func log_warning(msg := "") -> void:
var error_message = $CanvasLayer/VBoxContainer/Warning.duplicate()
error_message.text = "Warning - " + msg
error_message.visible = true
$CanvasLayer/VBoxContainer.add_child(error_message)
await get_tree().create_timer(10, false).timeout
error_message.queue_free()
func log_comment(msg := "") -> void:
var error_message = $CanvasLayer/VBoxContainer/Comment.duplicate()
error_message.text = msg
error_message.visible = true
$CanvasLayer/VBoxContainer.add_child(error_message)
await get_tree().create_timer(2, false).timeout
error_message.queue_free()
func level_editor_is_playtesting() -> bool:
if Global.current_game_mode == Global.GameMode.LEVEL_EDITOR:
if Global.level_editor.current_state == LevelEditor.EditorState.PLAYTESTING:
return true
return false
func unlock_achievement(achievement_id := AchievementID.SMB1_CLEAR) -> void:
achievements[achievement_id] = "1"
if achievement_id != AchievementID.COMPLETIONIST:
check_completionist_achievement()
SaveManager.write_achievements()
func check_completionist_achievement() -> void:
if achievements.count("0") == 1:
unlock_achievement(AchievementID.COMPLETIONIST)
const FONT = preload("res://Assets/Sprites/UI/Font.fnt")
func sanitize_string(string := "") -> String:
string = string.to_upper()
for i in string.length():
if FONT.has_char(string.unicode_at(i)) == false and string[i] != "\n":
string = string.replace(string[i], " ")
return string
func get_base_asset_version() -> int:
var json = JSON.parse_string(FileAccess.open(Global.config_path.path_join("BaseAssets/pack_info.json"), FileAccess.READ).get_as_text())
var version = json.version
return get_version_num_int(version)
func get_version_num_int(ver_num := "0.0.0") -> int:
return int(ver_num.replace(".", ""))
func merge_dict(target: Dictionary, source: Dictionary) -> void:
# SkyanUltra: Used to properly merge dictionaries JSONs rather than out right overwriting entries.
for key in source.keys():
if target.has(key) and target[key] is Dictionary and source[key] is Dictionary:
merge_dict(target[key], source[key])
else:
target[key] = source[key]