diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fe8de4b4..7d47f874 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,6 +48,17 @@ repos: files: \.py$ types_or: [text] + - repo: https://github.com/atelico/gdstyle + rev: v0.1.7 + hooks: + - id: gdstyle-fmt + files: \.gd$ + exclude: | + (?x)^( + game/addons/.* + ) + types_or: [text] + - repo: https://github.com/crate-ci/typos rev: v1.29.4 hooks: diff --git a/game/src/Autoload/CursorManager.gd b/game/src/Autoload/CursorManager.gd index ec4f1a2e..1e971c97 100644 --- a/game/src/Autoload/CursorManager.gd +++ b/game/src/Autoload/CursorManager.gd @@ -1,106 +1,32 @@ extends Node -class CompatCursor: - #cursor properties - var cursor_name : StringName - - var resolutions : PackedVector2Array - var frames : Array[ImageTexture] - var hotspots : PackedVector2Array - var is_animated : bool = false - var sequence : PackedInt32Array = [0] - var timings : PackedFloat32Array = [1.0] - - #Cursor state - var current_frame : int = 0 - var time_to_frame : float = 1.0 - - func _init(name_in : StringName) -> void: - cursor_name = name_in - resolutions = CursorSingleton.get_resolutions(cursor_name) - - frames = CursorSingleton.get_frames(cursor_name,0) - hotspots = CursorSingleton.get_hotspots(cursor_name,0) - - is_animated = len(frames) > 1 - if is_animated: - sequence = CursorSingleton.get_sequence(cursor_name) - timings = CursorSingleton.get_display_rates(cursor_name) - time_to_frame = timings[sequence[current_frame]] - - func reset() -> void: - current_frame = 0 - time_to_frame = timings[sequence[0]] - - func set_resolution(resolution : Vector2) -> void: - var index : int = resolutions.find(resolution) - if index != -1: - frames = CursorSingleton.get_frames(cursor_name,index) - return - - #couldnt find it, so generate it based on the highest res available - var highest_res_index : int = 0 - var highest_res_x : int = 0 - for i : int in range(len(resolutions)): - if resolutions[i].x > highest_res_x: - highest_res_x = resolutions[i].x - highest_res_index = i - generate_new_resolution(highest_res_index,resolution) - - resolutions = CursorSingleton.get_resolutions(cursor_name) - frames = CursorSingleton.get_frames(cursor_name,len(resolutions)-1) - hotspots = CursorSingleton.get_hotspots(cursor_name,len(resolutions)-1) - - assert(len(frames) != 0) - - func generate_new_resolution(base_res_index : int, resolution : Vector2) -> void: - # resolution wasn't in among the default, need to generate it ourselves - CursorSingleton.generate_resolution(cursor_name,base_res_index,resolution) - - #only bother with this if the cursor is animated - func _process_cursor(delta : float, shape : Input.CursorShape = Input.CURSOR_ARROW) -> void: - time_to_frame -= delta - if(time_to_frame <= 0): - current_frame = (current_frame + 1) % len(sequence) - time_to_frame += timings[sequence[current_frame]] - set_hardware_cursor(current_frame, shape) - - func set_hardware_cursor(frame : int=0, shape : Input.CursorShape = Input.CURSOR_ARROW) -> void: - var texture : ImageTexture = frames[sequence[frame]] - var hotspot : Vector2 = hotspots[sequence[frame]] - Input.set_custom_mouse_cursor(texture,shape,hotspot) - #TODO: This is set on game start, but we probably want this to be a video setting -var preferred_resolution : Vector2 = Vector2(32,32) -var active_cursor : CompatCursor -var active_shape : Input.CursorShape +var preferred_resolution: Vector2 = Vector2(32, 32) +var active_cursor: CompatCursor +var active_shape: Input.CursorShape + #Shape > Cursor dictionaries -var current_cursors : Dictionary = { - Input.CURSOR_ARROW:null, - Input.CURSOR_BUSY:null, - Input.CURSOR_IBEAM:null +var current_cursors: Dictionary = { + Input.CURSOR_ARROW: null, + Input.CURSOR_BUSY: null, + Input.CURSOR_IBEAM: null, } -var queued_cursors : Dictionary = { - Input.CURSOR_ARROW:null, - Input.CURSOR_BUSY:null, - Input.CURSOR_IBEAM:null +var queued_cursors: Dictionary = { + Input.CURSOR_ARROW: null, + Input.CURSOR_BUSY: null, + Input.CURSOR_IBEAM: null, } -var loaded_cursors : Dictionary = {} - -func load_cursors() -> void: - CursorSingleton.load_cursors() - for cursor_name : StringName in CursorSingleton.cursor_names: - var cursor : CompatCursor = CompatCursor.new(cursor_name) - cursor.set_resolution(preferred_resolution) - loaded_cursors[cursor_name] = cursor +var loaded_cursors: Dictionary = {} #Handle queued cursor changes and cursor animations -func _process(delta : float) -> void: - var mouse_shape : Input.CursorShape = Input.get_current_cursor_shape() + + +func _process(delta: float) -> void: + var mouse_shape: Input.CursorShape = Input.get_current_cursor_shape() for shape in current_cursors.keys(): if current_cursors[shape] != queued_cursors[shape]: @@ -114,7 +40,8 @@ func _process(delta : float) -> void: # reset the current cursor's frame, then switch the active cursor if mouse_shape != active_shape: #Current mouse type changed, need to make sure that if the cursor of this new type - # is animated, we are providing its frames instead of the frames of the previous active cursor + # is animated, we are providing its frames instead of the frames of the previous active + # cursor active_shape = mouse_shape active_cursor = current_cursors.get(active_shape, null) if active_cursor != null: @@ -125,15 +52,27 @@ func _process(delta : float) -> void: #if we didnt change cursors and are animated, do an update elif active_cursor != null and active_cursor.is_animated: - active_cursor._process_cursor(delta,active_shape) + active_cursor._process_cursor(delta, active_shape) -func set_preferred_resolution(res_in : Vector2) -> void: +func load_cursors() -> void: + CursorSingleton.load_cursors() + for cursor_name: StringName in CursorSingleton.cursor_names: + var cursor: CompatCursor = CompatCursor.new(cursor_name) + cursor.set_resolution(preferred_resolution) + loaded_cursors[cursor_name] = cursor + + +func set_preferred_resolution(res_in: Vector2) -> void: preferred_resolution = res_in -func set_compat_cursor(cursor_name : StringName, shape : Input.CursorShape = Input.CURSOR_ARROW) -> void: + +func set_compat_cursor( + cursor_name: StringName, + shape: Input.CursorShape = Input.CURSOR_ARROW, +) -> void: if cursor_name in loaded_cursors: - var cursor : CompatCursor = loaded_cursors[cursor_name] + var cursor: CompatCursor = loaded_cursors[cursor_name] cursor.set_resolution(preferred_resolution) queued_cursors[shape] = cursor else: @@ -141,20 +80,93 @@ func set_compat_cursor(cursor_name : StringName, shape : Input.CursorShape = Inp push_warning("Cursor name %s is not among loaded cursors" % cursor_name) queued_cursors[shape] = null + #NOTE: Each cursor has a corresponding "shape" -# to indicate when window is busy, normal, doing a drag-select, etc. -# You can set this per Control Node under Mouse > Default Cursor Shape +# to indicate when window is busy, normal, doing a drag-select, etc. +# You can set this per Control Node under Mouse > Default Cursor Shape # set_compat_cursor makes the named vic2 cursor the presently active # one for the shape it is currently associated with. By default a cursor # is associated with Input.CURSOR_ARROW, but you can override this with the second # argument. Use set_compat_cursor as you find it used here in initial_cursor_setup(). + func initial_cursor_setup() -> void: - set_preferred_resolution(Vector2(32,32)) + set_preferred_resolution(Vector2(32, 32)) load_cursors() set_compat_cursor(&"normal") # When hovered over a control node with mouse shape set to "busy" (ie. loading screens) # use the pocket watch cursor set_compat_cursor(&"busy", Input.CURSOR_BUSY) + + +class CompatCursor: + #cursor properties + var cursor_name: StringName + + var resolutions: PackedVector2Array + var frames: Array[ImageTexture] + var hotspots: PackedVector2Array + var is_animated: bool = false + var sequence: PackedInt32Array = [0] + var timings: PackedFloat32Array = [1.0] + + #Cursor state + var current_frame: int = 0 + var time_to_frame: float = 1.0 + + func _init(name_in: StringName) -> void: + cursor_name = name_in + resolutions = CursorSingleton.get_resolutions(cursor_name) + + frames = CursorSingleton.get_frames(cursor_name, 0) + hotspots = CursorSingleton.get_hotspots(cursor_name, 0) + + is_animated = len(frames) > 1 + if is_animated: + sequence = CursorSingleton.get_sequence(cursor_name) + timings = CursorSingleton.get_display_rates(cursor_name) + time_to_frame = timings[sequence[current_frame]] + + func reset() -> void: + current_frame = 0 + time_to_frame = timings[sequence[0]] + + func set_resolution(resolution: Vector2) -> void: + var index: int = resolutions.find(resolution) + if index != -1: + frames = CursorSingleton.get_frames(cursor_name, index) + return + + #couldnt find it, so generate it based on the highest res available + var highest_res_index: int = 0 + var highest_res_x: int = 0 + for i: int in range(len(resolutions)): + if resolutions[i].x > highest_res_x: + highest_res_x = resolutions[i].x + highest_res_index = i + generate_new_resolution(highest_res_index, resolution) + + resolutions = CursorSingleton.get_resolutions(cursor_name) + frames = CursorSingleton.get_frames(cursor_name, len(resolutions) - 1) + hotspots = CursorSingleton.get_hotspots(cursor_name, len(resolutions) - 1) + + assert(len(frames) != 0) + + func generate_new_resolution(base_res_index: int, resolution: Vector2) -> void: + # resolution wasn't in among the default, need to generate it ourselves + CursorSingleton.generate_resolution(cursor_name, base_res_index, resolution) + + #only bother with this if the cursor is animated + func _process_cursor(delta: float, shape: Input.CursorShape = Input.CURSOR_ARROW) -> void: + time_to_frame -= delta + if time_to_frame <= 0: + current_frame = (current_frame + 1) % len(sequence) + time_to_frame += timings[sequence[current_frame]] + set_hardware_cursor(current_frame, shape) + + func set_hardware_cursor(frame: int = 0, shape: Input.CursorShape = Input.CURSOR_ARROW) -> void: + var texture: ImageTexture = frames[sequence[frame]] + var hotspot: Vector2 = hotspots[sequence[frame]] + Input.set_custom_mouse_cursor(texture, shape, hotspot) diff --git a/game/src/Autoload/Events/Events.gd b/game/src/Autoload/Events/Events.gd index 10e34b61..07783b1c 100644 --- a/game/src/Autoload/Events/Events.gd +++ b/game/src/Autoload/Events/Events.gd @@ -1,11 +1,14 @@ -## Events are exclusively for the purpose of handling global signals -## This is to reduce "signal bubbling" which is when a signal callback is used to "bubble" the signal callbacks up the scene tree. -## It does such by providing a global interface of signals that are connected to and emitted by that are guaranteed to exist. extends Node +## Events are exclusively for the purpose of handling global signals +## This is to reduce "signal bubbling" which is when a signal callback is used to "bubble" the +## signal callbacks up the scene tree. +## It does such by providing a global interface of signals that are connected to and emitted by that +## are guaranteed to exist. + +var Options: OptionsEventsObject +var Multiplayer: MultiplayerEventsObject +var NationManagementScreens: NationManagementScreensEventsObject -var Options : OptionsEventsObject -var Multiplayer : MultiplayerEventsObject -var NationManagementScreens : NationManagementScreensEventsObject func _init() -> void: Options = OptionsEventsObject.new() diff --git a/game/src/Autoload/Events/Multiplayer.gd b/game/src/Autoload/Events/Multiplayer.gd index a58f355c..7e11e316 100644 --- a/game/src/Autoload/Events/Multiplayer.gd +++ b/game/src/Autoload/Events/Multiplayer.gd @@ -4,60 +4,68 @@ extends RefCounted signal save_ips(save_file: ConfigFile) signal load_ips(load_file: ConfigFile) -const ips_file_path_setting : String = "openvic/settings/ips_file_path" -const ips_file_path_default : String = "user://ips.cfg" +const ips_file_path_setting: String = "openvic/settings/ips_file_path" +const ips_file_path_default: String = "user://ips.cfg" -var _saved_ips_file_path : String = ProjectSettings.get_setting(ips_file_path_setting, ips_file_path_default) +var _saved_ips_file_path: String = ProjectSettings.get_setting( + ips_file_path_setting, + ips_file_path_default, +) var _saved_ips_file := ConfigFile.new() +var config_file_loaded: bool = false -var config_file_loaded : bool = false +const USER: StringName = &"USER" +const SERVER_NAMES: StringName = &"SERVER_NAMES" +const SERVER_IPS: StringName = &"SERVER_IPS" +const PLAYER_NAME: StringName = &"player_name" +const HOST_GAME_NAME: StringName = &"host_game_name" +const HOST_GAME_PASSWORD: StringName = &"host_game_password" +const DEFAULT_PLAYER_NAME: StringName = &"Player1" +const DEFAULT_GAME_NAME: StringName = &"Player1's Game" +const DEFAULT_GAME_PASSWORD: StringName = &"" -const USER : StringName = &"USER" -const SERVER_NAMES : StringName = &"SERVER_NAMES" -const SERVER_IPS : StringName = &"SERVER_IPS" -const PLAYER_NAME : StringName = &"player_name" -const HOST_GAME_NAME : StringName = &"host_game_name" -const HOST_GAME_PASSWORD : StringName = &"host_game_password" - -const DEFAULT_PLAYER_NAME : StringName = &"Player1" -const DEFAULT_GAME_NAME : StringName = &"Player1's Game" -const DEFAULT_GAME_PASSWORD : StringName = &"" func get_override_path() -> String: - var override_path : String = ProjectSettings.get_setting("application/config/project_settings_override", "") + var override_path: String = ProjectSettings.get_setting( + "application/config/project_settings_override", + "", + ) if override_path.is_empty(): override_path = _saved_ips_file_path return override_path + func get_ips_config_file() -> ConfigFile: if config_file_loaded: return _saved_ips_file else: return load_ips_config_file() + func load_ips_config_file() -> ConfigFile: var override_path := get_override_path() if FileAccess.file_exists(override_path): if _saved_ips_file.load(override_path) != OK: push_error("Failed to load overrides from %s" % override_path) else: - if !_saved_ips_file.has_section_key(USER,PLAYER_NAME): - _saved_ips_file.set_value(USER,PLAYER_NAME,DEFAULT_PLAYER_NAME) - if !_saved_ips_file.has_section_key(USER,HOST_GAME_NAME): - _saved_ips_file.set_value(USER,HOST_GAME_NAME,DEFAULT_GAME_NAME) - if !_saved_ips_file.has_section_key(USER,HOST_GAME_PASSWORD): - _saved_ips_file.set_value(USER,HOST_GAME_PASSWORD,DEFAULT_GAME_PASSWORD) + if not _saved_ips_file.has_section_key(USER, PLAYER_NAME): + _saved_ips_file.set_value(USER, PLAYER_NAME, DEFAULT_PLAYER_NAME) + if not _saved_ips_file.has_section_key(USER, HOST_GAME_NAME): + _saved_ips_file.set_value(USER, HOST_GAME_NAME, DEFAULT_GAME_NAME) + if not _saved_ips_file.has_section_key(USER, HOST_GAME_PASSWORD): + _saved_ips_file.set_value(USER, HOST_GAME_PASSWORD, DEFAULT_GAME_PASSWORD) save_ips_config_file() load_ips.emit(_saved_ips_file) config_file_loaded = true else: - _saved_ips_file.set_value(USER,PLAYER_NAME,DEFAULT_PLAYER_NAME) - _saved_ips_file.set_value(USER,HOST_GAME_NAME,DEFAULT_GAME_NAME) - _saved_ips_file.set_value(USER,HOST_GAME_PASSWORD,DEFAULT_GAME_PASSWORD) + _saved_ips_file.set_value(USER, PLAYER_NAME, DEFAULT_PLAYER_NAME) + _saved_ips_file.set_value(USER, HOST_GAME_NAME, DEFAULT_GAME_NAME) + _saved_ips_file.set_value(USER, HOST_GAME_PASSWORD, DEFAULT_GAME_PASSWORD) save_ips_config_file() - + return _saved_ips_file + func save_ips_config_file() -> void: var override_path := get_override_path() _saved_ips_file.save(_saved_ips_file_path) @@ -66,5 +74,6 @@ func save_ips_config_file() -> void: else: save_ips.emit(_saved_ips_file) + func _init() -> void: load_ips_config_file() diff --git a/game/src/Autoload/Events/NationManagementScreens.gd b/game/src/Autoload/Events/NationManagementScreens.gd index 4bdca6de..e2d86f34 100644 --- a/game/src/Autoload/Events/NationManagementScreens.gd +++ b/game/src/Autoload/Events/NationManagementScreens.gd @@ -1,25 +1,31 @@ class_name NationManagementScreensEventsObject extends RefCounted -signal update_active_nation_management_screen(screen : NationManagement.Screen) +signal update_active_nation_management_screen(screen: NationManagement.Screen) -var _current_screen : NationManagement.Screen = NationManagement.Screen.NONE +var _current_screen: NationManagement.Screen = NationManagement.Screen.NONE # Set the current nation management screen. This emits an update signal to force # the argument screen to update, even if it was already the current screen. # Used by miscellaneous screen opening buttons (e.g. in province overview panel) # and by the close and toggle functions below. -func open_nation_management_screen(screen : NationManagement.Screen) -> void: + + +func open_nation_management_screen(screen: NationManagement.Screen) -> void: _current_screen = screen update_active_nation_management_screen.emit(_current_screen) # Close the screen if it is already open. Used for screens' close buttons. -func close_nation_management_screen(screen : NationManagement.Screen) -> void: + + +func close_nation_management_screen(screen: NationManagement.Screen) -> void: if screen == _current_screen: open_nation_management_screen(NationManagement.Screen.NONE) # Either switch to the screen or close it if it is already open. Used for topbar's buttons. -func toggle_nation_management_screen(screen : NationManagement.Screen) -> void: + + +func toggle_nation_management_screen(screen: NationManagement.Screen) -> void: if screen == _current_screen: screen = NationManagement.Screen.NONE open_nation_management_screen(screen) diff --git a/game/src/Autoload/Events/Options.gd b/game/src/Autoload/Events/Options.gd index f3fd706b..3906cf6e 100644 --- a/game/src/Autoload/Events/Options.gd +++ b/game/src/Autoload/Events/Options.gd @@ -5,6 +5,7 @@ signal save_settings(save_file: ConfigFile) signal load_settings(load_file: ConfigFile) signal reset_settings() + func load_settings_from_file() -> void: load_settings.emit(_settings_file) @@ -12,25 +13,34 @@ func load_settings_from_file() -> void: # * SS-11 # * UIFUN-13 # * FS-563 + + func save_settings_to_file() -> void: save_settings.emit(_settings_file) _settings_file.save(_settings_file_path) + func try_reset_settings() -> void: reset_settings.emit() -const settings_file_path_setting : String = "openvic/settings/settings_file_path" + +const settings_file_path_setting: String = "openvic/settings/settings_file_path" # REQUIREMENTS # * FS-561 -const settings_file_path_default : String = "user://settings.cfg" +const settings_file_path_default: String = "user://settings.cfg" -var _settings_file_path : String = ProjectSettings.get_setting(settings_file_path_setting, settings_file_path_default) +var _settings_file_path: String = ProjectSettings.get_setting( + settings_file_path_setting, + settings_file_path_default, +) var _settings_file := ConfigFile.new() # REQUIREMENTS # * SS-9 # * UIFUN-7, UIFUN-12 # * FS-562 + + func _init() -> void: if FileAccess.file_exists(_settings_file_path): _settings_file.load(_settings_file_path) diff --git a/game/src/Autoload/GameLoader.gd b/game/src/Autoload/GameLoader.gd index 000ee208..6670df12 100644 --- a/game/src/Autoload/GameLoader.gd +++ b/game/src/Autoload/GameLoader.gd @@ -1,6 +1,7 @@ extends Node -var ShaderManager : ShaderManagerClass +var ShaderManager: ShaderManagerClass + func _init() -> void: ShaderManager = ShaderManagerClass.new() diff --git a/game/src/Autoload/MusicManager/MusicManager.gd b/game/src/Autoload/MusicManager/MusicManager.gd index c5373735..d2bc3656 100644 --- a/game/src/Autoload/MusicManager/MusicManager.gd +++ b/game/src/Autoload/MusicManager/MusicManager.gd @@ -1,71 +1,91 @@ extends Node -signal song_paused(paused : bool) -signal song_started(track_id : int) +signal song_paused(paused: bool) +signal song_started(track_id: int) ## Only triggers when song naturally ends -signal song_finished(track_id : int) -signal song_scrubbed(percentage : float, seconds : float) +signal song_finished(track_id: int) +signal song_scrubbed(percentage: float, seconds: float) + # REQUIREMENTS # * SS-67 -@export_dir var music_directory : String -@export var first_song_name : String -@export var _audio_stream_player : AudioStreamPlayer -var _audio_stream_paused : bool = false +@export_dir var music_directory: String +@export var first_song_name: String +@export var _audio_stream_player: AudioStreamPlayer +var _audio_stream_paused: bool = false var _selected_track := 0 -var _available_songs : Array[SongInfo] = [] -var _auto_play_next_song : bool = true - +var _available_songs: Array[SongInfo] = [] +var _auto_play_next_song: bool = true var playlist: Array[int] = [] -var playlist_index:int = 0 +var playlist_index: int = 0 var preferred_playlist_len: int = 7 var last_played: int = -1 - - ## True if music player should be visible. ## Used to keep keep consistency between scene changes -var is_music_player_visible : bool = true +var is_music_player_visible: bool = true + + +# REQUIREMENTS +# * SND-2, SND-3 + + +func _ready() -> void: + add_ootb_music() + #don't start the current song for compat mode, do that from + #GameStart so we can wait until the music is loaded + var auto_start_music := GameSettings.get_setting(GameSettings.AUDIO_MUSIC_START_PLAY) + set_startup_music(auto_start_music.value()) + func get_all_song_names() -> PackedStringArray: - var songNames : PackedStringArray = [] - for si : SongInfo in _available_songs: + var songNames: PackedStringArray = [] + for si: SongInfo in _available_songs: songNames.append(si.song_name) return songNames + func get_all_song_paths() -> PackedStringArray: - var songPaths : PackedStringArray = [] - for si : SongInfo in _available_songs: + var songPaths: PackedStringArray = [] + for si: SongInfo in _available_songs: songPaths.append(si.song_path) return songPaths + func get_current_song_index() -> int: return _selected_track + func get_current_song_name() -> String: return _available_songs[_selected_track].song_name + func scrub_song_by_percentage(percentage: float) -> void: - var percentInSeconds : float = (percentage / 100.0) * _audio_stream_player.stream.get_length() + var percentInSeconds: float = (percentage / 100.0) * _audio_stream_player.stream.get_length() _audio_stream_player.play(percentInSeconds) song_scrubbed.emit(percentage, percentInSeconds) + func get_current_song_progress_percentage() -> float: return 100 * (_audio_stream_player.get_playback_position() / _audio_stream_player.stream.get_length()) + func is_paused() -> bool: return _audio_stream_paused -func set_paused(paused : bool) -> void: + +func set_paused(paused: bool) -> void: _audio_stream_player.stream_paused = paused # stream_paused requires an active stream _audio_stream_paused = paused song_paused.emit(paused) + func toggle_play_pause() -> void: set_paused(not is_paused()) + func start_current_song() -> void: _audio_stream_player.stream = _available_songs[_selected_track].song_stream _audio_stream_player.play() @@ -73,14 +93,20 @@ func start_current_song() -> void: if _audio_stream_paused: set_paused(true) + # REQUIREMENTS # * SS-70 + + func start_song_by_index(id: int) -> void: _selected_track = id start_current_song() + # REQUIREMENTS # * SS-69 + + func select_next_song() -> void: #_selected_track = (_selected_track + 1) % len(_available_songs) if playlist_index >= preferred_playlist_len or playlist_index >= len(playlist): @@ -92,11 +118,13 @@ func select_next_song() -> void: _audio_stream_paused = false start_current_song() + func select_previous_song() -> void: _selected_track = (len(_available_songs) - 1) if (_selected_track == 0) else (_selected_track - 1) _audio_stream_paused = false start_current_song() + func setup_compat_song(file_name) -> void: var song = SongInfo.new() var stream = SoundSingleton.get_song(file_name) @@ -112,16 +140,18 @@ func setup_compat_song(file_name) -> void: if title == "": #use the file name without the extension if there's no metadata title = file_name.split(".")[0] - song.init_stream(file_name,title,stream) + song.init_stream(file_name, title, stream) _available_songs.append(song) + func add_compat_songs() -> void: - for file_name : String in SoundSingleton.song_list: + for file_name: String in SoundSingleton.song_list: setup_compat_song(file_name) + func add_ootb_music() -> void: var dir := DirAccess.open(music_directory) - for fname : String in dir.get_files(): + for fname: String in dir.get_files(): if fname.ends_with(".import"): fname = fname.get_basename() if fname.get_basename() == first_song_name: @@ -130,14 +160,15 @@ func add_ootb_music() -> void: song.init_file_path(music_directory, fname) _available_songs.append(song) + func generate_playlist() -> void: var song_names = MusicManager.get_all_song_paths() - var possible_indices = range(len(song_names)-1) + var possible_indices = range(len(song_names) - 1) var title_index = song_names.find(SoundSingleton.title_theme) possible_indices.remove_at(title_index) - var actual_playlist_len = min(preferred_playlist_len,len(possible_indices)) + var actual_playlist_len = min(preferred_playlist_len, len(possible_indices)) #if the playlist size is too large or small, make it the same size as what we #need to support @@ -151,26 +182,19 @@ func generate_playlist() -> void: #essentially shuffle-bag randomness, picking from a list of song indices for i in range(actual_playlist_len): - var ind = randi_range(0,len(possible_indices)-1) + var ind = randi_range(0, len(possible_indices) - 1) #add back the last song we just played as an option - if i==2: + if i == 2: possible_indices.append(last_played) playlist[i] = possible_indices[ind] possible_indices.remove_at(ind) -# REQUIREMENTS -# * SND-2, SND-3 -func _ready() -> void: - add_ootb_music() - #don't start the current song for compat mode, do that from - #GameStart so we can wait until the music is loaded - var auto_start_music := GameSettings.get_setting(GameSettings.AUDIO_MUSIC_START_PLAY) - set_startup_music(auto_start_music.value()) -func set_startup_music(play : bool) -> void: +func set_startup_music(play: bool) -> void: set_paused(not play) + func _on_audio_stream_player_finished() -> void: song_finished.emit(_selected_track) if _auto_play_next_song: diff --git a/game/src/Autoload/SaveManager.gd b/game/src/Autoload/SaveManager.gd index 688e1638..93a74180 100644 --- a/game/src/Autoload/SaveManager.gd +++ b/game/src/Autoload/SaveManager.gd @@ -2,40 +2,54 @@ extends Node # Requirements # * FS-28 + const save_directory_setting := &"openvic/data/saves_directory" -var current_save : SaveResource -var current_session_tag : StringName +var current_save: SaveResource +var current_session_tag: StringName +var _save_dictionary: Dictionary = {} +var _dirty_save: SaveResource -var _save_dictionary : Dictionary = {} -var _dirty_save : SaveResource func _ready() -> void: - var saves_dir_path : String = ProjectSettings.get_setting_with_override(save_directory_setting) + var saves_dir_path: String = ProjectSettings.get_setting_with_override(save_directory_setting) assert(saves_dir_path != null, "'%s' setting could not be found." % save_directory_setting) DirAccess.make_dir_recursive_absolute(saves_dir_path) var saves_dir := DirAccess.open(saves_dir_path) - for file : String in saves_dir.get_files(): + for file: String in saves_dir.get_files(): var save := SaveResource.new() save.load_save(saves_dir_path.path_join(file)) add_or_replace_save(save, true) -func get_save_file_name(save_name : StringName, session_tag : StringName = current_session_tag) -> StringName: + +func get_save_file_name( + save_name: StringName, + session_tag: StringName = current_session_tag, +) -> StringName: return ("%s - %s" % [save_name, session_tag]).validate_filename() -func make_new_save(save_name : String, session_tag : StringName = current_session_tag) -> SaveResource: + +func make_new_save( + save_name: String, + session_tag: StringName = current_session_tag, +) -> SaveResource: var file_name := get_save_file_name(save_name, session_tag) + ".tres" var new_save := SaveResource.new() - new_save.set_file_path(save_name, ProjectSettings.get_setting_with_override(save_directory_setting).path_join(file_name)) + new_save.set_file_path( + save_name, + ProjectSettings.get_setting_with_override(save_directory_setting).path_join(file_name), + ) print(new_save.file_path) new_save.session_tag = session_tag return new_save -func has_save(save_name : StringName, session_tag : StringName = current_session_tag) -> bool: + +func has_save(save_name: StringName, session_tag: StringName = current_session_tag) -> bool: return _save_dictionary.has(get_save_file_name(save_name, session_tag)) -func add_or_replace_save(save : SaveResource, ignore_dirty : bool = false) -> void: + +func add_or_replace_save(save: SaveResource, ignore_dirty: bool = false) -> void: var binded_func := _on_save_deleted_or_moved.bind(save) save.deleted.connect(binded_func) save.trash_moved.connect(binded_func) @@ -43,13 +57,16 @@ func add_or_replace_save(save : SaveResource, ignore_dirty : bool = false) -> vo if not ignore_dirty: _dirty_save = save -func delete_save(save : SaveResource) -> void: + +func delete_save(save: SaveResource) -> void: save.delete() + func flush_save() -> void: if _dirty_save == null: return _dirty_save.flush_save() _dirty_save = null -func _on_save_deleted_or_moved(save : SaveResource) -> void: + +func _on_save_deleted_or_moved(save: SaveResource) -> void: _save_dictionary.erase(get_save_file_name(save.save_name, save.session_tag)) diff --git a/game/src/Autoload/Settings/GameSettings.gd b/game/src/Autoload/Settings/GameSettings.gd index b1d2e291..9cac3658 100644 --- a/game/src/Autoload/Settings/GameSettings.gd +++ b/game/src/Autoload/Settings/GameSettings.gd @@ -2,28 +2,15 @@ extends "res://addons/kenyoni/app_settings/app_settings.gd" enum SaveGameFormat { BINARY, - TEXT + TEXT, } -const SAVE_GAME_FORMAT_DISPLAY_NAMES: PackedStringArray = [ - "OPTIONS_GENERAL_BINARY", - "OPTIONS_GENERAL_TEXT" -] - enum AutoSaveInterval { MONTHLY, BIMONTHLY, YEARLY, BIYEARLY, - NEVER + NEVER, } -const AUTO_SAVE_INTERVAL_DISPLAY_NAMES: PackedStringArray = [ - "OPTIONS_GENERAL_AUTOSAVE_MONTHLY", - "OPTIONS_GENERAL_AUTOSAVE_BIMONTHLY", - "OPTIONS_GENERAL_AUTOSAVE_YEARLY", - "OPTIONS_GENERAL_AUTOSAVE_BIYEARLY", - "OPTIONS_GENERAL_AUTOSAVE_NEVER" -] - enum RefreshRate { VSYNC, VSYNC_ADAPTIVE, @@ -34,8 +21,27 @@ enum RefreshRate { _120HZ, _144HZ, _365HZ, - UNLIMITED + UNLIMITED, } +enum GraphicsDetail { + LOW, + MEDIUM, + HIGH, + ULTRA, + CUSTOM, +} + +const SAVE_GAME_FORMAT_DISPLAY_NAMES: PackedStringArray = [ + "OPTIONS_GENERAL_BINARY", + "OPTIONS_GENERAL_TEXT", +] +const AUTO_SAVE_INTERVAL_DISPLAY_NAMES: PackedStringArray = [ + "OPTIONS_GENERAL_AUTOSAVE_MONTHLY", + "OPTIONS_GENERAL_AUTOSAVE_BIMONTHLY", + "OPTIONS_GENERAL_AUTOSAVE_YEARLY", + "OPTIONS_GENERAL_AUTOSAVE_BIYEARLY", + "OPTIONS_GENERAL_AUTOSAVE_NEVER", +] const REFRESH_RATE_DISPLAY_NAMES: PackedStringArray = [ "VSync", "Adaptive VSync", @@ -45,25 +51,16 @@ const REFRESH_RATE_DISPLAY_NAMES: PackedStringArray = [ "90hz", "120hz", "144hz", - "365hz", - "Unlimited" + "365hz", + "Unlimited", ] - -enum GraphicsDetail { - LOW, - MEDIUM, - HIGH, - ULTRA, - CUSTOM -} const GRAPHICS_DETAIL_DISPLAY_NAMES: PackedStringArray = [ "OPTIONS_VIDEO_QUALITY_LOW", "OPTIONS_VIDEO_QUALITY_MEDIUM", "OPTIONS_VIDEO_QUALITY_HIGH", "OPTIONS_VIDEO_QUALITY_ULTRA", - "OPTIONS_VIDEO_QUALITY_CUSTOM" + "OPTIONS_VIDEO_QUALITY_CUSTOM", ] - const RESOLUTIONS: Array[Vector2i] = [ Vector2i(3840, 2160), Vector2i(2560, 1080), @@ -74,7 +71,7 @@ const RESOLUTIONS: Array[Vector2i] = [ Vector2i(1440, 900), Vector2i(1600, 900), Vector2i(1024, 600), - Vector2i(800, 600) + Vector2i(800, 600), ] const RESOLUTION_DISPLAY_NAMES: PackedStringArray = [ "3840x2160", @@ -86,26 +83,28 @@ const RESOLUTION_DISPLAY_NAMES: PackedStringArray = [ "1440x900", "1600x900", "1024x600", - "800x600" + "800x600", ] - const SCREEN_MODES: PackedInt32Array = [ DisplayServer.WINDOW_MODE_FULLSCREEN, DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN, - DisplayServer.WINDOW_MODE_WINDOWED + DisplayServer.WINDOW_MODE_WINDOWED, ] const SCREEN_MODES_DISPLAY_NAMES: PackedStringArray = [ "OPTIONS_VIDEO_FULLSCREEN", "OPTIONS_VIDEO_BORDERLESS", - "OPTIONS_VIDEO_WINDOWED" + "OPTIONS_VIDEO_WINDOWED", ] +# File const SETTINGS_FILE := "user://settings.cfg" +# General Settings const GENERAL_SAVE_GAME_FORMAT := &"general/save_game_format" const GENERAL_AUTO_SAVE_INTERVAL := &"general/auto_save_interval" const GENERAL_LANGUAGE := &"general/language" +# Video Settings const VIDEO_RESOLUTION := &"video/resolution" const VIDEO_GUI_SCALING_FACTOR := &"video/gui_scaling_factor" const VIDEO_SCREEN_MODE := &"video/screen_mode" @@ -113,15 +112,21 @@ const VIDEO_MONITOR_SELECTION := &"video/monitor_selection" const VIDEO_REFRESH_RATE := &"video/refresh_rate" const VIDEO_QUALITY_PRESET := &"video/quality_preset" +# Audio Settings const AUDIO_MASTER_VOLUME := &"audio/master_volume" const AUDIO_MUSIC_VOLUME := &"audio/music_volume" const AUDIO_SFX_VOLUME := &"audio/sfx_volume" const AUDIO_MUSIC_START_PLAY := &"audio/music_start_play" +# Internal Settings const INTERNAL_WINDOW_WIDTH = &"display/window/size/viewport_width" const INTERNAL_WINDOW_HEIGHT = &"display/window/size/viewport_height" -var video_revert_group := RevertGroup.new("OPTIONS_VIDEO_REVERT_DIALOG_TITLE", "OPTIONS_VIDEO_REVERT_DIALOG_TEXT") +var video_revert_group := RevertGroup.new( + "OPTIONS_VIDEO_REVERT_DIALOG_TITLE", + "OPTIONS_VIDEO_REVERT_DIALOG_TEXT", +) + func _init() -> void: Localisation.initialize() @@ -257,9 +262,11 @@ func _init() -> void: # Preserves GUI scaling factor on scene change get_tree().scene_changed.connect(_gui_scaling_factor_apply.bind(self.get_setting(VIDEO_GUI_SCALING_FACTOR))) + func save() -> Error: return self.to_config().save(SETTINGS_FILE) + func load() -> Error: var cfg: ConfigFile = ConfigFile.new() var err: Error = cfg.load(SETTINGS_FILE) @@ -271,6 +278,7 @@ func load() -> Error: return Error.OK + func get_game_resolution() -> Vector2i: var window := get_window() assert(window != null) @@ -280,9 +288,11 @@ func get_game_resolution() -> Vector2i: _: return window.size + func _enum_validate(stg: Setting, val: Variant) -> bool: return val in stg.get_meta(&"values") + func _get_loaded_locales() -> PackedStringArray: var result := TranslationServer.get_loaded_locales() var default_locale := Localisation.get_default_locale() @@ -290,9 +300,13 @@ func _get_loaded_locales() -> PackedStringArray: result.push_back(default_locale) return result + func _get_loaded_locale_names() -> PackedStringArray: - var locales_country_rename : Dictionary = ProjectSettings.get_setting("internationalization/locale/country_short_name", {}) - + var locales_country_rename: Dictionary = ProjectSettings.get_setting( + "internationalization/locale/country_short_name", + {}, + ) + var result: PackedStringArray = [] for locale: String in _get_loaded_locales(): var locale_name := TranslationServer.get_locale_name(locale) @@ -305,6 +319,7 @@ func _get_loaded_locale_names() -> PackedStringArray: result.append(locale_name) return result + func _resolution_apply(stg: Setting) -> void: if Engine.is_embedded_in_editor(): _push_embedded_warning(str(stg.value())) @@ -317,13 +332,14 @@ func _resolution_apply(stg: Setting) -> void: _: window.size = stg.value() as Vector2i _set_window_override(window.size) - window.content_scale_size = Vector2i(0,0) + window.content_scale_size = Vector2i(0, 0) + func _resolution_translate_value(stg: Setting, value: Variant, _display_value: String) -> String: var resolution := value as Vector2i var format_dict := { "width": resolution.x, - "height": resolution.y + "height": resolution.y, } format_dict["name"] = tr("OPTIONS_VIDEO_RESOLUTION_{width}x{height}".format(format_dict)) if format_dict["name"].begins_with("OPTIONS"): format_dict["name"] = "" @@ -334,16 +350,22 @@ func _resolution_translate_value(stg: Setting, value: Variant, _display_value: S format_dict["height"] = Localisation.tr_number(resolution.y) return tr(result).format(format_dict) + func _gui_scaling_factor_apply(stg: Setting) -> void: if not is_inside_tree(): return get_tree().root.content_scale_factor = stg.value() + func _screen_mode_validate(_stg: Setting, val: Variant) -> bool: return _get_enum_values(&"DisplayServer", &"WindowMode").find_key(val) != null + func _screen_mode_apply(stg: Setting) -> void: if Engine.is_embedded_in_editor(): - var window_mode_name: String = _get_enum_values(&"DisplayServer", &"WindowMode").find_key(stg.value()) + var window_mode_name: String = _get_enum_values( + &"DisplayServer", + &"WindowMode", + ).find_key(stg.value()) _push_embedded_warning("DisplayServer." + window_mode_name) return var window := get_window() @@ -351,6 +373,7 @@ func _screen_mode_apply(stg: Setting) -> void: window.mode = stg.value() _set_window_override(window.size) + func _refresh_rate_apply(stg: Setting) -> void: var refresh_rate := stg.value() as RefreshRate match refresh_rate: @@ -368,36 +391,50 @@ func _refresh_rate_apply(stg: Setting) -> void: RefreshRate._365HZ: Engine.max_fps = 365 RefreshRate.UNLIMITED: Engine.max_fps = 0 + func _get_monitor_display_names() -> PackedStringArray: var result: PackedStringArray = [] for index: int in range(DisplayServer.get_screen_count()): result.append("Display " + str(index + 1)) return result + func _volume_apply(stg: Setting, bus_index: int) -> void: - const RATIO_FOR_LINEAR : float = 100 + const RATIO_FOR_LINEAR: float = 100 AudioServer.set_bus_volume_db(bus_index, linear_to_db(stg.value() / RATIO_FOR_LINEAR)) + func _volume_validate(stg: Setting, val: Variant) -> bool: - return val >= stg.get_meta(&"min", 0) && val <= stg.get_meta(&"max", 120) + return val >= stg.get_meta(&"min", 0) and val <= stg.get_meta(&"max", 120) + -func _set_window_override(size : Vector2i) -> void: +func _set_window_override(size: Vector2i) -> void: get_setting(INTERNAL_WINDOW_WIDTH).set_value(size.x) get_setting(INTERNAL_WINDOW_HEIGHT).set_value(size.y) + func _get_enum_values(clazz: StringName, enum_name: StringName) -> Dictionary[StringName, int]: var result: Dictionary[StringName, int] = {} for value_name: String in ClassDB.class_get_enum_constants(clazz, enum_name): result[value_name] = ClassDB.class_get_integer_constant(clazz, value_name) return result + func _push_embedded_warning(value: String) -> void: - push_warning("Setting screen mode to ", value, " for an editor embedded window, this will not be reflected in the embedded window.") + push_warning( + "Setting screen mode to ", + value, + " for an editor embedded window, this will not be reflected in the embedded window.", + ) + class RevertGroup: - var title : String - var text : String + var title: String + var text: String - func _init(ti: String = "Please Confirm...", tex: String = "< reverting in {time} seconds >") -> void: + func _init( + ti: String = "Please Confirm...", + tex: String = "< reverting in {time} seconds >", + ) -> void: title = ti text = tex diff --git a/game/src/Autoload/Settings/ModSettings.gd b/game/src/Autoload/Settings/ModSettings.gd index 24ba7ca5..5bcea01a 100644 --- a/game/src/Autoload/Settings/ModSettings.gd +++ b/game/src/Autoload/Settings/ModSettings.gd @@ -1,24 +1,27 @@ extends "res://addons/kenyoni/app_settings/app_settings.gd" const SETTINGS_FILE: String = "user://mods.cfg" - const MODS_LOAD_LIST := &"mods/load_list" + func _init() -> void: self.add_setting(Setting.new(MODS_LOAD_LIST, PackedStringArray()) .set_validate_fn(_load_list_validate) .add_meta(&"type", TYPE_PACKED_STRING_ARRAY)) - + self.load() self.apply_all() + func _notification(what: int) -> void: if what != NOTIFICATION_WM_CLOSE_REQUEST: return save() + func save() -> Error: return self.to_config().save(SETTINGS_FILE) + func load() -> Error: var cfg: ConfigFile = ConfigFile.new() var err: Error = cfg.load(SETTINGS_FILE) @@ -30,9 +33,11 @@ func load() -> Error: return Error.OK + func get_load_list() -> PackedStringArray: return get_setting(MODS_LOAD_LIST).value() as PackedStringArray + func _load_list_validate(_stg: Setting, val: Variant) -> bool: var result := true var array := val as PackedStringArray diff --git a/game/src/Autoload/Settings/Vic2Settings.gd b/game/src/Autoload/Settings/Vic2Settings.gd index 35441006..61af36ef 100644 --- a/game/src/Autoload/Settings/Vic2Settings.gd +++ b/game/src/Autoload/Settings/Vic2Settings.gd @@ -1,17 +1,15 @@ extends "res://addons/kenyoni/app_settings/app_settings.gd" -const LEGACY_SETTINGS_FILES : PackedStringArray = [] - -const BASE_DEFINES_LEGACY_PATHS : PackedStringArray = [ - "general/base_defines_path" +const LEGACY_SETTINGS_FILES: PackedStringArray = [] +const BASE_DEFINES_LEGACY_PATHS: PackedStringArray = [ + "general/base_defines_path", ] - const SETTINGS_FILE: String = "user://vic2.cfg" - const GENERAL_BASE_DEFINES_PATH := &"victoria_2/base_defines_path" var _base_path_find_dialog := FileDialog.new() + func _init() -> void: self.add_setting(Setting.new(GENERAL_BASE_DEFINES_PATH, "") .set_validate_fn(_base_defines_path_validate) @@ -38,16 +36,19 @@ func _init() -> void: _base_path_find_dialog.process_mode = Node.PROCESS_MODE_WHEN_PAUSED add_child(_base_path_find_dialog) + func _notification(what: int) -> void: if what != NOTIFICATION_WM_CLOSE_REQUEST: return save() + func save() -> Error: return self.to_config().save(SETTINGS_FILE) + func load() -> Error: var cfg := ConfigFile.new() - var err : Error + var err: Error var settings_with_legacy: Array[Setting] = get_section("", -1, func(stg: Setting) -> bool: return stg.has_meta(&"legacy_paths")) @@ -69,15 +70,18 @@ func load() -> Error: return Error.OK + func get_base_defines_path() -> String: return get_setting(GENERAL_BASE_DEFINES_PATH).value() + func find_base_path(search_path: String) -> String: var result := GameSingleton.search_for_game_path(search_path) if not result: push_warning("Failed to find base path using ", search_path) return result + func show_base_path_find_dialog() -> void: var setting := get_setting(GENERAL_BASE_DEFINES_PATH) @@ -95,23 +99,32 @@ func show_base_path_find_dialog() -> void: _base_path_find_dialog.canceled.disconnect(_on_base_path_find_dialog_failed) get_tree().paused = false + func _base_defines_path_validate(_stg: Setting, val: Variant) -> bool: return (val as String).is_absolute_path() and DirAccess.dir_exists_absolute(val) + func _show_alert() -> void: OS.alert(tr("ERROR_ASSET_PATH_NOT_FOUND_MESSAGE"), tr("ERROR_ASSET_PATH_NOT_FOUND")) + func _on_base_path_find_dialog_failed() -> void: get_window().mode = Window.MODE_WINDOWED _show_alert() get_tree().root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST) get_tree().quit() -func _on_base_path_find_dialog_dir_selected(dir : String) -> void: + +func _on_base_path_find_dialog_dir_selected(dir: String) -> void: var setting := get_setting(GENERAL_BASE_DEFINES_PATH) setting.set_value(GameSingleton.search_for_game_path(dir)) -func _load_legacy_value(config: ConfigFile, setting: Setting, skip_non_legacy: bool = false) -> void: + +func _load_legacy_value( + config: ConfigFile, + setting: Setting, + skip_non_legacy: bool = false, +) -> void: var split: PackedStringArray var legacy_paths: PackedStringArray = setting.get_meta(&"legacy_paths") diff --git a/game/src/Autoload/SoundManager.gd b/game/src/Autoload/SoundManager.gd index 520afb12..2f06f61c 100644 --- a/game/src/Autoload/SoundManager.gd +++ b/game/src/Autoload/SoundManager.gd @@ -3,23 +3,25 @@ extends Node # REQUIREMENTS: # * SS-68 -const _audio_directory_path : StringName = &"res://assets/audio/sfx/" +const _audio_directory_path: StringName = &"res://assets/audio/sfx/" -var _loaded_sound : Dictionary = {} - -var _bus_to_stream_player : Dictionary = {} +var _loaded_sound: Dictionary = {} +var _bus_to_stream_player: Dictionary = {} # REQUIREMENTS: # * SND-10 + + func _ready() -> void: var dir := DirAccess.open(_audio_directory_path) - for fname : String in dir.get_files(): + for fname: String in dir.get_files(): match fname.get_extension(): "ogg", "wav", "mp3": _loaded_sound[fname.get_basename()] = load(_audio_directory_path.path_join(fname)) # SND-10 -func play_stream(sound : AudioStream, bus_type : String, volume : float = 1.0) -> void: - var player : AudioStreamPlayer = _bus_to_stream_player.get(bus_type) + +func play_stream(sound: AudioStream, bus_type: String, volume: float = 1.0) -> void: + var player: AudioStreamPlayer = _bus_to_stream_player.get(bus_type) if player == null: player = AudioStreamPlayer.new() player.bus = bus_type @@ -27,27 +29,32 @@ func play_stream(sound : AudioStream, bus_type : String, volume : float = 1.0) - _bus_to_stream_player[bus_type] = player add_child(player) player.play() - var poly_playback : AudioStreamPlaybackPolyphonic = player.get_stream_playback() + var poly_playback: AudioStreamPlaybackPolyphonic = player.get_stream_playback() player.volume_db = linear_to_db(volume) poly_playback.play_stream(sound) -func play(sound : String, bus_type : String) -> void: + +func play(sound: String, bus_type: String) -> void: play_stream(_loaded_sound[sound], bus_type) # REQUIREMENTS: # * SND-7 -func play_effect_stream(sound : AudioStream, volume : float = 1.0) -> void: + + +func play_effect_stream(sound: AudioStream, volume: float = 1.0) -> void: play_stream(sound, "SFX", volume) -func play_effect(sound : String) -> void: + +func play_effect(sound: String) -> void: play(sound, "SFX") -func play_effect_compat(sfx : String, fallback : AudioStream=null) -> void: - var sound:AudioStreamWAV = SoundSingleton.get_sound_stream(sfx) - var volume:float = SoundSingleton.get_sound_base_volume(sfx) + +func play_effect_compat(sfx: String, fallback: AudioStream = null) -> void: + var sound: AudioStreamWAV = SoundSingleton.get_sound_stream(sfx) + var volume: float = SoundSingleton.get_sound_base_volume(sfx) if sound != null: - play_effect_stream(sound,volume) + play_effect_stream(sound, volume) elif fallback != null: push_warning("Failed to find sound %s, playing fallback instead" % sfx) play_effect_stream(fallback) diff --git a/game/src/Autoload/WindowOverride.gd b/game/src/Autoload/WindowOverride.gd index 7558218d..062e5374 100644 --- a/game/src/Autoload/WindowOverride.gd +++ b/game/src/Autoload/WindowOverride.gd @@ -1,13 +1,16 @@ extends Node + func _init() -> void: var window_id := DisplayServer.get_window_list()[0] DisplayServer.window_set_size(Vector2(1280.0, 720.0), window_id) + func _ready() -> void: if ArgumentParser.get_option_value(&"help"): return _on_SceneTree_idle() + func _on_SceneTree_idle() -> void: if Engine.is_embedded_in_editor(): return var window := get_window() @@ -18,4 +21,7 @@ func _on_SceneTree_idle() -> void: var screen_pos := DisplayServer.screen_get_position(window.current_screen) var screen_size := DisplayServer.screen_get_size(window.current_screen) window.position = screen_pos + (screen_size - window.size) / 2 - ProjectSettings.set_setting.call_deferred("display/window/per_pixel_transparency/allowed", false) + ProjectSettings.set_setting.call_deferred( + "display/window/per_pixel_transparency/allowed", + false, + ) diff --git a/game/src/Model/FileAccessUtils.gd b/game/src/Model/FileAccessUtils.gd index f5e02e13..96d97757 100644 --- a/game/src/Model/FileAccessUtils.gd +++ b/game/src/Model/FileAccessUtils.gd @@ -1,76 +1,92 @@ class_name FileAccessUtils -static func read_vec2(file : FileAccess) -> Vector2: + +static func read_vec2(file: FileAccess) -> Vector2: return Vector2(file.get_float(), file.get_float()) -static func read_vec3(file : FileAccess) -> Vector3: + +static func read_vec3(file: FileAccess) -> Vector3: return Vector3(file.get_float(), file.get_float(), file.get_float()) -static func read_pos(file : FileAccess) -> Vector3: - var pos : Vector3 = read_vec3(file) + +static func read_pos(file: FileAccess) -> Vector3: + var pos: Vector3 = read_vec3(file) pos.x = -pos.x return pos -static func read_vec4(file : FileAccess) -> Vector4: + +static func read_vec4(file: FileAccess) -> Vector4: return Vector4(file.get_float(), file.get_float(), file.get_float(), file.get_float()) # Because paradox may or may not be consistent with the xsm spec depending on if its Tuesday or not -static func read_quat(file : FileAccess, int16 : bool = false) -> Quaternion: + + +static func read_quat(file: FileAccess, int16: bool = false) -> Quaternion: if int16: return Quaternion(read_f16(file), -read_f16(file), -read_f16(file), read_f16(file)) else: return Quaternion(file.get_float(), -file.get_float(), -file.get_float(), file.get_float()) -static func read_f16(file : FileAccess) -> float: + +static func read_f16(file: FileAccess) -> float: # 32767 or 0x7FFF is the max magnitude of a signed int16 return float(read_int16(file)) / 32767.0 -static func replace_chars(string : String) -> String: + +static func replace_chars(string: String) -> String: return string.replace(":", "_").replace("\\", "_").replace("/", "_") -static func read_xac_str(file : FileAccess) -> String: - var length : int = file.get_32() - var buffer : PackedByteArray = file.get_buffer(length) + +static func read_xac_str(file: FileAccess) -> String: + var length: int = file.get_32() + var buffer: PackedByteArray = file.get_buffer(length) return buffer.get_string_from_ascii() -static func read_int32(file : FileAccess) -> int: - var bytes : int = file.get_32() - var negative : bool = bytes >> 31 - var val : int = bytes & 0x7FFFFFFF + +static func read_int32(file: FileAccess) -> int: + var bytes: int = file.get_32() + var negative: bool = bytes >> 31 + var val: int = bytes & 0x7fffffff if negative: - val = -((val ^ 0x7FFFFFFF) + 1) + val = -((val ^ 0x7fffffff) + 1) return val -static func read_int16(file : FileAccess) -> int: - var bytes : int = file.get_16() - var negative : bool = bytes >> 15 - var val : int = bytes & 0x7FFF + +static func read_int16(file: FileAccess) -> int: + var bytes: int = file.get_16() + var negative: bool = bytes >> 15 + var val: int = bytes & 0x7fff if negative: - val = -((val ^ 0x7FFF) + 1) + val = -((val ^ 0x7fff) + 1) return val -static func read_Color32(file : FileAccess) -> Color: + +static func read_Color32(file: FileAccess) -> Color: return Color8(file.get_8(), file.get_8(), file.get_8(), file.get_8()) -static func read_Color128(file : FileAccess) -> Color: + +static func read_Color128(file: FileAccess) -> Color: return Color( - file.get_32() / 0xFFFFFFFF, - file.get_32() / 0xFFFFFFFF, - file.get_32() / 0xFFFFFFFF, - file.get_32() / 0xFFFFFFFF + file.get_32() / 0xffffffff, + file.get_32() / 0xffffffff, + file.get_32() / 0xffffffff, + file.get_32() / 0xffffffff ) -static func read_mat4x4(file : FileAccess) -> xac_mat4x4: + +static func read_mat4x4(file: FileAccess) -> xac_mat4x4: return xac_mat4x4.new(read_vec4(file), read_vec4(file), read_vec4(file), read_vec4(file)) # This datatype is only ever used to hold a transform for nodes (bones) + + class xac_mat4x4: - var col1 : Vector4 - var col2 : Vector4 - var col3 : Vector4 - var col4 : Vector4 + var col1: Vector4 + var col2: Vector4 + var col3: Vector4 + var col4: Vector4 - func _init(col1 : Vector4, col2 : Vector4, col3 : Vector4, col4 : Vector4) -> void: + func _init(col1: Vector4, col2: Vector4, col3: Vector4, col4: Vector4) -> void: self.col1 = col1 self.col2 = col2 self.col3 = col3 diff --git a/game/src/Model/UnitModel.gd b/game/src/Model/UnitModel.gd index 947c0aa3..7cb5cfad 100644 --- a/game/src/Model/UnitModel.gd +++ b/game/src/Model/UnitModel.gd @@ -1,56 +1,65 @@ class_name UnitModel extends Node3D -var skeleton : Skeleton3D = null -var anim_player : AnimationPlayer = null -var anim_lib : AnimationLibrary = null -var sub_units : Array[UnitModel] -var meshes : Array[MeshInstance3D] +var skeleton: Skeleton3D = null +var anim_player: AnimationPlayer = null +var anim_lib: AnimationLibrary = null +var sub_units: Array[UnitModel] +var meshes: Array[MeshInstance3D] # COLOUR VARIABLES + @export_group("Colors") -@export var primary_colour : Color: +@export var primary_colour: Color: set(col_in): primary_colour = col_in _set_shader_parameter(&"colour_primary", primary_colour) - for unit : UnitModel in sub_units: + for unit: UnitModel in sub_units: unit.primary_colour = col_in - @export var secondary_colour: Color: set(col_in): secondary_colour = col_in _set_shader_parameter(&"colour_secondary", secondary_colour) - for unit : UnitModel in sub_units: + for unit: UnitModel in sub_units: unit.secondary_colour = col_in - -@export var tertiary_colour : Color: +@export var tertiary_colour: Color: set(col_in): tertiary_colour = col_in _set_shader_parameter(&"colour_tertiary", tertiary_colour) - for unit : UnitModel in sub_units: + for unit: UnitModel in sub_units: unit.tertiary_colour = col_in # ANIMATION VARIABLES -var _idle_anim_path : StringName -var _move_anim_path : StringName -var _attack_anim_path : StringName -func set_idle_anim(anim_in : Animation) -> void: +var _idle_anim_path: StringName +var _move_anim_path: StringName +var _attack_anim_path: StringName + + +func set_idle_anim(anim_in: Animation) -> void: _idle_anim_path = load_animation("idle", anim_in) -func set_move_anim(anim_in : Animation) -> void: + +func set_move_anim(anim_in: Animation) -> void: _move_anim_path = load_animation("move", anim_in) -func set_attack_anim(anim_in : Animation) -> void: + +func set_attack_anim(anim_in: Animation) -> void: _attack_anim_path = load_animation("attack", anim_in) -enum Anim { NONE, IDLE, MOVE, ATTACK } -const ANIMATION_LIBRARY : StringName = &"default_lib" +enum Anim { + NONE, + IDLE, + MOVE, + ATTACK, +} -@export var current_anim : Anim: +const ANIMATION_LIBRARY: StringName = &"default_lib" + +@export var current_anim: Anim: set(anim_in): - for unit : UnitModel in sub_units: + for unit: UnitModel in sub_units: unit.current_anim = anim_in if anim_player: @@ -83,31 +92,31 @@ const ANIMATION_LIBRARY : StringName = &"default_lib" # TEXTURE SCROLL SPEEDS (TANKS TRACKS AND SMOKE) @export_subgroup("Texture_Scroll") -@export var scroll_speed_idle : float: +@export var scroll_speed_idle: float: set(speed_in): scroll_speed_idle = speed_in - for unit : UnitModel in sub_units: + for unit: UnitModel in sub_units: unit.scroll_speed_idle = speed_in - -@export var scroll_speed_move : float: +@export var scroll_speed_move: float: set(speed_in): scroll_speed_move = speed_in - for unit : UnitModel in sub_units: + for unit: UnitModel in sub_units: unit.scroll_speed_move = speed_in - -@export var scroll_speed_attack : float: +@export var scroll_speed_attack: float: set(speed_in): scroll_speed_attack = speed_in - for unit : UnitModel in sub_units: + for unit: UnitModel in sub_units: unit.scroll_speed_attack = speed_in + func unit_init() -> void: - for child : Node in get_children(): + for child: Node in get_children(): if child is MeshInstance3D: meshes.append(child) elif child is Skeleton3D: skeleton = child + func add_anim_player() -> void: anim_player = AnimationPlayer.new() anim_player.name = "anim_player" @@ -118,21 +127,41 @@ func add_anim_player() -> void: add_child(anim_player) -func has_bone(bone_name : String) -> bool: + +func has_bone(bone_name: String) -> bool: return skeleton and skeleton.find_bone(bone_name) > -1 -func attach_model(bone_name : String, model : Node3D) -> Error: + +func attach_model(bone_name: String, model: Node3D) -> Error: if not model: push_error("Cannot attach null model to bone \"", bone_name, "\" of UnitModel ", get_name()) return FAILED if not skeleton: - push_error("Cannot attach model \"", model.get_name(), "\" to bone \"", bone_name, "\" of UnitModel ", get_name(), " - has no skeleton!") + push_error( + "Cannot attach model \"", + model.get_name(), + "\" to bone \"", + bone_name, + "\" of UnitModel ", + get_name(), + " - has no skeleton!", + ) return FAILED - var bone_idx : int = skeleton.find_bone(bone_name) + var bone_idx: int = skeleton.find_bone(bone_name) if bone_idx < 0 or bone_idx >= skeleton.get_bone_count(): - push_warning("Invalid bone \"", bone_name, "\" (index ", bone_idx, ") for attachment \"", model.get_name(), "\" to UnitModel \"", get_name(), "\"") + push_warning( + "Invalid bone \"", + bone_name, + "\" (index ", + bone_idx, + ") for attachment \"", + model.get_name(), + "\" to UnitModel \"", + get_name(), + "\"", + ) return FAILED var bone_attachment := BoneAttachment3D.new() @@ -150,23 +179,27 @@ func attach_model(bone_name : String, model : Node3D) -> Error: return OK -func _set_shader_parameter(param_name : StringName, param_val : Variant) -> void: - for mesh : MeshInstance3D in meshes: + +func _set_shader_parameter(param_name: StringName, param_val: Variant) -> void: + for mesh: MeshInstance3D in meshes: mesh.set_instance_shader_parameter(param_name, param_val) -func _set_tex_scroll(speed : float) -> void: + +func _set_tex_scroll(speed: float) -> void: _set_shader_parameter(&"scroll_speed", speed) -func set_flag_index(index : int) -> void: + +func set_flag_index(index: int) -> void: _set_shader_parameter(&"flag_index", index) -func load_animation(prop_name : String, animIn : Animation) -> StringName: + +func load_animation(prop_name: String, animIn: Animation) -> StringName: if not animIn: return &"" if not anim_player: add_anim_player() - var anim_path : StringName = anim_player.find_animation(animIn) + var anim_path: StringName = anim_player.find_animation(animIn) if anim_path.is_empty(): anim_lib.add_animation(prop_name, animIn) anim_path = anim_player.find_animation(animIn) diff --git a/game/src/Model/XACLoader.gd b/game/src/Model/XACLoader.gd index f5683436..019ea5be 100644 --- a/game/src/Model/XACLoader.gd +++ b/game/src/Model/XACLoader.gd @@ -1,33 +1,37 @@ class_name XACLoader -static var unit_shader : ShaderMaterial = preload("res://src/Model/unit_colours_mat.tres") -const MAX_UNIT_TEXTURES : int = 32 # max number of textures supported by the shader -const EXTRA_CULL_MARGIN : int = 2 # extra margin to stop sub-meshes from being culled near the edges of screens -static var added_unit_textures_spec : PackedStringArray -static var added_unit_textures_diffuse : PackedStringArray - -static var flag_shader : ShaderMaterial = preload("res://src/Model/flag_mat.tres") - -static var scrolling_shader : ShaderMaterial = preload("res://src/Model/scrolling_mat.tres") -const MAX_SCROLLING_TEXTURES : int = 32 # max number of textures supported by the shader -static var added_scrolling_textures_diffuse : PackedStringArray -const SCROLLING_MATERIAL_FACTORS : Dictionary = { - "TexAnim" : 2.5, # Tank tracks - "Smoke" : 0.3 # Buildings, factories, steam ships, sieges +const MAX_UNIT_TEXTURES: int = 32 # max number of textures supported by the shader +const EXTRA_CULL_MARGIN: int = 2 # extra margin to stop sub-meshes from being culled near the edges of screens +const MAX_SCROLLING_TEXTURES: int = 32 # max number of textures supported by the shader +const SCROLLING_MATERIAL_FACTORS: Dictionary = { + "TexAnim": 2.5, # Tank tracks + "Smoke": 0.3, # Buildings, factories, steam ships, sieges } +const LOAD_FAILED_MARKER: StringName = &"XAC LOAD FAILED" + +static var unit_shader: ShaderMaterial = preload("res://src/Model/unit_colours_mat.tres") +static var added_unit_textures_spec: PackedStringArray +static var added_unit_textures_diffuse: PackedStringArray +static var flag_shader: ShaderMaterial = preload("res://src/Model/flag_mat.tres") +static var scrolling_shader: ShaderMaterial = preload("res://src/Model/scrolling_mat.tres") +static var added_scrolling_textures_diffuse: PackedStringArray -static func setup_flag_shader() -> void: - flag_shader.set_shader_parameter(&"flag_dims", GameSingleton.get_flag_dims()) - flag_shader.set_shader_parameter(&"texture_flag_sheet_diffuse", GameSingleton.get_flag_sheet_texture()) # Keys: source_file (String) # Values: loaded model (UnitModel or Node3D) or LOAD_FAILED_MARKER (StringName) -static var xac_cache : Dictionary +static var xac_cache: Dictionary -const LOAD_FAILED_MARKER : StringName = &"XAC LOAD FAILED" -static func get_xac_model(source_file : String, is_unit : bool) -> Node3D: - var cached : Variant = xac_cache.get(source_file) +static func setup_flag_shader() -> void: + flag_shader.set_shader_parameter(&"flag_dims", GameSingleton.get_flag_dims()) + flag_shader.set_shader_parameter( + &"texture_flag_sheet_diffuse", + GameSingleton.get_flag_sheet_texture(), + ) + + +static func get_xac_model(source_file: String, is_unit: bool) -> Node3D: + var cached: Variant = xac_cache.get(source_file) if not cached: cached = _load_xac_model(source_file, is_unit) if cached: @@ -41,57 +45,58 @@ static func get_xac_model(source_file : String, is_unit : bool) -> Node3D: push_error("Failed to get XAC model \"", source_file, "\" (previous load failed)") return null - var node : Node3D = cached.duplicate() + var node: Node3D = cached.duplicate() if node is UnitModel: node.unit_init() return node -static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: - var source_path : String = GameSingleton.lookup_file_path(source_file) - var file : FileAccess = FileAccess.open(source_path, FileAccess.READ) + +static func _load_xac_model(source_file: String, is_unit: bool) -> Node3D: + var source_path: String = GameSingleton.lookup_file_path(source_file) + var file: FileAccess = FileAccess.open(source_path, FileAccess.READ) if file == null: push_error("Failed to load XAC ", source_file, " from looked up path ", source_path) return null - var metaDataChunk : MetadataChunk - var nodeHierarchyChunk : NodeHierarchyChunk - var materialTotalsChunk : MaterialTotalsChunk - var materialDefinitionChunks : Array[MaterialDefinitionChunk] = [] - var mesh_chunks : Array[MeshChunk] = [] - var skinningChunks : Array[SkinningChunk] = [] - var chunkType6s : Array[ChunkType6] = [] - var nodeChunks : Array[NodeChunk] = [] - var chunkType4s : Array[ChunkTypeUnknown] = [] - var chunkUnknowns : Array[ChunkTypeUnknown] = [] + var metaDataChunk: MetadataChunk + var nodeHierarchyChunk: NodeHierarchyChunk + var materialTotalsChunk: MaterialTotalsChunk + var materialDefinitionChunks: Array[MaterialDefinitionChunk] = [] + var mesh_chunks: Array[MeshChunk] = [] + var skinningChunks: Array[SkinningChunk] = [] + var chunkType6s: Array[ChunkType6] = [] + var nodeChunks: Array[NodeChunk] = [] + var chunkType4s: Array[ChunkTypeUnknown] = [] + var chunkUnknowns: Array[ChunkTypeUnknown] = [] readHeader(file) while file.get_position() < file.get_length(): - var type : int = FileAccessUtils.read_int32(file) - var length : int = FileAccessUtils.read_int32(file) - var version : int = FileAccessUtils.read_int32(file) + var type: int = FileAccessUtils.read_int32(file) + var length: int = FileAccessUtils.read_int32(file) + var version: int = FileAccessUtils.read_int32(file) match type: 0x7: metaDataChunk = readMetaDataChunk(file) - 0xB: + 0xb: nodeHierarchyChunk = readNodeHierarchyChunk(file) - 0xD: + 0xd: materialTotalsChunk = readMaterialTotalsChunk(file) 0x3: # Ver=1 Appears on old version of format - var chunk : MaterialDefinitionChunk = readMaterialDefinitionChunk(file, version==1) + var chunk: MaterialDefinitionChunk = readMaterialDefinitionChunk(file, version == 1) if chunk.has_specular(): is_unit = true materialDefinitionChunks.push_back(chunk) 0x1: mesh_chunks.push_back(readMeshChunk(file)) 0x2: - skinningChunks.push_back(readSkinningChunk(file,mesh_chunks, version==2)) + skinningChunks.push_back(readSkinningChunk(file, mesh_chunks, version == 2)) 0x6: chunkType6s.push_back(readChunkType6(file)) 0x0: # Appears on old version of format nodeChunks.push_back(readNodeChunk(file)) - 0xA: # Appears on old version of format + 0xa: # Appears on old version of format chunkUnknowns.push_back(readChunkTypeUnknown(file, length)) 0x4: # Appears on old version of format chunkType4s.push_back(readChunkTypeUnknown(file, length)) @@ -103,19 +108,22 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: break #BUILD THE GODOT MATERIALS - var materials : Array[MaterialDefinition] = make_materials(materialDefinitionChunks) + var materials: Array[MaterialDefinition] = make_materials(materialDefinitionChunks) #BUILD THE MESH - var node : Node3D = null + var node: Node3D = null if is_unit: node = UnitModel.new() else: node = Node3D.new() - node.name = metaDataChunk.origFileName.replace("\\", "/").split("/", false)[-1].get_slice(".", 0) + node.name = metaDataChunk.origFileName.replace( + "\\", + "/", + ).split("/", false)[-1].get_slice(".", 0) - var skeleton : Skeleton3D = null + var skeleton: Skeleton3D = null # build the skeleton hierarchy if nodeHierarchyChunk: @@ -128,29 +136,35 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: else: push_warning("MODEL HAS NO SKELETON: ", source_file) - var st : SurfaceTool = SurfaceTool.new() + var st: SurfaceTool = SurfaceTool.new() - for chunk : MeshChunk in mesh_chunks: - var mesh_chunk_name : String + for chunk: MeshChunk in mesh_chunks: + var mesh_chunk_name: String if nodeHierarchyChunk: mesh_chunk_name = nodeHierarchyChunk.nodes[chunk.nodeId].name elif not nodeChunks.is_empty(): mesh_chunk_name = nodeChunks[chunk.nodeId].name - const INVALID_MESHES : PackedStringArray = ["polySurface95"] + const INVALID_MESHES: PackedStringArray = ["polySurface95"] if mesh_chunk_name in INVALID_MESHES: - push_warning("Skipping unused mesh \"", mesh_chunk_name, "\" in model \"", node.name, "\"") + push_warning( + "Skipping unused mesh \"", + mesh_chunk_name, + "\" in model \"", + node.name, + "\"", + ) continue - var mesh : ArrayMesh = null - var verts : PackedVector3Array - var normals : PackedVector3Array - var tangents : Array[Vector4] - var uvs : Array[PackedVector2Array] = [] - var influenceRangeInd : PackedInt64Array + var mesh: ArrayMesh = null + var verts: PackedVector3Array + var normals: PackedVector3Array + var tangents: Array[Vector4] + var uvs: Array[PackedVector2Array] = [] + var influenceRangeInd: PackedInt64Array # vert attributes could be in any order, so search for them - for vertAttrib : VerticesAttribute in chunk.VerticesAttributes: + for vertAttrib: VerticesAttribute in chunk.VerticesAttributes: match vertAttrib.type: 0: # position verts = vertAttrib.data @@ -168,14 +182,14 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: #FIXME: find a better solution if possible #pCube1 hardcoding is to fix the cruiser which doesn't properly mark its collision mesh if chunk.bIsCollisionMesh or mesh_chunk_name == "pCube1": - var ar3d : Area3D = Area3D.new() + var ar3d: Area3D = Area3D.new() node.add_child(ar3d) ar3d.owner = node - for submesh : SubMesh in chunk.SubMeshes: - var shape : ConvexPolygonShape3D = ConvexPolygonShape3D.new() + for submesh: SubMesh in chunk.SubMeshes: + var shape: ConvexPolygonShape3D = ConvexPolygonShape3D.new() shape.points = verts - var col : CollisionShape3D = CollisionShape3D.new() + var col: CollisionShape3D = CollisionShape3D.new() col.shape = shape ar3d.add_child(col) @@ -183,9 +197,9 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: continue #TODO will this produce correct results? - var applyVertexWeights : bool = true - var skinning_chunk_ind : int = 0 - for skin : SkinningChunk in skinningChunks: + var applyVertexWeights: bool = true + var skinning_chunk_ind: int = 0 + for skin: SkinningChunk in skinningChunks: if skin.nodeId == chunk.nodeId: break skinning_chunk_ind += 1 @@ -197,13 +211,19 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: # but only in cases where it isn't an attachment # problem, this is also the body of the S-P infantry # so this should only be valid if inside an attachment or makes use of bone weights - const INVALID_IF_NOT_ONLY_MESH : PackedStringArray = ["polySurface97"] + const INVALID_IF_NOT_ONLY_MESH: PackedStringArray = ["polySurface97"] if influenceRangeInd.is_empty() or skinningChunks.is_empty() or not applyVertexWeights: if mesh_chunks.size() != 1 and mesh_chunk_name in INVALID_IF_NOT_ONLY_MESH: - push_warning("Skipping unused mesh \"", mesh_chunk_name, "\" in model \"", node.name, "\" because it was not the only mesh chunk in its file") + push_warning( + "Skipping unused mesh \"", + mesh_chunk_name, + "\" in model \"", + node.name, + "\" because it was not the only mesh chunk in its file", + ) break - var meshInstance : MeshInstance3D = MeshInstance3D.new() + var meshInstance: MeshInstance3D = MeshInstance3D.new() node.add_child(meshInstance) meshInstance.owner = node @@ -217,14 +237,14 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: meshInstance.skeleton = meshInstance.get_path_to(skeleton) if not verts.is_empty(): - var vert_total : int = 0 - var surfaceIndex : int = 0 + var vert_total: int = 0 + var surfaceIndex: int = 0 - for submesh : SubMesh in chunk.SubMeshes: + for submesh: SubMesh in chunk.SubMeshes: st.begin(Mesh.PRIMITIVE_TRIANGLES) - for i : int in submesh.relativeIndices.size(): - var rel_index : int = vert_total + submesh.relativeIndices[i] + for i: int in submesh.relativeIndices.size(): + var rel_index: int = vert_total + submesh.relativeIndices[i] if not normals.is_empty(): st.set_normal(normals[rel_index]) @@ -240,30 +260,33 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: if not uvs.is_empty(): st.set_uv(uvs[0][rel_index]) - if not influenceRangeInd.is_empty() and not skinningChunks.is_empty() and applyVertexWeights: + if (not influenceRangeInd.is_empty() + and not skinningChunks.is_empty() + and applyVertexWeights): #TODO: Which skinning Chunk? - # likely look at the skinning chunk's nodeId, see if it matches our mesh's id - var vert_inf_range_ind : int = influenceRangeInd[rel_index] - var skin_chunk : SkinningChunk = skinningChunks[skinning_chunk_ind] - var influenceRange : InfluenceRange = skin_chunk.influenceRange[vert_inf_range_ind] - var boneWeights : Array[InfluenceData] = skinningChunks[skinning_chunk_ind].influenceData.slice( + # likely look at the skinning chunk's nodeId, see if it matches our mesh's + # id + var vert_inf_range_ind: int = influenceRangeInd[rel_index] + var skin_chunk: SkinningChunk = skinningChunks[skinning_chunk_ind] + var influenceRange: InfluenceRange = skin_chunk.influenceRange[vert_inf_range_ind] + var boneWeights: Array[InfluenceData] = skinningChunks[skinning_chunk_ind].influenceData.slice( influenceRange.firstInfluenceIndex, influenceRange.firstInfluenceIndex + influenceRange.numInfluences ) if len(boneWeights) > 4: push_error("num BONE WEIGHTS WAS > 4, GODOT DOESN'T LIKE THIS") # TODO: Less hacky fix? - boneWeights = boneWeights.slice(0,4) + boneWeights = boneWeights.slice(0, 4) - var godotBoneIds : PackedInt32Array = PackedInt32Array() - var godotBoneWeights : PackedFloat32Array = PackedFloat32Array() + var godotBoneIds: PackedInt32Array = PackedInt32Array() + var godotBoneWeights: PackedFloat32Array = PackedFloat32Array() godotBoneIds.resize(4) godotBoneIds.fill(0) godotBoneWeights.resize(4) godotBoneWeights.fill(0) - var index : int = 0 - for bone : InfluenceData in boneWeights: + var index: int = 0 + for bone: InfluenceData in boneWeights: godotBoneIds.set(index, bone.boneId) godotBoneWeights.set(index, bone.fWeight) index += 1 @@ -285,42 +308,38 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: surfaceIndex += 1 if materials[submesh.materialId].spec_index != -1: - meshInstance.set_instance_shader_parameter(&"tex_index_specular", materials[submesh.materialId].spec_index) + meshInstance.set_instance_shader_parameter( + &"tex_index_specular", + materials[submesh.materialId].spec_index, + ) if materials[submesh.materialId].diffuse_index != -1: - meshInstance.set_instance_shader_parameter(&"tex_index_diffuse", materials[submesh.materialId].diffuse_index) + meshInstance.set_instance_shader_parameter( + &"tex_index_diffuse", + materials[submesh.materialId].diffuse_index, + ) if materials[submesh.materialId].scroll_index != -1: - meshInstance.set_instance_shader_parameter(&"scroll_tex_index_diffuse", materials[submesh.materialId].scroll_index) + meshInstance.set_instance_shader_parameter( + &"scroll_tex_index_diffuse", + materials[submesh.materialId].scroll_index, + ) return node -# Information needed to set up a material -# Leave the indices -1 if not using the unit shader -class MaterialDefinition: - var spec_index : int = -1 - var diffuse_index : int = -1 - var scroll_index : int = -1 - var mat : Material - - func _init(mat : Material, diffuse_ind : int = -1, spec_ind : int = -1, scroll_ind : int = -1) -> void: - self.mat = mat - self.diffuse_index = diffuse_ind - self.spec_index = spec_ind - self.scroll_index = scroll_ind -static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionChunk]) -> Array[MaterialDefinition]: - const TEXTURES_PATH : String = "gfx/anims/%s.dds" +static func make_materials(materialDefinitionChunks: Array[MaterialDefinitionChunk]) -> Array[MaterialDefinition]: + const TEXTURES_PATH: String = "gfx/anims/%s.dds" - var materials : Array[MaterialDefinition] = [] + var materials: Array[MaterialDefinition] = [] - for matdef : MaterialDefinitionChunk in materialDefinitionChunks: - var diffuse_name : String - var specular_name : String - var normal_name : String + for matdef: MaterialDefinitionChunk in materialDefinitionChunks: + var diffuse_name: String + var specular_name: String + var normal_name: String # Find important textures - for layer : Layer in matdef.Layers: + for layer: Layer in matdef.Layers: if layer.texture in ["nospec", "unionjacksquare", "test256texture"]: continue @@ -329,13 +348,23 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if not diffuse_name: diffuse_name = layer.texture else: - push_error("Multiple diffuse layers in material: ", diffuse_name, " and ", layer.texture) + push_error( + "Multiple diffuse layers in material: ", + diffuse_name, + " and ", + layer.texture, + ) 3: # specular if not specular_name: specular_name = layer.texture else: - push_error("Multiple specular layers in material: ", specular_name, " and ", layer.texture) + push_error( + "Multiple specular layers in material: ", + specular_name, + " and ", + layer.texture, + ) 4: # currently unused pass @@ -344,7 +373,12 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if not normal_name: normal_name = layer.texture else: - push_error("Multiple normal layers in material: ", normal_name, " and ", layer.texture) + push_error( + "Multiple normal layers in material: ", + normal_name, + " and ", + layer.texture, + ) _: push_error("Unknown layer type: ", layer.mapType) @@ -355,9 +389,9 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if normal_name: push_error("Normal texture present in unit colours material: ", normal_name) - var textures_index_spec : int = added_unit_textures_spec.find(specular_name) + var textures_index_spec: int = added_unit_textures_spec.find(specular_name) if textures_index_spec < 0: - var unit_colours_mask_texture : ImageTexture = AssetManager.get_texture(TEXTURES_PATH % specular_name) + var unit_colours_mask_texture: ImageTexture = AssetManager.get_texture(TEXTURES_PATH % specular_name) if unit_colours_mask_texture: added_unit_textures_spec.push_back(specular_name) @@ -365,18 +399,18 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if len(added_unit_textures_spec) >= MAX_UNIT_TEXTURES: push_error("Colour masks have exceeded max number of textures supported by unit shader!") - const param_texture_nation_colors_mask : StringName = &"texture_nation_colors_mask" + const param_texture_nation_colors_mask: StringName = &"texture_nation_colors_mask" - var colour_masks : Array = unit_shader.get_shader_parameter(param_texture_nation_colors_mask) + var colour_masks: Array = unit_shader.get_shader_parameter(param_texture_nation_colors_mask) colour_masks.push_back(unit_colours_mask_texture) textures_index_spec = len(colour_masks) - 1 unit_shader.set_shader_parameter(param_texture_nation_colors_mask, colour_masks) else: push_error("Failed to load specular texture: ", specular_name) - var textures_index_diffuse : int = added_unit_textures_diffuse.find(diffuse_name) + var textures_index_diffuse: int = added_unit_textures_diffuse.find(diffuse_name) if textures_index_diffuse < 0: - var diffuse_texture : ImageTexture = AssetManager.get_texture(TEXTURES_PATH % diffuse_name) + var diffuse_texture: ImageTexture = AssetManager.get_texture(TEXTURES_PATH % diffuse_name) if diffuse_texture: added_unit_textures_diffuse.push_back(diffuse_name) @@ -384,9 +418,9 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if len(added_unit_textures_diffuse) >= MAX_UNIT_TEXTURES: push_error("Diffuse textures have exceeded max number supported by unit shader!") - const param_texture_diffuse : StringName = &"texture_diffuse" + const param_texture_diffuse: StringName = &"texture_diffuse" - var diffuse_textures : Array = unit_shader.get_shader_parameter(param_texture_diffuse) + var diffuse_textures: Array = unit_shader.get_shader_parameter(param_texture_diffuse) diffuse_textures.push_back(diffuse_texture) textures_index_diffuse = len(diffuse_textures) - 1 unit_shader.set_shader_parameter(param_texture_diffuse, diffuse_textures) @@ -400,7 +434,7 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if specular_name: push_error("Specular texture present in flag material: ", specular_name) - var flag_normal_texture : ImageTexture = AssetManager.get_texture(TEXTURES_PATH % normal_name) + var flag_normal_texture: ImageTexture = AssetManager.get_texture(TEXTURES_PATH % normal_name) if flag_normal_texture: flag_shader.set_shader_parameter(&"texture_normal", flag_normal_texture) else: @@ -415,9 +449,9 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if normal_name: push_error("Normal texture present in scrolling material: ", normal_name) - var scroll_textures_index_diffuse : int = added_scrolling_textures_diffuse.find(diffuse_name) + var scroll_textures_index_diffuse: int = added_scrolling_textures_diffuse.find(diffuse_name) if scroll_textures_index_diffuse < 0: - var diffuse_texture : ImageTexture = AssetManager.get_texture(TEXTURES_PATH % diffuse_name) + var diffuse_texture: ImageTexture = AssetManager.get_texture(TEXTURES_PATH % diffuse_name) if diffuse_texture: added_scrolling_textures_diffuse.push_back(diffuse_name) @@ -425,16 +459,19 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if len(added_scrolling_textures_diffuse) >= MAX_SCROLLING_TEXTURES: push_error("Diffuse textures have exceeded max number supported by scrolling shader!") - const param_scroll_texture_diffuse : StringName = &"scroll_texture_diffuse" + const param_scroll_texture_diffuse: StringName = &"scroll_texture_diffuse" - var scroll_diffuse_textures : Array = scrolling_shader.get_shader_parameter(param_scroll_texture_diffuse) + var scroll_diffuse_textures: Array = scrolling_shader.get_shader_parameter(param_scroll_texture_diffuse) scroll_diffuse_textures.push_back(diffuse_texture) scroll_textures_index_diffuse = len(scroll_diffuse_textures) - 1 - scrolling_shader.set_shader_parameter(param_scroll_texture_diffuse, scroll_diffuse_textures) + scrolling_shader.set_shader_parameter( + param_scroll_texture_diffuse, + scroll_diffuse_textures, + ) - const param_scroll_factor : StringName = &"scroll_factor" + const param_scroll_factor: StringName = &"scroll_factor" - var scroll_factors : Array = scrolling_shader.get_shader_parameter(param_scroll_factor) + var scroll_factors: Array = scrolling_shader.get_shader_parameter(param_scroll_factor) scroll_factors.push_back(SCROLLING_MATERIAL_FACTORS[matdef.name]) scrolling_shader.set_shader_parameter(param_scroll_factor, scroll_factors) else: @@ -447,18 +484,18 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh if specular_name: push_error("Specular texture present in standard material: ", specular_name) - var mat : StandardMaterial3D = StandardMaterial3D.new() + var mat: StandardMaterial3D = StandardMaterial3D.new() mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_DEPTH_PRE_PASS if diffuse_name: - var diffuse_texture : ImageTexture = AssetManager.get_texture(TEXTURES_PATH % diffuse_name) + var diffuse_texture: ImageTexture = AssetManager.get_texture(TEXTURES_PATH % diffuse_name) if diffuse_texture: mat.set_texture(BaseMaterial3D.TEXTURE_ALBEDO, diffuse_texture) else: push_error("Failed to load diffuse texture: ", diffuse_name) if normal_name: - var normal_texture : ImageTexture = AssetManager.get_texture(TEXTURES_PATH % normal_name) + var normal_texture: ImageTexture = AssetManager.get_texture(TEXTURES_PATH % normal_name) if normal_texture: mat.normal_enabled = true mat.set_texture(BaseMaterial3D.TEXTURE_NORMAL, normal_texture) @@ -473,17 +510,21 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh return materials -static func build_armature(hierarchy_chunk : NodeHierarchyChunk) -> Skeleton3D: - var skeleton : Skeleton3D = Skeleton3D.new() + +static func build_armature(hierarchy_chunk: NodeHierarchyChunk) -> Skeleton3D: + var skeleton: Skeleton3D = Skeleton3D.new() skeleton.name = "skeleton" - var cur_id : int = 0 - for node : NodeData in hierarchy_chunk.nodes: + var cur_id: int = 0 + for node: NodeData in hierarchy_chunk.nodes: # godot doesn't like the ':' in bone names unlike paradox skeleton.add_bone(FileAccessUtils.replace_chars(node.name)) skeleton.set_bone_parent(cur_id, node.parentNodeId) #For now assume rest and current position are the same - skeleton.set_bone_rest(cur_id, Transform3D(Basis(node.rotation).scaled(node.scale), node.position)) + skeleton.set_bone_rest( + cur_id, + Transform3D(Basis(node.rotation).scaled(node.scale), node.position), + ) skeleton.set_bone_pose_position(cur_id, node.position) skeleton.set_bone_pose_rotation(cur_id, node.rotation) @@ -491,22 +532,27 @@ static func build_armature(hierarchy_chunk : NodeHierarchyChunk) -> Skeleton3D: cur_id += 1 # conveniently both godot and xac use a parent node id of -1 to represent no parent - # TODO: What is the point of xac having both a transform and separate vec3s for rotation, scale, pos, etc.? + # TODO: What is the point of xac having both a transform and separate vec3s for rotation, scale, + # pos, etc.? # for now, will assume the separate components are the truth # it might be that one is a current position and the other is a rest position? return skeleton -static func build_armature_chunk0(nodeChunks : Array[NodeChunk]) -> Skeleton3D: - var skeleton : Skeleton3D = Skeleton3D.new() + +static func build_armature_chunk0(nodeChunks: Array[NodeChunk]) -> Skeleton3D: + var skeleton: Skeleton3D = Skeleton3D.new() skeleton.name = "skeleton" - var cur_id : int = 0 - for node : NodeChunk in nodeChunks: + var cur_id: int = 0 + for node: NodeChunk in nodeChunks: # godot doesn't like the ':' in bone names unlike paradox skeleton.add_bone(FileAccessUtils.replace_chars(node.name)) skeleton.set_bone_parent(cur_id, node.parentBone) # For now assume rest and current position are the same - skeleton.set_bone_rest(cur_id, Transform3D(Basis(node.rotation).scaled(node.scale), node.position)) + skeleton.set_bone_rest( + cur_id, + Transform3D(Basis(node.rotation).scaled(node.scale), node.position), + ) skeleton.set_bone_pose_position(cur_id, node.position) skeleton.set_bone_pose_rotation(cur_id, node.rotation) @@ -515,15 +561,19 @@ static func build_armature_chunk0(nodeChunks : Array[NodeChunk]) -> Skeleton3D: cur_id += 1 return skeleton -static func readHeader(file : FileAccess) -> void: - var magic_bytes : PackedByteArray = [file.get_8(), file.get_8(), file.get_8(), file.get_8()] - var magic : String = magic_bytes.get_string_from_ascii() - var version : String = "%d.%d" % [file.get_8(), file.get_8()] - var bBigEndian : bool = file.get_8() - var multiplyOrder : int = file.get_8() + +static func readHeader(file: FileAccess) -> void: + var magic_bytes: PackedByteArray = [file.get_8(), file.get_8(), file.get_8(), file.get_8()] + var magic: String = magic_bytes.get_string_from_ascii() + var version: String = "%d.%d" % [file.get_8(), file.get_8()] + var bBigEndian: bool = file.get_8() + var multiplyOrder: int = file.get_8() + + #print(magic, ", version: ", version, ", bigEndian: ", bBigEndian, " multiplyOrder: ", multiplyOrder) -static func readMetaDataChunk(file : FileAccess) -> MetadataChunk: + +static func readMetaDataChunk(file: FileAccess) -> MetadataChunk: return MetadataChunk.new( file.get_32(), FileAccessUtils.read_int32(file), file.get_8(), file.get_8(), Vector2i(file.get_8(), file.get_8()), file.get_float(), @@ -531,29 +581,288 @@ static func readMetaDataChunk(file : FileAccess) -> MetadataChunk: FileAccessUtils.read_xac_str(file), FileAccessUtils.read_xac_str(file) ) + +# HIERARCHY + + +static func readNodeData(file: FileAccess) -> NodeData: + return NodeData.new( + FileAccessUtils.read_quat(file), FileAccessUtils.read_quat(file), + FileAccessUtils.read_pos(file), FileAccessUtils.read_vec3(file), + FileAccessUtils.read_vec3(file), # 3x unused floats + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), + FileAccessUtils.read_mat4x4(file), file.get_float(), FileAccessUtils.read_xac_str(file) + ) + + +static func readNodeHierarchyChunk(file: FileAccess) -> NodeHierarchyChunk: + var numNodes: int = FileAccessUtils.read_int32(file) + var numRootNodes: int = FileAccessUtils.read_int32(file) + var nodes: Array[NodeData] = [] + for i: int in numNodes: + nodes.push_back(readNodeData(file)) + return NodeHierarchyChunk.new(numNodes, numRootNodes, nodes) + + +# MATERIAL TOTALS + + +static func readMaterialTotalsChunk(file: FileAccess) -> MaterialTotalsChunk: + return MaterialTotalsChunk.new( + FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file), + ) + + +# MATERIAL DEFINITION + + +static func readLayer(file: FileAccess, isV1: bool) -> Layer: + var unknown: Vector3i + if isV1: + unknown = Vector3i( + FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file) + ) + var layer: Layer = Layer.new( + file.get_float(), file.get_float(), file.get_float(), file.get_float(), + file.get_float(), file.get_float(), FileAccessUtils.read_int16(file), + file.get_8(), file.get_8(), FileAccessUtils.read_xac_str(file), unknown + ) + return layer + + +# TODO: Might want to change this from vec4d to colours where appropriate + + +static func readMaterialDefinitionChunk(file: FileAccess, isV1: bool) -> MaterialDefinitionChunk: + var chunk: MaterialDefinitionChunk = MaterialDefinitionChunk.new( + FileAccessUtils.read_vec4(file), FileAccessUtils.read_vec4(file), + FileAccessUtils.read_vec4(file), FileAccessUtils.read_vec4(file), + file.get_float(), file.get_float(), file.get_float(), file.get_float(), + file.get_8(), file.get_8(), file.get_8(), file.get_8(), + FileAccessUtils.read_xac_str(file) + ) + var layers: Array[Layer] = [] + for i: int in chunk.numLayers: + layers.push_back(readLayer(file, isV1)) + chunk.setLayers(layers) + return chunk + + +# MESH + + +static func readVerticesAttribute(file: FileAccess, numVerts: int) -> VerticesAttribute: + var vertAttrib: VerticesAttribute = VerticesAttribute.new( + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), + file.get_8(), file.get_8(), Vector2i(file.get_8(), file.get_8())) + var data: Variant + match vertAttrib.type: + 0: # position + data = PackedVector3Array() + for i: int in numVerts: + data.push_back(FileAccessUtils.read_pos(file)) + 1: # normals + data = PackedVector3Array() + for i: int in numVerts: + data.push_back(FileAccessUtils.read_pos(file)) + 2: # tangents + data = [] as Array[Vector4] + for i: int in numVerts: + var tangent: Vector4 = FileAccessUtils.read_vec4(file) + #tangent.w *= -1 + data.push_back(tangent) + 3: # uvs + data = PackedVector2Array() + for i: int in numVerts: + data.push_back(FileAccessUtils.read_vec2(file)) + 4: # 32bit colors + data = PackedColorArray() + for i: int in numVerts: + data.push_back(FileAccessUtils.read_Color32(file)) + 5: # influence range indices + data = PackedInt64Array() + for i: int in numVerts: + data.push_back(file.get_32()) + 6: # 128bit colors + data = PackedColorArray() + for i: int in numVerts: + data.push_back(FileAccessUtils.read_Color128(file)) + _: + push_error("INVALID XAC VERTATTRIBUTE TYPE %d" % vertAttrib.type) + vertAttrib.setData(data) + return vertAttrib + + +static func readSubMesh(file: FileAccess) -> SubMesh: + var subMesh: SubMesh = SubMesh.new( + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file) + ) + var relIndices: PackedInt32Array = PackedInt32Array() + var boneIds: PackedInt32Array = PackedInt32Array() + for i: int in subMesh.numIndices: + relIndices.push_back(FileAccessUtils.read_int32(file)) + for i: int in subMesh.numBones: + boneIds.push_back(FileAccessUtils.read_int32(file)) + subMesh.setBoneIds(boneIds) + subMesh.setRelIndices(relIndices) + return subMesh + + +static func readMeshChunk(file: FileAccess) -> MeshChunk: + var mesh: MeshChunk = MeshChunk.new( + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), file.get_8(), + Vector3i(file.get_8(), file.get_8(), file.get_8()) + ) + var vertAttribs: Array[VerticesAttribute] = [] + var submeshes: Array[SubMesh] = [] + for i: int in mesh.numAttribLayers: + vertAttribs.push_back(readVerticesAttribute(file, mesh.numVertices)) + for i: int in mesh.numSubMeshes: + submeshes.push_back(readSubMesh(file)) + mesh.setVerticesAttributes(vertAttribs) + mesh.setSubMeshes(submeshes) + return mesh + + +# SKINNING + + +static func readInfluenceData(file: FileAccess) -> InfluenceData: + return InfluenceData.new( + file.get_float(), + FileAccessUtils.read_int16(file), + Vector2i(file.get_8(), file.get_8()), + ) + + +# For some reason influenceRange isn't being loaded, needs investigation +# Weird data on the flag + + +static func readInfluenceRange(file: FileAccess) -> InfluenceRange: + return InfluenceRange.new(FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file)) + + +static func readSkinningChunk( + file: FileAccess, + meshChunks: Array[MeshChunk], + isV2: bool, +) -> SkinningChunk: + var skinning: SkinningChunk = null + skinning = SkinningChunk.new( + FileAccessUtils.read_int32(file), -1 if isV2 else FileAccessUtils.read_int32(file), + FileAccessUtils.read_int32(file), file.get_8(), Vector3i(file.get_8(), file.get_8(), file.get_8()) + ) + var influenceData: Array[InfluenceData] = [] + var influenceRange: Array[InfluenceRange] = [] + for i: int in skinning.numInfluences: + influenceData.push_back(readInfluenceData(file)) + # search the list of mesh chunks for the one which matches IsCollisionMesh and NodeId? + # documentation is a little unclear about this (lists the mesh accessing by node, which isn't + # possible) + for chunk: MeshChunk in meshChunks: + if (chunk.nodeId == skinning.nodeId + and chunk.bIsCollisionMesh == skinning.bIsForCollisionMesh): + for i: int in chunk.numInfluenceRanges: + influenceRange.push_back(readInfluenceRange(file)) + break + skinning.setInfluenceData(influenceData) + skinning.setInfluenceRange(influenceRange) + return skinning + + +# TODO: What is chunk type6, figure out what the plausible datatypes are +# Currently, since XACs tend to use int32s and float (32bit) + strings, and no strings +# are evident in chunk type 6, to load these as arrays of int32 + + +static func readChunkType6(file: FileAccess) -> ChunkType6: + var intArr: PackedInt32Array = [] + var floatArr: PackedFloat32Array = [] + for i: int in 12: + intArr.push_back(FileAccessUtils.read_int32(file)) + for i: int in 9: + floatArr.push_back(file.get_float()) + return ChunkType6.new(intArr, floatArr, FileAccessUtils.read_int32(file)) + + +# Chunk type 0x0 + + +static func readNodeChunk(file: FileAccess) -> NodeChunk: + return NodeChunk.new( + FileAccessUtils.read_quat(file), FileAccessUtils.read_quat(file), + FileAccessUtils.read_pos(file), FileAccessUtils.read_vec3(file), + FileAccessUtils.read_vec3(file), # unused 3 floats + FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), # -1-1 + # 17 x 32bit unknowns, likely matrix and some other info + # last 32bit unknown is likely fImportanceFactor, (float 1f). Rest is likely matrix + (func() -> PackedInt32Array: + var array: PackedInt32Array = PackedInt32Array() + for i: int in 17: + array.push_back(FileAccessUtils.read_int32(file)) + return array).call(), + FileAccessUtils.read_xac_str(file) + ) + + +static func readChunkTypeUnknown(file: FileAccess, length: int) -> ChunkTypeUnknown: + return ChunkTypeUnknown.new(file.get_buffer(length)) + + +# Information needed to set up a material +# Leave the indices -1 if not using the unit shader + + +class MaterialDefinition: + var spec_index: int = -1 + var diffuse_index: int = -1 + var scroll_index: int = -1 + var mat: Material + + func _init( + mat: Material, + diffuse_ind: int = -1, + spec_ind: int = -1, + scroll_ind: int = -1, + ) -> void: + self.mat = mat + self.diffuse_index = diffuse_ind + self.spec_index = spec_ind + self.scroll_index = scroll_ind + + class MetadataChunk: - var repositionMask : int # uint32 - var repositioningNode : int # int32 - var exporterMajorVersion : int # byte - var exporterMinorVersion : int # byte - var unused : Vector2i # 2x byte - var retargetRootOffset : float - var sourceApp : String - var origFileName : String - var exportDate : String - var actorName : String + var repositionMask: int # uint32 + var repositioningNode: int # int32 + var exporterMajorVersion: int # byte + var exporterMinorVersion: int # byte + var unused: Vector2i # 2x byte + var retargetRootOffset: float + var sourceApp: String + var origFileName: String + var exportDate: String + var actorName: String func _init( - repMask : int, - repNode : int, - exMajV : int, - exMinV : int, - un : Vector2i, - retRootOff : float, - sourceApp : String, - origFile : String, - date : String, - actorName : String + repMask: int, + repNode: int, + exMajV: int, + exMinV: int, + un: Vector2i, + retRootOff: float, + sourceApp: String, + origFile: String, + date: String, + actorName: String ) -> void: self.repositionMask = repMask self.repositioningNode = repNode @@ -572,47 +881,36 @@ class MetadataChunk: print("Actor: %s, retargetRootOffset: %d, repositionMask: %d, repositionNode: %d" % [actorName, retargetRootOffset, repositionMask, repositioningNode]) -# HIERARCHY - -static func readNodeData(file : FileAccess) -> NodeData: - return NodeData.new( - FileAccessUtils.read_quat(file), FileAccessUtils.read_quat(file), - FileAccessUtils.read_pos(file), FileAccessUtils.read_vec3(file), - FileAccessUtils.read_vec3(file), # 3x unused floats - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), - FileAccessUtils.read_mat4x4(file), file.get_float(), FileAccessUtils.read_xac_str(file) - ) class NodeData: - var rotation : Quaternion - var scaleRotation : Quaternion - var position : Vector3 - var scale : Vector3 - var unused : Vector3 # 3x unused floats - var unknown : int # int32 - var unknown2 : int # int32 - var parentNodeId : int # int32 - var numChildNodes : int # int32 - var bIncludeInBoundsCalc : bool # int32 - var transform : FileAccessUtils.xac_mat4x4 - var fImportanceFactor : float - var name : String + var rotation: Quaternion + var scaleRotation: Quaternion + var position: Vector3 + var scale: Vector3 + var unused: Vector3 # 3x unused floats + var unknown: int # int32 + var unknown2: int # int32 + var parentNodeId: int # int32 + var numChildNodes: int # int32 + var bIncludeInBoundsCalc: bool # int32 + var transform: FileAccessUtils.xac_mat4x4 + var fImportanceFactor: float + var name: String func _init( - rot : Quaternion, - scaleRot : Quaternion, - pos : Vector3, - scale : Vector3, - unused : Vector3, - unknown : int, - unknown2 : int, - parentNodeId : int, - numChildNodes : int, - incInBoundsCalc : bool, - transform : FileAccessUtils.xac_mat4x4, - fImportanceFactor : float, - name : String + rot: Quaternion, + scaleRot: Quaternion, + pos: Vector3, + scale: Vector3, + unused: Vector3, + unknown: int, + unknown2: int, + parentNodeId: int, + numChildNodes: int, + incInBoundsCalc: bool, + transform: FileAccessUtils.xac_mat4x4, + fImportanceFactor: float, + name: String ) -> void: self.rotation = rot self.scaleRotation = scaleRot @@ -632,40 +930,29 @@ class NodeData: print("\tparentNodeId: %d,\t numChildNodes: %d,\t Node Name: %s" % [parentNodeId, numChildNodes, name]) print("\tunused %s,%s,%s, -1: %s, -1: %s" % [unused[0], unused[1], unused[2], unknown, unknown2]) -static func readNodeHierarchyChunk(file : FileAccess) -> NodeHierarchyChunk: - var numNodes : int = FileAccessUtils.read_int32(file) - var numRootNodes : int = FileAccessUtils.read_int32(file) - var nodes : Array[NodeData] = [] - for i : int in numNodes: - nodes.push_back(readNodeData(file)) - return NodeHierarchyChunk.new(numNodes, numRootNodes, nodes) class NodeHierarchyChunk: - var numNodes : int # int32 - var numRootNodes : int # int32 - var nodes : Array[NodeData] + var numNodes: int # int32 + var numRootNodes: int # int32 + var nodes: Array[NodeData] - func _init(numNodes : int, numRootNodes : int, nodes : Array[NodeData]) -> void: + func _init(numNodes: int, numRootNodes: int, nodes: Array[NodeData]) -> void: self.numNodes = numNodes self.numRootNodes = numRootNodes self.nodes = nodes func debugPrint() -> void: print("numNodes: %d, numRootNodes: %d" % [numNodes, numRootNodes]) - for node : NodeData in nodes: + for node: NodeData in nodes: node.debugPrint() -# MATERIAL TOTALS - -static func readMaterialTotalsChunk(file : FileAccess) -> MaterialTotalsChunk: - return MaterialTotalsChunk.new(FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file)) class MaterialTotalsChunk: - var numTotalMaterials : int # int32 - var numStandMaterials : int # int32 - var numFxMaterials : int # int32 + var numTotalMaterials: int # int32 + var numStandMaterials: int # int32 + var numFxMaterials: int # int32 - func _init(numTotalMaterials : int, numStandMaterials : int, numFxMaterials : int) -> void: + func _init(numTotalMaterials: int, numStandMaterials: int, numFxMaterials: int) -> void: self.numTotalMaterials = numTotalMaterials self.numStandMaterials = numStandMaterials self.numFxMaterials = numFxMaterials @@ -674,48 +961,32 @@ class MaterialTotalsChunk: print("totalMaterials: %d, standardMaterials: %d, fxMaterials: %d" % [numTotalMaterials, numStandMaterials, numFxMaterials]) -# MATERIAL DEFINITION - -static func readLayer(file : FileAccess, isV1 : bool) -> Layer: - var unknown : Vector3i - if isV1: - unknown = Vector3i( - FileAccessUtils.read_int32(file), - FileAccessUtils.read_int32(file), - FileAccessUtils.read_int32(file) - ) - var layer : Layer = Layer.new( - file.get_float(), file.get_float(), file.get_float(), file.get_float(), - file.get_float(), file.get_float(), FileAccessUtils.read_int16(file), - file.get_8(), file.get_8(), FileAccessUtils.read_xac_str(file), unknown - ) - return layer class Layer: - var amount : float - var uOffset : float - var vOffset : float - var uTiling : float - var vTiling : float - var rotInRad : float - var matId : int # int16 - var mapType : int # byte - var unused : int # byte - var texture : String - var unknown : Vector3i # Unknown 3 integers present in v1 of the chunk + var amount: float + var uOffset: float + var vOffset: float + var uTiling: float + var vTiling: float + var rotInRad: float + var matId: int # int16 + var mapType: int # byte + var unused: int # byte + var texture: String + var unknown: Vector3i # Unknown 3 integers present in v1 of the chunk func _init( - amount : float, - uOffset : float, - vOffset : float, - uTiling : float, - vTiling : float, - rotInRad : float, - matId : int, - mapType : int, - unused : int, - texture : String, - unknown : Vector3i + amount: float, + uOffset: float, + vOffset: float, + uTiling: float, + vTiling: float, + rotInRad: float, + matId: int, + mapType: int, + unused: int, + texture: String, + unknown: Vector3i ) -> void: self.amount = amount self.uOffset = uOffset @@ -737,51 +1008,37 @@ class Layer: func is_specular() -> bool: return mapType == 3 -# TODO: Might want to change this from vec4d to colours where appropriate -static func readMaterialDefinitionChunk(file : FileAccess, isV1 : bool) -> MaterialDefinitionChunk: - var chunk : MaterialDefinitionChunk = MaterialDefinitionChunk.new( - FileAccessUtils.read_vec4(file), FileAccessUtils.read_vec4(file), - FileAccessUtils.read_vec4(file), FileAccessUtils.read_vec4(file), - file.get_float(), file.get_float(), file.get_float(), file.get_float(), - file.get_8(), file.get_8(), file.get_8(), file.get_8(), - FileAccessUtils.read_xac_str(file) - ) - var layers : Array[Layer] = [] - for i : int in chunk.numLayers: - layers.push_back(readLayer(file, isV1)) - chunk.setLayers(layers) - return chunk class MaterialDefinitionChunk: - var ambientColor : Vector4 - var diffuseColor : Vector4 - var specularColor : Vector4 - var emissiveColor : Vector4 - var shine : float - var shineStrength : float - var opacity : float - var ior : float - var bDoubleSided : bool # byte - var bWireframe : bool # byte - var unused : int # 1 byte - var numLayers : int # byte - var name : String - var Layers : Array[Layer] + var ambientColor: Vector4 + var diffuseColor: Vector4 + var specularColor: Vector4 + var emissiveColor: Vector4 + var shine: float + var shineStrength: float + var opacity: float + var ior: float + var bDoubleSided: bool # byte + var bWireframe: bool # byte + var unused: int # 1 byte + var numLayers: int # byte + var name: String + var Layers: Array[Layer] func _init( - ambientColor : Vector4, - diffuseColor : Vector4, - specularColor : Vector4, - emissiveColor : Vector4, - shine : float, - shineStrength : float, - opacity : float, - ior : float, - bDoubleSided : bool, - bWireframe : bool, - unused : int, - numLayers : int, - name : String + ambientColor: Vector4, + diffuseColor: Vector4, + specularColor: Vector4, + emissiveColor: Vector4, + shine: float, + shineStrength: float, + opacity: float, + ior: float, + bDoubleSided: bool, + bWireframe: bool, + unused: int, + numLayers: int, + name: String ) -> void: self.ambientColor = ambientColor self.diffuseColor = diffuseColor @@ -797,13 +1054,13 @@ class MaterialDefinitionChunk: self.numLayers = numLayers self.name = name - func setLayers(layers : Array[Layer]) -> void: + func setLayers(layers: Array[Layer]) -> void: self.Layers = layers # Specular textures are used for country-specific unit colours, # which need a UnitModel to be set through func has_specular() -> bool: - for layer : Layer in Layers: + for layer: Layer in Layers: if layer.is_specular(): return true return false @@ -811,67 +1068,27 @@ class MaterialDefinitionChunk: func debugPrint() -> void: print("Material Name: %s, num layers: %d, doubleSided %s" % [name, numLayers, bDoubleSided]) print("\tshine:%s\tshineStrength:%s\t,opacity:%s,\tior:%s,\tunused:%s" % [shine, shineStrength, opacity, ior, unused]) - for layer : Layer in Layers: + for layer: Layer in Layers: layer.debugPrint() -# MESH - -static func readVerticesAttribute(file : FileAccess, numVerts : int) -> VerticesAttribute: - var vertAttrib : VerticesAttribute = VerticesAttribute.new( - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), - file.get_8(), file.get_8(), Vector2i(file.get_8(), file.get_8())) - var data : Variant - match vertAttrib.type: - 0: # position - data = PackedVector3Array() - for i : int in numVerts: - data.push_back(FileAccessUtils.read_pos(file)) - 1: # normals - data = PackedVector3Array() - for i : int in numVerts: - data.push_back(FileAccessUtils.read_pos(file)) - 2: # tangents - data = [] as Array[Vector4] - for i : int in numVerts: - var tangent : Vector4 = FileAccessUtils.read_vec4(file) - #tangent.w *= -1 - data.push_back(tangent) - 3: # uvs - data = PackedVector2Array() - for i : int in numVerts: - data.push_back(FileAccessUtils.read_vec2(file)) - 4: # 32bit colors - data = PackedColorArray() - for i : int in numVerts: - data.push_back(FileAccessUtils.read_Color32(file)) - 5: # influence range indices - data = PackedInt64Array() - for i : int in numVerts: - data.push_back(file.get_32()) - 6: # 128bit colors - data = PackedColorArray() - for i : int in numVerts: - data.push_back(FileAccessUtils.read_Color128(file)) - _: - push_error("INVALID XAC VERTATTRIBUTE TYPE %d" % vertAttrib.type) - vertAttrib.setData(data) - return vertAttrib # mesh has one of these for each vertex property (position, normals, etc) + + class VerticesAttribute: - var type : int # int32 - var attribSize : int # int32 - var bKeepOriginals : bool # byte - var bIsScaleFactor : bool # byte - var pad : Vector2i # 2x byte - var data : Variant # numVerts * attribSize + var type: int # int32 + var attribSize: int # int32 + var bKeepOriginals: bool # byte + var bIsScaleFactor: bool # byte + var pad: Vector2i # 2x byte + var data: Variant # numVerts * attribSize func _init( - type : int, - attribSize : int, - bKeepOriginals : bool, - bIsScaleFactor : bool, - pad : Vector2i + type: int, + attribSize: int, + bKeepOriginals: bool, + bIsScaleFactor: bool, + pad: Vector2i ) -> void: self.type = type self.attribSize = attribSize @@ -879,11 +1096,11 @@ class VerticesAttribute: self.bIsScaleFactor = bIsScaleFactor self.pad = pad - func setData(data : Variant) -> void: + func setData(data: Variant) -> void: self.data = data func debugPrint() -> void: - var typeStr : String + var typeStr: String match type: 0: typeStr = "Positions" @@ -905,83 +1122,53 @@ class VerticesAttribute: [attribSize, bKeepOriginals, bIsScaleFactor, typeStr]) print("\tpad: %s" % pad) -static func readSubMesh(file : FileAccess) -> SubMesh: - var subMesh : SubMesh = SubMesh.new( - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file) - ) - var relIndices : PackedInt32Array = PackedInt32Array() - var boneIds : PackedInt32Array = PackedInt32Array() - for i : int in subMesh.numIndices: - relIndices.push_back(FileAccessUtils.read_int32(file)) - for i : int in subMesh.numBones: - boneIds.push_back(FileAccessUtils.read_int32(file)) - subMesh.setBoneIds(boneIds) - subMesh.setRelIndices(relIndices) - return subMesh class SubMesh: - var numIndices : int # int32 - var numVertices : int # int32 - var materialId : int # int32 - var numBones : int # int32 - var relativeIndices : PackedInt32Array # int32 [numIndices] - var boneIds : PackedInt32Array # int32 [numBones], unused - - func _init(numIndices : int, numVertices : int, materialId : int, numBones : int) -> void: + var numIndices: int # int32 + var numVertices: int # int32 + var materialId: int # int32 + var numBones: int # int32 + var relativeIndices: PackedInt32Array # int32 [numIndices] + var boneIds: PackedInt32Array # int32 [numBones], unused + + func _init(numIndices: int, numVertices: int, materialId: int, numBones: int) -> void: self.numIndices = numIndices self.numVertices = numVertices self.materialId = materialId self.numBones = numBones - func setRelIndices(relativeIndices : PackedInt32Array) -> void: + func setRelIndices(relativeIndices: PackedInt32Array) -> void: self.relativeIndices = relativeIndices - func setBoneIds(boneIds : PackedInt32Array) -> void: + func setBoneIds(boneIds: PackedInt32Array) -> void: self.boneIds = boneIds func debugPrint() -> void: print("\tSubMesh:\t numIndices:%d,\t numVerts:%d,\t matId:%d,\t numBones:%d" % [numIndices, numVertices, materialId, numBones]) -static func readMeshChunk(file : FileAccess) -> MeshChunk: - var mesh : MeshChunk = MeshChunk.new( - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), file.get_8(), - Vector3i(file.get_8(), file.get_8(), file.get_8()) - ) - var vertAttribs : Array[VerticesAttribute] = [] - var submeshes : Array[SubMesh] = [] - for i : int in mesh.numAttribLayers: - vertAttribs.push_back(readVerticesAttribute(file,mesh.numVertices)) - for i : int in mesh.numSubMeshes: - submeshes.push_back(readSubMesh(file)) - mesh.setVerticesAttributes(vertAttribs) - mesh.setSubMeshes(submeshes) - return mesh class MeshChunk: - var nodeId : int # int32 - var numInfluenceRanges : int # int32 - var numVertices : int # int32 - var numIndices : int # int32 - var numSubMeshes : int # int32 - var numAttribLayers : int # int32 - var bIsCollisionMesh : bool # byte - var pad : Vector3i # 3x byte - var VerticesAttributes : Array[VerticesAttribute] - var SubMeshes : Array[SubMesh] + var nodeId: int # int32 + var numInfluenceRanges: int # int32 + var numVertices: int # int32 + var numIndices: int # int32 + var numSubMeshes: int # int32 + var numAttribLayers: int # int32 + var bIsCollisionMesh: bool # byte + var pad: Vector3i # 3x byte + var VerticesAttributes: Array[VerticesAttribute] + var SubMeshes: Array[SubMesh] func _init( - nodeId : int, - numInfluenceRanges : int, - numVertices : int, - numIndices : int, - numSubMeshes : int, - numAttribLayers : int, - bIsCollisionMesh : bool, - pad : Vector3i + nodeId: int, + numInfluenceRanges: int, + numVertices: int, + numIndices: int, + numSubMeshes: int, + numAttribLayers: int, + bIsCollisionMesh: bool, + pad: Vector3i ) -> void: self.nodeId = nodeId self.numInfluenceRanges = numInfluenceRanges @@ -992,10 +1179,10 @@ class MeshChunk: self.bIsCollisionMesh = bIsCollisionMesh self.pad = pad - func setVerticesAttributes(VerticesAttributes : Array[VerticesAttribute]) -> void: + func setVerticesAttributes(VerticesAttributes: Array[VerticesAttribute]) -> void: self.VerticesAttributes = VerticesAttributes - func setSubMeshes(SubMeshes : Array[SubMesh]) -> void: + func setSubMeshes(SubMeshes: Array[SubMesh]) -> void: self.SubMeshes = SubMeshes func debugPrint() -> void: @@ -1003,19 +1190,16 @@ class MeshChunk: [nodeId, numVertices, numSubMeshes, numAttribLayers, bIsCollisionMesh]) for vertAttrib in VerticesAttributes: vertAttrib.debugPrint() - for subMesh : SubMesh in SubMeshes: + for subMesh: SubMesh in SubMeshes: subMesh.debugPrint() -# SKINNING -static func readInfluenceData(file : FileAccess) -> InfluenceData: - return InfluenceData.new(file.get_float(), FileAccessUtils.read_int16(file), Vector2i(file.get_8(), file.get_8())) class InfluenceData: - var fWeight : float # (0..1) - var boneId : int # int16 - var pad : Vector2i # 2x byte + var fWeight: float # (0..1) + var boneId: int # int16 + var pad: Vector2i # 2x byte - func _init(fWeight : float, boneId : int, pad : Vector2i) -> void: + func _init(fWeight: float, boneId: int, pad: Vector2i) -> void: self.fWeight = fWeight self.boneId = boneId self.pad = pad @@ -1023,59 +1207,34 @@ class InfluenceData: func debugPrint() -> void: print("\tInfluenceData:\t boneId: %d,\t Weight: %s" % [boneId, fWeight]) -# For some reason influenceRange isn't being loaded, needs investigation -# Weird data on the flag - -static func readInfluenceRange(file : FileAccess) -> InfluenceRange: - return InfluenceRange.new(FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file)) class InfluenceRange: - var firstInfluenceIndex : int # int32 - var numInfluences : int # int32 + var firstInfluenceIndex: int # int32 + var numInfluences: int # int32 - func _init(firstInfluenceIndex : int, numInfluences : int) -> void: + func _init(firstInfluenceIndex: int, numInfluences: int) -> void: self.firstInfluenceIndex = firstInfluenceIndex self.numInfluences = numInfluences func debugPrint() -> void: print("\tInfluenceRange:\t firstIndex: %d,\t numInfluences: %d" % [firstInfluenceIndex, numInfluences]) -static func readSkinningChunk(file : FileAccess, meshChunks : Array[MeshChunk], isV2 : bool) -> SkinningChunk: - var skinning : SkinningChunk = null - skinning = SkinningChunk.new( - FileAccessUtils.read_int32(file), -1 if isV2 else FileAccessUtils.read_int32(file), - FileAccessUtils.read_int32(file), file.get_8(), Vector3i(file.get_8(), file.get_8(), file.get_8()) - ) - var influenceData : Array[InfluenceData] = [] - var influenceRange : Array[InfluenceRange] = [] - for i : int in skinning.numInfluences: - influenceData.push_back(readInfluenceData(file)) - # search the list of mesh chunks for the one which matches IsCollisionMesh and NodeId? - # documentation is a little unclear about this (lists the mesh accessing by node, which isn't possible) - for chunk : MeshChunk in meshChunks: - if chunk.nodeId == skinning.nodeId and chunk.bIsCollisionMesh == skinning.bIsForCollisionMesh: - for i : int in chunk.numInfluenceRanges: - influenceRange.push_back(readInfluenceRange(file)) - break - skinning.setInfluenceData(influenceData) - skinning.setInfluenceRange(influenceRange) - return skinning class SkinningChunk: - var nodeId : int # int32 - var numLocalBones : int # int32, of bones in the influence data - var numInfluences : int # int32 - var bIsForCollisionMesh : bool # byte boolean - var pad : Vector3i # 3x pad - var influenceData : Array[InfluenceData] - var influenceRange : Array[InfluenceRange] + var nodeId: int # int32 + var numLocalBones: int # int32, of bones in the influence data + var numInfluences: int # int32 + var bIsForCollisionMesh: bool # byte boolean + var pad: Vector3i # 3x pad + var influenceData: Array[InfluenceData] + var influenceRange: Array[InfluenceRange] func _init( - nodeId : int, - numLocalBones : int, - numInfluences : int, - bIsForCollisionMesh : bool, - pad : Vector3i + nodeId: int, + numLocalBones: int, + numInfluences: int, + bIsForCollisionMesh: bool, + pad: Vector3i ) -> void: self.nodeId = nodeId self.numLocalBones = numLocalBones @@ -1083,39 +1242,31 @@ class SkinningChunk: self.bIsForCollisionMesh = bIsForCollisionMesh self.pad = pad - func setInfluenceData(influenceData : Array[InfluenceData]) -> void: + func setInfluenceData(influenceData: Array[InfluenceData]) -> void: self.influenceData = influenceData - func setInfluenceRange(influenceRange : Array[InfluenceRange]) -> void: + func setInfluenceRange(influenceRange: Array[InfluenceRange]) -> void: self.influenceRange = influenceRange func debugPrint() -> void: print("Skinning: nodeId:%d, numInfluencedBones: %d, numInfluences: %d, CollisionMesh?: %d" % [nodeId, numLocalBones, numInfluences, bIsForCollisionMesh]) - for infDat : InfluenceData in influenceData: + for infDat: InfluenceData in influenceData: infDat.debugPrint() - for infRange : InfluenceRange in influenceRange: + for infRange: InfluenceRange in influenceRange: infRange.debugPrint() -# TODO: What is chunk type6, figure out what the plausible datatypes are -# Currently, since XACs tend to use int32s and float (32bit) + strings, and no strings -# are evident in chunk type 6, to load these as arrays of int32 - -static func readChunkType6(file : FileAccess) -> ChunkType6: - var intArr : PackedInt32Array = [] - var floatArr : PackedFloat32Array = [] - for i : int in 12: - intArr.push_back(FileAccessUtils.read_int32(file)) - for i : int in 9: - floatArr.push_back(file.get_float()) - return ChunkType6.new(intArr, floatArr, FileAccessUtils.read_int32(file)) class ChunkType6: - var unknown : PackedInt32Array - var unknown_floats : PackedFloat32Array - var maybe_node_id : int + var unknown: PackedInt32Array + var unknown_floats: PackedFloat32Array + var maybe_node_id: int - func _init(unknown : PackedInt32Array, unknown_floats : PackedFloat32Array, maybe_node_id : int) -> void: + func _init( + unknown: PackedInt32Array, + unknown_floats: PackedFloat32Array, + maybe_node_id: int, + ) -> void: self.unknown = unknown self.unknown_floats = unknown_floats self.maybe_node_id = maybe_node_id @@ -1123,44 +1274,28 @@ class ChunkType6: func debugPrint() -> void: print("\tUnknown: %s\n\tFloats: %s\n\tPerhaps NodeId? %s" % [self.unknown, self.unknown_floats, self.maybe_node_id]) -# Chunk type 0x0 -static func readNodeChunk(file : FileAccess) -> NodeChunk: - return NodeChunk.new( - FileAccessUtils.read_quat(file), FileAccessUtils.read_quat(file), - FileAccessUtils.read_pos(file), FileAccessUtils.read_vec3(file), - FileAccessUtils.read_vec3(file), # unused 3 floats - FileAccessUtils.read_int32(file), FileAccessUtils.read_int32(file), #-1-1 - # 17 x 32bit unknowns, likely matrix and some other info - # last 32bit unknown is likely fImportanceFactor, (float 1f). Rest is likely matrix - (func() -> PackedInt32Array: - var array : PackedInt32Array = PackedInt32Array() - for i : int in 17: - array.push_back(FileAccessUtils.read_int32(file)) - return array).call(), - FileAccessUtils.read_xac_str(file) - ) class NodeChunk: - var rotation : Quaternion - var scaleRotation : Quaternion - var position : Vector3 - var scale : Vector3 - var unused : Vector3 # 3x unused floats - var unknown : int # int32 - var parentBone : int # int32 - var unknown2 : PackedInt32Array # 17x int32 sized numbers - var name : String + var rotation: Quaternion + var scaleRotation: Quaternion + var position: Vector3 + var scale: Vector3 + var unused: Vector3 # 3x unused floats + var unknown: int # int32 + var parentBone: int # int32 + var unknown2: PackedInt32Array # 17x int32 sized numbers + var name: String func _init( - rot : Quaternion, - scaleRot : Quaternion, - pos : Vector3, - scale : Vector3, - unused : Vector3, - unknown : int, - parentBone : int, - unknown2 : PackedInt32Array, - name : String + rot: Quaternion, + scaleRot: Quaternion, + pos: Vector3, + scale: Vector3, + unused: Vector3, + unknown: int, + parentBone: int, + unknown2: PackedInt32Array, + name: String ) -> void: self.rotation = rot self.scaleRotation = scaleRot @@ -1177,13 +1312,11 @@ class NodeChunk: print("\tunused (%s) unknown: %s" % [unused, unknown]) print("\tunknown after -1-1: %s" % unknown2) -static func readChunkTypeUnknown(file : FileAccess, length : int) -> ChunkTypeUnknown: - return ChunkTypeUnknown.new(file.get_buffer(length)) class ChunkTypeUnknown: - var unknown : PackedByteArray + var unknown: PackedByteArray - func _init(unknown : PackedByteArray) -> void: + func _init(unknown: PackedByteArray) -> void: self.unknown = unknown func debugPrint() -> void: diff --git a/game/src/Model/XSMLoader.gd b/game/src/Model/XSMLoader.gd index e1706ae6..731b32ed 100644 --- a/game/src/Model/XSMLoader.gd +++ b/game/src/Model/XSMLoader.gd @@ -1,13 +1,17 @@ class_name XSMLoader +const LOAD_FAILED_MARKER: StringName = &"XSM LOAD FAILED" +const SKELETON_PATH: String = "./skeleton:%s" + + # Keys: source_file (String) # Values: loaded animation (Animation) or LOAD_FAILED_MARKER (StringName) -static var xsm_cache : Dictionary -const LOAD_FAILED_MARKER : StringName = &"XSM LOAD FAILED" +static var xsm_cache: Dictionary + -static func get_xsm_animation(source_file : String) -> Animation: - var cached : Variant = xsm_cache.get(source_file) +static func get_xsm_animation(source_file: String) -> Animation: + var cached: Variant = xsm_cache.get(source_file) if not cached: cached = _load_xsm_animation(source_file) if cached: @@ -23,70 +27,69 @@ static func get_xsm_animation(source_file : String) -> Animation: return cached -const SKELETON_PATH : String = "./skeleton:%s" -static func _load_xsm_animation(source_file : String) -> Animation: - var source_path : String = GameSingleton.lookup_file_path(source_file) - var file : FileAccess = FileAccess.open(source_path, FileAccess.READ) +static func _load_xsm_animation(source_file: String) -> Animation: + var source_path: String = GameSingleton.lookup_file_path(source_file) + var file: FileAccess = FileAccess.open(source_path, FileAccess.READ) if file == null: push_error("Failed to load XSM ", source_file, " from looked up path ", source_path) return null readHeader(file) - var metadataChunk : MetadataChunk = null - var boneAnimationChunks : Array[BoneAnimationChunk] = [] + var metadataChunk: MetadataChunk = null + var boneAnimationChunks: Array[BoneAnimationChunk] = [] while file.get_position() < file.get_length(): - var type : int = FileAccessUtils.read_int32(file) - var length : int = FileAccessUtils.read_int32(file) - var version : int = FileAccessUtils.read_int32(file) + var type: int = FileAccessUtils.read_int32(file) + var length: int = FileAccessUtils.read_int32(file) + var version: int = FileAccessUtils.read_int32(file) match type: - 0xC9: #Metadata + 0xc9: #Metadata metadataChunk = readMetadataChunk(file) - 0xCA: #Bone Animation + 0xca: #Bone Animation # v1 is float32 quaternions, v2 is int16 quaternions boneAnimationChunks.push_back(readBoneAnimationChunk(file, version == 2)) _: push_error(">> INVALID XSM CHUNK TYPE %X" % type) break - var animLength : float = 0.0 - var anim : Animation = Animation.new() - for anim_Chunk : BoneAnimationChunk in boneAnimationChunks: - for submotion : SkeletalSubMotion in anim_Chunk.SkeletalSubMotions: + var animLength: float = 0.0 + var anim: Animation = Animation.new() + for anim_Chunk: BoneAnimationChunk in boneAnimationChunks: + for submotion: SkeletalSubMotion in anim_Chunk.SkeletalSubMotions: # NOTE: godot uses ':' to specify properties, so we replace such characters with '_' - var skeleton_path : String = SKELETON_PATH % FileAccessUtils.replace_chars(submotion.nodeName) + var skeleton_path: String = SKELETON_PATH % FileAccessUtils.replace_chars(submotion.nodeName) if submotion.numPosKeys > 0: - var id : int = anim.add_track(Animation.TYPE_POSITION_3D) + var id: int = anim.add_track(Animation.TYPE_POSITION_3D) anim.track_set_path(id, skeleton_path) - for key : PosKey in submotion.PosKeys: + for key: PosKey in submotion.PosKeys: anim.position_track_insert_key(id, key.fTime, key.pos) if key.fTime > animLength: animLength = key.fTime else: # EXPERIMENTAL: see if setting posePos fixes idle3 - var id : int = anim.add_track(Animation.TYPE_POSITION_3D) + var id: int = anim.add_track(Animation.TYPE_POSITION_3D) anim.track_set_path(id, skeleton_path) anim.position_track_insert_key(id, 0, submotion.posePos) if submotion.numRotKeys > 0: - var id : int = anim.add_track(Animation.TYPE_ROTATION_3D) + var id: int = anim.add_track(Animation.TYPE_ROTATION_3D) anim.track_set_path(id, skeleton_path) - for key : RotKey in submotion.RotKeys: + for key: RotKey in submotion.RotKeys: anim.rotation_track_insert_key(id, key.fTime, key.rot) if key.fTime > animLength: animLength = key.fTime else: # EXPERIMENTAL: see if setting posePos fixes idle3 - var id : int = anim.add_track(Animation.TYPE_ROTATION_3D) + var id: int = anim.add_track(Animation.TYPE_ROTATION_3D) anim.track_set_path(id, skeleton_path) anim.rotation_track_insert_key(id, 0, submotion.poseRot) if submotion.numScaleKeys > 0: - var id : int = anim.add_track(Animation.TYPE_SCALE_3D) + var id: int = anim.add_track(Animation.TYPE_SCALE_3D) anim.track_set_path(id, skeleton_path) - for key : ScaleKey in submotion.ScaleKeys: + for key: ScaleKey in submotion.ScaleKeys: anim.scale_track_insert_key(id, key.fTime, key.scale) if key.fTime > animLength: animLength = key.fTime @@ -99,48 +102,125 @@ static func _load_xsm_animation(source_file : String) -> Animation: xsm_cache[source_file] = anim return anim -static func readHeader(file : FileAccess) -> void: - var magic_bytes : PackedByteArray = [file.get_8(), file.get_8(), file.get_8(), file.get_8()] - var magic : String = magic_bytes.get_string_from_ascii() - var version : String = "%d.%d" % [file.get_8(), file.get_8()] - var bBigEndian : bool = file.get_8() - var pad : int = file.get_8() + +static func readHeader(file: FileAccess) -> void: + var magic_bytes: PackedByteArray = [file.get_8(), file.get_8(), file.get_8(), file.get_8()] + var magic: String = magic_bytes.get_string_from_ascii() + var version: String = "%d.%d" % [file.get_8(), file.get_8()] + var bBigEndian: bool = file.get_8() + var pad: int = file.get_8() + + #print(magic, ", version: ", version, ", bigEndian: ", bBigEndian, " pad: ", pad) # NOTE: the "pad" variable is actually very important! # It seems to have something to do with whether paradox uses int16 or int32 # for quaternions (it's "pad" or version number, can't tell) -static func readMetadataChunk(file : FileAccess) -> MetadataChunk: + +static func readMetadataChunk(file: FileAccess) -> MetadataChunk: return MetadataChunk.new( file.get_float(), file.get_float(), FileAccessUtils.read_int32(file), file.get_8(), file.get_8(), file.get_16(), FileAccessUtils.read_xac_str(file), FileAccessUtils.read_xac_str(file), FileAccessUtils.read_xac_str(file), FileAccessUtils.read_xac_str(file) ) + +static func readPosKey(file: FileAccess) -> PosKey: + return PosKey.new(FileAccessUtils.read_pos(file), file.get_float()) + + +static func readRotKey(file: FileAccess, use_quat16: bool) -> RotKey: + return RotKey.new(FileAccessUtils.read_quat(file, use_quat16), file.get_float()) + + +static func readScaleKey(file: FileAccess) -> ScaleKey: + return ScaleKey.new(FileAccessUtils.read_vec3(file), file.get_float()) + + +static func readScaleRotKey(file: FileAccess, use_quat16: bool) -> ScaleRotKey: + return ScaleRotKey.new(FileAccessUtils.read_quat(file, use_quat16), file.get_float()) + + +static func readSkeletalSubMotion(file: FileAccess, use_quat16: bool) -> SkeletalSubMotion: + var a: Quaternion = FileAccessUtils.read_quat(file, use_quat16) + var b: Quaternion = FileAccessUtils.read_quat(file, use_quat16) + var c: Quaternion = FileAccessUtils.read_quat(file, use_quat16) + var d: Quaternion = FileAccessUtils.read_quat(file, use_quat16) + + var e: Vector3 = FileAccessUtils.read_pos(file) + var f: Vector3 = FileAccessUtils.read_vec3(file) + var g: Vector3 = FileAccessUtils.read_pos(file) + var h: Vector3 = FileAccessUtils.read_vec3(file) + + var p: int = FileAccessUtils.read_int32(file) + var j: int = FileAccessUtils.read_int32(file) + var k: int = FileAccessUtils.read_int32(file) + var l: int = FileAccessUtils.read_int32(file) + + var m: float = file.get_float() + var n: String = FileAccessUtils.read_xac_str(file) + + var submotion: SkeletalSubMotion = SkeletalSubMotion.new( + a, b, c, d, # quats + e, f, g, h, # vec3 + p, j, k, l, # ints + m, n + ) + var poskeys: Array[PosKey] = [] + var rotkeys: Array[RotKey] = [] + var scalekeys: Array[ScaleKey] = [] + var scalerotkeys: Array[ScaleRotKey] = [] + #FIXME: Did paradox store the number of pos keys as a float instead of int? + + for i: int in submotion.numPosKeys: + poskeys.push_back(readPosKey(file)) + for i: int in submotion.numRotKeys: + rotkeys.push_back(readRotKey(file, use_quat16)) + for i: int in submotion.numScaleKeys: + scalekeys.push_back(readScaleKey(file)) + for i: int in submotion.numScaleRotKeys: + scalerotkeys.push_back(readScaleRotKey(file, use_quat16)) + submotion.setPosKeys(poskeys) + submotion.setRotKeys(rotkeys) + submotion.setScaleKeys(scalekeys) + submotion.setScaleRotKeys(scalerotkeys) + return submotion + + +static func readBoneAnimationChunk(file: FileAccess, use_quat16: bool) -> BoneAnimationChunk: + var numSubMotions: int = FileAccessUtils.read_int32(file) + var animChunk: BoneAnimationChunk = BoneAnimationChunk.new(numSubMotions) + var submotions: Array[SkeletalSubMotion] = [] + for i: int in animChunk.numSubMotions: + submotions.push_back(readSkeletalSubMotion(file, use_quat16)) + animChunk.setSkeletalSubMotions(submotions) + return animChunk + + class MetadataChunk: - var unused : float - var fMaxAcceptableError : float - var fps : int # int32 - var exporterMajorVersion : int # byte - var exporterMinorVersion : int # byte - var pad : int # 2x byte - var sourceApp : String - var origFileName : String - var exportDate : String - var motionName : String + var unused: float + var fMaxAcceptableError: float + var fps: int # int32 + var exporterMajorVersion: int # byte + var exporterMinorVersion: int # byte + var pad: int # 2x byte + var sourceApp: String + var origFileName: String + var exportDate: String + var motionName: String func _init( - unused : float, - fMaxAcceptableError : float, - fps : int, - exporterMajorVersion : int, - exporterMinorVersion : int, - pad : int, - sourceApp : String, - origFileName : String, - exportDate : String, - motionName : String + unused: float, + fMaxAcceptableError: float, + fps: int, + exporterMajorVersion: int, + exporterMinorVersion: int, + pad: int, + sourceApp: String, + origFileName: String, + exportDate: String, + motionName: String ) -> void: self.unused = unused self.fMaxAcceptableError = fMaxAcceptableError @@ -159,143 +239,91 @@ class MetadataChunk: print("MotionName: %s, fps: %d, MaxError: %s" % [motionName, fps, fMaxAcceptableError]) -static func readPosKey(file : FileAccess) -> PosKey: - return PosKey.new(FileAccessUtils.read_pos(file), file.get_float()) class PosKey: - var pos : Vector3 - var fTime : float + var pos: Vector3 + var fTime: float - func _init(pos : Vector3, fTime : float) -> void: + func _init(pos: Vector3, fTime: float) -> void: self.pos = pos self.fTime = fTime func debugPrint() -> void: print("\t\tPos:%s, time:%s" % [pos, fTime]) -static func readRotKey(file : FileAccess, use_quat16 : bool) -> RotKey: - return RotKey.new(FileAccessUtils.read_quat(file, use_quat16), file.get_float()) class RotKey: - var rot : Quaternion - var fTime : float + var rot: Quaternion + var fTime: float - func _init(rot : Quaternion, fTime : float) -> void: + func _init(rot: Quaternion, fTime: float) -> void: self.rot = rot self.fTime = fTime func debugPrint() -> void: print("\t\tRot:%s, time:%s" % [rot, fTime]) -static func readScaleKey(file : FileAccess) -> ScaleKey: - return ScaleKey.new(FileAccessUtils.read_vec3(file), file.get_float()) class ScaleKey: - var scale : Vector3 - var fTime : float + var scale: Vector3 + var fTime: float - func _init(scale : Vector3, fTime : float) -> void: + func _init(scale: Vector3, fTime: float) -> void: self.scale = scale self.fTime = fTime func debugPrint() -> void: print("\t\tScale:%s, time:%s" % [scale, fTime]) -static func readScaleRotKey(file : FileAccess, use_quat16 : bool) -> ScaleRotKey: - return ScaleRotKey.new(FileAccessUtils.read_quat(file, use_quat16), file.get_float()) class ScaleRotKey: - var rot : Quaternion - var fTime : float + var rot: Quaternion + var fTime: float - func _init(rot : Quaternion, fTime : float) -> void: + func _init(rot: Quaternion, fTime: float) -> void: self.rot = rot self.fTime = fTime func debugPrint() -> void: print("\t\tScaleRot:%s, time:%s" % [rot, fTime]) -static func readSkeletalSubMotion(file : FileAccess, use_quat16 : bool) -> SkeletalSubMotion: - var a : Quaternion = FileAccessUtils.read_quat(file, use_quat16) - var b : Quaternion = FileAccessUtils.read_quat(file, use_quat16) - var c : Quaternion = FileAccessUtils.read_quat(file, use_quat16) - var d : Quaternion = FileAccessUtils.read_quat(file, use_quat16) - - var e : Vector3 = FileAccessUtils.read_pos(file) - var f : Vector3 = FileAccessUtils.read_vec3(file) - var g : Vector3 = FileAccessUtils.read_pos(file) - var h : Vector3 = FileAccessUtils.read_vec3(file) - - var p : int = FileAccessUtils.read_int32(file) - var j : int = FileAccessUtils.read_int32(file) - var k : int = FileAccessUtils.read_int32(file) - var l : int = FileAccessUtils.read_int32(file) - - var m : float = file.get_float() - var n : String = FileAccessUtils.read_xac_str(file) - - var submotion : SkeletalSubMotion = SkeletalSubMotion.new( - a, b, c, d, # quats - e, f, g, h, # vec3 - p, j, k, l, # ints - m, n - ) - var poskeys : Array[PosKey] = [] - var rotkeys : Array[RotKey] = [] - var scalekeys : Array[ScaleKey] = [] - var scalerotkeys : Array[ScaleRotKey] = [] - #FIXME: Did paradox store the number of pos keys as a float instead of int? - - for i : int in submotion.numPosKeys: - poskeys.push_back(readPosKey(file)) - for i : int in submotion.numRotKeys: - rotkeys.push_back(readRotKey(file, use_quat16)) - for i : int in submotion.numScaleKeys: - scalekeys.push_back(readScaleKey(file)) - for i : int in submotion.numScaleRotKeys: - scalerotkeys.push_back(readScaleRotKey(file, use_quat16)) - submotion.setPosKeys(poskeys) - submotion.setRotKeys(rotkeys) - submotion.setScaleKeys(scalekeys) - submotion.setScaleRotKeys(scalerotkeys) - return submotion class SkeletalSubMotion: - var poseRot : Quaternion - var bindPoseRot : Quaternion - var poseScaleRot : Quaternion - var bindPoseScaleRot : Quaternion - var posePos : Vector3 - var poseScale : Vector3 - var bindPosePos : Vector3 - var bindPoseScale : Vector3 - var numPosKeys : int # int32 - var numRotKeys : int # int32 - var numScaleKeys : int # int32 - var numScaleRotKeys : int # int32 - var fMaxError : float - var nodeName : String - - var PosKeys : Array[PosKey] - var RotKeys : Array[RotKey] - var ScaleKeys : Array[ScaleKey] - var ScaleRotKeys : Array[ScaleRotKey] + var poseRot: Quaternion + var bindPoseRot: Quaternion + var poseScaleRot: Quaternion + var bindPoseScaleRot: Quaternion + var posePos: Vector3 + var poseScale: Vector3 + var bindPosePos: Vector3 + var bindPoseScale: Vector3 + var numPosKeys: int # int32 + var numRotKeys: int # int32 + var numScaleKeys: int # int32 + var numScaleRotKeys: int # int32 + var fMaxError: float + var nodeName: String + + var PosKeys: Array[PosKey] + var RotKeys: Array[RotKey] + var ScaleKeys: Array[ScaleKey] + var ScaleRotKeys: Array[ScaleRotKey] func _init( - poseRot : Quaternion, - bindPoseRot : Quaternion, - poseScaleRot : Quaternion, - bindPoseScaleRot : Quaternion, - posePos : Vector3, - poseScale : Vector3, - bindPosePos : Vector3, - bindPoseScale : Vector3, - numPosKeys : int, - numRotKeys : int, - numScaleKeys : int, - numScaleRotKeys : int, - fMaxError : float, - nodeName : String + poseRot: Quaternion, + bindPoseRot: Quaternion, + poseScaleRot: Quaternion, + bindPoseScaleRot: Quaternion, + posePos: Vector3, + poseScale: Vector3, + bindPosePos: Vector3, + bindPoseScale: Vector3, + numPosKeys: int, + numRotKeys: int, + numScaleKeys: int, + numScaleRotKeys: int, + fMaxError: float, + nodeName: String ) -> void: self.poseRot = poseRot self.bindPoseRot = bindPoseRot @@ -312,50 +340,42 @@ class SkeletalSubMotion: self.fMaxError = fMaxError self.nodeName = nodeName - func setPosKeys(PosKeys : Array[PosKey]) -> void: + func setPosKeys(PosKeys: Array[PosKey]) -> void: self.PosKeys = PosKeys - func setRotKeys(RotKeys : Array[RotKey]) -> void: + func setRotKeys(RotKeys: Array[RotKey]) -> void: self.RotKeys = RotKeys - func setScaleKeys(ScaleKeys : Array[ScaleKey]) -> void: + func setScaleKeys(ScaleKeys: Array[ScaleKey]) -> void: self.ScaleKeys = ScaleKeys - func setScaleRotKeys(ScaleRotKeys : Array[ScaleRotKey]) -> void: + func setScaleRotKeys(ScaleRotKeys: Array[ScaleRotKey]) -> void: self.ScaleRotKeys = ScaleRotKeys func debugPrint() -> void: print("Node: %s, #PosKeys %d, #RotKeys %d, #ScaleKeys %d, #ScaleRotKeys %d" % [nodeName, numPosKeys, numRotKeys, numScaleKeys, numScaleRotKeys]) print("\tposeScaleRot %s,\tbindPoseScaleRot %s,\tposeScale %s,\tbindPoseScale %s" % [poseScaleRot, bindPoseScaleRot, poseScale, bindPoseScale]) - for key : PosKey in PosKeys: + for key: PosKey in PosKeys: key.debugPrint() - for key : RotKey in RotKeys: + for key: RotKey in RotKeys: key.debugPrint() - for key : ScaleKey in ScaleKeys: + for key: ScaleKey in ScaleKeys: key.debugPrint() - for key : ScaleRotKey in ScaleRotKeys: + for key: ScaleRotKey in ScaleRotKeys: key.debugPrint() -static func readBoneAnimationChunk(file : FileAccess, use_quat16 : bool) -> BoneAnimationChunk: - var numSubMotions : int = FileAccessUtils.read_int32(file) - var animChunk : BoneAnimationChunk = BoneAnimationChunk.new(numSubMotions) - var submotions : Array[SkeletalSubMotion] = [] - for i : int in animChunk.numSubMotions: - submotions.push_back(readSkeletalSubMotion(file, use_quat16)) - animChunk.setSkeletalSubMotions(submotions) - return animChunk class BoneAnimationChunk: - var numSubMotions : int # int32 - var SkeletalSubMotions : Array[SkeletalSubMotion] + var numSubMotions: int # int32 + var SkeletalSubMotions: Array[SkeletalSubMotion] - func _init(numSubMotions : int) -> void: + func _init(numSubMotions: int) -> void: self.numSubMotions = numSubMotions - func setSkeletalSubMotions(SkeletalSubMotions : Array[SkeletalSubMotion]) -> void: + func setSkeletalSubMotions(SkeletalSubMotions: Array[SkeletalSubMotion]) -> void: self.SkeletalSubMotions = SkeletalSubMotions func debugPrint() -> void: print("Number of Submotions: %d" % numSubMotions) - for submotion : SkeletalSubMotion in SkeletalSubMotions: + for submotion: SkeletalSubMotion in SkeletalSubMotions: submotion.debugPrint() diff --git a/game/src/Systems/Session/Billboard/BillboardManager.gd b/game/src/Systems/Session/Billboard/BillboardManager.gd index 8337e2fe..033cf1a4 100644 --- a/game/src/Systems/Session/Billboard/BillboardManager.gd +++ b/game/src/Systems/Session/Billboard/BillboardManager.gd @@ -1,41 +1,53 @@ extends MultiMeshInstance3D -@export var _map_view : MapView - -const SCALE_FACTOR : float = 1.0 / 96.0 +enum BillboardType { + NONE, + RGO, + CRIME, + NATIONAL_FOCUS, + CAPITAL, +} +enum MapModes { + REVOLT_RISK = 2, + INFRASTRUCTURE = 5, + COLONIAL = 6, + NATIONAL_FOCUS = 9, + RGO_OUTPUT = 10, +} -enum BillboardType { NONE, RGO, CRIME, NATIONAL_FOCUS, CAPITAL } -const BILLBOARD_NAMES : Dictionary = { +const SCALE_FACTOR: float = 1.0 / 96.0 +const BILLBOARD_NAMES: Dictionary = { BillboardType.RGO: &"tradegoods", BillboardType.CRIME: &"crimes", BillboardType.NATIONAL_FOCUS: &"national_focus", - BillboardType.CAPITAL: &"capital" + BillboardType.CAPITAL: &"capital", } -const BILLBOARD_DIMS : Dictionary = { +const BILLBOARD_DIMS: Dictionary = { # Should be 0.8, but something else seems to be contributing to vertical # stretching so we use 0.7 to account for that. BillboardType.RGO: Vector2(1.0, 0.7), BillboardType.CRIME: Vector2(1.0, 1.0), BillboardType.NATIONAL_FOCUS: Vector2(1.0, 1.0), - BillboardType.CAPITAL: Vector2(1.0, 1.0) + BillboardType.CAPITAL: Vector2(1.0, 1.0), } -enum MapModes { REVOLT_RISK = 2, INFRASTRUCTURE = 5, COLONIAL = 6, NATIONAL_FOCUS = 9, RGO_OUTPUT = 10 } -var provinces_size : int = 0 -var total_capitals_size : int = 0 +@export var _map_view: MapView + +var provinces_size: int = 0 +var total_capitals_size: int = 0 + # Given a BillboardType, get the index for the texture in the shader. # This is to reduce the number of magic indices in the code # to get the proper billboard image -var billboard_type_to_index : Dictionary +var billboard_type_to_index: Dictionary +var textures: Array[Texture2D] +var frames: PackedByteArray +var scales: PackedVector2Array +var current_province_billboard: BillboardType = BillboardType.NONE +var province_billboards_visible: bool = true -var textures : Array[Texture2D] -var frames : PackedByteArray -var scales : PackedVector2Array - -var current_province_billboard : BillboardType = BillboardType.NONE -var province_billboards_visible : bool = true # ============== Billboards ============= # Billboards are displayed using a multimesh (batch drawn mesh) @@ -43,7 +55,8 @@ var province_billboards_visible : bool = true # A shader controls which billboard and frame from icon strips are displayed # at each province. It also makes billboards "look at" the camera # To ensure billboards are displayed ontop of the map and units, it is contained in -# a subviewport which renders above the main viewport, with a camera set to follow the primary camera +# a subviewport which renders above the main viewport, with a camera set to follow the primary +# camera # multimesh only lets us send custom data to the shader as a single float vec4/Color variable # So we need to make the most of it. @@ -57,17 +70,18 @@ var province_billboards_visible : bool = true # for map modes such as RGO output, while "Capital billboards" refers to the # to country capitals. + func _ready() -> void: - const name_key : StringName = &"name" - const texture_key : StringName = &"texture" - const scale_key : StringName = &"scale" - const no_of_frames_key : StringName = &"noFrames" + const name_key: StringName = &"name" + const texture_key: StringName = &"texture" + const scale_key: StringName = &"scale" + const no_of_frames_key: StringName = &"noFrames" - for billboard : Dictionary in MapItemSingleton.get_billboards(): - var billboard_name : StringName = billboard[name_key] + for billboard: Dictionary in MapItemSingleton.get_billboards(): + var billboard_name: StringName = billboard[name_key] - var billboard_type : BillboardType = BillboardType.NONE - for key : BillboardType in BILLBOARD_NAMES: + var billboard_type: BillboardType = BillboardType.NONE + for key: BillboardType in BILLBOARD_NAMES: if billboard_name == BILLBOARD_NAMES[key]: billboard_type = key break @@ -75,11 +89,11 @@ func _ready() -> void: if billboard_type == BillboardType.NONE: continue - var texture_name : StringName = billboard[texture_key] - var billboard_scale : float = billboard[scale_key] - var no_of_frames : int = billboard[no_of_frames_key] + var texture_name: StringName = billboard[texture_key] + var billboard_scale: float = billboard[scale_key] + var no_of_frames: int = billboard[no_of_frames_key] - var texture : ImageTexture = AssetManager.get_texture(texture_name) + var texture: ImageTexture = AssetManager.get_texture(texture_name) if texture == null: push_error("Texture for billboard \"", billboard_name, "\" was null!") continue @@ -93,7 +107,7 @@ func _ready() -> void: frames.push_back(no_of_frames) scales.push_back(BILLBOARD_DIMS[billboard_type] * billboard_scale * SCALE_FACTOR) - var material : ShaderMaterial = multimesh.mesh.surface_get_material(0) + var material: ShaderMaterial = multimesh.mesh.surface_get_material(0) if material == null: push_error("ShaderMaterial for billboards was null") return @@ -103,7 +117,7 @@ func _ready() -> void: material.set_shader_parameter(&"sizes", scales) multimesh.mesh.surface_set_material(0, material) - var positions : PackedVector2Array = MapItemSingleton.get_province_positions() + var positions: PackedVector2Array = MapItemSingleton.get_province_positions() provinces_size = positions.size() total_capitals_size = MapItemSingleton.get_max_capital_count() @@ -116,7 +130,7 @@ func _ready() -> void: push_error("MapView export variable for BillboardManager must be set!") return - for province_index : int in provinces_size: + for province_index: int in provinces_size: multimesh.set_instance_transform( province_index + total_capitals_size, Transform3D(Basis(), _map_view._map_to_world_coords(positions[province_index])) @@ -127,13 +141,16 @@ func _ready() -> void: GameSingleton.mapmode_changed.connect(_on_map_mode_changed) GameSingleton.gamestate_updated.connect(_on_game_state_changed) + # Fetch the nation capitals and setup billboards for them + + func set_capitals() -> void: - var capital_positions : PackedVector2Array = MapItemSingleton.get_capital_positions() - var capitals_size : int = capital_positions.size() - var image_index : int = billboard_type_to_index[BillboardType.CAPITAL] + var capital_positions: PackedVector2Array = MapItemSingleton.get_capital_positions() + var capitals_size: int = capital_positions.size() + var image_index: int = billboard_type_to_index[BillboardType.CAPITAL] - for capital_index : int in capitals_size: + for capital_index: int in capitals_size: multimesh.set_instance_transform( capital_index, Transform3D(Basis(), _map_view._map_to_world_coords(capital_positions[capital_index])) @@ -149,20 +166,23 @@ func set_capitals() -> void: ) # For every country that doesn't exist, make the capital invisible - for capital_index : int in range(capitals_size, total_capitals_size): + for capital_index: int in range(capitals_size, total_capitals_size): multimesh.set_instance_custom_data( capital_index, Color(image_index, 0.0, 0.0, 0.0) ) + # Should provinces display RGO, crime, ..., or no billboard + + func update_province_billboards() -> void: # If current_province_billboard is NONE then image_index will fall back to -1 - var image_index : int = billboard_type_to_index.get(current_province_billboard, -1) + var image_index: int = billboard_type_to_index.get(current_province_billboard, -1) if not province_billboards_visible or image_index < 0: multimesh.visible_instance_count = total_capitals_size else: - var icons : PackedByteArray + var icons: PackedByteArray match current_province_billboard: BillboardType.RGO: icons = MapItemSingleton.get_rgo_icons() @@ -175,7 +195,7 @@ func update_province_billboards() -> void: return # Capitals are first in the array, so start iterating after them - for province_index : int in provinces_size: + for province_index: int in provinces_size: multimesh.set_instance_custom_data( province_index + total_capitals_size, Color(image_index, icons[province_index], 0.0, 0.0) @@ -183,24 +203,29 @@ func update_province_billboards() -> void: multimesh.visible_instance_count = total_capitals_size + provinces_size + func _on_game_state_changed() -> void: update_province_billboards() set_capitals() + # There are essentially 3 visibility states we can be in # 1: parchment view -> no billboards visible # 2: not parchment nor detail -> only capitals visible # 3: detail map -> province and capital billboards visible # So set_visible here is essentially to toggle the visibility of capitals -func detailed_map(visible : bool) -> void: + +func detailed_map(visible: bool) -> void: province_billboards_visible = visible update_province_billboards() -func parchment_view(is_parchment : bool) -> void: + +func parchment_view(is_parchment: bool) -> void: set_visible(not is_parchment) -func _on_map_mode_changed(map_mode : int) -> void: + +func _on_map_mode_changed(map_mode: int) -> void: match map_mode: MapModes.INFRASTRUCTURE, MapModes.COLONIAL, MapModes.RGO_OUTPUT: current_province_billboard = BillboardType.RGO diff --git a/game/src/Systems/Session/GameSession.gd b/game/src/Systems/Session/GameSession.gd index d03ba978..f9c6f6fe 100644 --- a/game/src/Systems/Session/GameSession.gd +++ b/game/src/Systems/Session/GameSession.gd @@ -1,8 +1,9 @@ extends Node -@export var _map_view : MapView -@export var _model_manager : ModelManager -@export var _game_session_menu : Control +@export var _map_view: MapView +@export var _model_manager: ModelManager +@export var _game_session_menu: Control + func _ready() -> void: if GameSingleton.start_game_session() != OK: @@ -15,36 +16,46 @@ func _ready() -> void: # In game, the province selector uses the normal glove cursor. CursorManager.set_compat_cursor(&"normal", Input.CURSOR_IBEAM) -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_PREDELETE: if GameSingleton.end_game_session() != OK: push_error("Failed to end game session") -func _process(_delta : float) -> void: + +func _process(_delta: float) -> void: GameSingleton.update_clock() # REQUIREMENTS: # * SS-42 + + func _on_game_session_menu_button_pressed() -> void: - _game_session_menu.visible = !_game_session_menu.visible + _game_session_menu.visible = not _game_session_menu.visible + func _on_map_view_ready() -> void: # Set the camera's starting position _map_view._camera.position = _map_view._map_to_world_coords( - # Start at the player country's capital position (when loading a save game in the lobby or entering the actual game) + # Start at the player country's capital position (when loading a save game in the lobby or + # entering the actual game) PlayerSingleton.get_player_country_capital_position() ) + func _on_map_view_province_hovered(province_number: int) -> void: _map_view.set_hovered_province_number(province_number) + func _on_map_view_province_unhovered() -> void: _map_view.unset_hovered_province() + func _on_map_view_province_clicked(province_number: int) -> void: PlayerSingleton.set_selected_province_by_number(province_number) + func _on_map_view_province_right_clicked(province_number: int) -> void: # TODO - open diplomacy screen on province owner or viewed country if province has no owner #Events.NationManagementScreens.open_nation_management_screen(NationManagement.Screen.DIPLOMACY) diff --git a/game/src/Systems/Session/Map/MapText.gd b/game/src/Systems/Session/Map/MapText.gd index 619c72ff..0978e0bc 100644 --- a/game/src/Systems/Session/Map/MapText.gd +++ b/game/src/Systems/Session/Map/MapText.gd @@ -1,36 +1,40 @@ class_name MapText extends Node3D -@export var _map_view : MapView +const _province_name_scale: float = 1.0 / 48.0 -var _province_name_font : Font +@export var _map_view: MapView + +var _province_name_font: Font -const _province_name_scale : float = 1.0 / 48.0 func _ready() -> void: _province_name_font = AssetManager.get_font(&"mapfont_56") + func _clear_children() -> void: - var child_count : int = get_child_count() + var child_count: int = get_child_count() while child_count > 0: child_count -= 1 - var child : Node = get_child(child_count) + var child: Node = get_child(child_count) remove_child(child) child.queue_free() + func generate_map_names() -> void: _clear_children() - for dict : Dictionary in GameSingleton.get_province_names(): + for dict: Dictionary in GameSingleton.get_province_names(): _add_province_name(dict) -func _add_province_name(dict : Dictionary) -> void: - const identifier_key : StringName = &"identifier" - const position_key : StringName = &"position" - const rotation_key : StringName = &"rotation" - const scale_key : StringName = &"scale" - var label : Label3D = Label3D.new() +func _add_province_name(dict: Dictionary) -> void: + const identifier_key: StringName = &"identifier" + const position_key: StringName = &"position" + const rotation_key: StringName = &"rotation" + const scale_key: StringName = &"scale" + + var label: Label3D = Label3D.new() label.set_draw_flag(Label3D.FLAG_DOUBLE_SIDED, false) label.set_modulate(Color.BLACK) @@ -38,7 +42,7 @@ func _add_province_name(dict : Dictionary) -> void: label.set_font(_province_name_font) label.set_vertical_alignment(VERTICAL_ALIGNMENT_BOTTOM) - var identifier : String = dict[identifier_key] + var identifier: String = dict[identifier_key] label.set_name(identifier) label.set_text(GUINode.format_province_name(identifier)) diff --git a/game/src/Systems/Session/Map/MapView.gd b/game/src/Systems/Session/Map/MapView.gd index 360add17..a1b6460f 100644 --- a/game/src/Systems/Session/Map/MapView.gd +++ b/game/src/Systems/Session/Map/MapView.gd @@ -1,72 +1,80 @@ class_name MapView extends Node3D -signal map_view_camera_changed(near_left : Vector2, far_left : Vector2, far_right : Vector2, near_right : Vector2) -signal parchment_view_changed(is_parchment_view : bool) -signal detailed_view_changed(is_detailed_view : bool) -signal province_hovered(index : int) +signal map_view_camera_changed( + near_left: Vector2, + far_left: Vector2, + far_right: Vector2, + near_right: Vector2, +) +signal parchment_view_changed(is_parchment_view: bool) +signal detailed_view_changed(is_detailed_view: bool) +signal province_hovered(index: int) signal province_unhovered() -signal province_clicked(index : int) -signal province_right_clicked(index : int) - -const _action_north : StringName = &"map_north" -const _action_east : StringName = &"map_east" -const _action_south : StringName = &"map_south" -const _action_west : StringName = &"map_west" -const _action_zoom_in : StringName = &"map_zoom_in" -const _action_zoom_out : StringName = &"map_zoom_out" -const _action_drag : StringName = &"map_drag" -const _action_click : StringName = &"map_click" -const _action_right_click : StringName = &"map_right_click" -const _action_select_add : StringName = &"select_add" - -@export var _camera : Camera3D - -@export var _cardinal_move_speed : float = 2.5 +signal province_clicked(index: int) +signal province_right_clicked(index: int) + +const _action_north: StringName = &"map_north" +const _action_east: StringName = &"map_east" +const _action_south: StringName = &"map_south" +const _action_west: StringName = &"map_west" +const _action_zoom_in: StringName = &"map_zoom_in" +const _action_zoom_out: StringName = &"map_zoom_out" +const _action_drag: StringName = &"map_drag" +const _action_click: StringName = &"map_click" +const _action_right_click: StringName = &"map_right_click" +const _action_select_add: StringName = &"select_add" + +@export var _camera: Camera3D +@export var _cardinal_move_speed: float = 2.5 @export var _edge_move_threshold: float = 0.025 @export var _edge_move_speed: float = 2.5 -var _drag_anchor : Vector2 -var _drag_active : bool = false -var _mouse_over_viewport : bool = true +var _drag_anchor: Vector2 +var _drag_active: bool = false +var _mouse_over_viewport: bool = true -@export var _zoom_target_min : float = 0.075 -@export var _zoom_target_max : float = 5.0 -@export var _zoom_target_step : float = (_zoom_target_max - _zoom_target_min) / 64.0 -@export var _zoom_epsilon : float = _zoom_target_step * 0.005 -@export var _zoom_speed : float = 5.0 +@export var _zoom_target_min: float = 0.075 +@export var _zoom_target_max: float = 5.0 +@export var _zoom_target_step: float = (_zoom_target_max - _zoom_target_min) / 64.0 +@export var _zoom_epsilon: float = _zoom_target_step * 0.005 +@export var _zoom_speed: float = 5.0 # _zoom_target's starting value is ignored as it is updated to the camera's height by _ready, # hence why it is not exported and just has _zoom_target_max as a placeholder. -var _zoom_target : float = _zoom_target_max: + +var _zoom_target: float = _zoom_target_max: get: return _zoom_target set(v): _zoom_target = clampf(v, _zoom_target_min, _zoom_target_max) + const _zoom_position_multiplier = 3.14159 # Horizontal movement coefficient during zoom -var _zoom_position : Vector2 + +var _zoom_position: Vector2 # Display the parchment map above this height -@export var _zoom_parchment_threshold : float = _zoom_target_min + (_zoom_target_max - _zoom_target_min) / 4 + +@export var _zoom_parchment_threshold: float = _zoom_target_min + (_zoom_target_max - _zoom_target_min) / 4 # Display details like models and province names below this height -@export var _zoom_detailed_threshold : float = _zoom_parchment_threshold / 2 +@export var _zoom_detailed_threshold: float = _zoom_parchment_threshold / 2 -var _is_parchment_view : bool = false -var _is_detailed_view : bool = false +var _is_parchment_view: bool = false +var _is_detailed_view: bool = false -@export var _map_mesh_instance : MeshInstance3D -var _map_mesh : MapMesh -var _map_shader_material : ShaderMaterial -var _map_mesh_corner : Vector2 -var _map_mesh_dims : Vector2 +@export var _map_mesh_instance: MeshInstance3D -@export var _map_background_instance : MeshInstance3D +var _map_mesh: MapMesh +var _map_shader_material: ShaderMaterial +var _map_mesh_corner: Vector2 +var _map_mesh_dims: Vector2 -var _mouse_pos_viewport : Vector2 = Vector2(0.5, 0.5) -var _mouse_pos_map : Vector2 = Vector2(0.5, 0.5) -var _viewport_dims : Vector2 = Vector2(1, 1) +@export var _map_background_instance: MeshInstance3D -@export var _map_text : MapText +var _mouse_pos_viewport: Vector2 = Vector2(0.5, 0.5) +var _mouse_pos_map: Vector2 = Vector2(0.5, 0.5) +var _viewport_dims: Vector2 = Vector2(1, 1) -@export var validMoveMarkers : ValidMoveMarkers -@export var selectionMarkers : SelectionMarkers +@export var _map_text: MapText +@export var validMoveMarkers: ValidMoveMarkers +@export var selectionMarkers: SelectionMarkers # ??? Strange Godot/GDExtension Bug ??? # Upon first opening a clone of this repo with the Godot Editor, @@ -75,6 +83,8 @@ var _viewport_dims : Vector2 = Vector2(1, 1) # to a failed HashMap lookup. I'm not sure if this is a bug in the # editor, GDExtension, my own extension, or a combination of them. # This was an absolute pain to track down. --- hop311 + + func _ready() -> void: if not _camera: push_error("MapView's _camera variable hasn't been set!") @@ -92,14 +102,18 @@ func _ready() -> void: _map_shader_material = map_material if not _map_mesh_instance.mesh is MapMesh: - push_error("Invalid map mesh class: ", _map_mesh_instance.mesh.get_class(), "(expected MapMesh)") + push_error( + "Invalid map mesh class: ", + _map_mesh_instance.mesh.get_class(), + "(expected MapMesh)", + ) return _map_mesh = _map_mesh_instance.mesh _map_mesh.set_aspect_ratio(GameSingleton.get_map_aspect_ratio()) # Get map mesh bounds - var map_mesh_aabb : AABB = _map_mesh.get_core_aabb() * _map_mesh_instance.transform + var map_mesh_aabb: AABB = _map_mesh.get_core_aabb() * _map_mesh_instance.transform _map_mesh_corner = Vector2( min(map_mesh_aabb.position.x, map_mesh_aabb.end.x), min(map_mesh_aabb.position.z, map_mesh_aabb.end.z) @@ -121,32 +135,40 @@ func _ready() -> void: return if not _map_background_instance.mesh is PlaneMesh: - push_error("Invalid map background mesh class: ", _map_background_instance.mesh.get_class(), "(expected PlaneMesh)") + push_error( + "Invalid map background mesh class: ", + _map_background_instance.mesh.get_class(), + "(expected PlaneMesh)", + ) return - var scaled_dims : Vector3 = _map_background_instance.transform.affine_inverse() * Vector3(_map_mesh_dims.x, 0.0, _map_mesh_dims.y) + var scaled_dims: Vector3 = _map_background_instance.transform.affine_inverse() * Vector3(_map_mesh_dims.x, 0.0, _map_mesh_dims.y) scaled_dims.x *= 1.0 + 2.0 * _map_mesh.get_repeat_proportion() scaled_dims.z *= 2.0 (_map_background_instance.mesh as PlaneMesh).set_size(Vector2(scaled_dims.x, scaled_dims.z)) _map_text.generate_map_names() + func _notification(what: int) -> void: if what == NOTIFICATION_WM_MOUSE_EXIT: _mouse_over_viewport = false province_unhovered.emit() -func _world_to_map_coords(pos : Vector3) -> Vector2: + +func _world_to_map_coords(pos: Vector3) -> Vector2: return (Vector2(pos.x, pos.z) - _map_mesh_corner) / _map_mesh_dims -func _map_to_world_coords(pos : Vector2) -> Vector3: + +func _map_to_world_coords(pos: Vector2) -> Vector3: pos = pos * _map_mesh_dims + _map_mesh_corner return Vector3(pos.x, 0, pos.y) -func _viewport_to_world_coords(pos_viewport : Vector2) -> Vector3: + +func _viewport_to_world_coords(pos_viewport: Vector2) -> Vector3: var ray_origin := _camera.project_ray_origin(pos_viewport) var ray_normal := _camera.project_ray_normal(pos_viewport) # Plane with normal (0,1,0) facing upwards, at a distance 0 from the origin - var intersection : Variant = Plane(0, 1, 0, 0).intersects_ray(ray_origin, ray_normal) + var intersection: Variant = Plane(0, 1, 0, 0).intersects_ray(ray_origin, ray_normal) if typeof(intersection) == TYPE_VECTOR3: return intersection else: @@ -155,53 +177,75 @@ func _viewport_to_world_coords(pos_viewport : Vector2) -> Vector3: push_error("Invalid intersection: ", intersection) return _map_to_world_coords(Vector2(0.5, 0.5)) -func _viewport_to_map_coords(pos_viewport : Vector2) -> Vector2: + +func _viewport_to_map_coords(pos_viewport: Vector2) -> Vector2: return _world_to_map_coords(_viewport_to_world_coords(pos_viewport)) -func look_at_map_position(pos : Vector2) -> void: - var viewport_centre : Vector2 = Vector2(0.5, 0.5) * _viewport_dims / get_tree().root.content_scale_factor - var pos_delta : Vector3 = _map_to_world_coords(pos) - _viewport_to_world_coords(viewport_centre) + +func look_at_map_position(pos: Vector2) -> void: + var viewport_centre: Vector2 = Vector2( + 0.5, + 0.5, + ) * _viewport_dims / get_tree().root.content_scale_factor + var pos_delta: Vector3 = _map_to_world_coords(pos) - _viewport_to_world_coords(viewport_centre) _camera.position.x += pos_delta.x _camera.position.z += pos_delta.z + func zoom_in() -> void: _zoom_target -= _zoom_target_step _zoom_position = (Vector2(0.5, 0.5) - _mouse_pos_viewport * get_tree().root.content_scale_factor / _viewport_dims) * _zoom_position_multiplier + func zoom_out() -> void: _zoom_target += _zoom_target_step # For some reason, zooming out in the original game does not consider the # cursor location. I'm not sure if we want to preserve this behavior. _zoom_position = Vector2() -func set_hovered_province_number(province_number : int) -> void: + +func set_hovered_province_number(province_number: int) -> void: if _map_shader_material: - _map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_hover_index, province_number) + _map_shader_material.set_shader_parameter( + GameLoader.ShaderManager.param_hover_index, + province_number, + ) -func set_hovered_province_at(pos : Vector2) -> void: + +func set_hovered_province_at(pos: Vector2) -> void: var province_number := GameSingleton.get_province_number_from_uv_coords(pos) set_hovered_province_number(province_number) + func unset_hovered_province() -> void: set_hovered_province_number(0) + var _province_hover_dirty := false + + func queue_province_hover_update() -> void: if not _mouse_over_viewport: return _province_hover_dirty = true + func _update_province_hover() -> void: if not _province_hover_dirty: return _province_hover_dirty = false if _mouse_over_viewport: province_hovered.emit(GameSingleton.get_province_number_from_uv_coords(_viewport_to_map_coords(_mouse_pos_viewport))) -func _on_province_selected(province_number : int) -> void: + +func _on_province_selected(province_number: int) -> void: if _map_shader_material: - _map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_selected_index, province_number) + _map_shader_material.set_shader_parameter( + GameLoader.ShaderManager.param_selected_index, + province_number, + ) print("Province selected with number: ", province_number) -func _input(event : InputEvent) -> void: + +func _input(event: InputEvent) -> void: if event is InputEventMouseMotion: _mouse_pos_viewport = get_window().get_mouse_position() elif _drag_active and event.is_action_released(_action_drag): @@ -210,8 +254,12 @@ func _input(event : InputEvent) -> void: # REQUIREMENTS # * SS-31 # * SS-75 + + var _cardinal_movement_vector := Vector2.ZERO -func _unhandled_input(event : InputEvent) -> void: + + +func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseMotion: _mouse_over_viewport = true queue_province_hover_update() @@ -228,16 +276,22 @@ func _unhandled_input(event : InputEvent) -> void: elif event.is_action_pressed(_action_select_add): if _mouse_over_viewport: if _map_mesh.is_valid_uv_coord(_mouse_pos_map): - var province_number : int = GameSingleton.get_province_number_from_uv_coords(_mouse_pos_map) + var province_number: int = GameSingleton.get_province_number_from_uv_coords(_mouse_pos_map) PlayerSingleton.set_selected_province_by_number(province_number) # TODO: Proper unit selection logic should replace this temporary behaviour - var port_province_number : int = MapItemSingleton.get_clicked_port_province_number(_mouse_pos_map) + var port_province_number: int = MapItemSingleton.get_clicked_port_province_number(_mouse_pos_map) if port_province_number != 0: - var port_pos : Vector2 = MapItemSingleton.get_port_position_by_province_number(port_province_number) - selectionMarkers.toggle_id_selected(5000 + port_province_number, _map_to_world_coords(port_pos)) + var port_pos: Vector2 = MapItemSingleton.get_port_position_by_province_number(port_province_number) + selectionMarkers.toggle_id_selected( + 5000 + port_province_number, + _map_to_world_coords(port_pos), + ) else: - var unit_position : Vector2 = MapItemSingleton.get_unit_position_by_province_number(province_number) - selectionMarkers.toggle_id_selected(province_number,_map_to_world_coords(unit_position)) + var unit_position: Vector2 = MapItemSingleton.get_unit_position_by_province_number(province_number) + selectionMarkers.toggle_id_selected( + province_number, + _map_to_world_coords(unit_position), + ) elif event.is_action_pressed(_action_click): if _mouse_over_viewport: @@ -250,16 +304,23 @@ func _unhandled_input(event : InputEvent) -> void: elif event.is_action_pressed(_action_right_click): if _mouse_over_viewport: if _map_mesh.is_valid_uv_coord(_mouse_pos_map): - var province_number : int = GameSingleton.get_province_number_from_uv_coords(_mouse_pos_map) + var province_number: int = GameSingleton.get_province_number_from_uv_coords(_mouse_pos_map) province_right_clicked.emit(province_number) - var port_province_number : int = MapItemSingleton.get_clicked_port_province_number(_mouse_pos_map) + var port_province_number: int = MapItemSingleton.get_clicked_port_province_number(_mouse_pos_map) if port_province_number != 0: - var port_pos : Vector2 = MapItemSingleton.get_port_position_by_province_number(port_province_number) - validMoveMarkers.add_move_marker(_map_to_world_coords(port_pos), randi_range(0,1)) + var port_pos: Vector2 = MapItemSingleton.get_port_position_by_province_number(port_province_number) + validMoveMarkers.add_move_marker( + _map_to_world_coords(port_pos), + randi_range(0, 1), + ) else: - var unit_position : Vector2 = MapItemSingleton.get_unit_position_by_province_number(province_number) - validMoveMarkers.add_move_marker(_map_to_world_coords(unit_position), randi_range(0,1)) - # TODO - open diplomacy screen on province owner or viewed country if province has no owner + var unit_position: Vector2 = MapItemSingleton.get_unit_position_by_province_number(province_number) + validMoveMarkers.add_move_marker( + _map_to_world_coords(unit_position), + randi_range(0, 1), + ) + # TODO - open diplomacy screen on province owner or viewed country if province has + # no owner #Events.NationManagementScreens.open_nation_management_screen(NationManagement.Screen.DIPLOMACY) PlayerSingleton.set_player_country_by_province_number(province_number) else: @@ -274,7 +335,8 @@ func _unhandled_input(event : InputEvent) -> void: elif event.is_action_pressed(_action_zoom_out, true): zoom_out() -func _process(delta : float) -> void: + +func _process(delta: float) -> void: if _cardinal_movement_vector != Vector2.ZERO and get_window().gui_get_focus_owner() != null: _cardinal_movement_vector = Vector2.ZERO @@ -300,8 +362,10 @@ func _process(delta : float) -> void: # REQUIREMENTS # * UIFUN-124 -func _movement_process(delta : float) -> void: - var direction : Vector2 + + +func _movement_process(delta: float) -> void: + var direction: Vector2 if _drag_active: direction = (_drag_anchor - _mouse_pos_map) * _map_mesh_dims else: @@ -314,26 +378,38 @@ func _movement_process(delta : float) -> void: # REQUIREMENTS # * UIFUN-125 + + func _edge_scrolling_vector() -> Vector2: if not _mouse_over_viewport: return Vector2() var mouse_vector := _mouse_pos_viewport * get_tree().root.content_scale_factor / _viewport_dims - Vector2(0.5, 0.5) # Only scroll if outside the move threshold. - if abs(mouse_vector.x) < 0.5 - _edge_move_threshold and abs(mouse_vector.y) < 0.5 - _edge_move_threshold: + if (abs(mouse_vector.x) < 0.5 - _edge_move_threshold + and abs(mouse_vector.y) < 0.5 - _edge_move_threshold): return Vector2() return mouse_vector * _edge_move_speed + func _clamp_over_map() -> void: - _camera.position.x = _map_mesh_corner.x + fposmod(_camera.position.x - _map_mesh_corner.x, _map_mesh_dims.x) - _camera.position.z = clampf(_camera.position.z, _map_mesh_corner.y, _map_mesh_corner.y + _map_mesh_dims.y) + _camera.position.x = _map_mesh_corner.x + fposmod( + _camera.position.x - _map_mesh_corner.x, + _map_mesh_dims.x, + ) + _camera.position.z = clampf( + _camera.position.z, + _map_mesh_corner.y, + _map_mesh_corner.y + _map_mesh_dims.y, + ) + -func _update_view_states(force_signal : bool) -> void: - var new_is_parchment_view : bool = _camera.position.y >= _zoom_parchment_threshold - _zoom_epsilon +func _update_view_states(force_signal: bool) -> void: + var new_is_parchment_view: bool = _camera.position.y >= _zoom_parchment_threshold - _zoom_epsilon if force_signal or new_is_parchment_view != _is_parchment_view: _is_parchment_view = new_is_parchment_view parchment_view_changed.emit(_is_parchment_view) - var new_is_detailed_view : bool = _camera.position.y <= _zoom_detailed_threshold + _zoom_epsilon + var new_is_detailed_view: bool = _camera.position.y <= _zoom_detailed_threshold + _zoom_epsilon if force_signal or new_is_detailed_view != _is_detailed_view: _is_detailed_view = new_is_detailed_view detailed_view_changed.emit(_is_detailed_view) @@ -341,7 +417,9 @@ func _update_view_states(force_signal : bool) -> void: # REQUIREMENTS # * SS-74 # * UIFUN-123 -func _zoom_process(delta : float) -> void: + + +func _zoom_process(delta: float) -> void: var height := _camera.position.y var zoom := _zoom_target - height var zoom_delta := zoom * _zoom_speed * delta @@ -357,19 +435,24 @@ func _zoom_process(delta : float) -> void: ) # TODO - smooth transition similar to smooth zoom _update_view_states(false) - var parchment_mapmode : bool = GameSingleton.is_parchment_mapmode_allowed() and _is_parchment_view + var parchment_mapmode: bool = GameSingleton.is_parchment_mapmode_allowed() and _is_parchment_view if _map_shader_material: - _map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_parchment_mix, float(parchment_mapmode)) + _map_shader_material.set_shader_parameter( + GameLoader.ShaderManager.param_parchment_mix, + float(parchment_mapmode), + ) + func _update_orientation() -> void: const up := Vector3(0, 0, -1) var dir := Vector3(0, -1, 0) if _is_detailed_view: # Zero at the transition point, increases as you zoom further in - var delta : float = (_zoom_detailed_threshold - _camera.position.y) / _zoom_detailed_threshold + var delta: float = (_zoom_detailed_threshold - _camera.position.y) / _zoom_detailed_threshold dir.z = -(delta ** 2) _camera.look_at(_camera.position + dir, up) + func _update_minimap_viewport() -> void: var near_left := _viewport_to_map_coords(Vector2(0, _viewport_dims.y)) var far_left := _viewport_to_map_coords(Vector2(0, 0)) @@ -377,23 +460,28 @@ func _update_minimap_viewport() -> void: var near_right := _viewport_to_map_coords(_viewport_dims) map_view_camera_changed.emit(near_left, far_left, far_right, near_right) + func _update_mouse_map_position() -> void: _mouse_pos_map = _viewport_to_map_coords(_mouse_pos_viewport) -func _on_minimap_clicked(pos_clicked : Vector2) -> void: + +func _on_minimap_clicked(pos_clicked: Vector2) -> void: pos_clicked *= _map_mesh_dims _camera.position.x = pos_clicked.x _camera.position.z = pos_clicked.y _clamp_over_map() queue_province_hover_update() + func _is_viewport_inactive() -> bool: return not get_window().has_focus() or get_window().is_input_handled() + func enable_processing() -> void: set_process_unhandled_input(true) set_process(true) + func disable_processing() -> void: set_process_unhandled_input(false) set_process(false) diff --git a/game/src/Systems/Session/Map/ProjectionManager.gd b/game/src/Systems/Session/Map/ProjectionManager.gd index c958077f..4c49460f 100644 --- a/game/src/Systems/Session/Map/ProjectionManager.gd +++ b/game/src/Systems/Session/Map/ProjectionManager.gd @@ -1,33 +1,36 @@ -extends Node3D class_name ProjectionManager +extends Node3D + +enum ProjectionType { + INVALIDTYPE, + SELECTED, + LEGAL_MOVE, + ILLEGAL_MOVE, +} -enum ProjectionType {INVALIDTYPE, SELECTED, LEGAL_MOVE, ILLEGAL_MOVE} -const PROJECTION_NAME_TO_TYPES : Dictionary = { +const PROJECTION_NAME_TO_TYPES: Dictionary = { &"selection_projection": ProjectionType.SELECTED, &"legal_selection_projection": ProjectionType.LEGAL_MOVE, - &"illegal_selection_projection": ProjectionType.ILLEGAL_MOVE + &"illegal_selection_projection": ProjectionType.ILLEGAL_MOVE, } - -@export var selectionMarkers : SelectionMarkers -@export var moveMarkers : ValidMoveMarkers - -var Type_to_Index : Dictionary - -var material : ShaderMaterial -var textures : Array[Texture2D] -var sizes : PackedFloat32Array -var spins : PackedFloat32Array -var expansions : PackedFloat32Array -var durations : PackedFloat32Array -var transparency_mode : PackedByteArray - -const SCALE_FACTOR : float = 1.0 / 256.0 -const SPIN_FACTOR : float = 4.0 -const HEIGHT_ADD_FACTOR : Vector3 = Vector3(0,0.002,0) -const GROW_FACTOR : float = 0.25 - -var times : PackedFloat32Array = [0,0,0] -var loop_times : PackedFloat32Array = [0,0,0] +const SCALE_FACTOR: float = 1.0 / 256.0 +const SPIN_FACTOR: float = 4.0 +const HEIGHT_ADD_FACTOR: Vector3 = Vector3(0, 0.002, 0) +const GROW_FACTOR: float = 0.25 + +@export var selectionMarkers: SelectionMarkers +@export var moveMarkers: ValidMoveMarkers + +var Type_to_Index: Dictionary +var material: ShaderMaterial +var textures: Array[Texture2D] +var sizes: PackedFloat32Array +var spins: PackedFloat32Array +var expansions: PackedFloat32Array +var durations: PackedFloat32Array +var transparency_mode: PackedByteArray +var times: PackedFloat32Array = [0, 0, 0] +var loop_times: PackedFloat32Array = [0, 0, 0] # For the markers (selection compass, legal/illegal move markers) # this class handles the setup of the projection shader, and the maintenance @@ -36,31 +39,41 @@ var loop_times : PackedFloat32Array = [0,0,0] func _ready() -> void: - const name_key : StringName = &"name"; - const texture_key : StringName = &"texture"; - const size_key : StringName = &"size"; - const spin_key : StringName = &"spin"; - const expanding_key : StringName = &"expanding"; - const duration_key : StringName = &"duration"; - const additative_key : StringName = &"additative"; - - for projection : Dictionary in MapItemSingleton.get_projections(): - var projection_name : StringName = projection[name_key] + const name_key: StringName = &"name" + + const texture_key: StringName = &"texture" + + const size_key: StringName = &"size" + + const spin_key: StringName = &"spin" + + const expanding_key: StringName = &"expanding" + + const duration_key: StringName = &"duration" + + const additative_key: StringName = &"additative" + + + for projection: Dictionary in MapItemSingleton.get_projections(): + var projection_name: StringName = projection[name_key] #keep only projections we are currently handling - var projection_type : ProjectionType = PROJECTION_NAME_TO_TYPES.get(projection_name, ProjectionType.INVALIDTYPE) + var projection_type: ProjectionType = PROJECTION_NAME_TO_TYPES.get( + projection_name, + ProjectionType.INVALIDTYPE, + ) if projection_type == ProjectionType.INVALIDTYPE: continue - var texture_name : StringName = projection[texture_key] - var size : float = projection[size_key] - var spin : float = projection[spin_key] - var expanding : float = projection[expanding_key] - var duration : float = projection[duration_key] - var additative : bool = projection[additative_key] + var texture_name: StringName = projection[texture_key] + var size: float = projection[size_key] + var spin: float = projection[spin_key] + var expanding: float = projection[expanding_key] + var duration: float = projection[duration_key] + var additative: bool = projection[additative_key] #fix the alpha edges of the projection textures - var texture : ImageTexture = AssetManager.get_texture(texture_name) + var texture: ImageTexture = AssetManager.get_texture(texture_name) if texture == null: push_error("Texture for projection \"", projection_name, "\" was null!") continue @@ -76,8 +89,8 @@ func _ready() -> void: expansions.push_back(expanding * GROW_FACTOR) durations.push_back(duration) transparency_mode.push_back(additative) - - var loop_time : float = 2*PI*size/(expanding*GROW_FACTOR) + + var loop_time: float = 2 * PI * size / (expanding * GROW_FACTOR) if duration != 0: loop_time *= duration loop_times[Type_to_Index[projection_type]] = loop_time @@ -92,24 +105,25 @@ func _ready() -> void: material.set_shader_parameter(&"spin", spins) material.set_shader_parameter(&"expanding", expansions) material.set_shader_parameter(&"additative", transparency_mode) - material.set_shader_parameter(&"duration",durations) + material.set_shader_parameter(&"duration", durations) material.set_shader_parameter(&"time", times) - + # to be safe, set selectionMarkers to be the same material selectionMarkers.multimesh.mesh.surface_set_material(0, material) moveMarkers.setup(loop_times) selectionMarkers.setup(loop_times[Type_to_Index[ProjectionType.SELECTED]]) -func _process(delta : float) -> void: - for i : int in times.size(): + +func _process(delta: float) -> void: + for i: int in times.size(): times[i] += delta - + material.set_shader_parameter(&"time", times) moveMarkers.set_time(times) selectionMarkers.set_time(times[Type_to_Index[ProjectionType.SELECTED]]) - - for i : int in times.size(): + + for i: int in times.size(): if times[i] >= loop_times[i]: # use this instead of setting to 0 so that any fractional # component rolls over with the projection diff --git a/game/src/Systems/Session/Map/SelectionMarkers.gd b/game/src/Systems/Session/Map/SelectionMarkers.gd index 769be2fc..8d07b833 100644 --- a/game/src/Systems/Session/Map/SelectionMarkers.gd +++ b/game/src/Systems/Session/Map/SelectionMarkers.gd @@ -1,61 +1,70 @@ class_name SelectionMarkers extends MultiMeshInstance3D -@export var manager : ProjectionManager +const MIN_INSTANCE_COUNT: int = 32 + -var time : float = 0.0 -var loop_time : float = 0.0 -const MIN_INSTANCE_COUNT : int = 32 # above this size, and clearing the selection will reset the buffer size to the min # so that it isn't kept large indefinitely -const INSTANCE_SOFT_CAP : int = 128 +const INSTANCE_SOFT_CAP: int = 128 + #When the number of selections exceeds the current instance count, grow the instance buffer size -# by this much. Larger the number, the less often the buffer is resized, but there is a less fine grained number of instances. -const INSTANCE_COUNT_GROW_AMOUNT : int = 8 -const HEIGHT_ADD_FACTOR : Vector3 = ProjectionManager.HEIGHT_ADD_FACTOR +# by this much. Larger the number, the less often the buffer is resized, but there is a less fine +# grained number of instances. +const INSTANCE_COUNT_GROW_AMOUNT: int = 8 +const HEIGHT_ADD_FACTOR: Vector3 = ProjectionManager.HEIGHT_ADD_FACTOR + # we can control the multimesh buffer directly # 12 floats for transform, 4 floats for colour, 4 floats for custom data # colour and custom data are optional -const TR_CUSTOM_SIZE : int = 16 -const MULTIMESH_START_TIME_OFFSET : int = 13 +const TR_CUSTOM_SIZE: int = 16 +const MULTIMESH_START_TIME_OFFSET: int = 13 +const SELECT_TYPE: ProjectionManager.ProjectionType = ProjectionManager.ProjectionType.SELECTED + +@export var manager: ProjectionManager -var id_to_index : Dictionary #int to int -var unused_indices : PackedInt32Array +var time: float = 0.0 +var loop_time: float = 0.0 +var id_to_index: Dictionary #int to int +var unused_indices: PackedInt32Array +var select_index: int = 0 -const SELECT_TYPE : ProjectionManager.ProjectionType = ProjectionManager.ProjectionType.SELECTED -var select_index : int = 0 # Called when the node enters the scene tree for the first time. -func setup(loop_time_in : float = loop_time) -> void: + + +func setup(loop_time_in: float = loop_time) -> void: select_index = manager.Type_to_Index[SELECT_TYPE] loop_time = loop_time_in # 1) setting instance_count clears and resizes the buffer # so we want to find the max size once and leave it # 2) resize must occur after setting the transform format - + #unlike billboards, we don't know how many of these we'll need. Quads are pretty small # and multimesh means we can have a lot of them, so be generous, and resize later if we have to multimesh.instance_count = MIN_INSTANCE_COUNT multimesh.visible_instance_count = MIN_INSTANCE_COUNT - + unused_indices.resize(MIN_INSTANCE_COUNT) - for i : int in unused_indices.size(): + for i: int in unused_indices.size(): unused_indices[i] = i - multimesh.set_instance_custom_data(i,Color( - select_index,time,0.0,0.0 + multimesh.set_instance_custom_data(i, Color( + select_index, time, 0.0, 0.0 )) -#interface for the instance uniforms is + +#interface for the instance uniforms is #INSTANCE_CUSTOM (COLOR).x = type selection, .y = start time, .z = transparent override -func add_selection_marker(unit_id : int, unit_position : Vector3) -> bool: - var index_to_use : int = -1 - if(id_to_index.get(unit_id,-1) != -1): + +func add_selection_marker(unit_id: int, unit_position: Vector3) -> bool: + var index_to_use: int = -1 + if id_to_index.get(unit_id, -1) != -1: return false # already has that id! - - for index : int in unused_indices: + + for index: int in unused_indices: if index != -1: index_to_use = index unused_indices[index] = -1 @@ -64,99 +73,115 @@ func add_selection_marker(unit_id : int, unit_position : Vector3) -> bool: index_to_use = unused_indices.size() _grow_buffer() unused_indices[index_to_use] = -1 - + id_to_index[unit_id] = index_to_use multimesh.set_instance_transform( - index_to_use,Transform3D(Basis(), unit_position + HEIGHT_ADD_FACTOR) + index_to_use, Transform3D(Basis(), unit_position + HEIGHT_ADD_FACTOR) ) - multimesh.set_instance_custom_data(index_to_use,Color( - select_index,time,1.0,0.0 + multimesh.set_instance_custom_data(index_to_use, Color( + select_index, time, 1.0, 0.0 )) - + return true -func update_selection_marker(unit_id : int, unit_position : Vector3) -> bool: - var index : int = id_to_index.get(unit_id,-1) + +func update_selection_marker(unit_id: int, unit_position: Vector3) -> bool: + var index: int = id_to_index.get(unit_id, -1) if index == -1: return false #doesn't have that id! - + multimesh.set_instance_transform( - index,Transform3D(Basis(), unit_position + HEIGHT_ADD_FACTOR) + index, Transform3D(Basis(), unit_position + HEIGHT_ADD_FACTOR) ) return true + #TODO: Perhaps there's a more efficient method... -func add_selection_markers(unit_ids : PackedInt32Array, unit_positions : PackedVector3Array) -> bool: + + +func add_selection_markers(unit_ids: PackedInt32Array, unit_positions: PackedVector3Array) -> bool: assert(unit_ids.size() == unit_positions.size()) # don't increase visible count all at once here, in-case an id is invalid - var ret : bool = true - for index : int in unit_ids.size(): - if not add_selection_marker(unit_ids[index],unit_positions[index]): + var ret: bool = true + for index: int in unit_ids.size(): + if not add_selection_marker(unit_ids[index], unit_positions[index]): ret = false return ret -func update_selection_markers(unit_ids : PackedInt32Array, unit_positions : PackedVector3Array) -> bool: + +func update_selection_markers( + unit_ids: PackedInt32Array, + unit_positions: PackedVector3Array, +) -> bool: assert(unit_ids.size() == unit_positions.size()) - var ret : bool = true - for index : int in unit_ids.size(): - if not update_selection_marker(unit_ids[index],unit_positions[index]): + var ret: bool = true + for index: int in unit_ids.size(): + if not update_selection_marker(unit_ids[index], unit_positions[index]): ret = false return ret + func clear_selection_markers() -> void: #no units should display - if multimesh.buffer.size() > INSTANCE_SOFT_CAP*TR_CUSTOM_SIZE: + if multimesh.buffer.size() > INSTANCE_SOFT_CAP * TR_CUSTOM_SIZE: #we exceeded the soft cap, reset instances to the minimum # now that we are clearing - setup() + setup() return else: - for i : int in unused_indices.size(): + for i: int in unused_indices.size(): unused_indices[i] = i id_to_index.clear() - for i : int in unused_indices.size(): + for i: int in unused_indices.size(): unused_indices[i] = i - multimesh.set_instance_custom_data(i,Color( - select_index,time,0.0,0.0 + multimesh.set_instance_custom_data(i, Color( + select_index, time, 0.0, 0.0 )) -func remove_selection_marker(unit_id : int) -> bool: - var index : int = id_to_index.get(unit_id,-1) + +func remove_selection_marker(unit_id: int) -> bool: + var index: int = id_to_index.get(unit_id, -1) if index == -1: return false # doesn't have that id! unused_indices[index] = index #mark that index as unused id_to_index.erase(unit_id) - multimesh.set_instance_custom_data(index,Color( - select_index,time,0.0,0.0 + multimesh.set_instance_custom_data(index, Color( + select_index, time, 0.0, 0.0 )) return true -func is_id_selected(unit_id : int) -> bool: + +func is_id_selected(unit_id: int) -> bool: return id_to_index.has(unit_id) -func toggle_id_selected(unit_id : int, unit_position : Vector3) -> bool: + +func toggle_id_selected(unit_id: int, unit_position: Vector3) -> bool: if is_id_selected(unit_id): return remove_selection_marker(unit_id) else: return add_selection_marker(unit_id, unit_position) -func set_time(timeIn : float) -> void: + +func set_time(timeIn: float) -> void: time = timeIn if timeIn > loop_time: - for i : int in id_to_index.values(): - if multimesh.buffer[TR_CUSTOM_SIZE*i + MULTIMESH_START_TIME_OFFSET] > 0: - multimesh.buffer[TR_CUSTOM_SIZE*i + MULTIMESH_START_TIME_OFFSET] -= loop_time + for i: int in id_to_index.values(): + if multimesh.buffer[TR_CUSTOM_SIZE * i + MULTIMESH_START_TIME_OFFSET] > 0: + multimesh.buffer[TR_CUSTOM_SIZE * i + MULTIMESH_START_TIME_OFFSET] -= loop_time + #Grow the multimesh without losing the current data -func _grow_buffer(amount : int = INSTANCE_COUNT_GROW_AMOUNT) -> void: - var temp_buffer : PackedFloat32Array = multimesh.buffer - temp_buffer.resize(temp_buffer.size() + amount*TR_CUSTOM_SIZE) - + + +func _grow_buffer(amount: int = INSTANCE_COUNT_GROW_AMOUNT) -> void: + var temp_buffer: PackedFloat32Array = multimesh.buffer + temp_buffer.resize(temp_buffer.size() + amount * TR_CUSTOM_SIZE) + multimesh.instance_count += amount multimesh.visible_instance_count += amount multimesh.set_buffer(temp_buffer) - - var offset : int = unused_indices.size() + + var offset: int = unused_indices.size() unused_indices.resize(offset + amount) - for i : int in amount: - unused_indices[offset+i] = offset+i + for i: int in amount: + unused_indices[offset + i] = offset + i diff --git a/game/src/Systems/Session/Map/ValidMoveMarkers.gd b/game/src/Systems/Session/Map/ValidMoveMarkers.gd index 22685bbf..3c8f4a40 100644 --- a/game/src/Systems/Session/Map/ValidMoveMarkers.gd +++ b/game/src/Systems/Session/Map/ValidMoveMarkers.gd @@ -1,26 +1,35 @@ class_name ValidMoveMarkers extends MultiMeshInstance3D -@export var manager : ProjectionManager +const INSTANCE_COUNT: int = 64 +const HEIGHT_ADD_FACTOR: Vector3 = ProjectionManager.HEIGHT_ADD_FACTOR +const LEGAL_TYPE: ProjectionManager.ProjectionType = ProjectionManager.ProjectionType.LEGAL_MOVE +const ILLEGAL_TYPE: ProjectionManager.ProjectionType = ProjectionManager.ProjectionType.ILLEGAL_MOVE +const TR_CUSTOM_SIZE: int = 16 +const MULTIMESH_TYPE_OFFSET: int = 12 +const MULTIMESH_START_TIME_OFFSET: int = 13 -var projection_type_to_index : Dictionary +@export var manager: ProjectionManager -var time_legal : float = 0.0 -var time_illegal : float = 0.0 -var loop_time_legal : float = 0.0 -var loop_time_illegal : float = 0.0 +var projection_type_to_index: Dictionary +var time_legal: float = 0.0 +var time_illegal: float = 0.0 +var loop_time_legal: float = 0.0 +var loop_time_illegal: float = 0.0 +var ages: PackedFloat32Array +var legal_index: int = 0 +var illegal_index: int = 0 -const INSTANCE_COUNT : int = 64 -const HEIGHT_ADD_FACTOR : Vector3 = ProjectionManager.HEIGHT_ADD_FACTOR -var ages : PackedFloat32Array -const LEGAL_TYPE : ProjectionManager.ProjectionType = ProjectionManager.ProjectionType.LEGAL_MOVE -const ILLEGAL_TYPE : ProjectionManager.ProjectionType = ProjectionManager.ProjectionType.ILLEGAL_MOVE -var legal_index : int = 0 -var illegal_index : int = 0 +func _process(delta: float) -> void: + for i: int in ages.size(): + ages[i] -= delta + # Called when the node enters the scene tree for the first time. -func setup(loop_times : PackedFloat32Array) -> void: + + +func setup(loop_times: PackedFloat32Array) -> void: legal_index = manager.Type_to_Index[LEGAL_TYPE] illegal_index = manager.Type_to_Index[ILLEGAL_TYPE] @@ -29,39 +38,41 @@ func setup(loop_times : PackedFloat32Array) -> void: ages.resize(INSTANCE_COUNT) ages.fill(0.0) - + #unlike billboards, we don't know how many of these we'll need. Quads are pretty small # and multimesh means we can have a lot of them, so be generous, and resize later if we have to multimesh.instance_count = INSTANCE_COUNT multimesh.visible_instance_count = INSTANCE_COUNT - - for i : int in INSTANCE_COUNT: + + for i: int in INSTANCE_COUNT: multimesh.set_instance_transform( - i,Transform3D(Basis(), Vector3(float(i),0.0,0.0)) + i, Transform3D(Basis(), Vector3(float(i), 0.0, 0.0)) ) -#interface for the instance uniforms is + +#interface for the instance uniforms is #INSTANCE_CUSTOM (COLOR).x = type selection, .y = start time, .z = transparent override -func add_move_marker(marker_position : Vector3, was_legal_move : bool) -> void: - var type : ProjectionManager.ProjectionType = ILLEGAL_TYPE - var time : float = time_illegal + +func add_move_marker(marker_position: Vector3, was_legal_move: bool) -> void: + var type: ProjectionManager.ProjectionType = ILLEGAL_TYPE + var time: float = time_illegal if was_legal_move: type = LEGAL_TYPE time = time_legal - - var duration : float = float(manager.durations[manager.Type_to_Index[type]]) - var oldest : int = 0 - var oldest_age : float = 0 - for i : int in ages.size(): - var age : float = ages[i] + var duration: float = float(manager.durations[manager.Type_to_Index[type]]) + + var oldest: int = 0 + var oldest_age: float = 0 + for i: int in ages.size(): + var age: float = ages[i] if age <= 0: multimesh.set_instance_transform( - i,Transform3D(Basis(), marker_position + HEIGHT_ADD_FACTOR) + i, Transform3D(Basis(), marker_position + HEIGHT_ADD_FACTOR) ) - multimesh.set_instance_custom_data(i,Color( - manager.Type_to_Index[type],time,1.0,0.0 + multimesh.set_instance_custom_data(i, Color( + manager.Type_to_Index[type], time, 1.0, 0.0 )) ages[i] = duration return @@ -70,34 +81,27 @@ func add_move_marker(marker_position : Vector3, was_legal_move : bool) -> void: oldest = i # all slots were filled, use the oldest multimesh.set_instance_transform( - oldest,Transform3D(Basis(), marker_position + HEIGHT_ADD_FACTOR) + oldest, Transform3D(Basis(), marker_position + HEIGHT_ADD_FACTOR) ) - multimesh.set_instance_custom_data(oldest,Color( - manager.Type_to_Index[type],time,1.0,0.0 + multimesh.set_instance_custom_data(oldest, Color( + manager.Type_to_Index[type], time, 1.0, 0.0 )) ages[oldest] = duration -const TR_CUSTOM_SIZE : int = 16 -const MULTIMESH_TYPE_OFFSET : int = 12 -const MULTIMESH_START_TIME_OFFSET : int = 13 -func set_time(times : PackedFloat32Array) -> void: +func set_time(times: PackedFloat32Array) -> void: time_legal = times[legal_index] time_illegal = times[illegal_index] - - var do_loop_legal : bool = time_legal > loop_time_legal - var do_loop_illegal : bool = time_illegal > loop_time_illegal + + var do_loop_legal: bool = time_legal > loop_time_legal + var do_loop_illegal: bool = time_illegal > loop_time_illegal if do_loop_legal or do_loop_illegal: - for i : int in ages.size(): - var type : int = multimesh.buffer[TR_CUSTOM_SIZE*i + MULTIMESH_TYPE_OFFSET] as int - var start_time := multimesh.buffer[TR_CUSTOM_SIZE*i + MULTIMESH_START_TIME_OFFSET] + for i: int in ages.size(): + var type: int = multimesh.buffer[TR_CUSTOM_SIZE * i + MULTIMESH_TYPE_OFFSET] as int + var start_time := multimesh.buffer[TR_CUSTOM_SIZE * i + MULTIMESH_START_TIME_OFFSET] # note: only do the subtraction if start_time was not already looped # (which we can check by looking for a negative start_time) if do_loop_legal and type == legal_index and start_time > 0: - multimesh.buffer[TR_CUSTOM_SIZE*i + MULTIMESH_START_TIME_OFFSET] = start_time-loop_time_legal + multimesh.buffer[TR_CUSTOM_SIZE * i + MULTIMESH_START_TIME_OFFSET] = start_time - loop_time_legal elif do_loop_illegal and type == illegal_index and start_time > 0: - multimesh.buffer[TR_CUSTOM_SIZE*i + MULTIMESH_START_TIME_OFFSET] = start_time-loop_time_illegal - -func _process(delta : float) -> void: - for i : int in ages.size(): - ages[i] -= delta + multimesh.buffer[TR_CUSTOM_SIZE * i + MULTIMESH_START_TIME_OFFSET] = start_time - loop_time_illegal diff --git a/game/src/Systems/Session/ModelManager.gd b/game/src/Systems/Session/ModelManager.gd index 9d740559..3291e655 100644 --- a/game/src/Systems/Session/ModelManager.gd +++ b/game/src/Systems/Session/ModelManager.gd @@ -1,47 +1,53 @@ class_name ModelManager extends Node3D -@export var _map_view : MapView +const MODEL_SCALE: float = 1.0 / 256.0 + +@export var _map_view: MapView -const MODEL_SCALE : float = 1.0 / 256.0 func generate_units() -> void: XACLoader.setup_flag_shader() - for unit : Dictionary in ModelSingleton.get_units(): + for unit: Dictionary in ModelSingleton.get_units(): _generate_unit(unit) -func _generate_unit(unit_dict : Dictionary) -> void: - const culture_key : StringName = &"culture" - const model_key : StringName = &"model" - const mount_model_key : StringName = &"mount_model" - const mount_attach_node_key : StringName = &"mount_attach_node" - const flag_index_key : StringName = &"flag_index" - const flag_floating_key : StringName = &"flag_floating" - const position_key : StringName = &"position" - const rotation_key : StringName = &"rotation" - const primary_colour_key : StringName = &"primary_colour" - const secondary_colour_key : StringName = &"secondary_colour" - const tertiary_colour_key : StringName = &"tertiary_colour" - - var model : Node3D = _generate_model(unit_dict[model_key], unit_dict[culture_key]) + +func _generate_unit(unit_dict: Dictionary) -> void: + const culture_key: StringName = &"culture" + const model_key: StringName = &"model" + const mount_model_key: StringName = &"mount_model" + const mount_attach_node_key: StringName = &"mount_attach_node" + const flag_index_key: StringName = &"flag_index" + const flag_floating_key: StringName = &"flag_floating" + const position_key: StringName = &"position" + const rotation_key: StringName = &"rotation" + const primary_colour_key: StringName = &"primary_colour" + const secondary_colour_key: StringName = &"secondary_colour" + const tertiary_colour_key: StringName = &"tertiary_colour" + + var model: Node3D = _generate_model(unit_dict[model_key], unit_dict[culture_key]) if not model: return if mount_model_key in unit_dict and mount_attach_node_key in unit_dict: # This must be a UnitModel so we can attach the rider to it - var mount_model : Node3D = _generate_model(unit_dict[mount_model_key], unit_dict[culture_key], true) + var mount_model: Node3D = _generate_model( + unit_dict[mount_model_key], + unit_dict[culture_key], + true, + ) if mount_model: if mount_model.attach_model(unit_dict[mount_attach_node_key], model) == OK: model = mount_model else: mount_model.free() - var rotation : float = unit_dict.get(rotation_key, 0.0) + var rotation: float = unit_dict.get(rotation_key, 0.0) - var flag_dict : Dictionary = ModelSingleton.get_flag_model(unit_dict.get(flag_floating_key, false)) + var flag_dict: Dictionary = ModelSingleton.get_flag_model(unit_dict.get(flag_floating_key, false)) if flag_dict: - var flag_model : UnitModel = _generate_model(flag_dict, "", true) + var flag_model: UnitModel = _generate_model(flag_dict, "", true) if flag_model: flag_model.set_flag_index(unit_dict[flag_index_key]) flag_model.current_anim = UnitModel.Anim.IDLE @@ -63,16 +69,18 @@ func _generate_unit(unit_dict : Dictionary) -> void: add_child(model) + func generate_buildings() -> void: - for building : Dictionary in ModelSingleton.get_buildings(): + for building: Dictionary in ModelSingleton.get_buildings(): _generate_building(building) -func _generate_building(building_dict : Dictionary) -> void: - const model_key : StringName = &"model" - const position_key : StringName = &"position" - const rotation_key : StringName = &"rotation" - var model : Node3D = _generate_model(building_dict[model_key]) +func _generate_building(building_dict: Dictionary) -> void: + const model_key: StringName = &"model" + const position_key: StringName = &"position" + const rotation_key: StringName = &"rotation" + + var model: Node3D = _generate_model(building_dict[model_key]) if not model: return @@ -82,19 +90,20 @@ func _generate_building(building_dict : Dictionary) -> void: add_child(model) -func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : bool = false) -> Node3D: - const file_key : StringName = &"file" - const scale_key : StringName = &"scale" - const idle_key : StringName = &"idle" - const move_key : StringName = &"move" - const attack_key : StringName = &"attack" - const attachments_key : StringName = &"attachments" - const animation_file_key : StringName = &"file" - const animation_time_key : StringName = &"time" +func _generate_model(model_dict: Dictionary, culture: String = "", is_unit: bool = false) -> Node3D: + const file_key: StringName = &"file" + const scale_key: StringName = &"scale" + const idle_key: StringName = &"idle" + const move_key: StringName = &"move" + const attack_key: StringName = &"attack" + const attachments_key: StringName = &"attachments" + + const animation_file_key: StringName = &"file" + const animation_time_key: StringName = &"time" - const attachment_node_key : StringName = &"node" - const attachment_model_key : StringName = &"model" + const attachment_node_key: StringName = &"node" + const attachment_model_key: StringName = &"model" # Model is_unit = is_unit or ( @@ -104,48 +113,54 @@ func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : b or attachments_key in model_dict ) - var model : Node3D = XACLoader.get_xac_model(model_dict[file_key], is_unit) + var model: Node3D = XACLoader.get_xac_model(model_dict[file_key], is_unit) if not model: return null model.scale *= model_dict[scale_key] if model is UnitModel: # Animations - var idle_dict : Dictionary = model_dict.get(idle_key, {}) + var idle_dict: Dictionary = model_dict.get(idle_key, {}) if idle_dict: model.set_idle_anim(XSMLoader.get_xsm_animation(idle_dict[animation_file_key])) model.scroll_speed_idle = idle_dict[animation_time_key] - var move_dict : Dictionary = model_dict.get(move_key, {}) + var move_dict: Dictionary = model_dict.get(move_key, {}) if move_dict: model.set_move_anim(XSMLoader.get_xsm_animation(move_dict[animation_file_key])) model.scroll_speed_move = move_dict[animation_time_key] - var attack_dict : Dictionary = model_dict.get(attack_key, {}) + var attack_dict: Dictionary = model_dict.get(attack_key, {}) if attack_dict: model.set_attack_anim(XSMLoader.get_xsm_animation(attack_dict[animation_file_key])) model.scroll_speed_attack = attack_dict[animation_time_key] # Attachments - for attachment_dict : Dictionary in model_dict.get(attachments_key, []): - var attachment_model : Node3D = _generate_model(attachment_dict[attachment_model_key], culture) - if attachment_model and model.attach_model(attachment_dict[attachment_node_key], attachment_model) != OK: + for attachment_dict: Dictionary in model_dict.get(attachments_key, []): + var attachment_model: Node3D = _generate_model( + attachment_dict[attachment_model_key], + culture, + ) + if attachment_model and model.attach_model( + attachment_dict[attachment_node_key], + attachment_model, + ) != OK: attachment_model.free() if culture: - const gun_bone_name : String = "GunNode" + const gun_bone_name: String = "GunNode" if model.has_bone(gun_bone_name): - var gun_dict : Dictionary = ModelSingleton.get_cultural_gun_model(culture) + var gun_dict: Dictionary = ModelSingleton.get_cultural_gun_model(culture) if gun_dict: - var gun_model : Node3D = _generate_model(gun_dict, culture) + var gun_model: Node3D = _generate_model(gun_dict, culture) if gun_model and model.attach_model(gun_bone_name, gun_model) != OK: gun_model.free() - const helmet_bone_name : String = "HelmetNode" + const helmet_bone_name: String = "HelmetNode" if model.has_bone(helmet_bone_name): - var helmet_dict : Dictionary = ModelSingleton.get_cultural_helmet_model(culture) + var helmet_dict: Dictionary = ModelSingleton.get_cultural_helmet_model(culture) if helmet_dict: - var helmet_model : Node3D = _generate_model(helmet_dict, culture) + var helmet_model: Node3D = _generate_model(helmet_dict, culture) if helmet_model and model.attach_model(helmet_bone_name, helmet_model) != OK: helmet_model.free() diff --git a/game/src/Systems/Startup/GameStart.gd b/game/src/Systems/Startup/GameStart.gd index 1d93ab95..fc6d69cd 100644 --- a/game/src/Systems/Startup/GameStart.gd +++ b/game/src/Systems/Startup/GameStart.gd @@ -4,12 +4,14 @@ const LoadingScreen := preload("res://src/Systems/Startup/LoadingScreen.gd") const GameMenuScene := preload("res://src/UI/GameMenu/GameMenu/GameMenu.tscn") @export_subgroup("Nodes") -@export var loading_screen : LoadingScreen +@export var loading_screen: LoadingScreen + func _enter_tree() -> void: - Keychain.keep_binding_check = func(action_name : StringName) -> bool: + Keychain.keep_binding_check = func(action_name: StringName) -> bool: return action_name.begins_with("button_") and action_name.ends_with("_hotkey") + func _ready() -> void: Keychain.actions = { # Map Group @@ -34,7 +36,7 @@ func _ready() -> void: "Map": Keychain.InputGroup.new("", false), "Time": Keychain.InputGroup.new("", false), "UI": Keychain.InputGroup.new("", false), - "Hotkeys": Keychain.InputGroup.new("UI") + "Hotkeys": Keychain.InputGroup.new("UI"), } if ArgumentParser.get_option_value(&"help"): @@ -48,17 +50,24 @@ func _ready() -> void: await _setup_compatibility_mode_paths() await loading_screen.start_loading_screen(_initialize_game) + func _setup_compatibility_mode_paths() -> void: - # To test mods, set your base path to Victoria II and then pass mods in reverse order with --mod="mod" for each mod. + # To test mods, set your base path to Victoria II and then pass mods in reverse order with + # --mod="mod" for each mod. - var arg_base_path : String = ArgumentParser.get_option_value(&"base-path") - var arg_search_path : String = ArgumentParser.get_option_value(&"search-path") + var arg_base_path: String = ArgumentParser.get_option_value(&"base-path") + var arg_search_path: String = ArgumentParser.get_option_value(&"search-path") var setting := Vic2Settings.get_setting(Vic2Settings.GENERAL_BASE_DEFINES_PATH) if arg_base_path: if arg_search_path: - push_warning("Exact base path and search base path arguments both used:\nBase: ", arg_base_path, "\nSearch: ", arg_search_path) + push_warning( + "Exact base path and search base path arguments both used:\nBase: ", + arg_base_path, + "\nSearch: ", + arg_search_path, + ) setting.set_value(arg_base_path) elif Vic2Settings.get_base_defines_path().is_empty() and arg_search_path: # This will also search for a Steam install if the hint doesn't help @@ -81,10 +90,11 @@ func _setup_compatibility_mode_paths() -> void: load_list.append(mod) load_list_setting.set_value(load_list) + func _load_compatibility_mode() -> void: if GameSingleton.set_compatibility_mode_roots(Vic2Settings.get_base_defines_path()) != OK: push_error("Errors setting game roots!") - + CursorManager.initial_cursor_setup() setup_title_theme() @@ -95,6 +105,7 @@ func _load_compatibility_mode() -> void: SoundSingleton.load_music() MusicManager.add_compat_songs() + func setup_title_theme() -> void: SoundSingleton.load_title_theme() @@ -111,6 +122,8 @@ func setup_title_theme() -> void: # REQUIREMENTS # * FS-333, FS-334, FS-335, FS-341 + + func _initialize_game() -> void: if Vic2Settings.get_base_defines_path().is_empty(): return @@ -127,10 +140,11 @@ func _initialize_game() -> void: loading_screen.try_update_loading_screen(100) var end := Time.get_ticks_usec() - print("Loading took ", float(end - start) / 1000000, " seconds") + print("Loading took ", float(end - start) / 1_000_000, " seconds") # change scene in a thread-safe way get_tree().change_scene_to_packed.call_deferred(GameMenuScene) + func _on_splash_container_splash_end() -> void: loading_screen.show() diff --git a/game/src/Systems/Startup/LoadingScreen.gd b/game/src/Systems/Startup/LoadingScreen.gd index 2b96bed5..4a896afb 100644 --- a/game/src/Systems/Startup/LoadingScreen.gd +++ b/game/src/Systems/Startup/LoadingScreen.gd @@ -1,7 +1,6 @@ extends Control -@export var quote_file_path : String = "res://assets/localisation/quotes.txt" - +@export var quote_file_path: String = "res://assets/localisation/quotes.txt" @export_subgroup("Nodes") @export var progress_bar: ProgressBar @export var quote_label: Label @@ -10,7 +9,24 @@ extends Control var thread: Thread var quotes: PackedStringArray = [] -func start_loading_screen(thread_safe_function : Callable) -> void: + +func _ready() -> void: + if Engine.is_editor_hint(): return + thread = Thread.new() + # FS-3, UI-30, UIFUN-35 + var quotes_file := FileAccess.open(quote_file_path, FileAccess.READ).get_as_text() + quotes = quotes_file.split("\n", false) + if quotes.is_empty(): + quotes = [""] + animation_player.play("loadingscreen_gear") + + +func _exit_tree() -> void: + if thread != null and thread.is_started(): + thread.wait_to_finish() + + +func start_loading_screen(thread_safe_function: Callable) -> void: if not is_node_ready(): await ready # set first quote @@ -25,23 +41,10 @@ func start_loading_screen(thread_safe_function : Callable) -> void: thread.start(thread_safe_function) + func try_update_loading_screen(percent_complete: float, quote_should_change := false) -> void: # forces the function to behave as if deferred await get_tree().process_frame progress_bar.value = percent_complete if quote_should_change and quotes.size() > 0: quote_label.text = quotes[randi() % quotes.size()] - -func _ready() -> void: - if Engine.is_editor_hint(): return - thread = Thread.new() - # FS-3, UI-30, UIFUN-35 - var quotes_file := FileAccess.open(quote_file_path, FileAccess.READ).get_as_text() - quotes = quotes_file.split("\n", false) - if quotes.is_empty(): - quotes = [""] - animation_player.play("loadingscreen_gear") - -func _exit_tree() -> void: - if thread != null and thread.is_started(): - thread.wait_to_finish() diff --git a/game/src/Systems/Startup/SplashContainer.gd b/game/src/Systems/Startup/SplashContainer.gd index e74620b2..859480be 100644 --- a/game/src/Systems/Startup/SplashContainer.gd +++ b/game/src/Systems/Startup/SplashContainer.gd @@ -2,18 +2,20 @@ extends Control signal splash_end -@export var _splash_finish : TextureRect -@export var _splash_image : TextureRect -@export var _splash_video : VideoStreamPlayer +@export var _splash_finish: TextureRect +@export var _splash_image: TextureRect +@export var _splash_video: VideoStreamPlayer -func _process(_delta : float) -> void: + +func _process(_delta: float) -> void: var stream_texture := _splash_video.get_video_texture() if stream_texture != null and not stream_texture.get_image().is_invisible(): _splash_image.hide() _splash_finish.show() set_process(false) -func _input(event : InputEvent) -> void: + +func _input(event: InputEvent) -> void: if (event is InputEventKey\ or event is InputEventMouse\ or event is InputEventScreenTouch\ @@ -22,6 +24,7 @@ func _input(event : InputEvent) -> void: _on_splash_startup_finished() accept_event() + func _on_splash_startup_finished() -> void: set_process_input(false) splash_end.emit() diff --git a/game/src/UI/GameMenu/CreditsMenu/CreditsMenu.gd b/game/src/UI/GameMenu/CreditsMenu/CreditsMenu.gd index 2f1e867a..868ed841 100644 --- a/game/src/UI/GameMenu/CreditsMenu/CreditsMenu.gd +++ b/game/src/UI/GameMenu/CreditsMenu/CreditsMenu.gd @@ -2,6 +2,8 @@ extends Control signal back_button_pressed +const title_key: String = "TITLE" + ############### # Credits CSV format # The project title row is the only requirement within the csv file, however @@ -15,25 +17,35 @@ signal back_button_pressed ############### @export_file("*.csv") -var core_credits_path : String - +var core_credits_path: String @export -var godot_engine_scene : PackedScene - +var godot_engine_scene: PackedScene @export_group("Label Variants", "label_variants_") @export -var label_variants_project : StringName - +var label_variants_project: StringName @export -var label_variants_role : StringName - +var label_variants_role: StringName @export -var label_variants_person : StringName - +var label_variants_person: StringName @export var credits_list: VBoxContainer -const title_key : String = "TITLE" + +# REQUIREMENTS: +# * SS-17 + + +func _ready() -> void: + _add_project_credits(_load_credit_file(core_credits_path)) + _add_godot_credits() + _add_licenses() + + +func _input(event: InputEvent) -> void: + if self.is_visible_in_tree(): + if event.is_action_pressed("ui_cancel"): + _on_back_button_pressed() + # REQUIREMENTS: # * 1.5 Credits Menu @@ -41,7 +53,9 @@ const title_key : String = "TITLE" # REQUIREMENTS # * FS-4 -func _load_credit_file(path : String) -> Dictionary: + + +func _load_credit_file(path: String) -> Dictionary: var roles := {} var core_credits := FileAccess.open(path, FileAccess.READ) if core_credits == null: @@ -79,36 +93,40 @@ func _load_credit_file(path : String) -> Dictionary: roles[title_key] = [roles[title_key][0]] else: push_warning("Credits file %s missing %s" % [path, title_key]) - for role_list : Array in roles.values(): - role_list.sort_custom(func(a : String, b : String) -> bool: return a.naturalnocasecmp_to(b) < 0) + for role_list: Array in roles.values(): + role_list.sort_custom(func(a: String, b: String) -> bool: return a.naturalnocasecmp_to(b) < 0) return roles -func _add_label(node : Node, text : String, type_variation : StringName) -> void: + +func _add_label(node: Node, text: String, type_variation: StringName) -> void: var label := Label.new() - label.name = 'Label' + text + label.name = "Label" + text label.text = text label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.theme_type_variation = type_variation node.add_child(label) + # REQUIREMENTS: # * UI-34, UI-35 -func _add_project_credits(project : Dictionary) -> void: + + +func _add_project_credits(project: Dictionary) -> void: var project_credits_list := VBoxContainer.new() - project_credits_list.name = 'Credits' + project_credits_list.name = "Credits" if title_key in project: - var title : String = project[title_key][0] + var title: String = project[title_key][0] project_credits_list.name += title _add_label(project_credits_list, title, label_variants_project) project_credits_list.add_child(HSeparator.new()) - for role : String in project: + for role: String in project: if role == title_key: continue var role_parent := VBoxContainer.new() - for person : String in project[role]: + for person: String in project[role]: _add_label(role_parent, person, label_variants_person) _add_label(project_credits_list, role, label_variants_role) @@ -117,9 +135,10 @@ func _add_project_credits(project : Dictionary) -> void: credits_list.add_child(project_credits_list) + func _add_godot_credits() -> void: var godot_credits_list := VBoxContainer.new() - godot_credits_list.name = 'CreditsGodot' + godot_credits_list.name = "CreditsGodot" var godot_engine := godot_engine_scene.instantiate() godot_credits_list.add_child(godot_engine) godot_credits_list.add_child(HSeparator.new()) @@ -127,10 +146,10 @@ func _add_godot_credits() -> void: var author_dict := Engine.get_author_info() _add_label(godot_credits_list, "Contributors", label_variants_role) - for role : String in author_dict: + for role: String in author_dict: var role_parent := VBoxContainer.new() - for person : String in author_dict[role]: + for person: String in author_dict[role]: _add_label(role_parent, person, label_variants_person) _add_label(godot_credits_list, role.replace("_", " ").capitalize(), label_variants_role) @@ -140,11 +159,11 @@ func _add_godot_credits() -> void: var donor_dict := Engine.get_donor_info() _add_label(godot_credits_list, "Donors", label_variants_role) - for role : String in donor_dict: + for role: String in donor_dict: if donor_dict[role].size() == 0 or donor_dict[role][0].begins_with("None"): continue var role_parent := VBoxContainer.new() - for person : String in donor_dict[role]: + for person: String in donor_dict[role]: _add_label(role_parent, person, label_variants_person) _add_label(godot_credits_list, role.replace("_", " ").capitalize(), label_variants_role) @@ -153,53 +172,53 @@ func _add_godot_credits() -> void: credits_list.add_child(godot_credits_list) -func _add_link_button(node : Node, text : String, url: String, type_variation : StringName) -> void: + +func _add_link_button(node: Node, text: String, url: String, type_variation: StringName) -> void: var button := LinkButton.new() - button.name = 'LinkButton' + text + button.name = "LinkButton" + text button.text = text button.uri = url button.size_flags_horizontal = SIZE_SHRINK_CENTER button.theme_type_variation = type_variation node.add_child(button) + func _add_licenses() -> void: var license_list := VBoxContainer.new() - license_list.name = 'Licenses' + license_list.name = "Licenses" _add_label(license_list, "Third-Party Licenses", label_variants_project) license_list.add_child(HSeparator.new()) var license_info := { "OpenVic": ["GPLv3", "https://github.com/OpenVicProject/OpenVic/blob/main/LICENSE.md"], "Godot": ["MIT", "https://github.com/godotengine/godot/blob/master/LICENSE.txt"], - "FreeType": ["FreeType License", "https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT"], + "FreeType": [ + "FreeType License", + "https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT", + ], "ENet": ["MIT", "http://enet.bespin.org/License.html"], - "mbed TLS": ["APLv2", "https://github.com/Mbed-TLS/mbedtls/blob/development/LICENSE"] + "mbed TLS": ["APLv2", "https://github.com/Mbed-TLS/mbedtls/blob/development/LICENSE"], } # Add additional licenses required for attribution here # These licenses should also either be displayed or exported alongside this project - for project : String in license_info: + for project: String in license_info: _add_label(license_list, project, label_variants_role) - _add_link_button(license_list, license_info[project][0], license_info[project][1], label_variants_person) + _add_link_button( + license_list, + license_info[project][0], + license_info[project][1], + label_variants_person, + ) license_list.add_child(HSeparator.new()) credits_list.add_child(license_list) -# REQUIREMENTS: -# * SS-17 -func _ready() -> void: - _add_project_credits(_load_credit_file(core_credits_path)) - _add_godot_credits() - _add_licenses() - -func _input(event : InputEvent) -> void: - if self.is_visible_in_tree(): - if event.is_action_pressed("ui_cancel"): - _on_back_button_pressed() - # REQUIREMENTS: # * UI-38 # * UIFUN-37 + + func _on_back_button_pressed() -> void: back_button_pressed.emit() diff --git a/game/src/UI/GameMenu/CreditsMenu/GodotEngineButton.gd b/game/src/UI/GameMenu/CreditsMenu/GodotEngineButton.gd index f8d6dd31..31f22654 100644 --- a/game/src/UI/GameMenu/CreditsMenu/GodotEngineButton.gd +++ b/game/src/UI/GameMenu/CreditsMenu/GodotEngineButton.gd @@ -1,4 +1,5 @@ extends Button + func _on_pressed() -> void: OS.shell_open("https://godotengine.org") diff --git a/game/src/UI/GameMenu/GameMenu/GameMenu.gd b/game/src/UI/GameMenu/GameMenu/GameMenu.gd index 03705dcc..691a86e6 100644 --- a/game/src/UI/GameMenu/GameMenu/GameMenu.gd +++ b/game/src/UI/GameMenu/GameMenu/GameMenu.gd @@ -1,13 +1,15 @@ extends Control -@export var _main_menu : Control -@export var _options_menu : Control -@export var _multiplayer_menu : Control -@export var _lobby_menu : Control -@export var _credits_menu : Control +@export var _main_menu: Control +@export var _options_menu: Control +@export var _multiplayer_menu: Control +@export var _lobby_menu: Control +@export var _credits_menu: Control # REQUIREMENTS # * SS-10 + + func _on_main_menu_new_game_button_pressed() -> void: _lobby_menu.show() _main_menu.hide() @@ -15,6 +17,8 @@ func _on_main_menu_new_game_button_pressed() -> void: # REQUIREMENTS # * SS-6 # * UIFUN-5 + + func _on_main_menu_options_button_pressed() -> void: _options_menu.show() _main_menu.hide() diff --git a/game/src/UI/GameMenu/LobbyMenu/LobbyMenu.gd b/game/src/UI/GameMenu/LobbyMenu/LobbyMenu.gd index cc63a51a..ee5706a5 100644 --- a/game/src/UI/GameMenu/LobbyMenu/LobbyMenu.gd +++ b/game/src/UI/GameMenu/LobbyMenu/LobbyMenu.gd @@ -1,32 +1,38 @@ extends HBoxContainer + # REQUIREMENTS: # * 1.4 Game Lobby Menu # * SS-12 signal back_button_pressed -signal save_game_selected(save : SaveResource) -signal start_date_selected(index : int) - -@export var lobby_panel_button : PackedScene -@export var save_scene : PackedScene +signal save_game_selected(save: SaveResource) +signal start_date_selected(index: int) +@export var lobby_panel_button: PackedScene +@export var save_scene: PackedScene @export_group("Nodes") -@export var game_select_start_date : BoxContainer -@export var game_select_save_tab : TabBar -@export var game_select_save_list : BoxContainer -@export var start_button : BaseButton -@export var session_tag_line_edit : LineEdit -@export var session_tag_dialog : ConfirmationDialog -@export var delete_dialog : ConfirmationDialog -@export var map_view : MapView +@export var game_select_start_date: BoxContainer +@export var game_select_save_tab: TabBar +@export var game_select_save_list: BoxContainer +@export var start_button: BaseButton +@export var session_tag_line_edit: LineEdit +@export var session_tag_dialog: ConfirmationDialog +@export var delete_dialog: ConfirmationDialog +@export var map_view: MapView + +var _id_to_tag: Array[StringName] = [] +var _requested_node_to_delete: Control +var _start_date_index: int = -1 + func _ready() -> void: # TODO: Needs to be able to set the map to the political mapmode pass -func filter_for_tag(tag : StringName) -> void: - for child : Control in game_select_save_list.get_children(): + +func filter_for_tag(tag: StringName) -> void: + for child: Control in game_select_save_list.get_children(): if tag.is_empty(): child.show() else: @@ -35,57 +41,67 @@ func filter_for_tag(tag : StringName) -> void: else: child.hide() + func _build_date_list() -> void: - const bookmark_info_name_key : StringName = &"bookmark_name" - const bookmark_info_date_key : StringName = &"bookmark_date" + const bookmark_info_name_key: StringName = &"bookmark_name" + const bookmark_info_date_key: StringName = &"bookmark_date" - for bookmark_dict : Dictionary in GameSingleton.get_bookmark_info(): + for bookmark_dict: Dictionary in GameSingleton.get_bookmark_info(): var start_date := lobby_panel_button.instantiate() start_date.set_name_text(bookmark_dict.get(bookmark_info_name_key, "MISSING BOOKMARK NAME")) start_date.set_date_text(bookmark_dict.get(bookmark_info_date_key, "MISSING BOOKMARK DATE")) start_date.pressed.connect(_on_start_date_panel_button_pressed.bind(start_date)) game_select_start_date.add_child(start_date) -var _id_to_tag : Array[StringName] = [] # Requirements # * FS-8 + + func _build_save_list() -> void: game_select_save_tab.add_tab("GAMELOBBY_SELECT_ALL") - for save_name : StringName in SaveManager._save_dictionary: - var save : SaveResource = SaveManager._save_dictionary[save_name] + for save_name: StringName in SaveManager._save_dictionary: + var save: SaveResource = SaveManager._save_dictionary[save_name] var save_node := _create_save_node(save) game_select_save_list.add_child(save_node) if not _id_to_tag.has(save.session_tag): _id_to_tag.append(save.session_tag) game_select_save_tab.add_tab(save.session_tag) -func _create_save_node(resource : SaveResource) -> Control: + +func _create_save_node(resource: SaveResource) -> Control: var save_node := save_scene.instantiate() save_node.resource = resource save_node.pressed.connect(_on_save_node_pressed.bind(save_node)) save_node.request_to_delete.connect(_on_save_node_delete_requested.bind(save_node)) return save_node + func _queue_clear_lists() -> void: var full_list := game_select_start_date.get_children() full_list.append_array(game_select_save_list.get_children()) - for child : Node in full_list: + for child: Node in full_list: child.queue_free() game_select_save_tab.clear_tabs() _id_to_tag.clear() + # REQUIREMENTS: # * SS-16 # * UIFUN-40 + + func _on_back_button_pressed() -> void: print("Returning to Main Menu.") SaveManager.current_session_tag = "" SaveManager.current_save = null back_button_pressed.emit() + # REQUIREMENTS: # * SS-21 + + func _on_start_button_pressed() -> void: print("Starting new game.") if SaveManager.current_session_tag == "": @@ -97,7 +113,7 @@ func _on_start_button_pressed() -> void: datetime["year"], datetime["hour"], datetime["minute"], - datetime["second"] + datetime["second"], ] if SaveManager.current_save == null and SaveManager.current_session_tag in _id_to_tag: session_tag_dialog.dialog_text = tr("GAMELOBBY_SESSIONTAG_DIALOG_TEXT").format({ "session_tag": SaveManager.current_session_tag }) @@ -106,21 +122,29 @@ func _on_start_button_pressed() -> void: else: _on_session_tag_dialog_confirmed() + # REQUIREMENTS: # * SS-19 + + func _on_game_select_list_item_selected(index) -> void: print("Selected save game: ", index) save_game_selected.emit(index) + # If the date is double-clicked, start the game! + + func _on_game_select_list_item_activated(index) -> void: _on_game_select_list_item_selected(index) _on_start_button_pressed() -func _on_session_tag_edit_text_submitted(new_text : String) -> void: + +func _on_session_tag_edit_text_submitted(new_text: String) -> void: SaveManager.current_session_tag = new_text _on_start_button_pressed() + func _on_session_tag_dialog_confirmed() -> void: if _start_date_index < 0: push_error("Cannot setup game, no start date selected!") @@ -131,15 +155,15 @@ func _on_session_tag_dialog_confirmed() -> void: push_error("Failed to setup game") get_tree().change_scene_to_file("res://src/Systems/Session/GameSession.tscn") -var _requested_node_to_delete : Control -func _on_save_node_delete_requested(node : Control) -> void: + +func _on_save_node_delete_requested(node: Control) -> void: _requested_node_to_delete = node delete_dialog.dialog_text = tr("GAMELOBBY_DELETE_DIALOG_TEXT").format({ "file_name": _requested_node_to_delete.resource.save_name }) delete_dialog.title = tr("GAMELOBBY_DELETE_DIALOG_TITLE").format({ "file_name": _requested_node_to_delete.resource.save_name }) delete_dialog.popup_centered() -var _start_date_index : int = -1 -func _on_start_date_panel_button_pressed(node : Control) -> void: + +func _on_start_date_panel_button_pressed(node: Control) -> void: if node is LobbyPanelButton and node.get_index(true) == _start_date_index: _on_start_button_pressed() return @@ -147,7 +171,8 @@ func _on_start_date_panel_button_pressed(node : Control) -> void: start_button.disabled = false start_date_selected.emit(_start_date_index) -func _on_save_node_pressed(node : Control) -> void: + +func _on_save_node_pressed(node: Control) -> void: if SaveManager.current_save != null and SaveManager.current_save == node.resource: SaveManager.current_session_tag = SaveManager.current_save.session_tag _on_start_button_pressed() @@ -160,16 +185,19 @@ func _on_save_node_pressed(node : Control) -> void: start_button.disabled = false save_game_selected.emit(SaveManager.current_save) -func _on_game_select_save_tab_tab_changed(tab : int) -> void: + +func _on_game_select_save_tab_tab_changed(tab: int) -> void: if tab == 0: filter_for_tag(&"") else: filter_for_tag(_id_to_tag[tab - 1]) + func _on_delete_dialog_confirmed() -> void: _requested_node_to_delete.resource.delete() _requested_node_to_delete.queue_free() + func _on_visibility_changed() -> void: if visible: _build_date_list() @@ -177,11 +205,14 @@ func _on_visibility_changed() -> void: else: _queue_clear_lists() + func _on_map_view_ready() -> void: # TODO: Start at the bookmark's start position (used when loading a bookmark in the lobby) # GameSingleton.get_bookmark_start_position() pass + func _on_map_view_province_clicked(province_number: int) -> void: - # TODO: need to be able to call something like PlayerSingleton.set_player_country_by_province_number(province_number) here + # TODO: need to be able to call something like + # PlayerSingleton.set_player_country_by_province_number(province_number) here pass diff --git a/game/src/UI/GameMenu/LobbyMenu/LobbyPanelButton.gd b/game/src/UI/GameMenu/LobbyMenu/LobbyPanelButton.gd index 2add942c..862aad23 100644 --- a/game/src/UI/GameMenu/LobbyMenu/LobbyPanelButton.gd +++ b/game/src/UI/GameMenu/LobbyMenu/LobbyPanelButton.gd @@ -5,42 +5,62 @@ extends Container signal button_down signal button_up signal pressed -signal toggled(button_pressed : bool) - -var is_start_date : bool: - get = _is_start_date - -func _is_start_date() -> bool: - return true +signal toggled(button_pressed: bool) @export_group("Nodes") -@export var background_button : BaseButton -@export var name_label : Label -@export var date_label : Label +@export var background_button: BaseButton +@export var name_label: Label +@export var date_label: Label -var name_text : String: +var is_start_date: bool: + get = _is_start_date +var name_text: String: get = get_name_text, set = set_name_text - -var date_text : String: +var date_text: String: get = get_date_text, set = set_date_text + +func _notification(what: int) -> void: + if what == NOTIFICATION_SORT_CHILDREN: + var _size := size + var offset := Vector2() + var style := _get_draw_mode_style() + if style != null: + _size -= style.get_minimum_size() + offset += style.get_offset() + + for child: Control in get_children(): + if child == null or not child.is_visible_in_tree() or child.top_level: + continue + + fit_child_in_rect(child, Rect2(offset, _size)) + + +func _is_start_date() -> bool: + return true + + func get_name_text() -> String: return name_label.text -func set_name_text(value : String) -> void: + +func set_name_text(value: String) -> void: name_label.text = value + func get_date_text() -> String: return date_label.text -func set_date_text(value : String) -> void: + +func set_date_text(value: String) -> void: date_label.text = value + func _get_minimum_size() -> Vector2: var result := Vector2() - for child : Control in get_children(): + for child: Control in get_children(): if child == null or not child.visible: continue if child.top_level: @@ -54,7 +74,8 @@ func _get_minimum_size() -> Vector2: return result -func _get_draw_mode_name(support_rtl : bool = true) -> StringName: + +func _get_draw_mode_name(support_rtl: bool = true) -> StringName: var rtl := support_rtl and background_button != null and background_button.is_layout_rtl() match background_button.get_draw_mode() if background_button != null else BaseButton.DrawMode.DRAW_NORMAL: BaseButton.DrawMode.DRAW_NORMAL: @@ -74,6 +95,7 @@ func _get_draw_mode_name(support_rtl : bool = true) -> StringName: return &"hover_pressed" return &"" + func _get_draw_mode_style() -> StyleBox: if background_button == null: return null var result := background_button.get_theme_stylebox(_get_draw_mode_name()) @@ -81,29 +103,18 @@ func _get_draw_mode_style() -> StyleBox: return background_button.get_theme_stylebox(_get_draw_mode_name(false)) return result -func _notification(what : int) -> void: - if what == NOTIFICATION_SORT_CHILDREN: - var _size := size - var offset := Vector2() - var style := _get_draw_mode_style() - if style != null: - _size -= style.get_minimum_size() - offset += style.get_offset() - - for child : Control in get_children(): - if child == null or not child.is_visible_in_tree() or child.top_level: - continue - - fit_child_in_rect(child, Rect2(offset, _size)) func _on_background_button_button_down() -> void: button_down.emit() + func _on_background_button_button_up() -> void: button_up.emit() + func _on_background_button_pressed() -> void: pressed.emit() -func _on_background_button_toggled(button_pressed : bool) -> void: + +func _on_background_button_toggled(button_pressed: bool) -> void: toggled.emit(button_pressed) diff --git a/game/src/UI/GameMenu/MainMenu/MainMenu.gd b/game/src/UI/GameMenu/MainMenu/MainMenu.gd index 5c87f7ba..d831b3d1 100644 --- a/game/src/UI/GameMenu/MainMenu/MainMenu.gd +++ b/game/src/UI/GameMenu/MainMenu/MainMenu.gd @@ -7,15 +7,16 @@ signal multiplayer_button_pressed signal continue_button_pressed @export -var _new_game_button : BaseButton - +var _new_game_button: BaseButton @export -var _bottom_margin : MarginContainer +var _bottom_margin: MarginContainer -var _language_button : SettingsContainer.EnumOptionButton +var _language_button: SettingsContainer.EnumOptionButton # REQUIREMENTS: # * SS-3 + + func _ready() -> void: _on_new_game_button_visibility_changed() @@ -27,6 +28,7 @@ func _ready() -> void: GameSettings.changed.connect(self._on_setting_updated) GameSettings.staged_changed.connect(self._on_setting_updated) + func _on_setting_updated(key: StringName) -> void: if key != GameSettings.GENERAL_LANGUAGE: return _language_button.update() @@ -34,18 +36,23 @@ func _on_setting_updated(key: StringName) -> void: # REQUIREMENTS: # * SS-14 # * UIFUN-32 + + func _on_new_game_button_pressed() -> void: print("Start a new game!") new_game_button_pressed.emit() + func _on_continue_button_pressed() -> void: print("Continue last game!") continue_button_pressed.emit() + func _on_multi_player_button_pressed() -> void: print("Have fun with friends!") multiplayer_button_pressed.emit() + func _on_options_button_pressed() -> void: print("Check out some options!") options_button_pressed.emit() @@ -53,17 +60,22 @@ func _on_options_button_pressed() -> void: # REQUIREMENTS # * UI-32 # * UIFUN-36 + + func _on_credits_button_pressed() -> void: credits_button_pressed.emit() # REQUIREMENTS # * SS-4 # * UIFUN-3 + + func _on_exit_button_pressed() -> void: print("See you later!") get_tree().root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST) get_tree().quit() + func _on_new_game_button_visibility_changed() -> void: if visible: _new_game_button.grab_focus.call_deferred() diff --git a/game/src/UI/GameMenu/MultiplayerMenu/ConnectionFailDialog.gd b/game/src/UI/GameMenu/MultiplayerMenu/ConnectionFailDialog.gd index a448ba96..c5c67a1a 100644 --- a/game/src/UI/GameMenu/MultiplayerMenu/ConnectionFailDialog.gd +++ b/game/src/UI/GameMenu/MultiplayerMenu/ConnectionFailDialog.gd @@ -1,21 +1,26 @@ extends AcceptDialog class_name ConnectionFailDialog -enum FAIL_REASONS {NO_SERVER, PASSWORD, BAD_IP} +enum FAIL_REASONS { + NO_SERVER, + PASSWORD, + BAD_IP, +} -@export var reason_text : String -@export var no_server_fail_text : String -@export var bad_password_fail_text : String -@export var bad_ip_text : String +@export var reason_text: String +@export var no_server_fail_text: String +@export var bad_password_fail_text: String +@export var bad_ip_text: String -@onready var reason_text_map : Dictionary = { - FAIL_REASONS.NO_SERVER : no_server_fail_text, - FAIL_REASONS.PASSWORD : bad_password_fail_text, - FAIL_REASONS.BAD_IP : bad_ip_text +@onready var reason_text_map: Dictionary = { + FAIL_REASONS.NO_SERVER: no_server_fail_text, + FAIL_REASONS.PASSWORD: bad_password_fail_text, + FAIL_REASONS.BAD_IP: bad_ip_text, } -@export var label : Label +@export var label: Label + -func display(reason : FAIL_REASONS) -> void: +func display(reason: FAIL_REASONS) -> void: label.text = "%s %s" % [tr(reason_text), tr(reason_text_map[reason])] popup_centered() diff --git a/game/src/UI/GameMenu/MultiplayerMenu/DirectConnectionEntry.gd b/game/src/UI/GameMenu/MultiplayerMenu/DirectConnectionEntry.gd index 35da725f..a0d0ec61 100644 --- a/game/src/UI/GameMenu/MultiplayerMenu/DirectConnectionEntry.gd +++ b/game/src/UI/GameMenu/MultiplayerMenu/DirectConnectionEntry.gd @@ -1,23 +1,27 @@ -extends HBoxContainer class_name DirectConnectionEntry +extends HBoxContainer -@export var ip_edit : SecretEdit -@export var name_edit : LineEdit -@export var connect_button : Button -@export var delete_button : Button # Collects connect and delete presses from its children, and emits # the corresponding signals for the connection controller to handle -signal connect_to_ip(ip : String) +signal connect_to_ip(ip: String) signal delete() -func setup(name_in : StringName, ip : StringName) -> void: +@export var ip_edit: SecretEdit +@export var name_edit: LineEdit +@export var connect_button: Button +@export var delete_button: Button + + +func setup(name_in: StringName, ip: StringName) -> void: name_edit.text = name_in ip_edit.set_text(ip) + func _on_connect_pressed() -> void: connect_to_ip.emit(ip_edit.get_text()) + func _on_delete_pressed() -> void: delete.emit() diff --git a/game/src/UI/GameMenu/MultiplayerMenu/DirectConnectionTab.gd b/game/src/UI/GameMenu/MultiplayerMenu/DirectConnectionTab.gd index 616eb86e..320eb392 100644 --- a/game/src/UI/GameMenu/MultiplayerMenu/DirectConnectionTab.gd +++ b/game/src/UI/GameMenu/MultiplayerMenu/DirectConnectionTab.gd @@ -1,74 +1,85 @@ -extends HBoxContainer class_name DirectConnectionTab +extends HBoxContainer -signal connect(ip : String, player_name : String) -signal save_ips(names : PackedStringArray, ips : PackedStringArray) +signal connect(ip: String, player_name: String) +signal save_ips(names: PackedStringArray, ips: PackedStringArray) signal revert_ips() -signal player_name_changed(player_name : String) +signal player_name_changed(player_name: String) @export var initial_focus: Control -@export var ip_edit : SecretEdit -@export var player_name_edit : LineEdit -@export var game_name_edit : LineEdit -@export var IP_grid : VBoxContainer +@export var ip_edit: SecretEdit +@export var player_name_edit: LineEdit +@export var game_name_edit: LineEdit +@export var IP_grid: VBoxContainer -var connection_entry : PackedScene = preload("res://src/UI/GameMenu/MultiplayerMenu/DirectConnectionEntry.tscn") +var connection_entry: PackedScene = preload("res://src/UI/GameMenu/MultiplayerMenu/DirectConnectionEntry.tscn") -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match(what): NOTIFICATION_VISIBILITY_CHANGED: if visible and is_inside_tree(): initial_focus.grab_focus() + func _on_connect_pressed() -> void: - var player_name : StringName = player_name_edit.text + var player_name: StringName = player_name_edit.text if player_name.is_empty(): player_name = player_name_edit.placeholder_text - connect.emit(ip_edit.get_text(),player_name) + connect.emit(ip_edit.get_text(), player_name) -func _on_connect_pressed_custom_ip(ip : String) -> void: - var player_name : StringName = player_name_edit.text + +func _on_connect_pressed_custom_ip(ip: String) -> void: + var player_name: StringName = player_name_edit.text if player_name.is_empty(): player_name = player_name_edit.placeholder_text - connect.emit(ip,player_name) + connect.emit(ip, player_name) + -func initial_setup(player_name : StringName) -> void: +func initial_setup(player_name: StringName) -> void: player_name_edit.text = player_name + func new_ip_entry() -> void: - var game_name : String = game_name_edit.text + var game_name: String = game_name_edit.text if game_name.is_empty(): game_name_edit.placeholder_text - add_ip_entry_to_list(game_name,ip_edit.get_text()) + add_ip_entry_to_list(game_name, ip_edit.get_text()) + -func add_ip_entry_to_list(connection_name : String, ip : String) -> void: - var entry : DirectConnectionEntry = connection_entry.instantiate() +func add_ip_entry_to_list(connection_name: String, ip: String) -> void: + var entry: DirectConnectionEntry = connection_entry.instantiate() IP_grid.add_child(entry) - + entry.name_edit.text = connection_name entry.ip_edit.set_text(ip) - + entry.connect_to_ip.connect(_on_connect_pressed_custom_ip) entry.delete.connect(delete_entry.bind(entry)) -func delete_entry(entry : DirectConnectionEntry) -> void: + +func delete_entry(entry: DirectConnectionEntry) -> void: entry.queue_free() -func set_player_name(player_name : String) -> void: + +func set_player_name(player_name: String) -> void: player_name_edit.text = player_name + func _on_save_ips_pressed() -> void: - var names : PackedStringArray = [] - var ips : PackedStringArray = [] - for entry : DirectConnectionEntry in IP_grid.get_children(): + var names: PackedStringArray = [] + var ips: PackedStringArray = [] + for entry: DirectConnectionEntry in IP_grid.get_children(): names.push_back(entry.name_edit.text) ips.push_back(entry.ip_edit.get_text()) - save_ips.emit(names,ips) + save_ips.emit(names, ips) + func _on_revert_ips_pressed() -> void: - for child : DirectConnectionEntry in IP_grid.get_children(): + for child: DirectConnectionEntry in IP_grid.get_children(): child.queue_free() revert_ips.emit() -func _on_player_name_changed(new_text : String) -> void: + +func _on_player_name_changed(new_text: String) -> void: player_name_changed.emit(new_text) diff --git a/game/src/UI/GameMenu/MultiplayerMenu/HostTab.gd b/game/src/UI/GameMenu/MultiplayerMenu/HostTab.gd index dab20648..ffe933a6 100644 --- a/game/src/UI/GameMenu/MultiplayerMenu/HostTab.gd +++ b/game/src/UI/GameMenu/MultiplayerMenu/HostTab.gd @@ -1,31 +1,38 @@ -extends HBoxContainer class_name HostTab +extends HBoxContainer -@export var initial_focus: Control +signal host(player_name: String, game_name: String, password: String, lan_only: bool) +signal player_name_changed(player_name: String) -@export var player_name_entry : LineEdit -@export var game_name_entry : LineEdit -@export var password_entry : SecretEdit -@export var is_lan : CheckButton +@export var initial_focus: Control +@export var player_name_entry: LineEdit +@export var game_name_entry: LineEdit +@export var password_entry: SecretEdit +@export var is_lan: CheckButton -signal host(player_name : String, game_name : String, password : String, lan_only : bool) -signal player_name_changed(player_name : String) -func _notification(what : int) -> void: +func _notification(what: int) -> void: match(what): NOTIFICATION_VISIBILITY_CHANGED: if visible and is_inside_tree(): initial_focus.grab_focus() -func set_user_values(player_name : StringName, game_name : StringName, game_password : StringName) -> void: + +func set_user_values( + player_name: StringName, + game_name: StringName, + game_password: StringName, +) -> void: player_name_entry.text = player_name game_name_entry.text = game_name password_entry.set_text(game_password) + func _on_host_pressed() -> void: var player_name := get_player_name() var game_name := game_name_entry.text - host.emit(player_name,game_name,password_entry.get_text(), is_lan.button_pressed) + host.emit(player_name, game_name, password_entry.get_text(), is_lan.button_pressed) + func get_player_name() -> StringName: var player_name := player_name_entry.text @@ -33,23 +40,29 @@ func get_player_name() -> StringName: player_name = player_name_entry.placeholder_text return player_name -func set_player_name(player_name : String) -> void: + +func set_player_name(player_name: String) -> void: player_name_entry.text = player_name -func set_game_name(game_name : String) -> void: + +func set_game_name(game_name: String) -> void: game_name_entry.text = game_name -func set_game_password(game_password : String) -> void: + +func set_game_password(game_password: String) -> void: password_entry.set_text(game_password) + func get_game_name() -> StringName: var game_name := game_name_entry.text if game_name.is_empty(): game_name = game_name_entry.placeholder_text return game_name + func get_password() -> StringName: return password_entry.get_text() + func _on_player_name_entry_text_changed(new_text: String) -> void: player_name_changed.emit(new_text) diff --git a/game/src/UI/GameMenu/MultiplayerMenu/MultiplayerMenu.gd b/game/src/UI/GameMenu/MultiplayerMenu/MultiplayerMenu.gd index 5811703e..0ae43e44 100644 --- a/game/src/UI/GameMenu/MultiplayerMenu/MultiplayerMenu.gd +++ b/game/src/UI/GameMenu/MultiplayerMenu/MultiplayerMenu.gd @@ -2,37 +2,44 @@ extends PanelContainer signal back_button_pressed -@export var _tab_container : TabContainer -@export var _password_dialog : PasswordDialog -@export var _connection_fail_dialog : ConnectionFailDialog -@export var _host_tab : HostTab -@export var _direct_connection_tab : DirectConnectionTab +#cg0: 127.0.0.1:80, cg1 = 123.456.789/localhost, cg2=123... only, cg3=localhost only, cg4=:8052, cg5=8052 + +static var IP_PATTERN: StringName = &"^((\\d{1,3}.\\d{1,3}.\\d{1,3}.\\d{1,3})|(localhost))(:(\\d+))?$" +static var IP_REGEX: RegEx = RegEx.new() -var last_ip : StringName = &"" -var last_player_name : StringName = &"" -var last_validated_ip : PackedInt32Array = [] +@export var _tab_container: TabContainer +@export var _password_dialog: PasswordDialog +@export var _connection_fail_dialog: ConnectionFailDialog +@export var _host_tab: HostTab +@export var _direct_connection_tab: DirectConnectionTab -var last_game_name : StringName = &"" -var last_game_password : StringName = &"" +var last_ip: StringName = &"" +var last_player_name: StringName = &"" +var last_validated_ip: PackedInt32Array = [] +var last_game_name: StringName = &"" +var last_game_password: StringName = &"" + +@onready var MP: MultiplayerEventsObject = Events.Multiplayer -@onready var MP : MultiplayerEventsObject = Events.Multiplayer # Called when the node enters the scene tree for the first time. + + func _ready() -> void: #Server browser not implemented yet _tab_container.set_tab_title(0, "MP_DIRECT_CONNECTION") _tab_container.set_tab_title(1, "MP_HOST") # Prepare options menu before loading user settings - var tab_bar : TabBar = _tab_container.get_tab_bar() + var tab_bar: TabBar = _tab_container.get_tab_bar() # This ends up easier to manage then trying to manually recreate the TabContainer's behavior # These buttons can be accessed regardless of the tab var button_list := HBoxContainer.new() button_list.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) button_list.alignment = BoxContainer.ALIGNMENT_END tab_bar.add_child(button_list) - + var back_button := Button.new() back_button.text = "MP_BACK" back_button.pressed.connect(_on_back_button_pressed) @@ -40,46 +47,56 @@ func _ready() -> void: get_viewport().get_window().close_requested.connect(_on_window_close_requested) load_saved_ips() -func _input(event : InputEvent) -> void: + +func _input(event: InputEvent) -> void: if is_visible_in_tree(): if event.is_action_pressed("ui_cancel"): _on_back_button_pressed() + +func _notification(what: int) -> void: + match what: + NOTIFICATION_CRASH: + _on_window_close_requested() + + +func _init() -> void: + if IP_REGEX.compile(IP_PATTERN) != OK: + push_error("Invalid Regex expression used in IP validation pattern IP_PATTERN") + + func _on_back_button_pressed() -> void: last_game_name = _host_tab.get_game_name() last_game_password = _host_tab.get_password() - _save_user(last_player_name,last_game_name,last_game_password) + _save_user(last_player_name, last_game_name, last_game_password) _direct_connection_tab._on_save_ips_pressed() back_button_pressed.emit() + func _on_window_close_requested() -> void: last_game_name = _host_tab.get_game_name() last_game_password = _host_tab.get_password() - _save_user(last_player_name,last_game_name,last_game_password) + _save_user(last_player_name, last_game_name, last_game_password) _direct_connection_tab._on_save_ips_pressed() -func _notification(what : int) -> void: - match what: - NOTIFICATION_CRASH: - _on_window_close_requested() -func join_game(ip : String, player_name : String) -> bool: - var success : bool = false - +func join_game(ip: String, player_name: String) -> bool: + var success: bool = false + last_ip = ip last_player_name = player_name - - var ip_segments : PackedInt32Array = validate_ipv4(ip) + + var ip_segments: PackedInt32Array = validate_ipv4(ip) print(ip_segments) - if(ip_segments.size() != 4 && ip_segments.size() != 5): + if ip_segments.size() != 4 and ip_segments.size() != 5: _connection_fail_dialog.display(ConnectionFailDialog.FAIL_REASONS.BAD_IP) return false last_validated_ip = ip_segments - + #TODO: Send a request to the host asking if a password is needed - var needs_password : bool = check_password_needed(ip) + var needs_password: bool = check_password_needed(ip) #TODO: if the connection fails, show the appropriate fail dialog - + if needs_password: #Let the password dialog call the connect to game function _password_dialog.clear_and_display() @@ -87,18 +104,22 @@ func join_game(ip : String, player_name : String) -> bool: connect_to_game("") return success -func check_password_needed(ip : String) -> bool: + +func check_password_needed(ip: String) -> bool: #TODO: perform the check return true + #This function continues from join_game after the player #has entered a password in the password dialog -func connect_to_game(password : String) -> bool: + + +func connect_to_game(password: String) -> bool: #Use last_ip and last_player_name - var success : bool = false - print("Connecting to a game: IP: %s, Username: %s, Password: %s" % [last_ip,last_player_name,password]) + var success: bool = false + print("Connecting to a game: IP: %s, Username: %s, Password: %s" % [last_ip, last_player_name, password]) #TODO: Connect, use last_validated_ip as the ip - + #TODO: if successful, move to the game lobby. Otherwise, show the appropriate fail dialog. if success: return true @@ -110,89 +131,98 @@ func connect_to_game(password : String) -> bool: _connection_fail_dialog.display(ConnectionFailDialog.FAIL_REASONS.NO_SERVER) return false -#cg0: 127.0.0.1:80, cg1 = 123.456.789/localhost, cg2=123... only, cg3=localhost only, cg4=:8052, cg5=8052 -static var IP_PATTERN : StringName = &"^((\\d{1,3}.\\d{1,3}.\\d{1,3}.\\d{1,3})|(localhost))(:(\\d+))?$" -static var IP_REGEX : RegEx = RegEx.new() - -func _init() -> void: - if IP_REGEX.compile(IP_PATTERN) != OK: - push_error("Invalid Regex expression used in IP validation pattern IP_PATTERN") #returns the subroutes and port in an integer array -func validate_ipv4(ip : String) -> PackedInt32Array: - var result : RegExMatch = IP_REGEX.search(ip) + + +func validate_ipv4(ip: String) -> PackedInt32Array: + var result: RegExMatch = IP_REGEX.search(ip) if result == null: return [] - var ret : PackedInt32Array = [] - if !result: + var ret: PackedInt32Array = [] + if not result: return ret - var domain : StringName = result.get_string(1) + var domain: StringName = result.get_string(1) if domain == &"localhost": - ret = [127,0,0,1] + ret = [127, 0, 0, 1] else: - var split : PackedStringArray = domain.split(".") + var split: PackedStringArray = domain.split(".") for val in split: ret.push_back(int(val)) - + if ret.size() == 4: - var port : String = result.get_string(5) - if !port.is_empty(): + var port: String = result.get_string(5) + if not port.is_empty(): ret.push_back(int(port)) - + return ret -func host_game(player_name : String, game_name : String, password : String = "", lan_only : bool = false) -> bool: + +func host_game( + player_name: String, + game_name: String, + password: String = "", + lan_only: bool = false, +) -> bool: #TODO: Check if connections to us are possible? - var success : bool = true - print("Host game with Name: %s, password: %s, Player: %s, Is lan only %s" % [game_name,password,player_name, lan_only]) + var success: bool = true + print("Host game with Name: %s, password: %s, Player: %s, Is lan only %s" % [game_name, password, player_name, lan_only]) #TODO: Go to the lobby return success + func load_saved_ips() -> void: - var file : ConfigFile = MP.get_ips_config_file() + var file: ConfigFile = MP.get_ips_config_file() - var player_name : StringName = file.get_value(MP.USER,MP.PLAYER_NAME, MP.DEFAULT_PLAYER_NAME) - var game_name : StringName = file.get_value(MP.USER,MP.HOST_GAME_NAME, MP.DEFAULT_GAME_NAME) - var game_password : StringName = file.get_value(MP.USER,MP.HOST_GAME_PASSWORD, MP.DEFAULT_GAME_PASSWORD) + var player_name: StringName = file.get_value(MP.USER, MP.PLAYER_NAME, MP.DEFAULT_PLAYER_NAME) + var game_name: StringName = file.get_value(MP.USER, MP.HOST_GAME_NAME, MP.DEFAULT_GAME_NAME) + var game_password: StringName = file.get_value( + MP.USER, + MP.HOST_GAME_PASSWORD, + MP.DEFAULT_GAME_PASSWORD, + ) last_player_name = player_name last_game_name = game_name last_game_password = game_password - _host_tab.set_user_values(player_name,game_name,game_password) + _host_tab.set_user_values(player_name, game_name, game_password) _direct_connection_tab.initial_setup(player_name) - if !file.has_section(MP.SERVER_NAMES): + if not file.has_section(MP.SERVER_NAMES): return - var indices : PackedStringArray = file.get_section_keys(MP.SERVER_NAMES) + var indices: PackedStringArray = file.get_section_keys(MP.SERVER_NAMES) #if the file is malformed, we will just be missing some entries - for index : String in indices: - var entry_name : String = file.get_value(MP.SERVER_NAMES,index,&"") - var entry_ip : String = file.get_value(MP.SERVER_IPS,index,&"") - _direct_connection_tab.add_ip_entry_to_list(entry_name,entry_ip) + for index: String in indices: + var entry_name: String = file.get_value(MP.SERVER_NAMES, index, &"") + var entry_ip: String = file.get_value(MP.SERVER_IPS, index, &"") + _direct_connection_tab.add_ip_entry_to_list(entry_name, entry_ip) + -func _save_ips(names : PackedStringArray, ips : PackedStringArray) -> void: +func _save_ips(names: PackedStringArray, ips: PackedStringArray) -> void: assert(names.size() == ips.size()) - var file : ConfigFile = MP.get_ips_config_file() + var file: ConfigFile = MP.get_ips_config_file() if file.has_section(MP.SERVER_NAMES): file.erase_section(MP.SERVER_NAMES) if file.has_section(MP.SERVER_IPS): file.erase_section(MP.SERVER_IPS) - - for i : int in names.size(): - var index_str : String = String.num_int64(i) + + for i: int in names.size(): + var index_str: String = String.num_int64(i) file.set_value(MP.SERVER_NAMES, index_str, names[i]) file.set_value(MP.SERVER_IPS, index_str, ips[i]) MP.save_ips_config_file() - -func _save_user(player_name : String, game_name : String, game_password : String) -> void: - var file : ConfigFile = MP.get_ips_config_file() - file.set_value(MP.USER,MP.PLAYER_NAME,player_name) - file.set_value(MP.USER,MP.HOST_GAME_NAME,game_name) - file.set_value(MP.USER,MP.HOST_GAME_PASSWORD,game_password) + + +func _save_user(player_name: String, game_name: String, game_password: String) -> void: + var file: ConfigFile = MP.get_ips_config_file() + file.set_value(MP.USER, MP.PLAYER_NAME, player_name) + file.set_value(MP.USER, MP.HOST_GAME_NAME, game_name) + file.set_value(MP.USER, MP.HOST_GAME_PASSWORD, game_password) MP.save_ips_config_file() - + + func _on_direct_connection_tab_revert_ips() -> void: last_game_name = _host_tab.get_game_name() last_game_password = _host_tab.get_password() @@ -202,10 +232,14 @@ func _on_direct_connection_tab_revert_ips() -> void: _host_tab.set_game_name(last_game_name) _host_tab.set_game_password(last_game_password) -func update_player_name(player_name : String) -> void: + +func update_player_name(player_name: String) -> void: last_player_name = player_name + #Make sure the player name entry is updated on the new tab -func _on_tab_changed(tab : int) -> void: + + +func _on_tab_changed(tab: int) -> void: _direct_connection_tab.set_player_name(last_player_name) _host_tab.set_player_name(last_player_name) diff --git a/game/src/UI/GameMenu/MultiplayerMenu/PasswordDialog.gd b/game/src/UI/GameMenu/MultiplayerMenu/PasswordDialog.gd index c98cf303..e320a4bf 100644 --- a/game/src/UI/GameMenu/MultiplayerMenu/PasswordDialog.gd +++ b/game/src/UI/GameMenu/MultiplayerMenu/PasswordDialog.gd @@ -1,13 +1,15 @@ -extends ConfirmationDialog class_name PasswordDialog +extends ConfirmationDialog -@export var secret : SecretEdit +signal pword_confirmed(password: String) + +@export var secret: SecretEdit -signal pword_confirmed(password : String) func _on_confirmed() -> void: pword_confirmed.emit(secret.get_text()) + func clear_and_display() -> void: secret.set_text("") popup_centered() diff --git a/game/src/UI/GameMenu/MultiplayerMenu/SecretEdit.gd b/game/src/UI/GameMenu/MultiplayerMenu/SecretEdit.gd index a86e0693..4fb79414 100644 --- a/game/src/UI/GameMenu/MultiplayerMenu/SecretEdit.gd +++ b/game/src/UI/GameMenu/MultiplayerMenu/SecretEdit.gd @@ -1,29 +1,35 @@ -extends HBoxContainer class_name SecretEdit +extends HBoxContainer -@export var editable : bool = true -@export var start_hidden : bool = true +@export var editable: bool = true +@export var start_hidden: bool = true +@export var line: LineEdit +@export var check: CheckButton -@export var line : LineEdit -@export var check : CheckButton # Called when the node enters the scene tree for the first time. + + func _ready() -> void: line.editable = editable - + check.button_pressed = start_hidden line.secret = start_hidden + func get_text() -> StringName: return line.text -func set_editable(value : bool) -> void: + +func set_editable(value: bool) -> void: editable = value line.editable = editable -func set_hidden(value : bool) -> void: + +func set_hidden(value: bool) -> void: check.button_pressed = value line.secret = value -func set_text(value : StringName) -> void: + +func set_text(value: StringName) -> void: line.text = value diff --git a/game/src/UI/GameMenu/OptionMenu/KeychainShortcutEdit.gd b/game/src/UI/GameMenu/OptionMenu/KeychainShortcutEdit.gd index 16d5cfdf..35d693ee 100644 --- a/game/src/UI/GameMenu/OptionMenu/KeychainShortcutEdit.gd +++ b/game/src/UI/GameMenu/OptionMenu/KeychainShortcutEdit.gd @@ -1,31 +1,12 @@ extends "res://addons/keychain/ShortcutEdit.gd" -func _add_hotkey(action : StringName, events : Array[InputEvent]) -> void: - Keychain.actions[action] = Keychain.InputAction.new( - action.erase(0, "button_".length()).left(-"_hotkey".length()).capitalize(), - "Hotkeys") - var display_name : String = Keychain.actions[action].display_name - if display_name.begins_with("Mapmode"): - var mapmode_index := display_name.replace("Mapmode", "").to_int() - display_name = tr(GameSingleton.get_mapmode_localisation_key(mapmode_index)) - if mapmode_index <= 10: - display_name = display_name\ - .replace(" Mapmode", "")\ - .replace("Mode de carte ", "")\ - .replace("-Kartenmodus", "")\ - .replace("Modo de mapa de ", "")\ - .replace("Modo mapa de ", "")\ - .replace("Modo mapa ", "") - display_name = tr("Mapmode %s") % display_name.capitalize() - Keychain.actions[action].display_name = display_name - Keychain.selected_profile.bindings[action] = events func _ready() -> void: - var profile_is_dirty : bool = false + var profile_is_dirty: bool = false var default := (Keychain.DEFAULT_PROFILE as ShortcutProfile) # Setup Keychain for Hotkeys - for action : StringName in InputMap.get_actions(): + for action: StringName in InputMap.get_actions(): if not Keychain.keep_binding_check.call(action): continue profile_is_dirty = true @@ -33,7 +14,7 @@ func _ready() -> void: _add_hotkey(action, default.bindings[action]) var has_hotkey_not_loaded_yet := false - for action : StringName in Keychain.selected_profile.bindings: + for action: StringName in Keychain.selected_profile.bindings: if InputMap.has_action(action): continue profile_is_dirty = true InputMap.add_action(action) @@ -48,3 +29,24 @@ func _ready() -> void: Keychain.selected_profile.save() super._ready() + + +func _add_hotkey(action: StringName, events: Array[InputEvent]) -> void: + Keychain.actions[action] = Keychain.InputAction.new( + action.erase(0, "button_".length()).left(-"_hotkey".length()).capitalize(), + "Hotkeys") + var display_name: String = Keychain.actions[action].display_name + if display_name.begins_with("Mapmode"): + var mapmode_index := display_name.replace("Mapmode", "").to_int() + display_name = tr(GameSingleton.get_mapmode_localisation_key(mapmode_index)) + if mapmode_index <= 10: + display_name = display_name\ + .replace(" Mapmode", "")\ + .replace("Mode de carte ", "")\ + .replace("-Kartenmodus", "")\ + .replace("Modo de mapa de ", "")\ + .replace("Modo mapa de ", "")\ + .replace("Modo mapa ", "") + display_name = tr("Mapmode %s") % display_name.capitalize() + Keychain.actions[action].display_name = display_name + Keychain.selected_profile.bindings[action] = events diff --git a/game/src/UI/GameMenu/OptionMenu/OptionsMenu.gd b/game/src/UI/GameMenu/OptionMenu/OptionsMenu.gd index a96fbdc8..59b9e2ba 100644 --- a/game/src/UI/GameMenu/OptionMenu/OptionsMenu.gd +++ b/game/src/UI/GameMenu/OptionMenu/OptionsMenu.gd @@ -1,20 +1,22 @@ extends Control -const AppSettings := preload("res://addons/kenyoni/app_settings/app_settings.gd") # REQUIREMENTS # * SS-13 signal back_button_pressed -@export var _tab_container : TabContainer +const AppSettings := preload("res://addons/kenyoni/app_settings/app_settings.gd") + +@export var _tab_container: TabContainer @export var _settings_container: PackedScene + func _ready() -> void: GameSettings.settings_applied.connect(_on_settings_applied) # Prepare options menu before loading user settings - var tab_bar : TabBar = _tab_container.get_tab_bar() + var tab_bar: TabBar = _tab_container.get_tab_bar() # This ends up easier to manage then trying to manually recreate the TabContainer's behavior # These buttons can be accessed regardless of the tab @@ -33,6 +35,21 @@ func _ready() -> void: _setup_settings() + +func _notification(what: int) -> void: + match what: + NOTIFICATION_VISIBILITY_CHANGED: + set_process_input(is_visible_in_tree()) + NOTIFICATION_CRASH, NOTIFICATION_WM_CLOSE_REQUEST: + _on_window_close_requested() + + +func _input(event: InputEvent) -> void: + if event.is_action_pressed("ui_cancel"): + _on_back_button_pressed() + accept_event() + + func _setup_settings() -> void: _iterate_settings_sections(GameSettings) _iterate_settings_sections(ModSettings) @@ -43,6 +60,7 @@ func _setup_settings() -> void: _tab_container.set_tab_title(3, "OPTIONS_CONTROLS") _tab_container.current_tab = 0 + func _iterate_settings_sections(app_setting: AppSettings) -> void: var tab_count := _tab_container.get_tab_count() - 1 @@ -51,6 +69,7 @@ func _iterate_settings_sections(app_setting: AppSettings) -> void: var section: String = sections[idx] _setup_settings_section(app_setting, tab_count + idx, section) + func _setup_settings_section(app_setting: AppSettings, section_index: int, section: String) -> void: var all_internal := true for setting: AppSettings.Setting in app_setting.get_section(section): @@ -74,24 +93,15 @@ func _setup_settings_section(app_setting: AppSettings, section_index: int, secti for setting: AppSettings.Setting in app_setting.get_section(section.path_join(sub_section)): container.add_setting(setting) -func _notification(what : int) -> void: - match what: - NOTIFICATION_VISIBILITY_CHANGED: - set_process_input(is_visible_in_tree()) - NOTIFICATION_CRASH, NOTIFICATION_WM_CLOSE_REQUEST: - _on_window_close_requested() - -func _input(event : InputEvent) -> void: - if event.is_action_pressed("ui_cancel"): - _on_back_button_pressed() - accept_event() func _on_back_button_pressed() -> void: back_button_pressed.emit() GameSettings.apply_staged_values() + func _on_window_close_requested() -> void: GameSettings.apply_staged_values() + func _on_settings_applied() -> void: GameSettings.save() diff --git a/game/src/UI/GameMenu/OptionMenu/SettingsContainer.gd b/game/src/UI/GameMenu/OptionMenu/SettingsContainer.gd index bc230228..2c0a3c32 100644 --- a/game/src/UI/GameMenu/OptionMenu/SettingsContainer.gd +++ b/game/src/UI/GameMenu/OptionMenu/SettingsContainer.gd @@ -2,15 +2,17 @@ class_name SettingsContainer extends ScrollContainer @export var _container: GridContainer + var section_key: StringName +var _revert_group_to_dialog: Dictionary[GameSettings.RevertGroup, RevertDialog] = {} -var _revert_group_to_dialog : Dictionary[GameSettings.RevertGroup, RevertDialog] = {} func _ready() -> void: GameSettings.changed.connect(_try_update) GameSettings.staged_changed.connect(_try_update) GameSettings.applied.connect(_on_setting_applied) + func add_section(section: String) -> void: if _container.get_child_count() > 0: var spacer: Control = Control.new() @@ -26,15 +28,18 @@ func add_section(section: String) -> void: _container.add_child(Control.new()) _container.add_child(Control.new()) + func add_setting(setting: GameSettings.Setting) -> void: _add_label(setting) _add_edit(setting) _add_reset_button(setting) + func _add_label(setting: GameSettings.Setting) -> void: var label := SettingLabel.new(setting) _container.add_child(label) + func _add_edit(setting: GameSettings.Setting) -> void: var typ: Variant.Type = setting.get_meta(&"type", TYPE_NIL) var type_hint: PropertyHint = setting.get_meta(&"hint", PROPERTY_HINT_NONE) @@ -61,6 +66,7 @@ func _add_edit(setting: GameSettings.Setting) -> void: control.name = setting.key().validate_node_name() _container.add_child(control) + func _add_reset_button(setting: GameSettings.Setting) -> void: # hide reset button if setting is marked having no default value if setting.get_meta(&"no_default", false): @@ -69,15 +75,17 @@ func _add_reset_button(setting: GameSettings.Setting) -> void: var reset_button: ResetButton = ResetButton.new(setting) _container.add_child(reset_button) + func _add_revert_dialog(setting: GameSettings.Setting) -> void: - var revert_group : GameSettings.RevertGroup = setting.get_meta(&"revert_group") - var revert_dialog : RevertDialog = _revert_group_to_dialog.get(revert_group) + var revert_group: GameSettings.RevertGroup = setting.get_meta(&"revert_group") + var revert_dialog: RevertDialog = _revert_group_to_dialog.get(revert_group) if revert_dialog == null: revert_dialog = RevertDialog.new(revert_group) _revert_group_to_dialog[revert_group] = revert_dialog add_child(revert_dialog) setting.set_meta(&"revert_value", setting.value()) + func _try_update(key: StringName) -> void: if not key.begins_with(section_key + "/"): return var child := _container.get_node(key.validate_node_name()) @@ -87,14 +95,15 @@ func _try_update(key: StringName) -> void: var button := (_container.get_child(child.get_index() + 1) as ResetButton) if button: button.update() + func _on_setting_applied(key: StringName) -> void: if not key.begins_with(section_key + "/"): return var stg := GameSettings.get_setting(key) if not stg.has_meta(&"revert_value"): return - var previous_value : Variant = stg.get_meta(&"revert_value") - var revert_group : GameSettings.RevertGroup = stg.get_meta(&"revert_group") - var revert_dialog : RevertDialog = _revert_group_to_dialog[revert_group] + var previous_value: Variant = stg.get_meta(&"revert_value") + var revert_group: GameSettings.RevertGroup = stg.get_meta(&"revert_group") + var revert_dialog: RevertDialog = _revert_group_to_dialog[revert_group] var callable := _on_revert_dialog_revert.bind(stg, previous_value) revert_dialog.reverted.connect(callable) stg.set_meta(&"revert_value", stg.value()) @@ -106,10 +115,12 @@ func _on_setting_applied(key: StringName) -> void: GameSettings.applied.connect(_on_setting_applied), CONNECT_ONE_SHOT) + func _on_revert_dialog_revert(stg: GameSettings.Setting, prev_value: Variant) -> void: stg.set_value(prev_value) stg.set_meta(&"revert_value", prev_value) + class SettingLabel extends Label: var setting: GameSettings.Setting @@ -124,6 +135,7 @@ class SettingLabel extends Label: mouse_filter = Control.MOUSE_FILTER_PASS set_meta(&"setting_key", setting.key()) + class BoolEnumButton extends OptionButton: var setting: GameSettings.Setting @@ -143,6 +155,7 @@ class BoolEnumButton extends OptionButton: else: select(1) + class BoolButton extends CheckButton: var setting: GameSettings.Setting @@ -157,24 +170,17 @@ class BoolButton extends CheckButton: disabled = setting.is_readonly() button_pressed = setting.staged_or_value() + class EnumOptionButton extends OptionButton: var setting: GameSettings.Setting - static func create_button(stg: GameSettings.Setting) -> Control: - var values: Array[Variant] = stg.get_meta(&"values", []) - var display_values: Array[String] = [] - display_values.assign(stg.get_meta(&"display_values", [])) - if values.size() != display_values.size(): - push_error("Setting %s has mismatched values and display_values size.".format(stg.key())) - return PlaceholderLabel.new(stg) - return EnumOptionButton.new(stg) func _init(stg: GameSettings.Setting) -> void: setting = stg var values: Array[Variant] = setting.get_meta(&"values", []) var display_values: Array[String] = [] display_values.assign(setting.get_meta(&"display_values", [])) - var translate_function : Callable = setting.get_meta( + var translate_function: Callable = setting.get_meta( &"translate_value_function", func(_s, _v, display_value: String) -> String: return display_value) @@ -182,7 +188,11 @@ class EnumOptionButton extends OptionButton: size_flags_horizontal = Control.SIZE_EXPAND_FILL var sel_idx: int = 0 for idx: int in range(len(values)): - var translated_display : String = translate_function.call(setting, values[idx], display_values[idx]) + var translated_display: String = translate_function.call( + setting, + values[idx], + display_values[idx], + ) add_item(translated_display) if setting.staged_or_value() == values[idx]: sel_idx = idx @@ -191,7 +201,18 @@ class EnumOptionButton extends OptionButton: setting.set_value(setting.get_meta(&"values")[idx]) ) - func update(index : int = -1) -> void: + + static func create_button(stg: GameSettings.Setting) -> Control: + var values: Array[Variant] = stg.get_meta(&"values", []) + var display_values: Array[String] = [] + display_values.assign(stg.get_meta(&"display_values", [])) + if values.size() != display_values.size(): + push_error("Setting %s has mismatched values and display_values size.".format(stg.key())) + return PlaceholderLabel.new(stg) + return EnumOptionButton.new(stg) + + + func update(index: int = -1) -> void: disabled = setting.is_readonly() if index == -1: var values: Array[Variant] = setting.get_meta(&"values", []) @@ -199,9 +220,10 @@ class EnumOptionButton extends OptionButton: else: select(index) + class RangeSlider extends HBoxContainer: var setting: GameSettings.Setting - var _slider := HSlider.new() + var _slider := HSlider.new() var _spinbox := SpinBox.new() func _init(stg: GameSettings.Setting) -> void: @@ -225,6 +247,7 @@ class RangeSlider extends HBoxContainer: _slider.editable = not setting.is_readonly() _slider.value = setting.staged_or_value() + class FileSelectEdit extends HBoxContainer: var setting: GameSettings.Setting var _line_edit := LineEdit.new() @@ -268,6 +291,7 @@ class FileSelectEdit extends HBoxContainer: setting.set_value(path) update() + class PlaceholderLabel extends Label: var setting: GameSettings.Setting @@ -280,6 +304,7 @@ class PlaceholderLabel extends Label: func update() -> void: pass + class ResetButton extends Button: var setting: GameSettings.Setting @@ -297,18 +322,21 @@ class ResetButton extends Button: func update() -> void: disabled = setting.staged_or_value() == setting.default_value() + class RevertDialog extends ConfirmationDialog: signal accepted() + + signal reverted() - + + var _timer := Timer.new() - var _revert_group : GameSettings.RevertGroup - func show_dialog() -> void: - _timer.start() - popup_centered(Vector2(1,1)) - func _init(revert_group : GameSettings.RevertGroup) -> void: + var _revert_group: GameSettings.RevertGroup + + + func _init(revert_group: GameSettings.RevertGroup) -> void: _revert_group = revert_group title = _revert_group.title cancel_button_text = "DIALOG_CANCEL" @@ -324,24 +352,34 @@ class RevertDialog extends ConfirmationDialog: _timer.one_shot = true _timer.timeout.connect(_on_timer_timeout) - func _process(_delta : float) -> void: + + func _process(_delta: float) -> void: dialog_text = tr(_revert_group.text).format({ "time": Localisation.tr_number(int(_timer.time_left)) }) - func _notification(what : int) -> void: + + func _notification(what: int) -> void: match what: NOTIFICATION_VISIBILITY_CHANGED: set_process(visible) NOTIFICATION_WM_CLOSE_REQUEST, NOTIFICATION_CRASH: _on_canceled() + + func show_dialog() -> void: + _timer.start() + popup_centered(Vector2(1, 1)) + + func _on_confirmed() -> void: _timer.stop() accepted.emit() + func _on_canceled() -> void: _timer.stop() reverted.emit() + func _on_timer_timeout() -> void: hide() reverted.emit() diff --git a/game/src/UI/GameMenu/ReleaseInfoBox/ReleaseInfoBox.gd b/game/src/UI/GameMenu/ReleaseInfoBox/ReleaseInfoBox.gd index 4bbe5c7e..4a725f2d 100644 --- a/game/src/UI/GameMenu/ReleaseInfoBox/ReleaseInfoBox.gd +++ b/game/src/UI/GameMenu/ReleaseInfoBox/ReleaseInfoBox.gd @@ -1,19 +1,19 @@ extends HBoxContainer @export -var _version_label : Button - +var _version_label: Button @export -var _commit_label : Button - +var _commit_label: Button @export -var _checksum_label : Button +var _checksum_label: Button -var _checksum : String = "????" +var _checksum: String = "????" # REQUIREMENTS: # * SS-104, SS-105, SS-106, SS-107 # * UIFUN-97, UIFUN-297, UIFUN-299 + + func _ready() -> void: _version_label.text = tr("MAIMENU_LATEST_RELEASE_NAME").format({ "release_name": GitInfo.release_name }) _version_label.tooltip_text = GitInfo.tag @@ -23,24 +23,30 @@ func _ready() -> void: _checksum = Checksum.get_checksum_text() _update_checksum_label_text() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_checksum_label_text() + func _update_checksum_label_text() -> void: _checksum_label.tooltip_text = tr("MAINMENU_CHECKSUM_TOOLTIP").format({ "checksum": _checksum }) _checksum_label.text = tr("MAINMENU_CHECKSUM").format({ "short_checksum": _checksum.substr(0, 4) }) + func _on_version_label_pressed() -> void: DisplayServer.clipboard_set(GitInfo.tag) + func _on_commit_label_pressed() -> void: DisplayServer.clipboard_set(GitInfo.commit_hash) + func _on_checksum_label_pressed() -> void: DisplayServer.clipboard_set(_checksum) + func _on_game_info_button_pressed() -> void: var project_name := ProjectSettings.get_setting("application/config/name") as String var tag_name := GitInfo.tag @@ -54,4 +60,16 @@ func _on_game_info_button_pressed() -> void: var cpu_name := OS.get_processor_name() var cpu_processor_count := OS.get_processor_count() DisplayServer.clipboard_set("%s %s (%s) [Godot %s] - %s %s - %s - %s (API: %s) - %s (%s Threads)" - % [project_name, tag_name, commit_sha, godot_version, os_name, date_str, display_server, gpu_name, gpu_api_version, cpu_name, cpu_processor_count]) + % [ + project_name, + tag_name, + commit_sha, + godot_version, + os_name, + date_str, + display_server, + gpu_name, + gpu_api_version, + cpu_name, + cpu_processor_count, + ]) diff --git a/game/src/UI/GameMenu/SaveLoadMenu/SaveLoadMenu.gd b/game/src/UI/GameMenu/SaveLoadMenu/SaveLoadMenu.gd index a1351cb6..5b410f53 100644 --- a/game/src/UI/GameMenu/SaveLoadMenu/SaveLoadMenu.gd +++ b/game/src/UI/GameMenu/SaveLoadMenu/SaveLoadMenu.gd @@ -1,21 +1,23 @@ extends Control -@export var _save_scene : PackedScene - +@export var _save_scene: PackedScene @export_group("Nodes") -@export var _label : Label -@export var _scroll_list : BoxContainer -@export var _save_line_edit : LineEdit -@export var _save_load_button : Button -@export var _tag_selection_tab : TabBar -@export var _delete_dialog : ConfirmationDialog -@export var _overwrite_dialog : ConfirmationDialog - -var is_save_menu : bool = true -var _id_to_tag : Array[StringName] = [] - -func filter_for_tag(tag : StringName) -> void: - for child : Control in _scroll_list.get_children(): +@export var _label: Label +@export var _scroll_list: BoxContainer +@export var _save_line_edit: LineEdit +@export var _save_load_button: Button +@export var _tag_selection_tab: TabBar +@export var _delete_dialog: ConfirmationDialog +@export var _overwrite_dialog: ConfirmationDialog + +var is_save_menu: bool = true +var _id_to_tag: Array[StringName] = [] +var _submitted_text: String = "" +var _requested_node_to_delete: Control + + +func filter_for_tag(tag: StringName) -> void: + for child: Control in _scroll_list.get_children(): if tag.is_empty(): child.show() else: @@ -24,8 +26,11 @@ func filter_for_tag(tag : StringName) -> void: else: child.hide() + # Requirements # * UIFUN-78 + + func show_for_load() -> void: _label.text = "SAVELOADMENU_LOAD_TITLE" _save_load_button.text = "SAVELOADMENU_LOAD_BUTTON" @@ -33,8 +38,11 @@ func show_for_load() -> void: is_save_menu = false show() + # Requirements # * UIFUN-77 + + func show_for_save() -> void: _label.text = "SAVELOADMENU_SAVE_TITLE" _save_load_button.text = "SAVELOADMENU_SAVE_BUTTON" @@ -42,46 +50,56 @@ func show_for_save() -> void: is_save_menu = true show() + func _build_save_list() -> void: _tag_selection_tab.add_tab("SAVELOADMENU_TABSELECTIONTABBAR_ALL") - for save_name : StringName in SaveManager._save_dictionary: - var save : SaveResource = SaveManager._save_dictionary[save_name] + for save_name: StringName in SaveManager._save_dictionary: + var save: SaveResource = SaveManager._save_dictionary[save_name] var save_node := _create_save_node(save) _scroll_list.add_child(save_node) if not _id_to_tag.has(save.session_tag): _id_to_tag.append(save.session_tag) _tag_selection_tab.add_tab(save.session_tag) -func _create_save_node(resource : SaveResource) -> Control: + +func _create_save_node(resource: SaveResource) -> Control: var save_node = _save_scene.instantiate() save_node.resource = resource save_node.pressed.connect(_on_save_node_pressed.bind(save_node)) save_node.request_to_delete.connect(_on_save_node_delete_requested.bind(save_node)) return save_node + func _queue_clear_scroll_list() -> void: - for child : Node in _scroll_list.get_children(): + for child: Node in _scroll_list.get_children(): child.queue_free() _tag_selection_tab.clear_tabs() _id_to_tag.clear() + # REQUIREMENTS: # * UIFUN-84 # * UIFUN-89 + + func _on_close_button_pressed() -> void: hide() + func _on_delete_dialog_confirmed() -> void: _requested_node_to_delete.resource.delete() _requested_node_to_delete.queue_free() + # REQUIREMENTS: # * UIFUN-83 + + func _on_overwrite_dialog_confirmed() -> void: SaveManager.add_or_replace_save(SaveManager.make_new_save(_submitted_text)) _on_close_button_pressed() -var _submitted_text : String = "" + func _on_save_line_edit_text_submitted(new_text) -> void: _submitted_text = new_text if SaveManager.has_save(new_text): @@ -91,30 +109,36 @@ func _on_save_line_edit_text_submitted(new_text) -> void: return _on_overwrite_dialog_confirmed() + func _on_save_load_button_pressed() -> void: if is_save_menu: _save_line_edit.text_submitted.emit(_save_line_edit.text) -var _requested_node_to_delete : Control -func _on_save_node_delete_requested(node : Control) -> void: + +func _on_save_node_delete_requested(node: Control) -> void: _requested_node_to_delete = node _delete_dialog.dialog_text = tr("SAVELOADMENU_DELETE_DIALOG_TEXT").format({ "file_name": _requested_node_to_delete.resource.save_name }) _delete_dialog.title = tr("SAVELOADMENU_DELETE_DIALOG_TITLE").format({ "file_name": _requested_node_to_delete.resource.save_name }) _delete_dialog.popup_centered() + # REQUIREMENTS: # * UIFUN-81 # * UIFUN-86 -func _on_save_node_pressed(node : Control) -> void: + + +func _on_save_node_pressed(node: Control) -> void: if is_save_menu: _save_line_edit.text = node.resource.save_name + func _on_tag_selection_tab_bar_tab_changed(tab) -> void: if tab == 0: filter_for_tag(&"") else: filter_for_tag(_id_to_tag[tab - 1]) + func _on_visibility_changed() -> void: if visible: _build_save_list() diff --git a/game/src/UI/GameMenu/SaveLoadMenu/SavePanelButton.gd b/game/src/UI/GameMenu/SaveLoadMenu/SavePanelButton.gd index 69a587d8..744e176d 100644 --- a/game/src/UI/GameMenu/SaveLoadMenu/SavePanelButton.gd +++ b/game/src/UI/GameMenu/SaveLoadMenu/SavePanelButton.gd @@ -4,10 +4,10 @@ extends LobbyPanelButton signal request_to_delete @export_group("Nodes") -@export var country_flag : TextureRect -@export var delete_button : BaseButton +@export var country_flag: TextureRect +@export var delete_button: BaseButton -var resource : SaveResource: +var resource: SaveResource: get: return resource set(value): @@ -18,23 +18,29 @@ var resource : SaveResource: resource.changed.connect(_resource_changed) _resource_changed() + +func _ready() -> void: + _resource_changed() + + func get_text() -> StringName: return resource.save_name -func set_text(value : StringName) -> void: + +func set_text(value: StringName) -> void: if resource != null: resource.save_name = value -func _ready() -> void: - _resource_changed() func _is_start_date() -> bool: return false + func _resource_changed() -> void: if resource == null: return name_label.text = resource.save_name date_label.text = Time.get_datetime_string_from_unix_time(resource.get_save_file_time(), true) + func _on_delete_button_pressed() -> void: request_to_delete.emit() diff --git a/game/src/UI/GameMenu/SaveLoadMenu/SaveResource.gd b/game/src/UI/GameMenu/SaveLoadMenu/SaveResource.gd index 63cfd7fe..3cdb2d0c 100644 --- a/game/src/UI/GameMenu/SaveLoadMenu/SaveResource.gd +++ b/game/src/UI/GameMenu/SaveLoadMenu/SaveResource.gd @@ -1,43 +1,46 @@ extends Resource class_name SaveResource -signal file_flushed(path : String) +signal file_flushed(path: String) signal file_loaded signal file_moved_to_trash signal file_deleted signal trash_moved signal deleted -var save_name : StringName: +var save_name: StringName: get: return save_name set(v): save_name = v file.set_value("Save", "name", save_name) emit_changed() -var session_tag : StringName: +var session_tag: StringName: get: return session_tag set(v): session_tag = v file.set_value("Save", "session_tag", v) emit_changed() -var file_path : String: +var file_path: String: get: return file_path set(v): file_path = v emit_changed() -var file : ConfigFile = ConfigFile.new() +var file: ConfigFile = ConfigFile.new() -func set_file_path(name : StringName, path : String) -> void: + +func set_file_path(name: StringName, path: String) -> void: file_path = path save_name = name + func flush_save() -> Error: file_flushed.emit(file_path) var result := file.save(file_path) file.clear() return result -func load_save(path : String = file_path) -> Error: + +func load_save(path: String = file_path) -> Error: file_loaded.emit() var result := file.load(path) session_tag = file.get_value("Save", "session_tag", session_tag) @@ -45,14 +48,17 @@ func load_save(path : String = file_path) -> Error: set_file_path(file.get_value("Save", "name", save_name), path) return result + func get_save_file_time() -> int: return FileAccess.get_modified_time(file_path) + func move_to_trash() -> Error: trash_moved.emit() file_moved_to_trash.emit() return OS.move_to_trash(file_path) + func delete() -> Error: deleted.emit() file_deleted.emit() diff --git a/game/src/UI/Session/BudgetMenu.gd b/game/src/UI/Session/BudgetMenu.gd index 333a614c..00d4575f 100644 --- a/game/src/UI/Session/BudgetMenu.gd +++ b/game/src/UI/Session/BudgetMenu.gd @@ -1,27 +1,31 @@ extends GUINode -var _active : bool = false -var _incVal : int = 0 # incremental value to see the UI update, replace later by real values +const SCREEN := NationManagement.Screen.BUDGET + +var _active: bool = false +var _incVal: int = 0 # incremental value to see the UI update, replace later by real values + # debt -var _national_bank_label : GUILabel -var _debt_val_label : GUILabel -var _interest_val_label : GUILabel +var _national_bank_label: GUILabel +var _debt_val_label: GUILabel +var _interest_val_label: GUILabel -# costs -var _overseas_cost_val_label : GUILabel -var _ind_sub_val_label : GUILabel -var _lower_class_chart : GUIPieChart -var _middle_class_chart : GUIPieChart -var _upper_class_chart : GUIPieChart -var _debt_chart : GUIPieChart +# costs +var _overseas_cost_val_label: GUILabel +var _ind_sub_val_label: GUILabel +var _lower_class_chart: GUIPieChart +var _middle_class_chart: GUIPieChart +var _upper_class_chart: GUIPieChart +var _debt_chart: GUIPieChart -const SCREEN := NationManagement.Screen.BUDGET -# TODO - testing function, should be replaced with calls to SIM which trigger UI updates through gamestate_updated +# TODO - testing function, should be replaced with calls to SIM which trigger UI updates through +# gamestate_updated # slider.set_tooltip_string("%s: §Y%s%%" % [tr(tooltip), GUINode.float_to_string_dp(value, 1)]) + func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -32,7 +36,7 @@ func _ready() -> void: set_click_mask_from_nodepaths([^"./country_budget/main_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/close_button") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/close_button") if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(SCREEN)) @@ -45,22 +49,22 @@ func _ready() -> void: _ind_sub_val_label = get_gui_label_from_nodepath(^"./country_budget/ind_sub_val") # debt buttons - var _tab_takenloans_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/tab_takenloans") + var _tab_takenloans_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/tab_takenloans") if _tab_takenloans_button: _tab_takenloans_button.pressed.connect(_switch_loans_tab.bind(true)) - var _tab_givenloans_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/tab_givenloans") + var _tab_givenloans_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/tab_givenloans") if _tab_givenloans_button: _tab_givenloans_button.pressed.connect(_switch_loans_tab.bind(false)) - var _debt_sort_country_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/debt_sort_country") + var _debt_sort_country_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/debt_sort_country") if _debt_sort_country_button: _debt_sort_country_button.pressed.connect(_sort_loans.bind(true)) - var _debt_sort_amount_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/debt_sort_amount") + var _debt_sort_amount_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/debt_sort_amount") if _debt_sort_amount_button: _debt_sort_amount_button.pressed.connect(_sort_loans.bind(false)) - var _take_loan_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/take_loan") + var _take_loan_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/take_loan") if _take_loan_button: _take_loan_button.pressed.connect(_take_loan) - var _repay_loan_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/repay_loan") + var _repay_loan_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_budget/repay_loan") if _repay_loan_button: _repay_loan_button.pressed.connect(_repay_loan) @@ -74,20 +78,25 @@ func _ready() -> void: _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() NOTIFICATION_PREDELETE: - # If the C++ BudgetMenu isn't freed before the destruction of this GUINode, then its update method, triggered by - # gamestate updates, could be called after all the child UI nodes they refer to have been freed, + # If the C++ BudgetMenu isn't freed before the destruction of this GUINode, then its + # update method, triggered by + # gamestate updates, could be called after all the child UI nodes they refer to have + # been freed, # meaning the C++ BudgetMenu would be dereferencing invalid pointers. MenuSingleton.unlink_budget_menu_from_cpp() -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: + +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: _active = active_screen == SCREEN _update_info() + func _update_info() -> void: # TODO - remove _incVal and link the true data with the UI _incVal += 1 @@ -112,22 +121,26 @@ func _update_info() -> void: else: hide() -func _switch_loans_tab(taken_loans : bool) -> void: + +func _switch_loans_tab(taken_loans: bool) -> void: # TODO - code the necessary logic #if taken_loans: #else: # given loans pass -func _sort_loans(sort_by_country : bool) -> void: + +func _sort_loans(sort_by_country: bool) -> void: # TODO - code the necessary logic #if sort_by_country: #else: # sort by amount pass + func _take_loan() -> void: # TODO - code the necessary logic pass + func _repay_loan() -> void: # TODO - code the necessary logic pass diff --git a/game/src/UI/Session/DiplomacyMenu.gd b/game/src/UI/Session/DiplomacyMenu.gd index bf0d46ed..6f34c9ae 100644 --- a/game/src/UI/Session/DiplomacyMenu.gd +++ b/game/src/UI/Session/DiplomacyMenu.gd @@ -1,8 +1,9 @@ extends GUINode -var _active : bool = false +const _screen: NationManagement.Screen = NationManagement.Screen.DIPLOMACY + +var _active: bool = false -const _screen : NationManagement.Screen = NationManagement.Screen.DIPLOMACY func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -13,21 +14,24 @@ func _ready() -> void: set_click_mask_from_nodepaths([^"./country_diplomacy/main_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_diplomacy/close_button") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_diplomacy/close_button") if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: + +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: _active = active_screen == _screen _update_info() + func _update_info() -> void: if _active: # TODO - update UI state diff --git a/game/src/UI/Session/Ledger.gd b/game/src/UI/Session/Ledger.gd index 00769e90..cd097b8e 100644 --- a/game/src/UI/Session/Ledger.gd +++ b/game/src/UI/Session/Ledger.gd @@ -12,9 +12,10 @@ enum Page { PROVINCE_PRODUCTION, FACTORY_PRODUCTION, PRICE_HISTORY, - NUMBER_OF_PAGES + NUMBER_OF_PAGES, } -const _page_titles : PackedStringArray = [ + +const _page_titles: PackedStringArray = [ "LEDGER_HEADER_RANK", "LEDGER_HEADER_COUNTRYCOMPARE", "LEDGER_HEADER_COUNTRYPARTY", @@ -25,10 +26,10 @@ const _page_titles : PackedStringArray = [ "LEDGER_HEADER_PROVINCE_POPS", "LEDGER_HEADER_PROVINCEPRODUCTION", "LEDGER_HEADER_FACTORYPRODUCTION", - "LEDGER_HEADER_GOODS_PRICEHISTORY" + "LEDGER_HEADER_GOODS_PRICEHISTORY", ] -var _current_page : Page = Page.NATION_RANKING: +var _current_page: Page = Page.NATION_RANKING: get: return _current_page set(new_page): _current_page = new_page @@ -36,11 +37,11 @@ var _current_page : Page = Page.NATION_RANKING: _current_page += Page.NUMBER_OF_PAGES _current_page %= Page.NUMBER_OF_PAGES _update_info() - -var _page_title_label : GUILabel -var _page_number_label : GUILabel +var _page_title_label: GUILabel +var _page_number_label: GUILabel # TODO - add variables to store any nodes you'll need to refer in more than one function call + func _ready(): MenuSingleton.search_cache_changed.connect(_update_info) @@ -48,15 +49,15 @@ func _ready(): set_click_mask_from_nodepaths([^"./ledger/ledger_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./ledger/close") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./ledger/close") if close_button: close_button.pressed.connect(hide) - var previous_page_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./ledger/prev") + var previous_page_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./ledger/prev") if previous_page_button: previous_page_button.pressed.connect(func() -> void: _current_page -= 1) - var next_page_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./ledger/next") + var next_page_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./ledger/next") if next_page_button: next_page_button.pressed.connect(func() -> void: _current_page += 1) @@ -67,6 +68,7 @@ func _ready(): hide() + func toggle_visibility() -> void: if is_visible(): hide() @@ -74,6 +76,7 @@ func toggle_visibility() -> void: show() _update_info() + func _update_info() -> void: if is_visible(): if _page_title_label: diff --git a/game/src/UI/Session/Menubar.gd b/game/src/UI/Session/Menubar.gd index 7f606ad0..21839451 100644 --- a/game/src/UI/Session/Menubar.gd +++ b/game/src/UI/Session/Menubar.gd @@ -5,28 +5,18 @@ signal ledger_button_pressed signal search_button_pressed signal zoom_in_button_pressed signal zoom_out_button_pressed -signal minimap_clicked(pos_clicked : Vector2) +signal minimap_clicked(pos_clicked: Vector2) + +var _menu_button: GUIIconButton +var _mapmode_button_group: ButtonGroup -var _menu_button : GUIIconButton -var _mapmode_button_group : ButtonGroup # We use this instead of the ButtonGroup's get_buttons() as we can add null # entries for any missing buttons, ensuring each button is at the right index. -var _mapmode_buttons : Array[GUIIconButton] -var _minimap_icon : GUIIcon -var _viewport_points : PackedVector2Array +var _mapmode_buttons: Array[GUIIconButton] +var _minimap_icon: GUIIcon +var _viewport_points: PackedVector2Array -# REQUIREMENTS: -# * UI-550, UI-552, UI-554, UI-561, UI-562, UI-563 -func _add_mapmode_button() -> void: - var index : int = _mapmode_buttons.size() - var button : GUIIconButton = get_gui_icon_button_from_nodepath("./menubar/mapmode_%d" % (index + 1)) - if button: - button.tooltip_string = GameSingleton.get_mapmode_localisation_key(index) - button.toggle_mode = true - button.button_group = _mapmode_button_group - button.pressed.connect(_mapmode_pressed.bind(index)) - _mapmode_buttons.push_back(button) func _ready() -> void: add_gui_element("menubar", "menubar") @@ -50,10 +40,11 @@ func _ready() -> void: set_click_mask_from_nodepaths([ ^"./menubar/minimap_bg", - ^"./menubar/menubar_bg" + ^"./menubar/menubar_bg", ]) - # TODO: add keyboard shortcuts (and shortcut tooltips) where vanilla does by default + use key bindings in settings + # TODO: add keyboard shortcuts (and shortcut tooltips) where vanilla does by default + use key + # bindings in settings _menu_button = get_gui_icon_button_from_nodepath(^"./menubar/menu_button") if _menu_button: @@ -61,21 +52,21 @@ func _ready() -> void: _menu_button.pressed.connect(_on_game_session_menu_button_pressed) # TODO: implement ledger - var ledger_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./menubar/ledger_button") + var ledger_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./menubar/ledger_button") if ledger_button: ledger_button.tooltip_string = "M_LEDGER_BUTTON" ledger_button.pressed.connect(_on_ledger_button_pressed) - var search_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./menubar/button_goto") + var search_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./menubar/button_goto") if search_button: search_button.tooltip_string = "M_GOTO_BUTTON" search_button.pressed.connect(_on_search_button_pressed) - var zoom_in_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./menubar/map_zoom_in") + var zoom_in_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./menubar/map_zoom_in") if zoom_in_button: zoom_in_button.pressed.connect(_on_zoom_in_button_pressed) - var zoom_out_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./menubar/map_zoom_out") + var zoom_out_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./menubar/map_zoom_out") if zoom_out_button: zoom_out_button.pressed.connect(_on_zoom_out_button_pressed) @@ -87,93 +78,139 @@ func _ready() -> void: add_child(_minimap_icon) _mapmode_button_group = ButtonGroup.new() - for index : int in GameSingleton.get_mapmode_count(): + for index: int in GameSingleton.get_mapmode_count(): _add_mapmode_button() GameSingleton.mapmode_changed.connect(_on_mapmode_changed) - # This will set the mapmode in GameSingleton which in turn updates the buttons so that the right one is pressed + # This will set the mapmode in GameSingleton which in turn updates the buttons so that the right + # one is pressed _mapmode_pressed(0) + func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("menu_pause"): _menu_button.pressed.emit() + +# REQUIREMENTS: +# * UI-550, UI-552, UI-554, UI-561, UI-562, UI-563 + + +func _add_mapmode_button() -> void: + var index: int = _mapmode_buttons.size() + var button: GUIIconButton = get_gui_icon_button_from_nodepath("./menubar/mapmode_%d" % (index + 1)) + if button: + button.tooltip_string = GameSingleton.get_mapmode_localisation_key(index) + button.toggle_mode = true + button.button_group = _mapmode_button_group + button.pressed.connect(_mapmode_pressed.bind(index)) + _mapmode_buttons.push_back(button) + + # REQUIREMENTS: # * UIFUN-10 + + func _on_game_session_menu_button_pressed() -> void: game_session_menu_button_pressed.emit() + func _on_ledger_button_pressed() -> void: ledger_button_pressed.emit() + func _on_search_button_pressed() -> void: search_button_pressed.emit() + # REQUIREMENTS: # * SS-76 # * UIFUN-129, UIFUN-131, UIFUN-133, UIFUN-140, UIFUN-141, UIFUN-142 -func _mapmode_pressed(index : int) -> void: + + +func _mapmode_pressed(index: int) -> void: GameSingleton.set_mapmode(index) print("Mapmode set to \"%s\" (index: %d, identifier: %s)" % [ - tr(GameSingleton.get_mapmode_localisation_key(index)), index, GameSingleton.get_mapmode_identifier(index) + tr(GameSingleton.get_mapmode_localisation_key(index)), index, GameSingleton.get_mapmode_identifier(index), ]) -func _on_mapmode_changed(index : int) -> void: - var current_mapmode_button : GUIIconButton = _mapmode_button_group.get_pressed_button() - var new_mapmode_button : GUIIconButton = _mapmode_buttons[index] if 0 <= index and index < _mapmode_buttons.size() else null + +func _on_mapmode_changed(index: int) -> void: + var current_mapmode_button: GUIIconButton = _mapmode_button_group.get_pressed_button() + var new_mapmode_button: GUIIconButton = _mapmode_buttons[index] if 0 <= index and index < _mapmode_buttons.size() else null if current_mapmode_button != new_mapmode_button: if new_mapmode_button: # This will also automatically unpress current_mapmode_button (if it isn't null) new_mapmode_button.button_pressed = true else: - # current_mapmode_button can't be null as it isn't equal to new_mapmode_button which is null + # current_mapmode_button can't be null as it isn't equal to new_mapmode_button which is + # null current_mapmode_button.button_pressed = false + # REQUIREMENTS: # * UIFUN-269 + + func _on_zoom_in_button_pressed() -> void: zoom_in_button_pressed.emit() + # REQUIREMENTS: # * UIFUN-270 + + func _on_zoom_out_button_pressed() -> void: zoom_out_button_pressed.emit() + # REQUIREMENTS # * SS-80 # * UI-752 + + func _minimap_draw() -> void: if _viewport_points.size() > 1: _minimap_icon.draw_multiline(_viewport_points, Color.WHITE, -1) + # REQUIREMENTS # * SS-81 # * UIFUN-127 -func _minimap_gui_input(_event : InputEvent) -> void: - const _action_click : StringName = &"map_click" + + +func _minimap_gui_input(_event: InputEvent) -> void: + const _action_click: StringName = &"map_click" if Input.is_action_pressed(_action_click): - var pos_clicked : Vector2 = _minimap_icon.get_local_mouse_position() / _minimap_icon.size - Vector2(0.5, 0.5) + var pos_clicked: Vector2 = _minimap_icon.get_local_mouse_position() / _minimap_icon.size - Vector2(0.5, 0.5) if abs(pos_clicked.x) < 0.5 and abs(pos_clicked.y) < 0.5: minimap_clicked.emit(pos_clicked) + # Returns the point on the line going through p and q with the specific x coord -func _intersect_x(p : Vector2, q : Vector2, x : float) -> Vector2: + + +func _intersect_x(p: Vector2, q: Vector2, x: float) -> Vector2: if p.x == q.x: return Vector2(x, 0.5 * (p.y + q.y)) - var t : float = (x - q.x) / (p.x - q.x) + var t: float = (x - q.x) / (p.x - q.x) return q + t * (p - q) + # Returns the point on the line going through p and q with the specific y coord -func _intersect_y(p : Vector2, q : Vector2, y : float) -> Vector2: + + +func _intersect_y(p: Vector2, q: Vector2, y: float) -> Vector2: if p.y == q.y: return Vector2(0.5 * (p.x + q.x), y) - var t : float = (y - q.y) / (p.y - q.y) + var t: float = (y - q.y) / (p.y - q.y) return q + t * (p - q) -func _add_line_looped_over_x(left : Vector2, right : Vector2) -> void: - const _one_x : Vector2 = Vector2(1, 0) + +func _add_line_looped_over_x(left: Vector2, right: Vector2) -> void: + const _one_x: Vector2 = Vector2(1, 0) if left.x < 0: if right.x < 0: _viewport_points.push_back(left + _one_x) @@ -200,8 +237,16 @@ func _add_line_looped_over_x(left : Vector2, right : Vector2) -> void: _viewport_points.push_back(left) _viewport_points.push_back(right) + # This can break if the viewport is rotated too far! -func _on_map_view_camera_changed(near_left : Vector2, far_left : Vector2, far_right : Vector2, near_right : Vector2) -> void: + + +func _on_map_view_camera_changed( + near_left: Vector2, + far_left: Vector2, + far_right: Vector2, + near_right: Vector2, +) -> void: # Bound far y coords if far_left.y < 0: far_left = _intersect_y(near_left, far_left, 0) @@ -220,6 +265,6 @@ func _on_map_view_camera_changed(near_left : Vector2, far_left : Vector2, far_ri _add_line_looped_over_x(near_right, far_right) if _minimap_icon: - for i : int in _viewport_points.size(): + for i: int in _viewport_points.size(): _viewport_points[i] *= _minimap_icon.size _minimap_icon.queue_redraw() diff --git a/game/src/UI/Session/MilitaryMenu.gd b/game/src/UI/Session/MilitaryMenu.gd index c03fda2d..c452a739 100644 --- a/game/src/UI/Session/MilitaryMenu.gd +++ b/game/src/UI/Session/MilitaryMenu.gd @@ -1,64 +1,63 @@ extends GUINode -var _active : bool = false +enum Table { + LEADERS, ARMIES, NAVIES, +} -const _screen : NationManagement.Screen = NationManagement.Screen.MILITARY +const _screen: NationManagement.Screen = NationManagement.Screen.MILITARY +const _gui_file: String = "country_military" +const TABLE_UNSORTED: int = 255 +const TABLE_COLUMN_KEYS: Array[StringName] = [&"COLUMN_0", &"COLUMN_1", &"COLUMN_2", &"COLUMN_3"] +const TABLE_UNSORTABLE_KEY: StringName = &"UNSORTABLE" # Set to true for unsortable items (always go to the bottom of the list), otherwise defaults to false +const SORT_DESCENDING: int = 0 +const SORT_ASCENDING: int = 1 -const _gui_file : String = "country_military" +var _active: bool = false -# Military stats -var _war_exhaustion_label : GUILabel -var _supply_consumption_label : GUILabel -var _organisation_regain_label : GUILabel -var _land_organisation_label : GUILabel -var _naval_organisation_label : GUILabel -var _unit_start_experience_label : GUILabel -var _recruit_time_label : GUILabel -var _combat_width_label : GUILabel -var _dig_in_cap_label : GUILabel -var _military_tactics_label : GUILabel -# Mobilisation -var _mobilise_button : GUIIconButton -var _demobilise_button : GUIIconButton -var _mobilisation_progress_bar : GUIProgressBar -var _mobilisation_progress_label : GUILabel -var _mobilisation_size_label : GUILabel -var _mobilisation_economy_impact_label : GUILabel - -# Leaders -var _general_count_label : GUILabel -var _admiral_count_label : GUILabel -var _create_general_button : GUIIconButton -var _create_admiral_button : GUIIconButton -var _auto_create_leader_button : GUIIconButton -var _auto_assign_leader_button : GUIIconButton +# Military stats +var _war_exhaustion_label: GUILabel +var _supply_consumption_label: GUILabel +var _organisation_regain_label: GUILabel +var _land_organisation_label: GUILabel +var _naval_organisation_label: GUILabel +var _unit_start_experience_label: GUILabel +var _recruit_time_label: GUILabel +var _combat_width_label: GUILabel +var _dig_in_cap_label: GUILabel +var _military_tactics_label: GUILabel -# Armies and Navies -var _army_count_label : GUILabel -var _in_progress_brigade_count_label : GUILabel -var _disarmed_army_icon : GUIIcon -var _build_army_button : GUIIconButton -var _navy_count_label : GUILabel -var _in_progress_ship_count_label : GUILabel -var _disarmed_navy_icon : GUIIcon -var _build_navy_button : GUIIconButton +# Mobilisation +var _mobilise_button: GUIIconButton +var _demobilise_button: GUIIconButton +var _mobilisation_progress_bar: GUIProgressBar +var _mobilisation_progress_label: GUILabel +var _mobilisation_size_label: GUILabel +var _mobilisation_economy_impact_label: GUILabel -enum Table { - LEADERS, ARMIES, NAVIES -} -var _table_listboxes : Array[GUIListBox] +# Leaders +var _general_count_label: GUILabel +var _admiral_count_label: GUILabel +var _create_general_button: GUIIconButton +var _create_admiral_button: GUIIconButton +var _auto_create_leader_button: GUIIconButton +var _auto_assign_leader_button: GUIIconButton -const TABLE_UNSORTED : int = 255 -const TABLE_COLUMN_KEYS : Array[StringName] = [&"COLUMN_0", &"COLUMN_1", &"COLUMN_2", &"COLUMN_3"] -const TABLE_UNSORTABLE_KEY : StringName = &"UNSORTABLE" # Set to true for unsortable items (always go to the bottom of the list), otherwise defaults to false -var _table_sort_columns : PackedByteArray -const SORT_DESCENDING : int = 0 -const SORT_ASCENDING : int = 1 -var _table_sort_directions : PackedByteArray +# Armies and Navies +var _army_count_label: GUILabel +var _in_progress_brigade_count_label: GUILabel +var _disarmed_army_icon: GUIIcon +var _build_army_button: GUIIconButton +var _navy_count_label: GUILabel +var _in_progress_ship_count_label: GUILabel +var _disarmed_navy_icon: GUIIcon +var _build_navy_button: GUIIconButton +var _table_listboxes: Array[GUIListBox] +var _table_sort_columns: PackedByteArray +var _table_sort_directions: PackedByteArray func _ready() -> void: @@ -68,18 +67,18 @@ func _ready() -> void: add_gui_element(_gui_file, "country_military") - var military_menu : Panel = get_panel_from_nodepath(^"./country_military") + var military_menu: Panel = get_panel_from_nodepath(^"./country_military") if not military_menu: return set_click_mask_from_nodepaths([^"./country_military/main_bg"]) - var close_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(military_menu.get_node(^"./close_button")) + var close_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(military_menu.get_node(^"./close_button")) if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) # Military stats - var stats_panel : Panel = GUINode.get_panel_from_node(military_menu.get_node(^"./stats")) + var stats_panel: Panel = GUINode.get_panel_from_node(military_menu.get_node(^"./stats")) if stats_panel: _war_exhaustion_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./war_exhaustion")) _supply_consumption_label = GUINode.get_gui_label_from_node(stats_panel.get_node(^"./supply_consumption")) @@ -106,23 +105,23 @@ func _ready() -> void: _mobilisation_economy_impact_label = GUINode.get_gui_label_from_node(military_menu.get_node(^"./mob_impact")) # Leaders - var leaders_panel : Panel = GUINode.get_panel_from_node(military_menu.get_node(^"./leaders")) + var leaders_panel: Panel = GUINode.get_panel_from_node(military_menu.get_node(^"./leaders")) if leaders_panel: _general_count_label = GUINode.get_gui_label_from_node(leaders_panel.get_node(^"./generals")) _admiral_count_label = GUINode.get_gui_label_from_node(leaders_panel.get_node(^"./admirals")) - var sort_leader_prestige_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_prestige")) + var sort_leader_prestige_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_prestige")) if sort_leader_prestige_button: sort_leader_prestige_button.pressed.connect(_change_table_sorting.bind(Table.LEADERS, 0)) sort_leader_prestige_button.set_tooltip_string("SORT_BY_PRESTIGE") - var sort_leader_type_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_type")) + var sort_leader_type_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_type")) if sort_leader_type_button: sort_leader_type_button.pressed.connect(_change_table_sorting.bind(Table.LEADERS, 1)) sort_leader_type_button.set_tooltip_string("MILITARY_SORT_BY_TYPE_TOOLTIP") - var sort_leader_name_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_name")) + var sort_leader_name_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_name")) if sort_leader_name_button: sort_leader_name_button.pressed.connect(_change_table_sorting.bind(Table.LEADERS, 2)) sort_leader_name_button.set_tooltip_string("MILITARY_SORT_BY_NAME_TOOLTIP") - var sort_leader_army_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_army")) + var sort_leader_army_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(leaders_panel.get_node(^"./sort_leader_army")) if sort_leader_army_button: sort_leader_army_button.set_text("MILITARY_SORT_ARMY") sort_leader_army_button.pressed.connect(_change_table_sorting.bind(Table.LEADERS, 3)) @@ -149,8 +148,8 @@ func _ready() -> void: _table_sort_directions.push_back(SORT_DESCENDING) # Armies and Navies - var army_pos : Vector2 = GUINode.get_gui_position(_gui_file, "army_pos") - var army_window : Panel = GUINode.generate_gui_element(_gui_file, "unit_window") + var army_pos: Vector2 = GUINode.get_gui_position(_gui_file, "army_pos") + var army_window: Panel = GUINode.generate_gui_element(_gui_file, "unit_window") if army_window: army_window.set_position(army_pos) military_menu.add_child(army_window) @@ -159,11 +158,11 @@ func _ready() -> void: _in_progress_brigade_count_label = GUINode.get_gui_label_from_node(army_window.get_node(^"./under_construction")) _disarmed_army_icon = GUINode.get_gui_icon_from_node(army_window.get_node(^"./cut_down_to_size")) - var sort_armies_name_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./sort_name")) + var sort_armies_name_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./sort_name")) if sort_armies_name_button: sort_armies_name_button.pressed.connect(_change_table_sorting.bind(Table.ARMIES, 0)) sort_armies_name_button.set_tooltip_string("MILITARY_SORT_BY_NAME_TOOLTIP") - var sort_armies_strength_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./sort_strength")) + var sort_armies_strength_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(army_window.get_node(^"./sort_strength")) if sort_armies_strength_button: sort_armies_strength_button.pressed.connect(_change_table_sorting.bind(Table.ARMIES, 1)) sort_armies_strength_button.set_tooltip_string("MILITARY_SORT_BY_STRENGTH_TOOLTIP") @@ -180,8 +179,8 @@ func _ready() -> void: _table_sort_columns.push_back(TABLE_UNSORTED) _table_sort_directions.push_back(SORT_DESCENDING) - var navy_pos : Vector2 = GUINode.get_gui_position(_gui_file, "navy_pos") - var navy_window : Panel = GUINode.generate_gui_element(_gui_file, "unit_window") + var navy_pos: Vector2 = GUINode.get_gui_position(_gui_file, "navy_pos") + var navy_window: Panel = GUINode.generate_gui_element(_gui_file, "unit_window") if navy_window: navy_window.set_position(navy_pos) military_menu.add_child(navy_window) @@ -190,11 +189,11 @@ func _ready() -> void: _in_progress_ship_count_label = GUINode.get_gui_label_from_node(navy_window.get_node(^"./under_construction")) _disarmed_navy_icon = GUINode.get_gui_icon_from_node(navy_window.get_node(^"./cut_down_to_size")) - var sort_navies_name_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./sort_name")) + var sort_navies_name_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./sort_name")) if sort_navies_name_button: sort_navies_name_button.pressed.connect(_change_table_sorting.bind(Table.NAVIES, 0)) sort_navies_name_button.set_tooltip_string("MILITARY_SORT_BY_NAME_TOOLTIP") - var sort_navies_strength_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./sort_strength")) + var sort_navies_strength_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(navy_window.get_node(^"./sort_strength")) if sort_navies_strength_button: sort_navies_strength_button.pressed.connect(_change_table_sorting.bind(Table.NAVIES, 1)) sort_navies_strength_button.set_tooltip_string("MILITARY_SORT_BY_STRENGTH_TOOLTIP") @@ -213,253 +212,286 @@ func _ready() -> void: _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: + +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: _active = active_screen == _screen _update_info() -func _update_unit_group_list(table : Table, unit_groups : Array[Dictionary], in_progress_units : Array[Dictionary]) -> void: - var listbox : GUIListBox = _table_listboxes[table] + +func _update_unit_group_list( + table: Table, + unit_groups: Array[Dictionary], + in_progress_units: Array[Dictionary], +) -> void: + var listbox: GUIListBox = _table_listboxes[table] if not listbox: return - var is_army : bool = table == Table.ARMIES + var is_army: bool = table == Table.ARMIES - var total_entry_count : int = unit_groups.size() + in_progress_units.size() + var total_entry_count: int = unit_groups.size() + in_progress_units.size() listbox.clear_children(total_entry_count) while listbox.get_child_count() < total_entry_count: - var entry : Panel = GUINode.generate_gui_element(_gui_file, "unit_entry") + var entry: Panel = GUINode.generate_gui_element(_gui_file, "unit_entry") if not entry: break if not is_army: - var men_label : GUILabel = GUINode.get_gui_label_from_node(entry.get_node(^"./men")) + var men_label: GUILabel = GUINode.get_gui_label_from_node(entry.get_node(^"./men")) if men_label: men_label.hide() - var dig_in_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry.get_node(^"./digin")) + var dig_in_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry.get_node(^"./digin")) if dig_in_icon: dig_in_icon.hide() listbox.add_child(entry) - const military_info_unit_group_leader_picture_key : StringName = &"unit_group_leader_picture" - const military_info_unit_group_leader_tooltip_key : StringName = &"unit_group_leader_tooltip" - const military_info_unit_group_name_key : StringName = &"unit_group_name" - const military_info_unit_group_location_key : StringName = &"unit_group_location" - const military_info_unit_group_unit_count_key : StringName = &"unit_group_unit_count" - const military_info_unit_group_men_count_key : StringName = &"unit_group_men_count" # armies only - const military_info_unit_group_max_men_count_key : StringName = &"unit_group_max_men_count" # armies only - const military_info_unit_group_organisation_key : StringName = &"unit_group_organisation" - const military_info_unit_group_strength_key : StringName = &"unit_group_strength" - const military_info_unit_group_moving_tooltip_key : StringName = &"unit_group_moving_tooltip" - const military_info_unit_group_dig_in_tooltip_key : StringName = &"unit_group_dig_in_tooltip" # armies only - const military_info_unit_group_combat_key : StringName = &"unit_group_combat" - - for index : int in mini(unit_groups.size(), listbox.get_child_count()): - var entry_menu : Panel = GUINode.get_panel_from_node(listbox.get_child(index)) - var unit_group_dict : Dictionary = unit_groups[index] + const military_info_unit_group_leader_picture_key: StringName = &"unit_group_leader_picture" + const military_info_unit_group_leader_tooltip_key: StringName = &"unit_group_leader_tooltip" + const military_info_unit_group_name_key: StringName = &"unit_group_name" + const military_info_unit_group_location_key: StringName = &"unit_group_location" + const military_info_unit_group_unit_count_key: StringName = &"unit_group_unit_count" + const military_info_unit_group_men_count_key: StringName = &"unit_group_men_count" # armies only + const military_info_unit_group_max_men_count_key: StringName = &"unit_group_max_men_count" # armies only + const military_info_unit_group_organisation_key: StringName = &"unit_group_organisation" + const military_info_unit_group_strength_key: StringName = &"unit_group_strength" + const military_info_unit_group_moving_tooltip_key: StringName = &"unit_group_moving_tooltip" + const military_info_unit_group_dig_in_tooltip_key: StringName = &"unit_group_dig_in_tooltip" # armies only + const military_info_unit_group_combat_key: StringName = &"unit_group_combat" + + for index: int in mini(unit_groups.size(), listbox.get_child_count()): + var entry_menu: Panel = GUINode.get_panel_from_node(listbox.get_child(index)) + var unit_group_dict: Dictionary = unit_groups[index] entry_menu.remove_meta(TABLE_UNSORTABLE_KEY) - var entry_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_unit_entry_bg")) + var entry_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_unit_entry_bg")) if entry_button: # TODO - sort out repeat connections!!! entry_button.pressed.connect(func() -> void: print("OPENING UNIT GROUP")) - var unit_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./unit_progress")) + var unit_progress_bar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./unit_progress")) if unit_progress_bar: unit_progress_bar.hide() - var leader_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) + var leader_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) if leader_icon: - var leader_texture : Texture2D = unit_group_dict.get(military_info_unit_group_leader_picture_key, null) + var leader_texture: Texture2D = unit_group_dict.get( + military_info_unit_group_leader_picture_key, + null, + ) if leader_texture: leader_icon.show() leader_icon.set_texture(leader_texture) leader_icon.set_tooltip_string(unit_group_dict.get(military_info_unit_group_leader_tooltip_key, "")) else: leader_icon.hide() - var unit_strip_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./unit_strip")) + var unit_strip_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./unit_strip")) if unit_strip_icon: unit_strip_icon.hide() - var name : String = unit_group_dict.get(military_info_unit_group_name_key, "") + var name: String = unit_group_dict.get(military_info_unit_group_name_key, "") entry_menu.set_meta(TABLE_COLUMN_KEYS[0], name) - var name_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) + var name_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) if name_label: name_label.set_text(name) - var location_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) + var location_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) if location_label: location_label.set_text(GUINode.format_province_name(unit_group_dict.get(military_info_unit_group_location_key, ""))) - var unit_eta_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./unit_eta")) + var unit_eta_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./unit_eta")) if unit_eta_label: unit_eta_label.hide() - var unit_count : int = unit_group_dict.get(military_info_unit_group_unit_count_key, 0) + var unit_count: int = unit_group_dict.get(military_info_unit_group_unit_count_key, 0) entry_menu.set_meta(TABLE_COLUMN_KEYS[1], unit_count) - var unit_count_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) + var unit_count_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) if unit_count_label: unit_count_label.show() - var unit_count_string : String = str(unit_count) + var unit_count_string: String = str(unit_count) unit_count_label.set_text(unit_count_string) unit_count_label.set_tooltip_string(tr(&"MILITARY_REGIMENTS_TOOLTIP" if is_army else &"MILITARY_SHIPS_TOOLTIP").replace("$VALUE$", unit_count_string)) - var strength : float = unit_group_dict.get(military_info_unit_group_strength_key, 0) - var strength_tooltip : String = tr(&"MILITARY_STRENGTH_TOOLTIP2" if is_army else &"MILITARY_SHIPSTRENGTH_TOOLTIP2").replace("$PERCENT$", str(int(strength * 100))) + var strength: float = unit_group_dict.get(military_info_unit_group_strength_key, 0) + var strength_tooltip: String = tr(&"MILITARY_STRENGTH_TOOLTIP2" if is_army else &"MILITARY_SHIPSTRENGTH_TOOLTIP2").replace("$PERCENT$", str(int(strength * 100))) if is_army: - var men_count : int = unit_group_dict.get(military_info_unit_group_men_count_key, 0) if is_army else 0 - var max_men_count : int = unit_group_dict.get(military_info_unit_group_max_men_count_key, 0) if is_army else 0 - var men_count_string : String = GUINode.int_to_string_commas(men_count) if is_army else "" - strength_tooltip = strength_tooltip.replace("$VALUE$", men_count_string).replace("$MAX$", GUINode.int_to_string_commas(max_men_count)) - var men_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./men")) + var men_count: int = unit_group_dict.get( + military_info_unit_group_men_count_key, + 0, + ) if is_army else 0 + var max_men_count: int = unit_group_dict.get( + military_info_unit_group_max_men_count_key, + 0, + ) if is_army else 0 + var men_count_string: String = GUINode.int_to_string_commas(men_count) if is_army else "" + strength_tooltip = strength_tooltip.replace( + "$VALUE$", + men_count_string, + ).replace("$MAX$", GUINode.int_to_string_commas(max_men_count)) + var men_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./men")) if men_label: men_label.show() men_label.set_text(men_count_string) men_label.set_tooltip_string(strength_tooltip) - var military_cancel_unit_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_cancel_unit")) + var military_cancel_unit_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_cancel_unit")) if military_cancel_unit_button: military_cancel_unit_button.hide() - var organisation_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./morale_progress")) + var organisation_progress_bar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./morale_progress")) if organisation_progress_bar: organisation_progress_bar.show() - var organisation : float = unit_group_dict.get(military_info_unit_group_organisation_key, 0.0) + var organisation: float = unit_group_dict.get( + military_info_unit_group_organisation_key, + 0.0, + ) organisation_progress_bar.set_value_no_signal(organisation) organisation_progress_bar.set_tooltip_string(tr(&"MILITARY_MORALE_TOOLTIP").replace("$VALUE$", str(int(organisation * 100)))) - var strength_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./strength_progress")) + var strength_progress_bar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./strength_progress")) if strength_progress_bar: strength_progress_bar.show() strength_progress_bar.set_value_no_signal(strength) strength_progress_bar.set_tooltip_string(strength_tooltip) - var moving_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./moving")) + var moving_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./moving")) if moving_icon: - var moving_tooltip : String = unit_group_dict.get(military_info_unit_group_moving_tooltip_key, "") + var moving_tooltip: String = unit_group_dict.get( + military_info_unit_group_moving_tooltip_key, + "", + ) moving_icon.set_visible(not moving_tooltip.is_empty()) moving_icon.set_tooltip_string(moving_tooltip) - var dig_in_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./digin")) + var dig_in_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./digin")) if is_army: if dig_in_icon: - var dig_in_tooltip : String = unit_group_dict.get(military_info_unit_group_dig_in_tooltip_key, "") + var dig_in_tooltip: String = unit_group_dict.get( + military_info_unit_group_dig_in_tooltip_key, + "", + ) dig_in_icon.set_visible(not dig_in_tooltip.is_empty()) dig_in_icon.set_tooltip_string(dig_in_tooltip) - var combat_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./combat")) + var combat_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./combat")) if combat_icon: combat_icon.set_visible(unit_group_dict.get(military_info_unit_group_combat_key, false)) - const military_info_unit_progress_key : StringName = &"unit_progress" - const military_info_unit_icon_key : StringName = &"unit_icon" - const military_info_unit_name_key : StringName = &"unit_name" - const military_info_unit_location_key : StringName = &"unit_location" - const military_info_unit_eta_key : StringName = &"unit_eta" - const military_info_unit_tooltip_key : StringName = &"unit_tooltip" - - for index : int in clampi(listbox.get_child_count() - unit_groups.size(), 0, in_progress_units.size()): - var entry_menu : Panel = GUINode.get_panel_from_node(listbox.get_child(index + unit_groups.size())) - var unit_dict : Dictionary = in_progress_units[index] - var unit_tooltip : String = unit_dict.get(military_info_unit_tooltip_key, "") + const military_info_unit_progress_key: StringName = &"unit_progress" + const military_info_unit_icon_key: StringName = &"unit_icon" + const military_info_unit_name_key: StringName = &"unit_name" + const military_info_unit_location_key: StringName = &"unit_location" + const military_info_unit_eta_key: StringName = &"unit_eta" + const military_info_unit_tooltip_key: StringName = &"unit_tooltip" + + for index: int in clampi( + listbox.get_child_count() - unit_groups.size(), + 0, + in_progress_units.size(), + ): + var entry_menu: Panel = GUINode.get_panel_from_node(listbox.get_child(index + unit_groups.size())) + var unit_dict: Dictionary = in_progress_units[index] + var unit_tooltip: String = unit_dict.get(military_info_unit_tooltip_key, "") entry_menu.set_meta(TABLE_UNSORTABLE_KEY, true) - var unit_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./unit_progress")) + var unit_progress_bar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./unit_progress")) if unit_progress_bar: unit_progress_bar.show() unit_progress_bar.set_value_no_signal(unit_dict.get(military_info_unit_progress_key, 0)) # This is enough to show the tooltip everywhere we need, the only place # in the base game that obviously also has it is the ETA date label unit_progress_bar.set_tooltip_string(unit_tooltip) - var leader_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) + var leader_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) if leader_icon: leader_icon.hide() - var unit_strip_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./unit_strip")) + var unit_strip_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./unit_strip")) if unit_strip_icon: unit_strip_icon.show() unit_strip_icon.set_icon_index(unit_dict.get(military_info_unit_icon_key, 0)) - var name_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) + var name_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) if name_label: name_label.set_text(unit_dict.get(military_info_unit_name_key, "")) - var location_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) + var location_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) if location_label: location_label.set_text(GUINode.format_province_name(unit_dict.get(military_info_unit_location_key, ""))) - var unit_eta_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./unit_eta")) + var unit_eta_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./unit_eta")) if unit_eta_label: unit_eta_label.show() unit_eta_label.set_text(unit_dict.get(military_info_unit_eta_key, "")) - var unit_count_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) + var unit_count_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./regiments")) if unit_count_label: unit_count_label.hide() if is_army: - var men_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./men")) + var men_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./men")) if men_label: men_label.hide() - var military_cancel_unit_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_cancel_unit")) + var military_cancel_unit_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./military_cancel_unit")) if military_cancel_unit_button: military_cancel_unit_button.show() # TODO - sort out repeat connections!!! military_cancel_unit_button.pressed.connect(func() -> void: print("CANCELLED UNIT CONSTRUCTION")) - var organisation_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./morale_progress")) + var organisation_progress_bar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./morale_progress")) if organisation_progress_bar: organisation_progress_bar.hide() - var strength_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./strength_progress")) + var strength_progress_bar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./strength_progress")) if strength_progress_bar: strength_progress_bar.hide() - var moving_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./moving")) + var moving_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./moving")) if moving_icon: moving_icon.hide() if is_army: - var dig_in_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./digin")) + var dig_in_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./digin")) if dig_in_icon: dig_in_icon.hide() - var combat_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./combat")) + var combat_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./combat")) if combat_icon: combat_icon.hide() _sort_table(table) + func _update_info() -> void: if _active: # Military stats - const military_info_war_exhaustion_key : StringName = &"war_exhaustion" - const military_info_war_exhaustion_tooltip_key : StringName = &"war_exhaustion_tooltip" - const military_info_supply_consumption_key : StringName = &"supply_consumption" - const military_info_supply_consumption_tooltip_key : StringName = &"supply_consumption_tooltip" - const military_info_organisation_regain_key : StringName = &"organisation_regain" - const military_info_organisation_regain_tooltip_key : StringName = &"organisation_regain_tooltip" - const military_info_land_organisation_key : StringName = &"land_organisation" - const military_info_land_organisation_tooltip_key : StringName = &"land_organisation_tooltip" - const military_info_naval_organisation_key : StringName = &"naval_organisation" - const military_info_naval_organisation_tooltip_key : StringName = &"naval_organisation_tooltip" - const military_info_land_unit_start_experience_key : StringName = &"land_unit_start_experience" - const military_info_naval_unit_start_experience_key : StringName = &"naval_unit_start_experience" - const military_info_unit_start_experience_tooltip_key : StringName = &"unit_start_experience_tooltip" - const military_info_recruit_time_key : StringName = &"recruit_time" - const military_info_recruit_time_tooltip_key : StringName = &"recruit_time_tooltip" - const military_info_combat_width_key : StringName = &"combat_width" - const military_info_combat_width_tooltip_key : StringName = &"combat_width_tooltip" - const military_info_dig_in_cap_key : StringName = &"dig_in_cap" - const military_info_military_tactics_key : StringName = &"military_tactics" + const military_info_war_exhaustion_key: StringName = &"war_exhaustion" + const military_info_war_exhaustion_tooltip_key: StringName = &"war_exhaustion_tooltip" + const military_info_supply_consumption_key: StringName = &"supply_consumption" + const military_info_supply_consumption_tooltip_key: StringName = &"supply_consumption_tooltip" + const military_info_organisation_regain_key: StringName = &"organisation_regain" + const military_info_organisation_regain_tooltip_key: StringName = &"organisation_regain_tooltip" + const military_info_land_organisation_key: StringName = &"land_organisation" + const military_info_land_organisation_tooltip_key: StringName = &"land_organisation_tooltip" + const military_info_naval_organisation_key: StringName = &"naval_organisation" + const military_info_naval_organisation_tooltip_key: StringName = &"naval_organisation_tooltip" + const military_info_land_unit_start_experience_key: StringName = &"land_unit_start_experience" + const military_info_naval_unit_start_experience_key: StringName = &"naval_unit_start_experience" + const military_info_unit_start_experience_tooltip_key: StringName = &"unit_start_experience_tooltip" + const military_info_recruit_time_key: StringName = &"recruit_time" + const military_info_recruit_time_tooltip_key: StringName = &"recruit_time_tooltip" + const military_info_combat_width_key: StringName = &"combat_width" + const military_info_combat_width_tooltip_key: StringName = &"combat_width_tooltip" + const military_info_dig_in_cap_key: StringName = &"dig_in_cap" + const military_info_military_tactics_key: StringName = &"military_tactics" # Mobilisation - const military_info_is_mobilised_key : StringName = &"is_mobilised" - const military_info_mobilisation_progress_key : StringName = &"mobilisation_progress" - const military_info_mobilisation_size_key : StringName = &"mobilisation_size" - const military_info_mobilisation_size_tooltip_key : StringName = &"mobilisation_size_tooltip" - const military_info_mobilisation_impact_tooltip_key : StringName = &"mobilisation_impact_tooltip" - const military_info_mobilisation_economy_impact_key : StringName = &"mobilisation_economy_impact" - const military_info_mobilisation_economy_impact_tooltip_key : StringName = &"mobilisation_economy_impact_tooltip" + const military_info_is_mobilised_key: StringName = &"is_mobilised" + const military_info_mobilisation_progress_key: StringName = &"mobilisation_progress" + const military_info_mobilisation_size_key: StringName = &"mobilisation_size" + const military_info_mobilisation_size_tooltip_key: StringName = &"mobilisation_size_tooltip" + const military_info_mobilisation_impact_tooltip_key: StringName = &"mobilisation_impact_tooltip" + const military_info_mobilisation_economy_impact_key: StringName = &"mobilisation_economy_impact" + const military_info_mobilisation_economy_impact_tooltip_key: StringName = &"mobilisation_economy_impact_tooltip" # Leaders - const military_info_general_count_key : StringName = &"general_count" - const military_info_admiral_count_key : StringName = &"admiral_count" - const military_info_create_leader_count_key : StringName = &"create_leader_count" - const military_info_create_leader_cost_key : StringName = &"create_leader_cost" - const military_info_auto_create_leaders_key : StringName = &"auto_create_leaders" - const military_info_auto_assign_leaders_key : StringName = &"auto_assign_leaders" - const military_info_leaders_list_key : StringName = &"leaders_list" + const military_info_general_count_key: StringName = &"general_count" + const military_info_admiral_count_key: StringName = &"admiral_count" + const military_info_create_leader_count_key: StringName = &"create_leader_count" + const military_info_create_leader_cost_key: StringName = &"create_leader_cost" + const military_info_auto_create_leaders_key: StringName = &"auto_create_leaders" + const military_info_auto_assign_leaders_key: StringName = &"auto_assign_leaders" + const military_info_leaders_list_key: StringName = &"leaders_list" # Armies and Navies - const military_info_is_disarmed_key : StringName = &"is_disarmed" - const military_info_armies_key : StringName = &"armies" - const military_info_in_progress_brigades_key : StringName = &"in_progress_brigades" - const military_info_navies_key : StringName = &"navies" - const military_info_in_progress_ships_key : StringName = &"in_progress_ships" + const military_info_is_disarmed_key: StringName = &"is_disarmed" + const military_info_armies_key: StringName = &"armies" + const military_info_in_progress_brigades_key: StringName = &"in_progress_brigades" + const military_info_navies_key: StringName = &"navies" + const military_info_in_progress_ships_key: StringName = &"in_progress_ships" - var military_info : Dictionary = MenuSingleton.get_military_menu_info() + var military_info: Dictionary = MenuSingleton.get_military_menu_info() # Military stats if _war_exhaustion_label: @@ -480,8 +512,14 @@ func _update_info() -> void: if _unit_start_experience_label: _unit_start_experience_label.set_text( "%s/%s" % [ - GUINode.float_to_string_dp(military_info.get(military_info_land_unit_start_experience_key, 0), 2), - GUINode.float_to_string_dp(military_info.get(military_info_naval_unit_start_experience_key, 0), 2) + GUINode.float_to_string_dp( + military_info.get(military_info_land_unit_start_experience_key, 0), + 2, + ), + GUINode.float_to_string_dp( + military_info.get(military_info_naval_unit_start_experience_key, 0), + 2, + ), ] ) _unit_start_experience_label.set_tooltip_string(military_info.get(military_info_unit_start_experience_tooltip_key, "")) @@ -497,11 +535,17 @@ func _update_info() -> void: _military_tactics_label.set_text("%s%%" % GUINode.float_to_string_dp(100 * military_info.get(military_info_military_tactics_key, 0), 2)) # Mobilisation - var is_mobilised : bool = military_info.get(military_info_is_mobilised_key, false) - var mobilisation_size : int = military_info.get(military_info_mobilisation_size_key, 0) - var can_mobilise : bool = mobilisation_size > 0 - var mobilisation_size_tooltip : String = military_info.get(military_info_mobilisation_size_tooltip_key, "") - var mobilisation_impact_tooltip : String = military_info.get(military_info_mobilisation_impact_tooltip_key, "") + var is_mobilised: bool = military_info.get(military_info_is_mobilised_key, false) + var mobilisation_size: int = military_info.get(military_info_mobilisation_size_key, 0) + var can_mobilise: bool = mobilisation_size > 0 + var mobilisation_size_tooltip: String = military_info.get( + military_info_mobilisation_size_tooltip_key, + "", + ) + var mobilisation_impact_tooltip: String = military_info.get( + military_info_mobilisation_impact_tooltip_key, + "", + ) if _mobilise_button: _mobilise_button.set_visible(not is_mobilised) _mobilise_button.set_disabled(not can_mobilise) @@ -512,8 +556,14 @@ func _update_info() -> void: ) if _demobilise_button: _demobilise_button.set_visible(is_mobilised) - var mobilisation_progress : float = military_info.get(military_info_mobilisation_progress_key, 0) - var mobilisation_progress_string : String = GUINode.float_to_string_dp(100 * mobilisation_progress, 2) + var mobilisation_progress: float = military_info.get( + military_info_mobilisation_progress_key, + 0, + ) + var mobilisation_progress_string: String = GUINode.float_to_string_dp( + 100 * mobilisation_progress, + 2, + ) if _mobilisation_progress_bar: _mobilisation_progress_bar.set_value_no_signal(mobilisation_progress) _mobilisation_progress_bar.set_tooltip_string( @@ -535,11 +585,12 @@ func _update_info() -> void: _general_count_label.set_text(str(military_info.get(military_info_general_count_key, 0))) if _admiral_count_label: _admiral_count_label.set_text(str(military_info.get(military_info_admiral_count_key, 0))) - var create_leader_count : int = military_info.get(military_info_create_leader_count_key, 0) - var can_create_leaders : bool = create_leader_count > 0 - var create_leader_count_string : String = str(create_leader_count) - var create_leader_cost_tooltip : String = tr(&"MILITARY_LACK_OF_LEADER").replace("$VALUE$", str(military_info.get(military_info_create_leader_cost_key, 0))) - # TODO - leave the main text set and only update $VALUE$ using substitution dict functionality once buttons get proper text support + var create_leader_count: int = military_info.get(military_info_create_leader_count_key, 0) + var can_create_leaders: bool = create_leader_count > 0 + var create_leader_count_string: String = str(create_leader_count) + var create_leader_cost_tooltip: String = tr(&"MILITARY_LACK_OF_LEADER").replace("$VALUE$", str(military_info.get(military_info_create_leader_cost_key, 0))) + # TODO - leave the main text set and only update $VALUE$ using substitution dict + # functionality once buttons get proper text support if _create_general_button: _create_general_button.set_text(tr(&"MILITARY_CREATE_GENERAL").replace("$VALUE$", create_leader_count_string)) _create_general_button.set_disabled(not can_create_leaders) @@ -557,102 +608,124 @@ func _update_info() -> void: _auto_create_leader_button.set_pressed(military_info.get(military_info_auto_create_leaders_key, false)) if _auto_assign_leader_button: _auto_assign_leader_button.set_pressed(military_info.get(military_info_auto_assign_leaders_key, false)) - var leader_listbox : GUIListBox = _table_listboxes[Table.LEADERS] + var leader_listbox: GUIListBox = _table_listboxes[Table.LEADERS] if leader_listbox: - var leader_entries : Array[Dictionary] = military_info.get(military_info_leaders_list_key, [] as Array[Dictionary]) + var leader_entries: Array[Dictionary] = military_info.get(military_info_leaders_list_key, [] as Array[Dictionary]) leader_listbox.clear_children(leader_entries.size()) while leader_listbox.get_child_count() < leader_entries.size(): - var unit_entry : Panel = GUINode.generate_gui_element(_gui_file, "milview_leader_entry") + var unit_entry: Panel = GUINode.generate_gui_element( + _gui_file, + "milview_leader_entry", + ) if not unit_entry: break leader_listbox.add_child(unit_entry) - const military_info_leader_id_key : StringName = &"leader_id" - const military_info_leader_branch_key : StringName = &"leader_branch" # 1 = land, 2 = naval - const military_info_leader_name_key : StringName = &"leader_name" - const military_info_leader_picture_key : StringName = &"leader_picture" - const military_info_leader_prestige_key : StringName = &"leader_prestige" - const military_info_leader_prestige_tooltip_key : StringName = &"leader_prestige_tooltip" - const military_info_leader_background_key : StringName = &"leader_background" - const military_info_leader_personality_key : StringName = &"leader_personality" - const military_info_leader_can_be_used_key : StringName = &"leader_can_be_used" - const military_info_leader_assignment_key : StringName = &"leader_assignment" - const military_info_leader_location_key : StringName = &"leader_location" - const military_info_leader_tooltip_key : StringName = &"leader_tooltip" - - for index : int in mini(leader_entries.size(), leader_listbox.get_child_count()): - var entry_menu : Panel = GUINode.get_panel_from_node(leader_listbox.get_child(index)) - var leader_dict : Dictionary = leader_entries[index] - - var leader_id : int = leader_dict.get(military_info_leader_id_key, 0) + const military_info_leader_id_key: StringName = &"leader_id" + const military_info_leader_branch_key: StringName = &"leader_branch" # 1 = land, 2 = naval + const military_info_leader_name_key: StringName = &"leader_name" + const military_info_leader_picture_key: StringName = &"leader_picture" + const military_info_leader_prestige_key: StringName = &"leader_prestige" + const military_info_leader_prestige_tooltip_key: StringName = &"leader_prestige_tooltip" + const military_info_leader_background_key: StringName = &"leader_background" + const military_info_leader_personality_key: StringName = &"leader_personality" + const military_info_leader_can_be_used_key: StringName = &"leader_can_be_used" + const military_info_leader_assignment_key: StringName = &"leader_assignment" + const military_info_leader_location_key: StringName = &"leader_location" + const military_info_leader_tooltip_key: StringName = &"leader_tooltip" + + for index: int in mini(leader_entries.size(), leader_listbox.get_child_count()): + var entry_menu: Panel = GUINode.get_panel_from_node(leader_listbox.get_child(index)) + var leader_dict: Dictionary = leader_entries[index] + + var leader_id: int = leader_dict.get(military_info_leader_id_key, 0) if leader_id == 0: - push_error("Leader ID is 0 or missing in leader dictionary for entry index ", index, ", skipping!") + push_error( + "Leader ID is 0 or missing in leader dictionary for entry index ", + index, + ", skipping!", + ) continue else: entry_menu.set_meta(military_info_leader_id_key, leader_id) - var prestige : float = leader_dict.get(military_info_leader_prestige_key, 0) + var prestige: float = leader_dict.get(military_info_leader_prestige_key, 0) entry_menu.set_meta(TABLE_COLUMN_KEYS[0], prestige) - var prestige_progress_bar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./leader_prestige_bar")) + var prestige_progress_bar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(entry_menu.get_node(^"./leader_prestige_bar")) if prestige_progress_bar: prestige_progress_bar.set_value_no_signal(prestige) prestige_progress_bar.set_tooltip_string(leader_dict.get(military_info_leader_prestige_tooltip_key, "")) - entry_menu.set_meta(TABLE_COLUMN_KEYS[1], leader_dict.get(military_info_leader_branch_key, 0)) + entry_menu.set_meta( + TABLE_COLUMN_KEYS[1], + leader_dict.get(military_info_leader_branch_key, 0), + ) - var leader_name : String = leader_dict.get(military_info_leader_name_key, "") - var leader_tooltip : String = leader_dict.get(military_info_leader_tooltip_key, "") + var leader_name: String = leader_dict.get(military_info_leader_name_key, "") + var leader_tooltip: String = leader_dict.get(military_info_leader_tooltip_key, "") entry_menu.set_meta(TABLE_COLUMN_KEYS[2], leader_name) - var entry_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./military_leader_entry_bg")) + var entry_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./military_leader_entry_bg")) if entry_icon: entry_icon.set_tooltip_string(leader_tooltip) - var leader_icon : GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) + var leader_icon: GUIIcon = GUINode.get_gui_icon_from_node(entry_menu.get_node(^"./leader")) if leader_icon: - var leader_texture : Texture2D = leader_dict.get(military_info_leader_picture_key, null) + var leader_texture: Texture2D = leader_dict.get( + military_info_leader_picture_key, + null, + ) if leader_texture: leader_icon.set_texture(leader_texture) leader_icon.show() else: leader_icon.hide() leader_icon.set_tooltip_string(leader_tooltip) - var name_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) + var name_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./name")) if name_label: name_label.set_text(leader_name) name_label.set_tooltip_string(leader_tooltip) - var background_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./background")) + var background_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./background")) if background_label: background_label.set_text(leader_dict.get(military_info_leader_background_key, "")) background_label.set_tooltip_string(leader_tooltip) - var personality_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./personality")) + var personality_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./personality")) if personality_label: personality_label.set_text(leader_dict.get(military_info_leader_personality_key, "")) personality_label.set_tooltip_string(leader_tooltip) - var use_leader_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./use_leader")) + var use_leader_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(entry_menu.get_node(^"./use_leader")) if use_leader_button: # TODO - investigate why "set_pressed_no_signal" wasn't enough use_leader_button.set_pressed(leader_dict.get(military_info_leader_can_be_used_key, false)) # TODO - ensure only one connection? use_leader_button.toggled.connect( - func(state : bool) -> void: - PlayerSingleton.set_can_use_leader(entry_menu.get_meta(military_info_leader_id_key, 0) as int, state) + func(state: bool) -> void: + PlayerSingleton.set_can_use_leader( + entry_menu.get_meta(military_info_leader_id_key, 0) as int, + state, + ) ) use_leader_button.set_tooltip_string("USE_LEADER" if use_leader_button.is_pressed() else "") - var army_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./army")) - var location_label : GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) + var army_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./army")) + var location_label: GUILabel = GUINode.get_gui_label_from_node(entry_menu.get_node(^"./location")) - var leader_assignment_untype : Variant = leader_dict.get(military_info_leader_assignment_key, null) + var leader_assignment_untype: Variant = leader_dict.get( + military_info_leader_assignment_key, + null, + ) if leader_assignment_untype != null: - var leader_assignment : String = leader_assignment_untype as String + var leader_assignment: String = leader_assignment_untype as String entry_menu.set_meta(TABLE_COLUMN_KEYS[3], leader_assignment) - var leader_location : String = GUINode.format_province_name(leader_dict.get(military_info_leader_location_key, ""), true) - var assignment_tooltip : String = tr(&"MILITARY_LEADER_NAME_TOOLTIP").replace("$NAME$", leader_name).replace("$ARMY$", leader_assignment).replace("$LOCATION$", leader_location) + var leader_location: String = GUINode.format_province_name( + leader_dict.get(military_info_leader_location_key, ""), + true, + ) + var assignment_tooltip: String = tr(&"MILITARY_LEADER_NAME_TOOLTIP").replace("$NAME$", leader_name).replace("$ARMY$", leader_assignment).replace("$LOCATION$", leader_location) if army_label: army_label.set_text(leader_assignment) @@ -672,16 +745,16 @@ func _update_info() -> void: _sort_table(Table.LEADERS) # Armies and Navies - var is_disarmed : bool = military_info.get(military_info_is_disarmed_key, false) + var is_disarmed: bool = military_info.get(military_info_is_disarmed_key, false) - var armies : Array[Dictionary] = military_info.get(military_info_armies_key, [] as Array[Dictionary]) - var in_progress_brigades : Array[Dictionary] = military_info.get(military_info_in_progress_brigades_key, [] as Array[Dictionary]) + var armies: Array[Dictionary] = military_info.get(military_info_armies_key, [] as Array[Dictionary]) + var in_progress_brigades: Array[Dictionary] = military_info.get(military_info_in_progress_brigades_key, [] as Array[Dictionary]) if _army_count_label: - var army_size_string : String = str(armies.size()) + var army_size_string: String = str(armies.size()) _army_count_label.set_text(army_size_string) _army_count_label.set_tooltip_string(tr(&"MILITARY_ARMY_COUNT_TOOLTIP").replace("$VALUE$", army_size_string)) if _in_progress_brigade_count_label: - var constructing_brigades_count_string : String = str(in_progress_brigades.size()) + var constructing_brigades_count_string: String = str(in_progress_brigades.size()) _in_progress_brigade_count_label.set_text("(+%s)" % constructing_brigades_count_string) _in_progress_brigade_count_label.set_tooltip_string(tr(&"MILITARY_ARMY_CONSTRUCTION_TOOLTIP").replace("$VALUE$", constructing_brigades_count_string)) if _disarmed_army_icon: @@ -690,14 +763,14 @@ func _update_info() -> void: _build_army_button.set_disabled(is_disarmed) _update_unit_group_list(Table.ARMIES, armies, in_progress_brigades) - var navies : Array[Dictionary] = military_info.get(military_info_navies_key, [] as Array[Dictionary]) - var in_progress_ships : Array[Dictionary] = military_info.get(military_info_in_progress_ships_key, [] as Array[Dictionary]) + var navies: Array[Dictionary] = military_info.get(military_info_navies_key, [] as Array[Dictionary]) + var in_progress_ships: Array[Dictionary] = military_info.get(military_info_in_progress_ships_key, [] as Array[Dictionary]) if _navy_count_label: - var navy_size_string : String = str(navies.size()) + var navy_size_string: String = str(navies.size()) _navy_count_label.set_text(navy_size_string) _navy_count_label.set_tooltip_string(tr(&"MILITARY_NAVY_COUNT_TOOLTIP").replace("$VALUE$", navy_size_string)) if _in_progress_ship_count_label: - var constructing_ships_count_string : String = str(in_progress_ships.size()) + var constructing_ships_count_string: String = str(in_progress_ships.size()) _in_progress_ship_count_label.set_text("(+%s)" % constructing_ships_count_string) _in_progress_ship_count_label.set_tooltip_string(tr(&"MILITARY_NAVY_CONSTRUCTION_TOOLTIP").replace("$VALUE$", constructing_ships_count_string)) if _disarmed_navy_icon: @@ -710,34 +783,40 @@ func _update_info() -> void: else: hide() -func _change_table_sorting(table : Table, column : int) -> void: + +func _change_table_sorting(table: Table, column: int) -> void: _table_sort_columns[table] = column _table_sort_directions[table] = SORT_ASCENDING if _table_sort_directions[table] == SORT_DESCENDING else SORT_DESCENDING _sort_table(table) -func _make_unsortable_check(callable : Callable) -> Callable: - return func(a : Node, b : Node) -> bool: + +func _make_unsortable_check(callable: Callable) -> Callable: + return func(a: Node, b: Node) -> bool: # Unsortable entries are equal to each other and < all sortable entries, so: # - sortable < sortable - normal check # - sortable < unsortable - always true # - unsortable < sortable - always false # - unsortable < unsortable - always false - return not a.get_meta(TABLE_UNSORTABLE_KEY, false) and (b.get_meta(TABLE_UNSORTABLE_KEY, false) or callable.call(a, b)) + return not a.get_meta( + TABLE_UNSORTABLE_KEY, + false, + ) and (b.get_meta(TABLE_UNSORTABLE_KEY, false) or callable.call(a, b)) + -func _sort_table(table : Table) -> void: - var table_listbox : GUIListBox = _table_listboxes[table] +func _sort_table(table: Table) -> void: + var table_listbox: GUIListBox = _table_listboxes[table] if not table_listbox: return - var column : int = _table_sort_columns[table] + var column: int = _table_sort_columns[table] if column == TABLE_UNSORTED: return - var sort_key : StringName = TABLE_COLUMN_KEYS[column] + var sort_key: StringName = TABLE_COLUMN_KEYS[column] table_listbox.sort_children(_make_unsortable_check( - (func(a : Node, b : Node) -> bool: return a.get_meta(sort_key) > b.get_meta(sort_key)) + (func(a: Node, b: Node) -> bool: return a.get_meta(sort_key) > b.get_meta(sort_key)) if _table_sort_directions[table] == SORT_DESCENDING else - (func(a : Node, b : Node) -> bool: return a.get_meta(sort_key) < b.get_meta(sort_key)) + (func(a: Node, b: Node) -> bool: return a.get_meta(sort_key) < b.get_meta(sort_key)) )) diff --git a/game/src/UI/Session/PauseMenu/PauseMenu.gd b/game/src/UI/Session/PauseMenu/PauseMenu.gd index 680ce45b..420a679e 100644 --- a/game/src/UI/Session/PauseMenu/PauseMenu.gd +++ b/game/src/UI/Session/PauseMenu/PauseMenu.gd @@ -1,27 +1,34 @@ extends PanelContainer -@export var _main_menu_scene : PackedScene - -@export var _main_menu_dialog : AcceptDialog -@export var _quit_dialog : AcceptDialog - -var _main_menu_save_button : Button -var _main_menu_save_separator : Control -var _quit_save_button : Button -var _quit_save_separator : Control - signal save_button_pressed signal load_button_pressed signal options_button_pressed +@export var _main_menu_scene: PackedScene +@export var _main_menu_dialog: AcceptDialog +@export var _quit_dialog: AcceptDialog + +var _main_menu_save_button: Button +var _main_menu_save_separator: Control +var _quit_save_button: Button +var _quit_save_separator: Control + + func _ready() -> void: - _main_menu_save_button = _main_menu_dialog.add_button("DIALOG_SAVE_AND_RESIGN", true, &"save_and_main_menu") + _main_menu_save_button = _main_menu_dialog.add_button( + "DIALOG_SAVE_AND_RESIGN", + true, + &"save_and_main_menu", + ) _quit_save_button = _quit_dialog.add_button("DIALOG_SAVE_AND_QUIT", true, &"save_and_quit") # Necessary to center the save buttons and preserve the reference to the separator elements - var dialog_hbox : HBoxContainer = _main_menu_dialog.get_child(2, true) + var dialog_hbox: HBoxContainer = _main_menu_dialog.get_child(2, true) var index := _main_menu_save_button.get_index(true) - dialog_hbox.move_child(_main_menu_save_button, _main_menu_dialog.get_ok_button().get_index(true)) + dialog_hbox.move_child( + _main_menu_save_button, + _main_menu_dialog.get_ok_button().get_index(true), + ) dialog_hbox.move_child(_main_menu_dialog.get_ok_button(), index) _main_menu_save_separator = dialog_hbox.get_child(_main_menu_save_button.get_index(true) - 1) @@ -31,52 +38,67 @@ func _ready() -> void: dialog_hbox.move_child(_quit_dialog.get_ok_button(), index) _quit_save_separator = dialog_hbox.get_child(_quit_save_button.get_index(true) - 1) + func hide_save_dialog_button() -> void: _main_menu_save_button.hide() _main_menu_save_separator.hide() _quit_save_button.hide() _quit_save_separator.hide() + func show_save_dialog_button() -> void: _main_menu_save_button.show() _main_menu_save_separator.show() _quit_save_button.show() _quit_save_separator.show() + # REQUIREMENTS: # * SS-47 # * UIFUN-69 + + func _on_main_menu_confirmed() -> void: SaveManager.current_session_tag = "" SaveManager.current_save = null CursorManager.set_compat_cursor(&"", Input.CURSOR_IBEAM) get_tree().change_scene_to_packed(_main_menu_scene) + # REQUIREMENTS: # * SS-48 # * UIFUN-70 + + func _on_quit_confirmed() -> void: get_tree().root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST) get_tree().quit() + # REQUIREMENTS: # * SS-7, SS-46 # * UIFUN-11 + + func _on_options_button_pressed() -> void: options_button_pressed.emit() -func _on_main_menu_dialog_custom_action(action : StringName) -> void: + +func _on_main_menu_dialog_custom_action(action: StringName) -> void: match action: &"save_and_main_menu": _on_main_menu_confirmed() -func _on_quit_dialog_custom_action(action : StringName) -> void: + +func _on_quit_dialog_custom_action(action: StringName) -> void: match action: &"save_and_quit": _on_quit_confirmed() + func _on_save_button_pressed() -> void: save_button_pressed.emit() + func _on_load_button_pressed() -> void: load_button_pressed.emit() diff --git a/game/src/UI/Session/PoliticsMenu.gd b/game/src/UI/Session/PoliticsMenu.gd index abcb117f..5a37fa99 100644 --- a/game/src/UI/Session/PoliticsMenu.gd +++ b/game/src/UI/Session/PoliticsMenu.gd @@ -1,62 +1,70 @@ extends GUINode -var _active : bool = false +const _screen: NationManagement.Screen = NationManagement.Screen.POLITICS +const _gui_file: String = "country_politics" -const _screen : NationManagement.Screen = NationManagement.Screen.POLITICS +var _active: bool = false -const _gui_file : String = "country_politics" # Main Window -var _country_modifiers_array : Array[GUIIconButton] -var _government_name : GUILabel -var _national_value : GUIIcon -var _government_desc : GUILabel -var _plurality_value : GUILabel -var _revanchism_value : GUILabel -var _hold_election_button : GUIIconButton -var _voters_ideology_chart : GUIPieChart -var _peoples_ideology_chart : GUIPieChart +var _country_modifiers_array: Array[GUIIconButton] +var _government_name: GUILabel +var _national_value: GUIIcon +var _government_desc: GUILabel +var _plurality_value: GUILabel +var _revanchism_value: GUILabel +var _hold_election_button: GUIIconButton +var _voters_ideology_chart: GUIPieChart +var _peoples_ideology_chart: GUIPieChart + # Upper House -var _upperhouse_chart : GUIPieChart -var _upperhouse_listbox : GUIListBox -var _social_reform_allowed : GUILabel +var _upperhouse_chart: GUIPieChart +var _upperhouse_listbox: GUIListBox +var _social_reform_allowed: GUILabel var _political_reform_allowed: GUILabel -var _s_reform_allowed_icon : GUIIcon -var _p_reform_allowed_icon : GUIIcon +var _s_reform_allowed_icon: GUIIcon +var _p_reform_allowed_icon: GUIIcon + # Issues -var _sort_issues : GUIIconButton -var _sort_voters : GUIIconButton -var _sort_people : GUIIconButton -var _issue_listbox : GUIListBox +var _sort_issues: GUIIconButton +var _sort_voters: GUIIconButton +var _sort_people: GUIIconButton +var _issue_listbox: GUIListBox + #Tabs -var _reforms_tab : GUIIconButton -var _movements_tab : GUIIconButton -var _decisions_tab : GUIIconButton -var _release_nations_tab : GUIIconButton +var _reforms_tab: GUIIconButton +var _movements_tab: GUIIconButton +var _decisions_tab: GUIIconButton +var _release_nations_tab: GUIIconButton + # Decisions -var _decisions_listbox : GUIListBox +var _decisions_listbox: GUIListBox + # Movements -var _suppression_value : GUILabel -var _sortby_size_button : GUIIconButton +var _suppression_value: GUILabel +var _sortby_size_button: GUIIconButton var _sortby_radical_button: GUIIconButton -var _sortby_name_button : GUIIconButton -var _movements_listbox : GUIListBox -var _rebels_listbox : GUIListBox +var _sortby_name_button: GUIIconButton +var _movements_listbox: GUIListBox +var _rebels_listbox: GUIListBox + # Reforms # Unciv Reforms -var _research_points_value : GUILabel -var _unciv_progress : GUIProgressBar -var _westernize_button : GUIIconButton +var _research_points_value: GUILabel +var _unciv_progress: GUIProgressBar +var _westernize_button: GUIIconButton + # Release Nations -var _release_nation_listbox : GUIListBox +var _release_nation_listbox: GUIListBox + func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -67,11 +75,11 @@ func _ready() -> void: set_click_mask_from_nodepaths([^"./country_politics/main_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_politics/close_button") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_politics/close_button") if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) - var politics_menu : Panel = get_panel_from_nodepath(^"./country_politics") + var politics_menu: Panel = get_panel_from_nodepath(^"./country_politics") if not politics_menu: return @@ -82,7 +90,7 @@ func _ready() -> void: _plurality_value = GUINode.get_gui_label_from_node(politics_menu.get_node(^"./plurality_value")) _revanchism_value = GUINode.get_gui_label_from_node(politics_menu.get_node(^"./revanchism_value")) _hold_election_button = GUINode.get_gui_icon_button_from_node(politics_menu.get_node(^"./hold_election")) - if _hold_election_button : + if _hold_election_button: _hold_election_button.pressed.connect(func() -> void: print("ELECTION TO BE HELD")) _voters_ideology_chart = GUINode.get_gui_pie_chart_from_node(politics_menu.get_node(^"./chart_voters_ideologies")) _peoples_ideology_chart = GUINode.get_gui_pie_chart_from_node(politics_menu.get_node(^"./chart_people_ideologies")) @@ -97,13 +105,13 @@ func _ready() -> void: #Issues _sort_issues = GUINode.get_gui_icon_button_from_node(politics_menu.get_node(^"./sort_by_issue_name")) - if _sort_issues : + if _sort_issues: _sort_issues.pressed.connect(func() -> void: print("ISSUES LIST SORTED BY ISSUE NAME")) _sort_voters = GUINode.get_gui_icon_button_from_node(politics_menu.get_node(^"./sort_by_voters")) - if _sort_voters : + if _sort_voters: _sort_voters.pressed.connect(func() -> void: print("ISSUES LIST SORTED BY VOTER PREFERENCE")) _sort_people = GUINode.get_gui_icon_button_from_node(politics_menu.get_node(^"./sort_by_people")) - if _sort_people : + if _sort_people: _sort_people.pressed.connect(func() -> void: print("ISSUES LIST SORTED BY PEOPLES PREFERENCE")) _issue_listbox = GUINode.get_gui_listbox_from_node(politics_menu.get_node(^"./issue_listbox")) @@ -114,54 +122,57 @@ func _ready() -> void: _release_nations_tab = GUINode.get_gui_icon_button_from_node(politics_menu.get_node(^"./release_nations_tab")) # Decisions - var decisions_window : Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./decision_window")) - if decisions_window : + var decisions_window: Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./decision_window")) + if decisions_window: _decisions_listbox = GUINode.get_gui_listbox_from_node(decisions_window.get_node(^"./decision_listbox")) # Movements - var movements_window : Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./movements_window")) - if movements_window : + var movements_window: Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./movements_window")) + if movements_window: _suppression_value = GUINode.get_gui_label_from_node(movements_window.get_node(^"./suppression_value")) _sortby_size_button = GUINode.get_gui_icon_button_from_node(movements_window.get_node(^"./sortby_size_button")) - if _sortby_size_button : + if _sortby_size_button: _sortby_size_button.pressed.connect(func() -> void: print("MOVEMENTS LIST SORTED BY MOVEMENT SIZE")) _sortby_radical_button = GUINode.get_gui_icon_button_from_node(movements_window.get_node(^"./sortby_radical_button")) - if _sortby_radical_button : + if _sortby_radical_button: _sortby_radical_button.pressed.connect(func() -> void: print("MOVEMENTS LIST SORTED BY MOVEMENT RADICALISM")) _sortby_name_button = GUINode.get_gui_icon_button_from_node(movements_window.get_node(^"./sortby_name_button")) - if _sortby_name_button : + if _sortby_name_button: _sortby_name_button.pressed.connect(func() -> void: print("MOVEMENTS LIST SORTED BY MOVEMENT NAME")) _movements_listbox = GUINode.get_gui_listbox_from_node(movements_window.get_node(^"./movements_listbox")) _rebels_listbox = GUINode.get_gui_listbox_from_node(movements_window.get_node(^"./rebel_listbox")) - var reforms_window : Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./reforms_window")) + var reforms_window: Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./reforms_window")) # Unciv Reforms - var unciv_reforms_window : Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./unciv_reforms_window")) - if unciv_reforms_window : + var unciv_reforms_window: Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./unciv_reforms_window")) + if unciv_reforms_window: _research_points_value = GUINode.get_gui_label_from_node(unciv_reforms_window.get_node(^"./research_points_val")) _unciv_progress = GUINode.get_gui_progress_bar_from_node(unciv_reforms_window.get_node(^"./civ_progress")) _westernize_button = GUINode.get_gui_icon_button_from_node(unciv_reforms_window.get_node(^"./westernize_button")) - if _westernize_button : + if _westernize_button: _westernize_button.pressed.connect(func() -> void: print("COUNTRY HAS WESTERNIZED")) # Release Nations - var release_nations_window : Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./release_nation")) - if release_nations_window : + var release_nations_window: Panel = GUINode.get_panel_from_node(politics_menu.get_node(^"./release_nation")) + if release_nations_window: _release_nation_listbox = GUINode.get_gui_listbox_from_node(release_nations_window.get_node(^"./nations")) _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: + +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: _active = active_screen == _screen _update_info() + func _update_info() -> void: if _active: # TODO - update UI state diff --git a/game/src/UI/Session/PopulationMenu.gd b/game/src/UI/Session/PopulationMenu.gd index adfb711e..609ee3d9 100644 --- a/game/src/UI/Session/PopulationMenu.gd +++ b/game/src/UI/Session/PopulationMenu.gd @@ -1,65 +1,58 @@ extends GUINode -var _active : bool = false +const _screen: NationManagement.Screen = NationManagement.Screen.POPULATION +const _scene_name: String = "country_pops" -const _screen : NationManagement.Screen = NationManagement.Screen.POPULATION - -const _scene_name : String = "country_pops" - -var _pop_screen_panel : Panel - -var _province_list_type_pool : Dictionary = { +var _active: bool = false +var _pop_screen_panel: Panel +var _province_list_type_pool: Dictionary = { MenuSingleton.LIST_ENTRY_COUNTRY: [], MenuSingleton.LIST_ENTRY_STATE: [], - MenuSingleton.LIST_ENTRY_PROVINCE: [] + MenuSingleton.LIST_ENTRY_PROVINCE: [], } +var _province_listbox: GUIListBox +var _province_list_scroll_index: int = 0 +var _province_list_types: Array[MenuSingleton.ProvinceListEntry] +var _province_list_indices: PackedInt32Array +var _province_list_panels: Array[Panel] +var _province_list_buttons: Array[GUIIconButton] +var _province_list_name_labels: Array[GUILabel] +var _province_list_size_labels: Array[GUILabel] +var _province_list_growth_icons: Array[GUIIcon] +var _province_list_colony_buttons: Array[GUIIconButton] +var _province_list_national_focus_buttons: Array[GUIIconButton] +var _province_list_expand_buttons: Array[GUIIconButton] +var _pop_filter_buttons: Array[GUIIconButton] +var _pop_filter_icons: Array[GFXSpriteTexture] +var _pop_filter_selected_icons: Array[GFXButtonStateTexture] +var _pop_filter_hover_icons: Array[GFXButtonStateTexture] +var _distribution_charts: Array[GUIPieChart] +var _distribution_lists: Array[GUIListBox] +var _pop_list_scrollbar: GUIScrollbar +var _pop_list_scroll_index: int = 0 +var _pop_list_rows: Array[Panel] +var _pop_list_size_labels: Array[GUILabel] +var _pop_list_type_buttons: Array[GUIIconButton] +var _pop_list_producing_icons: Array[GUIIcon] +var _pop_list_culture_labels: Array[GUILabel] +var _pop_list_religion_icons: Array[GUIIcon] +var _pop_list_location_labels: Array[GUILabel] +var _pop_list_militancy_labels: Array[GUILabel] +var _pop_list_consciousness_labels: Array[GUILabel] +var _pop_list_ideology_charts: Array[GUIPieChart] +var _pop_list_issues_charts: Array[GUIPieChart] +var _pop_list_unemployment_progressbars: Array[GUIProgressBar] +var _pop_list_cash_labels: Array[GUILabel] +var _pop_list_life_needs_progressbars: Array[GUIProgressBar] +var _pop_list_everyday_needs_progressbars: Array[GUIProgressBar] +var _pop_list_luxury_needs_progressbars: Array[GUIProgressBar] +var _pop_list_rebel_icons: Array[GUIIcon] +var _pop_list_social_movement_icons: Array[GUIIcon] +var _pop_list_political_movement_icons: Array[GUIIcon] +var _pop_list_national_movement_flags: Array[GUIMaskedFlag] +var _pop_list_size_change_icons: Array[GUIIcon] +var _pop_list_literacy_labels: Array[GUILabel] -var _province_listbox : GUIListBox -var _province_list_scroll_index : int = 0 -var _province_list_types : Array[MenuSingleton.ProvinceListEntry] -var _province_list_indices : PackedInt32Array -var _province_list_panels : Array[Panel] -var _province_list_buttons : Array[GUIIconButton] -var _province_list_name_labels : Array[GUILabel] -var _province_list_size_labels : Array[GUILabel] -var _province_list_growth_icons : Array[GUIIcon] -var _province_list_colony_buttons : Array[GUIIconButton] -var _province_list_national_focus_buttons : Array[GUIIconButton] -var _province_list_expand_buttons : Array[GUIIconButton] - -var _pop_filter_buttons : Array[GUIIconButton] -var _pop_filter_icons : Array[GFXSpriteTexture] -var _pop_filter_selected_icons : Array[GFXButtonStateTexture] -var _pop_filter_hover_icons : Array[GFXButtonStateTexture] - -var _distribution_charts : Array[GUIPieChart] -var _distribution_lists : Array[GUIListBox] - -var _pop_list_scrollbar : GUIScrollbar -var _pop_list_scroll_index : int = 0 - -var _pop_list_rows : Array[Panel] -var _pop_list_size_labels : Array[GUILabel] -var _pop_list_type_buttons : Array[GUIIconButton] -var _pop_list_producing_icons : Array[GUIIcon] -var _pop_list_culture_labels : Array[GUILabel] -var _pop_list_religion_icons : Array[GUIIcon] -var _pop_list_location_labels : Array[GUILabel] -var _pop_list_militancy_labels : Array[GUILabel] -var _pop_list_consciousness_labels : Array[GUILabel] -var _pop_list_ideology_charts : Array[GUIPieChart] -var _pop_list_issues_charts : Array[GUIPieChart] -var _pop_list_unemployment_progressbars : Array[GUIProgressBar] -var _pop_list_cash_labels : Array[GUILabel] -var _pop_list_life_needs_progressbars : Array[GUIProgressBar] -var _pop_list_everyday_needs_progressbars : Array[GUIProgressBar] -var _pop_list_luxury_needs_progressbars : Array[GUIProgressBar] -var _pop_list_rebel_icons : Array[GUIIcon] -var _pop_list_social_movement_icons : Array[GUIIcon] -var _pop_list_political_movement_icons : Array[GUIIcon] -var _pop_list_national_movement_flags : Array[GUIMaskedFlag] -var _pop_list_size_change_icons : Array[GUIIcon] -var _pop_list_literacy_labels : Array[GUILabel] func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -75,7 +68,7 @@ func _ready() -> void: set_click_mask_from_nodepaths([^"./country_pop/main_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_pop/close_button") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_pop/close_button") if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) @@ -89,13 +82,25 @@ func _ready() -> void: _update_info() -func _resize_province_list(list_size : int) -> void: + +func _notification(what: int) -> void: + match what: + NOTIFICATION_TRANSLATION_CHANGED: + MenuSingleton.population_menu_update_locale_sort_cache() + _update_info() + NOTIFICATION_PREDELETE: + for k: MenuSingleton.ProvinceListEntry in _province_list_type_pool: + for panel: Panel in _province_list_type_pool[k]: + if panel: panel.queue_free() + + +func _resize_province_list(list_size: int) -> void: list_size = max(list_size, 8) if _province_list_types.size() <= list_size: _province_list_types.resize(list_size) var old_ind_size := _province_list_indices.size() _province_list_indices.resize(list_size) - for i : int in range(old_ind_size, _province_list_indices.size()): + for i: int in range(old_ind_size, _province_list_indices.size()): _province_list_indices[i] = -1 _province_list_panels.resize(list_size) _province_list_buttons.resize(list_size) @@ -105,10 +110,11 @@ func _resize_province_list(list_size : int) -> void: _province_list_colony_buttons.resize(list_size) _province_list_national_focus_buttons.resize(list_size) _province_list_expand_buttons.resize(list_size) - for k : MenuSingleton.ProvinceListEntry in _province_list_type_pool: + for k: MenuSingleton.ProvinceListEntry in _province_list_type_pool: _province_list_type_pool[k].resize(list_size) -func _generate_province_list_row(index : int, type : MenuSingleton.ProvinceListEntry) -> Error: + +func _generate_province_list_row(index: int, type: MenuSingleton.ProvinceListEntry) -> Error: if _province_list_types.size() <= index: _resize_province_list(_province_list_types.size() + 1) elif _province_list_types[index] == type: @@ -120,13 +126,13 @@ func _generate_province_list_row(index : int, type : MenuSingleton.ProvinceListE _province_listbox.remove_child(_province_list_panels[index]) return OK - const gui_element_names : Dictionary = { + const gui_element_names: Dictionary = { MenuSingleton.LIST_ENTRY_COUNTRY: "poplistitem_country", MenuSingleton.LIST_ENTRY_STATE: "poplistitem_state", - MenuSingleton.LIST_ENTRY_PROVINCE: "poplistitem_province" + MenuSingleton.LIST_ENTRY_PROVINCE: "poplistitem_province", } - var entry_panel : Panel = _province_list_type_pool[type][index] + var entry_panel: Panel = _province_list_type_pool[type][index] if not entry_panel: entry_panel = GUINode.generate_gui_element(_scene_name, gui_element_names[type]) if not entry_panel: @@ -168,6 +174,7 @@ func _generate_province_list_row(index : int, type : MenuSingleton.ProvinceListE return OK + func _setup_province_list() -> void: if not _province_listbox: _province_listbox = get_gui_listbox_from_nodepath(^"./country_pop/pop_province_list") @@ -177,15 +184,23 @@ func _setup_province_list() -> void: _province_listbox.scroll_index_changed.connect(_update_province_list) if _province_list_panels.size() < 1 or not _province_list_panels[0]: - if _generate_province_list_row(0, MenuSingleton.LIST_ENTRY_COUNTRY) != OK or _province_list_panels.size() < 1 or not _province_list_panels[0]: + if _generate_province_list_row( + 0, + MenuSingleton.LIST_ENTRY_COUNTRY, + ) != OK or _province_list_panels.size() < 1 or not _province_list_panels[0]: push_error("Failed to generate country row in population menu province list to determine row height!") return - _province_listbox.set_fixed(MenuSingleton.get_population_menu_province_list_row_count(), _province_list_panels[0].size.y, false) + _province_listbox.set_fixed( + MenuSingleton.get_population_menu_province_list_row_count(), + _province_list_panels[0].size.y, + false, + ) + func _setup_sort_buttons() -> void: # button_path : NodePath, clear_text : bool, sort_key : GameSingleton.PopSortKey - const sort_button_info : Array[Array] = [ + const sort_button_info: Array[Array] = [ [^"./country_pop/sortby_size_button", false, MenuSingleton.SORT_SIZE], [^"./country_pop/sortby_type_button", false, MenuSingleton.SORT_TYPE], [^"./country_pop/sortby_nationality_button", false, MenuSingleton.SORT_CULTURE], @@ -202,31 +217,32 @@ func _setup_sort_buttons() -> void: [^"./country_pop/sortby_luxury_button", true, MenuSingleton.SORT_LUXURY_NEEDS], [^"./country_pop/sortby_revoltrisk_button", true, MenuSingleton.SORT_REBEL_FACTION], [^"./country_pop/sortby_change_button", true, MenuSingleton.SORT_SIZE_CHANGE], - [^"./country_pop/sortby_literacy_button", true, MenuSingleton.SORT_LITERACY] + [^"./country_pop/sortby_literacy_button", true, MenuSingleton.SORT_LITERACY], ] - for button_info : Array in sort_button_info: - var sort_button : GUIIconButton = get_gui_icon_button_from_nodepath(button_info[0]) + for button_info: Array in sort_button_info: + var sort_button: GUIIconButton = get_gui_icon_button_from_nodepath(button_info[0]) if sort_button: if button_info[1]: sort_button.set_text("") sort_button.pressed.connect(MenuSingleton.population_menu_select_sort_key.bind(button_info[2])) + func _setup_pop_filter_buttons() -> void: if not _pop_screen_panel: push_error("Cannot set up pop filter buttons without pop screen to add them to") return - var pop_filter_sprite_indices : PackedInt32Array = MenuSingleton.get_population_menu_pop_filter_setup_info() + var pop_filter_sprite_indices: PackedInt32Array = MenuSingleton.get_population_menu_pop_filter_setup_info() - var pop_filter_start : Vector2 = GUINode.get_gui_position(_scene_name, "popfilter_start") - var pop_filter_step : Vector2 = GUINode.get_gui_position(_scene_name, "popfilter_offset") + var pop_filter_start: Vector2 = GUINode.get_gui_position(_scene_name, "popfilter_start") + var pop_filter_step: Vector2 = GUINode.get_gui_position(_scene_name, "popfilter_offset") - for index : int in pop_filter_sprite_indices.size(): - var pop_filter_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(GUINode.generate_gui_element(_scene_name, "pop_filter_button")) - var pop_filter_icon : GFXSpriteTexture = null - var pop_filter_selected_icon : GFXButtonStateTexture = null - var pop_filter_hover_icon : GFXButtonStateTexture = null + for index: int in pop_filter_sprite_indices.size(): + var pop_filter_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(GUINode.generate_gui_element(_scene_name, "pop_filter_button")) + var pop_filter_icon: GFXSpriteTexture = null + var pop_filter_selected_icon: GFXButtonStateTexture = null + var pop_filter_hover_icon: GFXButtonStateTexture = null if pop_filter_button: _pop_screen_panel.add_child(pop_filter_button) @@ -244,36 +260,40 @@ func _setup_pop_filter_buttons() -> void: _pop_filter_selected_icons.push_back(pop_filter_selected_icon) _pop_filter_hover_icons.push_back(pop_filter_hover_icon) - var select_all_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_pop/popfilter_ALL") + var select_all_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_pop/popfilter_ALL") if select_all_button: select_all_button.pressed.connect(MenuSingleton.population_menu_select_all_pop_filters) - var deselect_all_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_pop/popfilter_DESELECT_ALL") + var deselect_all_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_pop/popfilter_DESELECT_ALL") if deselect_all_button: deselect_all_button.pressed.connect(MenuSingleton.population_menu_deselect_all_pop_filters) + func _setup_distribution_windows() -> void: if not _pop_screen_panel: push_error("Cannot set up distribution windows without pop screen to add them to") return - const columns : int = 3 + const columns: int = 3 - var distribution_names : PackedStringArray = MenuSingleton.get_population_menu_distribution_setup_info() + var distribution_names: PackedStringArray = MenuSingleton.get_population_menu_distribution_setup_info() - var distribution_start : Vector2 = GUINode.get_gui_position(_scene_name, "popdistribution_start") - var distribution_step : Vector2 = GUINode.get_gui_position(_scene_name, "popdistribution_offset") + var distribution_start: Vector2 = GUINode.get_gui_position(_scene_name, "popdistribution_start") + var distribution_step: Vector2 = GUINode.get_gui_position(_scene_name, "popdistribution_offset") - for index : int in distribution_names.size(): - var distribution_panel : Panel = GUINode.generate_gui_element(_scene_name, "distribution_window") - var distribution_chart : GUIPieChart = null - var distribution_list : GUIListBox = null + for index: int in distribution_names.size(): + var distribution_panel: Panel = GUINode.generate_gui_element( + _scene_name, + "distribution_window", + ) + var distribution_chart: GUIPieChart = null + var distribution_list: GUIListBox = null if distribution_panel: _pop_screen_panel.add_child(distribution_panel) distribution_panel.set_position(distribution_start + distribution_step * Vector2(index % columns, index / columns)) - var name_label : GUILabel = GUINode.get_gui_label_from_node(distribution_panel.get_node(^"./item_name")) + var name_label: GUILabel = GUINode.get_gui_label_from_node(distribution_panel.get_node(^"./item_name")) if name_label: name_label.set_text(distribution_names[index]) @@ -283,23 +303,24 @@ func _setup_distribution_windows() -> void: _distribution_charts.push_back(distribution_chart) _distribution_lists.push_back(distribution_list) + func _setup_pop_list() -> void: _pop_list_scrollbar = get_gui_scrollbar_from_nodepath(^"./country_pop/external_scroll_slider") - var pop_list_panel : Panel = get_panel_from_nodepath(^"./country_pop/pop_list") + var pop_list_panel: Panel = get_panel_from_nodepath(^"./country_pop/pop_list") if not pop_list_panel: return pop_list_panel.mouse_filter = Control.MOUSE_FILTER_PASS if _pop_list_scrollbar: _pop_list_scrollbar.value_changed.connect( - func (value : int) -> void: + func (value: int) -> void: _pop_list_scroll_index = value _update_pop_list() ) pop_list_panel.gui_input.connect( - func (event : InputEvent) -> void: + func (event: InputEvent) -> void: if event is InputEventMouseButton: if event.is_pressed(): if event.get_button_index() == MOUSE_BUTTON_WHEEL_UP: @@ -308,9 +329,12 @@ func _setup_pop_list() -> void: _pop_list_scrollbar.increment_value() ) - var height : float = 0.0 + var height: float = 0.0 while height < pop_list_panel.size.y: - var pop_row_panel : Panel = GUINode.generate_gui_element(_scene_name, "popinfomember_popview") + var pop_row_panel: Panel = GUINode.generate_gui_element( + _scene_name, + "popinfomember_popview", + ) if not pop_row_panel: break @@ -321,7 +345,7 @@ func _setup_pop_list() -> void: _pop_list_size_labels.push_back(GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_size"))) - var pop_type_button : GUIIconButton = GUINode.get_gui_icon_button_from_node(pop_row_panel.get_node(^"./pop_type")) + var pop_type_button: GUIIconButton = GUINode.get_gui_icon_button_from_node(pop_row_panel.get_node(^"./pop_type")) # TODO - open pop details menu on pop type button press _pop_list_type_buttons.push_back(pop_type_button) @@ -345,12 +369,12 @@ func _setup_pop_list() -> void: _pop_list_cash_labels.push_back(GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_cash"))) - var pop_list_life_needs_progressbar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(pop_row_panel.get_node(^"./lifeneed_progress")) + var pop_list_life_needs_progressbar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(pop_row_panel.get_node(^"./lifeneed_progress")) _pop_list_life_needs_progressbars.push_back(pop_list_life_needs_progressbar) if pop_list_life_needs_progressbar: pop_list_life_needs_progressbar.position += Vector2(1, 0) - var pop_list_everyday_needs_progressbar : GUIProgressBar = GUINode.get_gui_progress_bar_from_node(pop_row_panel.get_node(^"./eveneed_progress")) + var pop_list_everyday_needs_progressbar: GUIProgressBar = GUINode.get_gui_progress_bar_from_node(pop_row_panel.get_node(^"./eveneed_progress")) _pop_list_everyday_needs_progressbars.push_back(pop_list_everyday_needs_progressbar) if pop_list_everyday_needs_progressbar: pop_list_everyday_needs_progressbar.position += Vector2(1, 0) @@ -369,20 +393,12 @@ func _setup_pop_list() -> void: _pop_list_literacy_labels.push_back(GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_literacy"))) -func _notification(what : int) -> void: - match what: - NOTIFICATION_TRANSLATION_CHANGED: - MenuSingleton.population_menu_update_locale_sort_cache() - _update_info() - NOTIFICATION_PREDELETE: - for k : MenuSingleton.ProvinceListEntry in _province_list_type_pool: - for panel : Panel in _province_list_type_pool[k]: - if panel: panel.queue_free() -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: _active = active_screen == _screen _update_info() + func _update_info() -> void: if _active: # Province list @@ -395,10 +411,12 @@ func _update_info() -> void: else: hide() -func get_growth_icon_index(size_change : int) -> int: + +func get_growth_icon_index(size_change: int) -> int: return 1 + int(size_change <= 0) + int(size_change < 0) -func _update_province_list(scroll_index : int = -1) -> void: + +func _update_province_list(scroll_index: int = -1) -> void: if not _province_listbox: return @@ -407,21 +425,21 @@ func _update_province_list(scroll_index : int = -1) -> void: _province_list_scroll_index = _province_listbox.get_scroll_index() - var province_list_info_list : Array[Dictionary] = MenuSingleton.get_population_menu_province_list_rows(_province_list_scroll_index, _province_listbox.get_fixed_visible_items()) + var province_list_info_list: Array[Dictionary] = MenuSingleton.get_population_menu_province_list_rows(_province_list_scroll_index, _province_listbox.get_fixed_visible_items()) - for index : int in province_list_info_list.size(): - const type_key : StringName = &"type" - const index_key : StringName = &"index" - const name_key : StringName = &"name" - const size_key : StringName = &"size" - const change_key : StringName = &"change" - const selected_key : StringName = &"selected" - const expanded_key : StringName = &"expanded" - const colony_key : StringName = &"colony" + for index: int in province_list_info_list.size(): + const type_key: StringName = &"type" + const index_key: StringName = &"index" + const name_key: StringName = &"name" + const size_key: StringName = &"size" + const change_key: StringName = &"change" + const selected_key: StringName = &"selected" + const expanded_key: StringName = &"expanded" + const colony_key: StringName = &"colony" - var province_list_info : Dictionary = province_list_info_list[index] + var province_list_info: Dictionary = province_list_info_list[index] - var type : MenuSingleton.ProvinceListEntry = province_list_info[type_key] + var type: MenuSingleton.ProvinceListEntry = province_list_info[type_key] if _generate_province_list_row(index, type) != OK: continue @@ -456,31 +474,33 @@ func _update_province_list(scroll_index : int = -1) -> void: # TODO - set _province_list_national_focus_buttons[index] # Clear any excess rows - for index : int in range(province_list_info_list.size(), _province_list_types.size()): + for index: int in range(province_list_info_list.size(), _province_list_types.size()): _generate_province_list_row(index, MenuSingleton.LIST_ENTRY_NONE) + func _update_pops() -> void: _update_pop_filters() _update_distributions() _update_pop_list() + func _update_pop_filters() -> void: - var pop_filter_info_list : Array[Dictionary] = MenuSingleton.get_population_menu_pop_filter_info() + var pop_filter_info_list: Array[Dictionary] = MenuSingleton.get_population_menu_pop_filter_info() - for index : int in pop_filter_info_list.size(): - const pop_filter_count_key : StringName = &"count" - const pop_filter_change_key : StringName = &"change" - const pop_filter_selected_key : StringName = &"selected" + for index: int in pop_filter_info_list.size(): + const pop_filter_count_key: StringName = &"count" + const pop_filter_change_key: StringName = &"change" + const pop_filter_selected_key: StringName = &"selected" - var pop_filter_info : Dictionary = pop_filter_info_list[index] + var pop_filter_info: Dictionary = pop_filter_info_list[index] - var pop_filter_button : GUIIconButton = _pop_filter_buttons[index] + var pop_filter_button: GUIIconButton = _pop_filter_buttons[index] if not pop_filter_button: continue pop_filter_button.disabled = pop_filter_info[pop_filter_count_key] <= 0 - const normal_theme : StringName = &"normal" - const hover_theme : StringName = &"hover" + const normal_theme: StringName = &"normal" + const hover_theme: StringName = &"hover" if pop_filter_info[pop_filter_selected_key] or pop_filter_button.disabled: pop_filter_button.get_theme_stylebox(normal_theme).set_texture(_pop_filter_icons[index]) @@ -488,58 +508,62 @@ func _update_pop_filters() -> void: else: pop_filter_button.get_theme_stylebox(normal_theme).set_texture(_pop_filter_selected_icons[index]) pop_filter_button.get_theme_stylebox(hover_theme).set_texture(_pop_filter_selected_icons[index]) + + # TODO - size and promotion/demotion change tooltip + func _update_distributions(): - const slice_identifier_key : StringName = &"identifier" - const slice_tooltip_key : StringName = &"tooltip" - const slice_colour_key : StringName = &"colour" - const slice_weight_key : StringName = &"weight" + const slice_identifier_key: StringName = &"identifier" + const slice_tooltip_key: StringName = &"tooltip" + const slice_colour_key: StringName = &"colour" + const slice_weight_key: StringName = &"weight" - var distribution_info_list : Array[Array] = MenuSingleton.get_population_menu_distribution_info() + var distribution_info_list: Array[Array] = MenuSingleton.get_population_menu_distribution_info() - for distribution_index : int in distribution_info_list.size(): - var distribution_info : Array[Dictionary] = distribution_info_list[distribution_index] + for distribution_index: int in distribution_info_list.size(): + var distribution_info: Array[Dictionary] = distribution_info_list[distribution_index] if _distribution_charts[distribution_index]: _distribution_charts[distribution_index].set_slices_array(distribution_info) if _distribution_lists[distribution_index]: - distribution_info.sort_custom(func(a : Dictionary, b : Dictionary) -> bool: return a[slice_weight_key] > b[slice_weight_key]) + distribution_info.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[slice_weight_key] > b[slice_weight_key]) - var list : GUIListBox = _distribution_lists[distribution_index] + var list: GUIListBox = _distribution_lists[distribution_index] list.clear_children(distribution_info.size()) while list.get_child_count() < distribution_info.size(): - var child : Panel = GUINode.generate_gui_element(_scene_name, "pop_legend_item") + var child: Panel = GUINode.generate_gui_element(_scene_name, "pop_legend_item") if not child: break child.set_mouse_filter(Control.MOUSE_FILTER_IGNORE) list.add_child(child) - for list_index : int in min(list.get_child_count(), distribution_info.size()): + for list_index: int in min(list.get_child_count(), distribution_info.size()): - var child : Panel = list.get_child(list_index) + var child: Panel = list.get_child(list_index) - var distribution_row : Dictionary = distribution_info[list_index] + var distribution_row: Dictionary = distribution_info[list_index] - var colour_icon : GUIIcon = GUINode.get_gui_icon_from_node(child.get_node(^"./legend_color")) + var colour_icon: GUIIcon = GUINode.get_gui_icon_from_node(child.get_node(^"./legend_color")) if colour_icon: colour_icon.set_modulate(distribution_row[slice_colour_key]) colour_icon.set_tooltip_string(distribution_row[slice_tooltip_key]) - var identifier_label : GUILabel = GUINode.get_gui_label_from_node(child.get_node(^"./legend_title")) + var identifier_label: GUILabel = GUINode.get_gui_label_from_node(child.get_node(^"./legend_title")) if identifier_label: identifier_label.set_text(distribution_row[slice_identifier_key]) - var weight_label : GUILabel = GUINode.get_gui_label_from_node(child.get_node(^"./legend_value")) + var weight_label: GUILabel = GUINode.get_gui_label_from_node(child.get_node(^"./legend_value")) if weight_label: weight_label.set_text("%s%%" % GUINode.float_to_string_dp(distribution_row[slice_weight_key] * 100.0, 1)) + func _update_pop_list() -> void: if _pop_list_scrollbar: - var max_scroll_index : int = MenuSingleton.get_population_menu_pop_row_count() - _pop_list_rows.size() + var max_scroll_index: int = MenuSingleton.get_population_menu_pop_row_count() - _pop_list_rows.size() if max_scroll_index > 0: _pop_list_scrollbar.set_step_count(max_scroll_index) _pop_list_scrollbar.show() @@ -547,32 +571,35 @@ func _update_pop_list() -> void: _pop_list_scrollbar.set_step_count(0) _pop_list_scrollbar.hide() - var pop_rows = MenuSingleton.get_population_menu_pop_rows(_pop_list_scroll_index, _pop_list_rows.size()) + var pop_rows = MenuSingleton.get_population_menu_pop_rows( + _pop_list_scroll_index, + _pop_list_rows.size(), + ) - for index : int in _pop_list_rows.size(): + for index: int in _pop_list_rows.size(): if not _pop_list_rows[index]: continue if index < pop_rows.size(): - const pop_size_key : StringName = &"size" - const pop_type_icon_key : StringName = &"pop_type_icon" - const pop_culture_key : StringName = &"culture" - const pop_religion_icon_key : StringName = &"religion_icon" - const pop_location_key : StringName = &"location" - const pop_militancy_key : StringName = &"militancy" - const pop_consciousness_key : StringName = &"consciousness" - const pop_ideology_key : StringName = &"ideology" - const pop_issues_key : StringName = &"issues" - const pop_unemployment_key : StringName = &"unemployment" - const pop_cash_key : StringName = &"cash" - const pop_daily_money_key : StringName = &"daily_money" - const pop_life_needs_key : StringName = &"life_needs" - const pop_everyday_needs_key : StringName = &"everyday_needs" - const pop_luxury_needs_key : StringName = &"luxury_needs" - const pop_rebel_icon_key : StringName = &"rebel_icon" - const pop_size_change_key : StringName = &"size_change" - const pop_literacy_key : StringName = &"literacy" - - var pop_row : Dictionary = pop_rows[index] + const pop_size_key: StringName = &"size" + const pop_type_icon_key: StringName = &"pop_type_icon" + const pop_culture_key: StringName = &"culture" + const pop_religion_icon_key: StringName = &"religion_icon" + const pop_location_key: StringName = &"location" + const pop_militancy_key: StringName = &"militancy" + const pop_consciousness_key: StringName = &"consciousness" + const pop_ideology_key: StringName = &"ideology" + const pop_issues_key: StringName = &"issues" + const pop_unemployment_key: StringName = &"unemployment" + const pop_cash_key: StringName = &"cash" + const pop_daily_money_key: StringName = &"daily_money" + const pop_life_needs_key: StringName = &"life_needs" + const pop_everyday_needs_key: StringName = &"everyday_needs" + const pop_luxury_needs_key: StringName = &"luxury_needs" + const pop_rebel_icon_key: StringName = &"rebel_icon" + const pop_size_change_key: StringName = &"size_change" + const pop_literacy_key: StringName = &"literacy" + + var pop_row: Dictionary = pop_rows[index] if _pop_list_size_labels[index]: _pop_list_size_labels[index].set_text(GUINode.int_to_string_suffixed(pop_row[pop_size_key])) @@ -588,7 +615,7 @@ func _update_pop_list() -> void: # TODO - replace with actual religion _pop_list_religion_icons[index].set_tooltip_string("Religion #%d" % pop_row[pop_religion_icon_key]) if _pop_list_location_labels[index]: - var province_name : String = GUINode.format_province_name(pop_row.get(pop_location_key, "")) + var province_name: String = GUINode.format_province_name(pop_row.get(pop_location_key, "")) _pop_list_location_labels[index].set_text(province_name) _pop_list_location_labels[index].set_tooltip_string(province_name) if _pop_list_militancy_labels[index]: @@ -604,36 +631,42 @@ func _update_pop_list() -> void: if _pop_list_issues_charts[index]: _pop_list_issues_charts[index].set_slices_array(pop_row[pop_issues_key]) if _pop_list_unemployment_progressbars[index]: - var unemployment : float = pop_row[pop_unemployment_key] + var unemployment: float = pop_row[pop_unemployment_key] _pop_list_unemployment_progressbars[index].set_value_no_signal(unemployment) _pop_list_unemployment_progressbars[index].set_tooltip_string("%s: §Y%s%%" % [ - tr(&"UNEMPLOYMENT"), GUINode.float_to_string_dp(unemployment * 100.0, 3) + tr(&"UNEMPLOYMENT"), GUINode.float_to_string_dp(unemployment * 100.0, 3), ]) if _pop_list_cash_labels[index]: _pop_list_cash_labels[index].set_text("%s¤" % GUINode.float_to_string_dp(pop_row[pop_cash_key], 2)) _pop_list_cash_labels[index].set_tooltip_string_and_substitution_dict("POP_DAILY_MONEY", { - "VAL": GUINode.float_to_string_dp(pop_row[pop_daily_money_key], 2) + "VAL": GUINode.float_to_string_dp(pop_row[pop_daily_money_key], 2), }) if _pop_list_life_needs_progressbars[index]: - var life_needs : float = pop_row[pop_life_needs_key] + var life_needs: float = pop_row[pop_life_needs_key] _pop_list_life_needs_progressbars[index].set_value_no_signal(life_needs) _pop_list_life_needs_progressbars[index].set_tooltip_string_and_substitution_dict("GETTING_NEEDS", { - "NEED": "LIFE_NEEDS", "VAL": GUINode.float_to_string_dp(life_needs * 100.0, 1) + "NEED": "LIFE_NEEDS", "VAL": GUINode.float_to_string_dp(life_needs * 100.0, 1), }) if _pop_list_everyday_needs_progressbars[index]: - var everyday_needs : float = pop_row[pop_everyday_needs_key] + var everyday_needs: float = pop_row[pop_everyday_needs_key] _pop_list_everyday_needs_progressbars[index].set_value_no_signal(everyday_needs) _pop_list_everyday_needs_progressbars[index].set_tooltip_string_and_substitution_dict("GETTING_NEEDS", { - "NEED": "EVERYDAY_NEEDS", "VAL": GUINode.float_to_string_dp(everyday_needs * 100.0, 1) + "NEED": "EVERYDAY_NEEDS", "VAL": GUINode.float_to_string_dp( + everyday_needs * 100.0, + 1, + ), }) if _pop_list_luxury_needs_progressbars[index]: - var luxury_needs : float = pop_row[pop_luxury_needs_key] + var luxury_needs: float = pop_row[pop_luxury_needs_key] _pop_list_luxury_needs_progressbars[index].set_value_no_signal(luxury_needs) _pop_list_luxury_needs_progressbars[index].set_tooltip_string_and_substitution_dict("GETTING_NEEDS", { - "NEED": "LUXURY_NEEDS", "VAL": GUINode.float_to_string_dp(luxury_needs * 100.0, 1) + "NEED": "LUXURY_NEEDS", "VAL": GUINode.float_to_string_dp( + luxury_needs * 100.0, + 1, + ), }) if _pop_list_rebel_icons[index]: - var rebel_icon : int = pop_row.get(pop_rebel_icon_key, 0) + var rebel_icon: int = pop_row.get(pop_rebel_icon_key, 0) if rebel_icon > 0: _pop_list_rebel_icons[index].set_icon_index(rebel_icon) _pop_list_rebel_icons[index].show() @@ -649,15 +682,15 @@ func _update_pop_list() -> void: _pop_list_national_movement_flags[index].hide() if _pop_list_size_change_icons[index]: - var pop_change : int = pop_row[pop_size_change_key] + var pop_change: int = pop_row[pop_size_change_key] _pop_list_size_change_icons[index].set_icon_index(get_growth_icon_index(pop_change)) _pop_list_size_change_icons[index].set_tooltip_string("%s §%s%s" % [ - tr(&"POPULATION_CHANGED_BY"), "G+" if pop_change > 0 else "Y+" if pop_change == 0 else "R", str(pop_change) + tr(&"POPULATION_CHANGED_BY"), "G+" if pop_change > 0 else "Y+" if pop_change == 0 else "R", str(pop_change), ]) if _pop_list_literacy_labels[index]: _pop_list_literacy_labels[index].set_text("%s%%" % GUINode.float_to_string_dp(pop_row[pop_literacy_key] * 100, 2)) _pop_list_literacy_labels[index].set_tooltip_string("%s: §G%s%%" % [ - tr(&"LIT_CHANGE"), GUINode.float_to_string_dp(pop_row[pop_literacy_key] / 64.0, 2) + tr(&"LIT_CHANGE"), GUINode.float_to_string_dp(pop_row[pop_literacy_key] / 64.0, 2), ]) _pop_list_rows[index].show() diff --git a/game/src/UI/Session/ProductionMenu.gd b/game/src/UI/Session/ProductionMenu.gd index c83a3516..9c90785a 100644 --- a/game/src/UI/Session/ProductionMenu.gd +++ b/game/src/UI/Session/ProductionMenu.gd @@ -1,8 +1,9 @@ extends GUINode -var _active : bool = false +const _screen: NationManagement.Screen = NationManagement.Screen.PRODUCTION + +var _active: bool = false -const _screen : NationManagement.Screen = NationManagement.Screen.PRODUCTION func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -11,26 +12,29 @@ func _ready() -> void: add_gui_element("country_production", "country_production") - var project_listbox : GUIListBox = get_gui_listbox_from_nodepath(^"./country_production/good_production/project_listbox") + var project_listbox: GUIListBox = get_gui_listbox_from_nodepath(^"./country_production/good_production/project_listbox") if project_listbox: project_listbox.mouse_filter = Control.MOUSE_FILTER_IGNORE set_click_mask_from_nodepaths([^"./country_production/main_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_production/close_button") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_production/close_button") if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: + +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: _active = active_screen == _screen _update_info() + func _update_info() -> void: if _active: # TODO - update UI state diff --git a/game/src/UI/Session/ProvinceOverviewPanel.gd b/game/src/UI/Session/ProvinceOverviewPanel.gd index 17eaf799..159c3ce9 100644 --- a/game/src/UI/Session/ProvinceOverviewPanel.gd +++ b/game/src/UI/Session/ProvinceOverviewPanel.gd @@ -1,120 +1,56 @@ extends GUINode -# Header -var _province_name_label : GUILabel -var _state_name_label : GUILabel -var _slave_status_icon : GUIIcon -var _colony_status_button : GUIIconButton -var _administrative_percentage_label : GUILabel -var _owner_percentage_label : GUILabel -var _province_modifiers_overlapping_elements_box : GUIOverlappingElementsBox -var _terrain_type_icon : GUIIcon -var _life_rating_bar : GUIProgressBar -var _controller_flag : GUIMaskedFlag - -# Statistics -var _rgo_icon : GUIIcon -var _rgo_produced_label : GUILabel -var _rgo_income_label : GUILabel -var _rgo_employment_percentage_icon : GUIIcon -var _rgo_employment_population_label : GUILabel -var _rgo_employment_percentage_label : GUILabel -var _crime_name_label : GUILabel -var _crime_icon : GUIIcon -var _crime_fighting_label : GUILabel -var _total_population_label : GUILabel -var _migration_label : GUILabel -var _population_growth_label : GUILabel -var _pop_types_piechart : GUIPieChart -var _pop_ideologies_piechart : GUIPieChart -var _pop_cultures_piechart : GUIPieChart -var _supply_limit_label : GUILabel -var _cores_overlapping_elements_box : GUIOverlappingElementsBox - -# Buildings -var _buildings_panel : Panel -var _building_slots : Array[BuildingSlot] +enum ColonyStatus { + STATE, + PROTECTORATE, + COLONY, +} -# REQUIREMENTS: -# * UI-183, UI-185, UI-186, UI-765, UI-187, UI-188, UI-189 -# * UI-191, UI-193, UI-194, UI-766, UI-195, UI-196, UI-197 -# * UI-199, UI-201, UI-202, UI-767, UI-203, UI-204, UI-205 -class BuildingSlot: - var _slot_index : int - var _slot_node : Control - var _building_icon : GUIIcon - var _expand_button : GUIIconButton - var _expanding_icon : GUIIcon - var _expanding_progress_bar : GUIProgressBar - var _expanding_label : GUILabel - - func _init(new_slot_index : int, new_slot_node : Control) -> void: - if new_slot_index < 0: - push_error("Invalid building slot index: ", new_slot_index) - return - _slot_index = new_slot_index - if not new_slot_node: - push_error("Invalid building slot node: null!") - return - _slot_node = new_slot_node - - for icon_index : int in MenuSingleton.get_province_building_count(): - var icon := _slot_node.get_node("build_icon%d" % icon_index) - if icon: - if icon_index == _slot_index: - _building_icon = GUINode.get_gui_icon_from_node(icon) - else: - icon.hide() - - var building_name := GUINode.get_gui_label_from_node(_slot_node.get_node(^"./description")) - if building_name: - building_name.text = MenuSingleton.get_province_building_identifier(_slot_index) - _expand_button = GUINode.get_gui_icon_button_from_node(_slot_node.get_node(^"./expand")) - if _expand_button: - _expand_button.pressed.connect(func() -> void: PlayerSingleton.expand_selected_province_building(_slot_index)) - _expanding_icon = GUINode.get_gui_icon_from_node(_slot_node.get_node(^"./underconstruction_icon")) - _expanding_progress_bar = GUINode.get_gui_progress_bar_from_node(_slot_node.get_node(^"./building_progress")) - if _expanding_progress_bar: - _expanding_progress_bar.max_value = 1.0 - _expanding_progress_bar.step = _expanding_progress_bar.max_value / 100 - _expanding_label = GUINode.get_gui_label_from_node(_slot_node.get_node(^"./expand_text")) - - enum ExpansionState { CannotExpand, CanExpand, Preparing, Expanding } - - func update_info(info : Dictionary) -> void: - const building_info_level_key : StringName = &"level" - const building_info_expansion_state_key : StringName = &"expansion_state" - const building_info_start_date_key : StringName = &"start_date" - const building_info_end_date_key : StringName = &"end_date" - const building_info_expansion_progress_key : StringName = &"expansion_progress" - - if _building_icon: - _building_icon.set_icon_index(info.get(building_info_level_key, 0) + 1) - - var expansion_state : int = info.get(building_info_expansion_state_key, ExpansionState.CannotExpand) - var expansion_in_progress : bool = expansion_state == ExpansionState.Preparing or expansion_state == ExpansionState.Expanding +# Header - if _expand_button: - _expand_button.visible = not expansion_in_progress - _expand_button.disabled = expansion_state != ExpansionState.CanExpand +var _province_name_label: GUILabel +var _state_name_label: GUILabel +var _slave_status_icon: GUIIcon +var _colony_status_button: GUIIconButton +var _administrative_percentage_label: GUILabel +var _owner_percentage_label: GUILabel +var _province_modifiers_overlapping_elements_box: GUIOverlappingElementsBox +var _terrain_type_icon: GUIIcon +var _life_rating_bar: GUIProgressBar +var _controller_flag: GUIMaskedFlag - if _expanding_icon: - _expanding_icon.visible = expansion_in_progress - if _expanding_progress_bar: - _expanding_progress_bar.visible = expansion_in_progress - _expanding_progress_bar.value = info.get(building_info_expansion_progress_key, 0) +# Statistics +var _rgo_icon: GUIIcon +var _rgo_produced_label: GUILabel +var _rgo_income_label: GUILabel +var _rgo_employment_percentage_icon: GUIIcon +var _rgo_employment_population_label: GUILabel +var _rgo_employment_percentage_label: GUILabel +var _crime_name_label: GUILabel +var _crime_icon: GUIIcon +var _crime_fighting_label: GUILabel +var _total_population_label: GUILabel +var _migration_label: GUILabel +var _population_growth_label: GUILabel +var _pop_types_piechart: GUIPieChart +var _pop_ideologies_piechart: GUIPieChart +var _pop_cultures_piechart: GUIPieChart +var _supply_limit_label: GUILabel +var _cores_overlapping_elements_box: GUIOverlappingElementsBox - if _expanding_label: - _expanding_label.visible = expansion_in_progress -var _selected_province_number : int: +# Buildings +var _buildings_panel: Panel +var _building_slots: Array[BuildingSlot] +var _selected_province_number: int: get: return _selected_province_number set(v): _selected_province_number = v _update_info() -var _province_info : Dictionary +var _province_info: Dictionary + func _ready() -> void: PlayerSingleton.province_selected.connect(_on_province_selected) @@ -130,7 +66,7 @@ func _ready() -> void: prov_view.mouse_filter = Control.MOUSE_FILTER_IGNORE set_click_mask_from_nodepaths([^"./province_view/background"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./province_view/close_button") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./province_view/close_button") if close_button: close_button.pressed.connect(_on_close_button_pressed) @@ -144,13 +80,16 @@ func _ready() -> void: if _slave_status_icon: _slave_status_icon.set_icon_index(MenuSingleton.get_slave_pop_icon_index()) _colony_status_button = get_gui_icon_button_from_nodepath(^"./province_view/province_view_header/colony_button") - var admin_icon : GUIIcon = get_gui_icon_from_nodepath(^"./province_view/province_view_header/admin_icon") + var admin_icon: GUIIcon = get_gui_icon_from_nodepath(^"./province_view/province_view_header/admin_icon") if admin_icon: admin_icon.set_icon_index(MenuSingleton.get_administrative_pop_icon_index()) _administrative_percentage_label = get_gui_label_from_nodepath(^"./province_view/province_view_header/admin_efficiency") _owner_percentage_label = get_gui_label_from_nodepath(^"./province_view/province_view_header/owner_presence") _province_modifiers_overlapping_elements_box = get_gui_overlapping_elements_box_from_nodepath(^"./province_view/province_view_header/province_modifiers") - if _province_modifiers_overlapping_elements_box and _province_modifiers_overlapping_elements_box.set_gui_child_element_name("province_interface", "prov_state_modifier") != OK: + if _province_modifiers_overlapping_elements_box and _province_modifiers_overlapping_elements_box.set_gui_child_element_name( + "province_interface", + "prov_state_modifier", + ) != OK: _province_modifiers_overlapping_elements_box = null # hide province modifiers box since we can't do anything with it _terrain_type_icon = get_gui_icon_from_nodepath(^"./province_view/province_view_header/prov_terrain") _life_rating_bar = get_gui_progress_bar_from_nodepath(^"./province_view/province_view_header/liferating") @@ -172,7 +111,7 @@ func _ready() -> void: _pop_types_piechart = get_gui_pie_chart_from_nodepath(^"./province_view/province_statistics/workforce_chart") _pop_ideologies_piechart = get_gui_pie_chart_from_nodepath(^"./province_view/province_statistics/ideology_chart") _pop_cultures_piechart = get_gui_pie_chart_from_nodepath(^"./province_view/province_statistics/culture_chart") - var population_menu_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./province_view/province_statistics/open_popscreen") + var population_menu_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./province_view/province_statistics/open_popscreen") if population_menu_button: population_menu_button.pressed.connect( func() -> void: @@ -182,22 +121,34 @@ func _ready() -> void: ) _supply_limit_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/supply_limit_label") _cores_overlapping_elements_box = get_gui_overlapping_elements_box_from_nodepath(^"./province_view/province_statistics/core_icons") - if _cores_overlapping_elements_box and _cores_overlapping_elements_box.set_gui_child_element_name("province_interface", "province_core") != OK: + if _cores_overlapping_elements_box and _cores_overlapping_elements_box.set_gui_child_element_name( + "province_interface", + "province_core", + ) != OK: _cores_overlapping_elements_box = null # hide cores box since we can't do anything with it _buildings_panel = get_panel_from_nodepath(^"./province_view/province_buildings") if _buildings_panel: - var target_slot_count : int = MenuSingleton.get_province_building_count() - var slot_y : float = 0.0 - for current_slot_count : int in target_slot_count: - var slot := GUINode.generate_gui_element("province_interface", "building", "building_slot_%d" % current_slot_count) + var target_slot_count: int = MenuSingleton.get_province_building_count() + var slot_y: float = 0.0 + for current_slot_count: int in target_slot_count: + var slot := GUINode.generate_gui_element( + "province_interface", + "building", + "building_slot_%d" % current_slot_count, + ) if slot: _buildings_panel.add_child(slot) slot.set_position(Vector2(0.0, slot_y)) slot_y += slot.get_size().y _building_slots.push_back(BuildingSlot.new(current_slot_count, slot)) else: - push_error("Failed to generate building slot ", current_slot_count, " / ", target_slot_count) + push_error( + "Failed to generate building slot ", + current_slot_count, + " / ", + target_slot_count, + ) break hide_nodes([ @@ -215,48 +166,51 @@ func _ready() -> void: _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() -enum ColonyStatus { STATE, PROTECTORATE, COLONY } # This assumes _cores_overlapping_elements_box is non-null -func _set_core_flag(core_index : int, country : String) -> void: - var core_flag_button : GUIMaskedFlagButton = GUINode.get_gui_masked_flag_button_from_node( + + +func _set_core_flag(core_index: int, country: String) -> void: + var core_flag_button: GUIMaskedFlagButton = GUINode.get_gui_masked_flag_button_from_node( _cores_overlapping_elements_box.get_child(core_index).get_node(^"./country_flag") ) if core_flag_button: core_flag_button.set_flag_country_name(country) + func _update_info() -> void: - const _province_info_province_key : StringName = &"province" - const _province_info_state_key : StringName = &"state" - const _province_info_slave_status_key : StringName = &"slave_status" - const _province_info_colony_status_key : StringName = &"colony_status" - const _province_info_terrain_type_key : StringName = &"terrain_type" - const _province_info_terrain_type_tooltip_key : StringName = &"terrain_type_tooltip" - const _province_info_life_rating_key : StringName = &"life_rating" - const _province_info_controller_key : StringName = &"controller" - const _province_info_controller_tooltip_key : StringName = &"controller_tooltip" - const _province_info_rgo_icon_key : StringName = &"rgo_icon" - const _province_info_rgo_production_tooltip_key : StringName = &"rgo_production_tooltip" - const _province_info_rgo_total_employees_key : StringName = &"rgo_total_employees" - const _province_info_rgo_employment_percentage_key : StringName = &"rgo_employment_percentage" - const _province_info_rgo_employment_tooltip_key : StringName = &"rgo_employment_tooltip" - const _province_info_rgo_output_quantity_yesterday_key : StringName = &"rgo_output_quantity_yesterday" - const _province_info_rgo_revenue_yesterday_key : StringName = &"rgo_revenue_yesterday" - const _province_info_crime_name_key : StringName = &"crime_name" - const _province_info_crime_icon_key : StringName = &"crime_icon" - const _province_info_total_population_key : StringName = &"total_population" - const _province_info_pop_types_key : StringName = &"pop_types" - const _province_info_pop_ideologies_key : StringName = &"pop_ideologies" - const _province_info_pop_cultures_key : StringName = &"pop_cultures" - const _province_info_cores_key : StringName = &"cores" - const _province_info_buildings_key : StringName = &"buildings" - - const _missing_suffix : String = "_MISSING" + const _province_info_province_key: StringName = &"province" + const _province_info_state_key: StringName = &"state" + const _province_info_slave_status_key: StringName = &"slave_status" + const _province_info_colony_status_key: StringName = &"colony_status" + const _province_info_terrain_type_key: StringName = &"terrain_type" + const _province_info_terrain_type_tooltip_key: StringName = &"terrain_type_tooltip" + const _province_info_life_rating_key: StringName = &"life_rating" + const _province_info_controller_key: StringName = &"controller" + const _province_info_controller_tooltip_key: StringName = &"controller_tooltip" + const _province_info_rgo_icon_key: StringName = &"rgo_icon" + const _province_info_rgo_production_tooltip_key: StringName = &"rgo_production_tooltip" + const _province_info_rgo_total_employees_key: StringName = &"rgo_total_employees" + const _province_info_rgo_employment_percentage_key: StringName = &"rgo_employment_percentage" + const _province_info_rgo_employment_tooltip_key: StringName = &"rgo_employment_tooltip" + const _province_info_rgo_output_quantity_yesterday_key: StringName = &"rgo_output_quantity_yesterday" + const _province_info_rgo_revenue_yesterday_key: StringName = &"rgo_revenue_yesterday" + const _province_info_crime_name_key: StringName = &"crime_name" + const _province_info_crime_icon_key: StringName = &"crime_icon" + const _province_info_total_population_key: StringName = &"total_population" + const _province_info_pop_types_key: StringName = &"pop_types" + const _province_info_pop_ideologies_key: StringName = &"pop_ideologies" + const _province_info_pop_cultures_key: StringName = &"pop_cultures" + const _province_info_cores_key: StringName = &"cores" + const _province_info_buildings_key: StringName = &"buildings" + + const _missing_suffix: String = "_MISSING" _province_info = MenuSingleton.get_province_info_from_number(_selected_province_number) if _province_info: @@ -265,12 +219,15 @@ func _update_info() -> void: _province_name_label.text = GUINode.format_province_name(_province_info.get(_province_info_province_key, _missing_suffix)) if _state_name_label: - _state_name_label.text = _province_info.get(_province_info_state_key, tr(_province_info_state_key + _missing_suffix)) + _state_name_label.text = _province_info.get( + _province_info_state_key, + tr(_province_info_state_key + _missing_suffix), + ) if _slave_status_icon: _slave_status_icon.visible = _province_info.get(_province_info_slave_status_key, false) - var colony_status : ColonyStatus = _province_info.get(_province_info_colony_status_key, 0) + var colony_status: ColonyStatus = _province_info.get(_province_info_colony_status_key, 0) if _colony_status_button: if colony_status == ColonyStatus.STATE: _colony_status_button.hide() @@ -287,17 +244,17 @@ func _update_info() -> void: if _province_modifiers_overlapping_elements_box: # TODO - replace example icons with those from the province's list of modifier instances _province_modifiers_overlapping_elements_box.set_child_count(8) - for i : int in _province_modifiers_overlapping_elements_box.get_child_count(): - var button : GUIIconButton = GUINode.get_gui_icon_button_from_node( + for i: int in _province_modifiers_overlapping_elements_box.get_child_count(): + var button: GUIIconButton = GUINode.get_gui_icon_button_from_node( _province_modifiers_overlapping_elements_box.get_child(i).get_node(^"./modifier") ) if button: button.set_icon_index(2 * i + (i & 1) + 1) if _terrain_type_icon: - var terrain_type : String = _province_info.get(_province_info_terrain_type_key, "") + var terrain_type: String = _province_info.get(_province_info_terrain_type_key, "") if terrain_type: - const _terrain_type_prefix : String = "GFX_terrainimg_" + const _terrain_type_prefix: String = "GFX_terrainimg_" if _terrain_type_icon.set_gfx_texture_sprite_name(_terrain_type_prefix + terrain_type) != OK: push_error("Failed to set terrain type texture: ", terrain_type) _terrain_type_icon.set_tooltip_string(_province_info.get(_province_info_terrain_type_tooltip_key, "")) @@ -310,22 +267,37 @@ func _update_info() -> void: _controller_flag.set_tooltip_string(_province_info.get(_province_info_controller_tooltip_key, "")) # Statistics - var rgo_production_tooltip : String = _province_info.get(_province_info_rgo_production_tooltip_key, "") + var rgo_production_tooltip: String = _province_info.get( + _province_info_rgo_production_tooltip_key, + "", + ) if _rgo_icon: _rgo_icon.set_icon_index(_province_info.get(_province_info_rgo_icon_key, -1) + 2) _rgo_icon.set_tooltip_string(rgo_production_tooltip) if _rgo_produced_label: - _rgo_produced_label.text = GUINode.float_to_string_dp(_province_info.get(_province_info_rgo_output_quantity_yesterday_key, 0), 3) + _rgo_produced_label.text = GUINode.float_to_string_dp( + _province_info.get(_province_info_rgo_output_quantity_yesterday_key, 0), + 3, + ) _rgo_produced_label.set_tooltip_string(rgo_production_tooltip) if _rgo_income_label: - _rgo_income_label.text = "%s¤" % GUINode.float_to_string_dp(_province_info.get(_province_info_rgo_revenue_yesterday_key, 0), 3) + _rgo_income_label.text = "%s¤" % GUINode.float_to_string_dp( + _province_info.get(_province_info_rgo_revenue_yesterday_key, 0), + 3, + ) _rgo_income_label.set_tooltip_string(rgo_production_tooltip) - var rgo_employment_percentage : int = _province_info.get(_province_info_rgo_employment_percentage_key, 0) - var rgo_employment_tooltip : String = _province_info.get(_province_info_rgo_employment_tooltip_key, "") + var rgo_employment_percentage: int = _province_info.get( + _province_info_rgo_employment_percentage_key, + 0, + ) + var rgo_employment_tooltip: String = _province_info.get( + _province_info_rgo_employment_tooltip_key, + "", + ) if _rgo_employment_percentage_icon: _rgo_employment_percentage_icon.set_icon_index(rgo_employment_percentage / 10 + 1) @@ -370,19 +342,25 @@ func _update_info() -> void: pass if _cores_overlapping_elements_box: - var cores : PackedStringArray = _province_info.get(_province_info_cores_key, []) + var cores: PackedStringArray = _province_info.get(_province_info_cores_key, []) _cores_overlapping_elements_box.set_child_count(cores.size()) - for core_index : int in min(cores.size(), _cores_overlapping_elements_box.get_child_count()): + for core_index: int in min( + cores.size(), + _cores_overlapping_elements_box.get_child_count(), + ): _set_core_flag(core_index, cores[core_index]) - for core_index : int in range(cores.size(), _cores_overlapping_elements_box.get_child_count()): + for core_index: int in range( + cores.size(), + _cores_overlapping_elements_box.get_child_count(), + ): _set_core_flag(core_index, "") # Buildings if _buildings_panel: - var buildings : Array[Dictionary] = _province_info.get(_province_info_buildings_key, [] as Array[Dictionary]) - for slot_index : int in min(buildings.size(), _building_slots.size()): + var buildings: Array[Dictionary] = _province_info.get(_province_info_buildings_key, [] as Array[Dictionary]) + for slot_index: int in min(buildings.size(), _building_slots.size()): _building_slots[slot_index].update_info(buildings[slot_index]) - for slot_index : int in range(buildings.size(), _building_slots.size()): + for slot_index: int in range(buildings.size(), _building_slots.size()): _building_slots[slot_index].update_info({}) show() @@ -390,8 +368,104 @@ func _update_info() -> void: hide() mouse_exited.emit() -func _on_province_selected(province_number : int) -> void: + +func _on_province_selected(province_number: int) -> void: _selected_province_number = province_number + func _on_close_button_pressed() -> void: PlayerSingleton.unset_selected_province() + + +# REQUIREMENTS: +# * UI-183, UI-185, UI-186, UI-765, UI-187, UI-188, UI-189 +# * UI-191, UI-193, UI-194, UI-766, UI-195, UI-196, UI-197 +# * UI-199, UI-201, UI-202, UI-767, UI-203, UI-204, UI-205 + + +class BuildingSlot: + enum ExpansionState { CannotExpand, CanExpand, Preparing, Expanding } + + + var _slot_index: int + + + var _slot_node: Control + + + var _building_icon: GUIIcon + + + var _expand_button: GUIIconButton + + + var _expanding_icon: GUIIcon + + + var _expanding_progress_bar: GUIProgressBar + + + var _expanding_label: GUILabel + + + func _init(new_slot_index: int, new_slot_node: Control) -> void: + if new_slot_index < 0: + push_error("Invalid building slot index: ", new_slot_index) + return + _slot_index = new_slot_index + if not new_slot_node: + push_error("Invalid building slot node: null!") + return + _slot_node = new_slot_node + + for icon_index: int in MenuSingleton.get_province_building_count(): + var icon := _slot_node.get_node("build_icon%d" % icon_index) + if icon: + if icon_index == _slot_index: + _building_icon = GUINode.get_gui_icon_from_node(icon) + else: + icon.hide() + + var building_name := GUINode.get_gui_label_from_node(_slot_node.get_node(^"./description")) + if building_name: + building_name.text = MenuSingleton.get_province_building_identifier(_slot_index) + _expand_button = GUINode.get_gui_icon_button_from_node(_slot_node.get_node(^"./expand")) + if _expand_button: + _expand_button.pressed.connect(func() -> void: PlayerSingleton.expand_selected_province_building(_slot_index)) + _expanding_icon = GUINode.get_gui_icon_from_node(_slot_node.get_node(^"./underconstruction_icon")) + _expanding_progress_bar = GUINode.get_gui_progress_bar_from_node(_slot_node.get_node(^"./building_progress")) + if _expanding_progress_bar: + _expanding_progress_bar.max_value = 1.0 + _expanding_progress_bar.step = _expanding_progress_bar.max_value / 100 + _expanding_label = GUINode.get_gui_label_from_node(_slot_node.get_node(^"./expand_text")) + + + func update_info(info: Dictionary) -> void: + const building_info_level_key: StringName = &"level" + const building_info_expansion_state_key: StringName = &"expansion_state" + const building_info_start_date_key: StringName = &"start_date" + const building_info_end_date_key: StringName = &"end_date" + const building_info_expansion_progress_key: StringName = &"expansion_progress" + + if _building_icon: + _building_icon.set_icon_index(info.get(building_info_level_key, 0) + 1) + + var expansion_state: int = info.get( + building_info_expansion_state_key, + ExpansionState.CannotExpand, + ) + var expansion_in_progress: bool = expansion_state == ExpansionState.Preparing or expansion_state == ExpansionState.Expanding + + if _expand_button: + _expand_button.visible = not expansion_in_progress + _expand_button.disabled = expansion_state != ExpansionState.CanExpand + + if _expanding_icon: + _expanding_icon.visible = expansion_in_progress + + if _expanding_progress_bar: + _expanding_progress_bar.visible = expansion_in_progress + _expanding_progress_bar.value = info.get(building_info_expansion_progress_key, 0) + + if _expanding_label: + _expanding_label.visible = expansion_in_progress diff --git a/game/src/UI/Session/SearchPanel.gd b/game/src/UI/Session/SearchPanel.gd index 82cbcb03..067ba4d3 100644 --- a/game/src/UI/Session/SearchPanel.gd +++ b/game/src/UI/Session/SearchPanel.gd @@ -1,14 +1,14 @@ extends GUINode -@export var _map_view : MapView +@export var _map_view: MapView -var _search_panel : Panel -var _search_line_edit : LineEdit -var _results_list_box : GUIListBox -var _result_buttons : Array[GUIIconButton] +var _search_panel: Panel +var _search_line_edit: LineEdit +var _results_list_box: GUIListBox +var _result_buttons: Array[GUIIconButton] +var _drag_active: bool = false +var _drag_anchor: Vector2 -var _drag_active : bool = false -var _drag_anchor : Vector2 func _ready() -> void: MenuSingleton.search_cache_changed.connect(_update_results_base) @@ -21,11 +21,11 @@ func _ready() -> void: set_click_mask_from_nodepaths([^"./goto_box/goto_box"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./goto_box/cancel") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./goto_box/cancel") if close_button: close_button.pressed.connect(hide) - var panel_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./goto_box/goto_box") + var panel_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./goto_box/goto_box") if panel_button: panel_button.button_down.connect(_start_drag) panel_button.button_up.connect(_end_drag) @@ -37,7 +37,7 @@ func _ready() -> void: if _search_line_edit: _search_line_edit.text_changed.connect(_search_string_updated) _search_line_edit.text_submitted.connect( - func(new_text : String) -> void: + func(new_text: String) -> void: _result_selected(0) ) # Restrict to desired size (by default it's a bit too tall, probably due to font size) @@ -53,6 +53,18 @@ func _ready() -> void: MenuSingleton.generate_search_cache() + +func _input(event: InputEvent) -> void: + if _drag_active and event is InputEventMouseMotion: + _search_panel.set_position(_drag_anchor + get_window().get_mouse_position()) + + +func _notification(what: int) -> void: + match what: + NOTIFICATION_TRANSLATION_CHANGED: + MenuSingleton.generate_search_cache() + + func toggle_visibility() -> void: if is_visible(): hide() @@ -61,49 +73,45 @@ func toggle_visibility() -> void: if _search_line_edit: _search_line_edit.grab_focus() + func _start_drag() -> void: if _search_panel: _drag_anchor = _search_panel.get_position() - get_window().get_mouse_position() _drag_active = true + func _end_drag() -> void: _drag_active = false -func _input(event : InputEvent) -> void: - if _drag_active and event is InputEventMouseMotion: - _search_panel.set_position(_drag_anchor + get_window().get_mouse_position()) -func _notification(what : int) -> void: - match what: - NOTIFICATION_TRANSLATION_CHANGED: - MenuSingleton.generate_search_cache() - -func _search_string_updated(search_string : String) -> void: +func _search_string_updated(search_string: String) -> void: MenuSingleton.update_search_results(search_string) _update_results_base() + func _update_results_base() -> void: if not _results_list_box: return - var result_count : int = MenuSingleton.get_search_result_row_count() + var result_count: int = MenuSingleton.get_search_result_row_count() - var result_height : float = 0.0 + var result_height: float = 0.0 if result_count > 0 and (_results_list_box.get_child_count() > 0 or _add_result_button()): result_height = _results_list_box.get_child(0).get_size().y _results_list_box.set_fixed(result_count, result_height, false) _update_results_scroll() + func _add_result_button() -> bool: if not _results_list_box: return false - var child : Panel = GUINode.generate_gui_element("menubar", "save_game_entry") + var child: Panel = GUINode.generate_gui_element("menubar", "save_game_entry") if not child: return false - var button : GUIIconButton = GUINode.get_gui_icon_button_from_node(child.get_node(^"./game")) + var button: GUIIconButton = GUINode.get_gui_icon_button_from_node(child.get_node(^"./game")) if not button: child.queue_free() return false @@ -117,7 +125,8 @@ func _add_result_button() -> bool: return true -func _update_results_scroll(scroll_index : int = -1) -> void: + +func _update_results_scroll(scroll_index: int = -1) -> void: if not _results_list_box: return @@ -126,7 +135,10 @@ func _update_results_scroll(scroll_index : int = -1) -> void: scroll_index = _results_list_box.get_scroll_index() - var results : PackedStringArray = MenuSingleton.get_search_result_rows(scroll_index, _results_list_box.get_fixed_visible_items()) + var results: PackedStringArray = MenuSingleton.get_search_result_rows( + scroll_index, + _results_list_box.get_fixed_visible_items(), + ) if results.size() < _result_buttons.size(): _result_buttons.resize(results.size()) @@ -135,10 +147,11 @@ func _update_results_scroll(scroll_index : int = -1) -> void: while _result_buttons.size() < results.size() and _add_result_button(): pass # Button is added in the loop condition - for index : int in min(results.size(), _result_buttons.size()): + for index: int in min(results.size(), _result_buttons.size()): _result_buttons[index].set_text(results[index]) -func _result_selected(index : int) -> void: + +func _result_selected(index: int) -> void: if not _result_buttons.is_empty(): if _map_view: _map_view.look_at_map_position(MenuSingleton.get_search_result_position(index)) @@ -146,7 +159,8 @@ func _result_selected(index : int) -> void: push_error("SearchPanel missing MapView reference!") if _search_line_edit: - # This triggers a search results update, preventing further get_search_result_position(index) calls + # This triggers a search results update, preventing further + # get_search_result_position(index) calls _search_line_edit.clear() hide() diff --git a/game/src/UI/Session/TechnologyMenu.gd b/game/src/UI/Session/TechnologyMenu.gd index 8386fef3..213e6553 100644 --- a/game/src/UI/Session/TechnologyMenu.gd +++ b/game/src/UI/Session/TechnologyMenu.gd @@ -1,8 +1,9 @@ extends GUINode -var _active : bool = false +const _screen: NationManagement.Screen = NationManagement.Screen.TECHNOLOGY + +var _active: bool = false -const _screen : NationManagement.Screen = NationManagement.Screen.TECHNOLOGY func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -13,21 +14,24 @@ func _ready() -> void: set_click_mask_from_nodepaths([^"./country_technology/main_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_technology/close_button") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_technology/close_button") if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: + +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: _active = active_screen == _screen _update_info() + func _update_info() -> void: if _active: # TODO - update UI state diff --git a/game/src/UI/Session/Topbar.gd b/game/src/UI/Session/Topbar.gd index b1143157..808bc8b5 100644 --- a/game/src/UI/Session/Topbar.gd +++ b/game/src/UI/Session/Topbar.gd @@ -1,64 +1,85 @@ extends GUINode +enum CountryStatus { + GREAT_POWER, + SECONDARY_POWER, + CIVILISED, + PARTIALLY_CIVILISED, + UNCIVILISED, + PRIMITIVE, +} + + # Country info -var _country_flag_button : GUIMaskedFlagButton + +var _country_flag_button: GUIMaskedFlagButton + # Time controls -var _speed_up_button : GUIIconButton -var _speed_down_button : GUIIconButton -var _pause_bg_button : GUIButton -var _speed_indicator_button : GUIIconButton -var _date_label : GUILabel +var _speed_up_button: GUIIconButton +var _speed_down_button: GUIIconButton +var _pause_bg_button: GUIButton +var _speed_indicator_button: GUIIconButton +var _date_label: GUILabel + # NationManagement.Screen-GUIIconButton -var _nation_management_buttons : Dictionary +var _nation_management_buttons: Dictionary + # Production -var _production_top_goods_icons : Array[GUIIcon] -var _production_alert_building_icon : GUIIcon -var _production_alert_closed_icon : GUIIcon -var _production_alert_unemployment_icon : GUIIcon +var _production_top_goods_icons: Array[GUIIcon] +var _production_alert_building_icon: GUIIcon +var _production_alert_closed_icon: GUIIcon +var _production_alert_unemployment_icon: GUIIcon + # Technology -var _technology_progress_bar : GUIProgressBar -var _technology_current_research_label : GUILabel -var _technology_literacy_label : GUILabel -var _technology_research_points_label : GUILabel +var _technology_progress_bar: GUIProgressBar +var _technology_current_research_label: GUILabel +var _technology_literacy_label: GUILabel +var _technology_research_points_label: GUILabel + # Politics -var _politics_party_icon : GUIIcon -var _politics_party_label : GUILabel -var _politics_suppression_points_label : GUILabel -var _politics_infamy_label : GUILabel -var _politics_reforms_button : GUIButton -var _politics_decisions_button : GUIIconButton -var _politics_election_icon : GUIIcon -var _politics_rebels_button : GUIIconButton +var _politics_party_icon: GUIIcon +var _politics_party_label: GUILabel +var _politics_suppression_points_label: GUILabel +var _politics_infamy_label: GUILabel +var _politics_reforms_button: GUIButton +var _politics_decisions_button: GUIIconButton +var _politics_election_icon: GUIIcon +var _politics_rebels_button: GUIIconButton + # Population -var _population_total_size_label : GUILabel -var _population_national_foci_label : GUILabel -var _population_militancy_label : GUILabel -var _population_consciousness_label : GUILabel +var _population_total_size_label: GUILabel +var _population_national_foci_label: GUILabel +var _population_militancy_label: GUILabel +var _population_consciousness_label: GUILabel + # Trade -var _trade_imported_icons : Array[GUIIcon] -var _trade_exported_icons : Array[GUIIcon] +var _trade_imported_icons: Array[GUIIcon] +var _trade_exported_icons: Array[GUIIcon] + # Diplomacy -var _diplomacy_peace_label : GUILabel -var _diplomacy_war_enemies_overlapping_elements_box : GUIOverlappingElementsBox -var _diplomacy_diplomatic_points_label : GUILabel -var _diplomacy_alert_colony_button : GUIIconButton -var _diplomacy_alert_crisis_icon : GUIIcon -var _diplomacy_alert_sphere_icon : GUIIcon -var _diplomacy_alert_great_power_icon : GUIIcon +var _diplomacy_peace_label: GUILabel +var _diplomacy_war_enemies_overlapping_elements_box: GUIOverlappingElementsBox +var _diplomacy_diplomatic_points_label: GUILabel +var _diplomacy_alert_colony_button: GUIIconButton +var _diplomacy_alert_crisis_icon: GUIIcon +var _diplomacy_alert_sphere_icon: GUIIcon +var _diplomacy_alert_great_power_icon: GUIIcon + # Military -var _military_army_size_label : GUILabel -var _military_navy_size_label : GUILabel -var _military_mobilisation_size_label : GUILabel -var _military_leadership_points_label : GUILabel +var _military_army_size_label: GUILabel +var _military_navy_size_label: GUILabel +var _military_mobilisation_size_label: GUILabel +var _military_leadership_points_label: GUILabel + func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -68,7 +89,7 @@ func _ready() -> void: hide_nodes([ ^"./topbar/topbar_outlinerbutton_bg", - ^"./topbar/topbar_outlinerbutton" + ^"./topbar/topbar_outlinerbutton", ]) set_click_mask_from_nodepaths([^"./topbar/topbar_bg", ^"./topbar/topbar_paper"]) @@ -112,18 +133,18 @@ func _ready() -> void: _date_label = get_gui_label_from_nodepath(^"./topbar/DateText") # Nation management screens - const screen_nodepaths : Dictionary = { - NationManagement.Screen.PRODUCTION : ^"./topbar/topbarbutton_production", - NationManagement.Screen.BUDGET : ^"./topbar/topbarbutton_budget", - NationManagement.Screen.TECHNOLOGY : ^"./topbar/topbarbutton_tech", - NationManagement.Screen.POLITICS : ^"./topbar/topbarbutton_politics", - NationManagement.Screen.POPULATION : ^"./topbar/topbarbutton_pops", - NationManagement.Screen.TRADE : ^"./topbar/topbarbutton_trade", - NationManagement.Screen.DIPLOMACY : ^"./topbar/topbarbutton_diplomacy", - NationManagement.Screen.MILITARY : ^"./topbar/topbarbutton_military" + const screen_nodepaths: Dictionary = { + NationManagement.Screen.PRODUCTION: ^"./topbar/topbarbutton_production", + NationManagement.Screen.BUDGET: ^"./topbar/topbarbutton_budget", + NationManagement.Screen.TECHNOLOGY: ^"./topbar/topbarbutton_tech", + NationManagement.Screen.POLITICS: ^"./topbar/topbarbutton_politics", + NationManagement.Screen.POPULATION: ^"./topbar/topbarbutton_pops", + NationManagement.Screen.TRADE: ^"./topbar/topbarbutton_trade", + NationManagement.Screen.DIPLOMACY: ^"./topbar/topbarbutton_diplomacy", + NationManagement.Screen.MILITARY: ^"./topbar/topbarbutton_military", } - for screen : NationManagement.Screen in screen_nodepaths: - var button : GUIIconButton = get_gui_icon_button_from_nodepath(screen_nodepaths[screen]) + for screen: NationManagement.Screen in screen_nodepaths: + var button: GUIIconButton = get_gui_icon_button_from_nodepath(screen_nodepaths[screen]) if button: button.pressed.connect( Events.NationManagementScreens.toggle_nation_management_screen.bind(screen) @@ -136,15 +157,15 @@ func _ready() -> void: ) # Production - const PRODUCED_GOOD_COUNT : int = 5 - for idx : int in PRODUCED_GOOD_COUNT: + const PRODUCED_GOOD_COUNT: int = 5 + for idx: int in PRODUCED_GOOD_COUNT: _production_top_goods_icons.push_back(get_gui_icon_from_nodepath("./topbar/topbar_produced%d" % idx)) _production_alert_building_icon = get_gui_icon_from_nodepath(^"./topbar/alert_building_factories") _production_alert_closed_icon = get_gui_icon_from_nodepath(^"./topbar/alert_closed_factories") _production_alert_unemployment_icon = get_gui_icon_from_nodepath(^"./topbar/alert_unemployed_workers") # Technology - var tech_button : GUIIconButton = _nation_management_buttons[NationManagement.Screen.TECHNOLOGY] + var tech_button: GUIIconButton = _nation_management_buttons[NationManagement.Screen.TECHNOLOGY] _technology_progress_bar = get_gui_progress_bar_from_nodepath(^"./topbar/topbar_tech_progress") if _technology_progress_bar and tech_button: _technology_progress_bar.reparent(tech_button) @@ -167,7 +188,7 @@ func _ready() -> void: # Politics _politics_party_icon = get_gui_icon_from_nodepath(^"./topbar/politics_party_icon") _politics_party_label = get_gui_label_from_nodepath(^"./topbar/politics_ruling_party") - var politics_suppression_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./topbar/topbar_supression_icon") + var politics_suppression_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./topbar/topbar_supression_icon") if politics_suppression_button: politics_suppression_button.pressed.connect( func() -> void: @@ -206,7 +227,7 @@ func _ready() -> void: _population_consciousness_label = get_gui_label_from_nodepath(^"./topbar/population_avg_con_value") # Trade - const TRADE_GOOD_COUNT : int = 3 + const TRADE_GOOD_COUNT: int = 3 for idx in TRADE_GOOD_COUNT: _trade_imported_icons.push_back(get_gui_icon_from_nodepath("./topbar/topbar_import%d" % idx)) _trade_exported_icons.push_back(get_gui_icon_from_nodepath("./topbar/topbar_export%d" % idx)) @@ -244,36 +265,31 @@ func _ready() -> void: _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() NOTIFICATION_PREDELETE: - # If the C++ TopBar isn't freed before the destruction of this GUINode, then its update method, triggered by - # gamestate updates, could be called after all the child UI nodes they refer to have been freed, + # If the C++ TopBar isn't freed before the destruction of this GUINode, then its update + # method, triggered by + # gamestate updates, could be called after all the child UI nodes they refer to have + # been freed, # meaning the C++ TopBar would be dereferencing invalid pointers. MenuSingleton.unlink_top_bar_from_cpp() -enum CountryStatus { - GREAT_POWER, - SECONDARY_POWER, - CIVILISED, - PARTIALLY_CIVILISED, - UNCIVILISED, - PRIMITIVE -} func _update_info() -> void: _update_speed_controls() - var topbar_info : Dictionary = MenuSingleton.get_topbar_info() + var topbar_info: Dictionary = MenuSingleton.get_topbar_info() ## Time control if _date_label: _date_label.text = MenuSingleton.get_longform_date() ## Production - for idx : int in _production_top_goods_icons.size(): + for idx: int in _production_top_goods_icons.size(): if _production_top_goods_icons[idx]: _production_top_goods_icons[idx].set_icon_index(idx + 2) @@ -287,13 +303,13 @@ func _update_info() -> void: _production_alert_unemployment_icon.set_icon_index(2) ## Technology - const research_key : StringName = &"research" - const research_tooltip_key : StringName = &"research_tooltip" - const research_progress_key : StringName = &"research_progress" - const literacy_key : StringName = &"literacy" - const literacy_change_key : StringName = &"literacy_change" - const research_points_key : StringName = &"research_points" - const research_points_tooltip_key : StringName = &"research_points_tooltip" + const research_key: StringName = &"research" + const research_tooltip_key: StringName = &"research_tooltip" + const research_progress_key: StringName = &"research_progress" + const literacy_key: StringName = &"literacy" + const literacy_change_key: StringName = &"literacy_change" + const research_points_key: StringName = &"research_points" + const research_points_tooltip_key: StringName = &"research_points_tooltip" if _technology_progress_bar: _technology_progress_bar.set_value(topbar_info.get(research_progress_key, 0)) @@ -303,8 +319,8 @@ func _update_info() -> void: _technology_current_research_label.set_tooltip_string(topbar_info.get(research_tooltip_key, "")) if _technology_literacy_label: - var literacy_float : float = topbar_info.get(literacy_key, 0.0) * 100 - var literacy_string : String = GUINode.float_to_string_dp(literacy_float, 1) + var literacy_float: float = topbar_info.get(literacy_key, 0.0) * 100 + var literacy_string: String = GUINode.float_to_string_dp(literacy_float, 1) _technology_literacy_label.set_text("§Y%s§W%%" % literacy_string) _technology_literacy_label.set_tooltip_string( tr(&"TOPBAR_AVG_LITERACY").replace("$AVG$", literacy_string) + "\n" + tr(&"TOPBAR_AVG_CHANGE").replace( @@ -343,8 +359,8 @@ func _update_info() -> void: ## Population if _population_total_size_label: # TODO - suffixes on both numbers should be white! - var total_population : int = 16000000 - var growth : int = 1500 + var total_population: int = 16_000_000 + var growth: int = 1500 _population_total_size_label.set_text("§Y%s§!(§%s%s§!)" % [ GUINode.int_to_string_suffixed(total_population), "G" if growth >= 0 else "R", @@ -352,8 +368,8 @@ func _update_info() -> void: ]) if _population_national_foci_label: - var foci_used : int = 1 - var max_foci : int = 1 + var foci_used: int = 1 + var max_foci: int = 1 _population_national_foci_label.set_text("§%s%d/%d" % ["R" if foci_used < max_foci else "G", foci_used, max_foci]) if _population_militancy_label: @@ -363,11 +379,11 @@ func _update_info() -> void: _population_consciousness_label.set_text("§Y%s" % GUINode.float_to_string_dp(0.05, 2)) ## Trade - for idx : int in _trade_imported_icons.size(): + for idx: int in _trade_imported_icons.size(): if _trade_imported_icons[idx]: _trade_imported_icons[idx].set_icon_index(idx + 2 + _production_top_goods_icons.size()) - for idx : int in _trade_exported_icons.size(): + for idx: int in _trade_exported_icons.size(): if _trade_exported_icons[idx]: _trade_exported_icons[idx].set_icon_index(idx + 2 + _production_top_goods_icons.size() + _trade_imported_icons.size()) @@ -393,14 +409,16 @@ func _update_info() -> void: _diplomacy_alert_great_power_icon.set_icon_index(2) ## Military - const regiment_count_key : StringName = &"regiment_count"; - const max_supported_regiments_key : StringName = &"max_supported_regiments"; + const regiment_count_key: StringName = &"regiment_count" - var regiment_count : String = str(topbar_info.get(regiment_count_key, 0)) + const max_supported_regiments_key: StringName = &"max_supported_regiments" + + + var regiment_count: String = str(topbar_info.get(regiment_count_key, 0)) if _military_army_size_label: - var army_size_dict : Dictionary = { - "CURR": regiment_count, "MAX": str(topbar_info.get(max_supported_regiments_key, 0)) + var army_size_dict: Dictionary = { + "CURR": regiment_count, "MAX": str(topbar_info.get(max_supported_regiments_key, 0)), } _military_army_size_label.set_substitution_dict(army_size_dict) _military_army_size_label.set_tooltip_substitution_dict(army_size_dict) @@ -409,31 +427,32 @@ func _update_info() -> void: _military_navy_size_label.set_text("§Y%d/%d" % [0, 0]) # TODO - navy size tooltip - const is_mobilised_key : StringName = &"is_mobilised" - const mobilisation_regiments_key : StringName = &"mobilisation_regiments" - const mobilisation_impact_tooltip_key : StringName = &"mobilisation_impact_tooltip" + const is_mobilised_key: StringName = &"is_mobilised" + const mobilisation_regiments_key: StringName = &"mobilisation_regiments" + const mobilisation_impact_tooltip_key: StringName = &"mobilisation_impact_tooltip" if _military_mobilisation_size_label: if topbar_info.get(is_mobilised_key, false): _military_mobilisation_size_label.set_text("§R-") _military_mobilisation_size_label.set_tooltip_string("TOPBAR_MOBILIZED") else: - var mobilisation_regiments : String = str(topbar_info.get(mobilisation_regiments_key, 0)) + var mobilisation_regiments: String = str(topbar_info.get(mobilisation_regiments_key, 0)) _military_mobilisation_size_label.set_text("§Y%s" % mobilisation_regiments) _military_mobilisation_size_label.set_tooltip_string( tr(&"TOPBAR_MOBILIZE_TOOLTIP").replace("$CURR$", mobilisation_regiments) + "\n\n" + topbar_info.get(mobilisation_impact_tooltip_key, "") ) - const leadership_key : StringName = &"leadership" - const leadership_tooltip_key : StringName = &"leadership_tooltip" + const leadership_key: StringName = &"leadership" + const leadership_tooltip_key: StringName = &"leadership_tooltip" if _military_leadership_points_label: _military_leadership_points_label.set_text("§Y%d" % topbar_info.get(leadership_key, 0)) _military_leadership_points_label.set_tooltip_string(topbar_info.get(leadership_tooltip_key, "")) + func _update_speed_controls() -> void: - var paused : bool = MenuSingleton.is_paused() - var speed : int = MenuSingleton.get_speed() + var paused: bool = MenuSingleton.is_paused() + var speed: int = MenuSingleton.get_speed() # TODO - decide whether to disable these or not # (they don't appear to get disabled in the base game) @@ -447,23 +466,24 @@ func _update_speed_controls() -> void: _pause_bg_button.set_tooltip_string("TOPBAR_DATE_IS_PAUSED" if paused else "TOPBAR_DATE") if _speed_indicator_button: - var index : int = 1 + var index: int = 1 if paused: _speed_indicator_button.set_tooltip_string("TOPBAR_PAUSE_INDICATOR") else: index += speed + 1 - const SPEED_NAMES : PackedStringArray = [ + const SPEED_NAMES: PackedStringArray = [ "SLOWEST_SPEED", "SLOW_SPEED", "NORMAL_SPEED", "FAST_SPEED", - "FASTEST_SPEED" + "FASTEST_SPEED", ] _speed_indicator_button.set_tooltip_string_and_substitution_dict( "TOPBAR_SPEED_INDICATOR", { "SPEED": SPEED_NAMES[speed] if speed < SPEED_NAMES.size() else str(speed) } ) _speed_indicator_button.set_icon_index(index) -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: - for screen : NationManagement.Screen in _nation_management_buttons: + +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: + for screen: NationManagement.Screen in _nation_management_buttons: _nation_management_buttons[screen].set_icon_index(1 + int(screen == active_screen)) diff --git a/game/src/UI/Session/TradeMenu.gd b/game/src/UI/Session/TradeMenu.gd index bc736917..16381ca6 100644 --- a/game/src/UI/Session/TradeMenu.gd +++ b/game/src/UI/Session/TradeMenu.gd @@ -1,68 +1,70 @@ extends GUINode -var _active : bool = false - -const _screen : NationManagement.Screen = NationManagement.Screen.TRADE - -const _gui_file : String = "country_trade" - -var _trade_detail_good_index : int = -1 - -# Trade details -var _trade_detail_good_icon : GUIIcon -var _trade_detail_good_name_label : GUILabel -var _trade_detail_good_price_label : GUILabel -var _trade_detail_good_price_line_chart : GUILineChart -var _trade_detail_good_price_low_label : GUILabel -var _trade_detail_good_price_high_label : GUILabel -var _trade_detail_good_chart_time_label : GUILabel -var _trade_detail_automate_checkbox : GUIIconButton -var _trade_detail_buy_sell_stockpile_checkbox : GUIIconButton -var _trade_detail_buy_sell_stockpile_label : GUILabel -var _trade_detail_stockpile_slider_description_label : GUILabel -var _trade_detail_stockpile_slider_scrollbar : GUIScrollbar -var _trade_detail_stockpile_slider_amount_label : GUILabel -var _trade_detail_confirm_trade_button : GUIIconButton -var _trade_detail_government_good_needs_label : GUILabel -var _trade_detail_factory_good_needs_label : GUILabel -var _trade_detail_pop_good_needs_label : GUILabel -var _trade_detail_good_available_label : GUILabel # Goods tables + enum Table { GOVERNMENT_NEEDS, FACTORY_NEEDS, POP_NEEDS, MARKET_ACTIVITY, STOCKPILE, - COMMON_MARKET + COMMON_MARKET, } -const TABLE_NAMES : PackedStringArray = [ - "government_needs", "factory_needs", "pop_needs", "market_activity", "stockpile", "common_market" + +const _screen: NationManagement.Screen = NationManagement.Screen.TRADE +const _gui_file: String = "country_trade" +const TABLE_NAMES: PackedStringArray = [ + "government_needs", "factory_needs", "pop_needs", "market_activity", "stockpile", "common_market", ] -const TABLE_ENTRY_NAMES : PackedStringArray = [ - "goods_needs_entry", "goods_needs_entry", "goods_needs_entry", "market_activity_entry", "stockpile_entry", "common_market_entry" +const TABLE_ENTRY_NAMES: PackedStringArray = [ + "goods_needs_entry", "goods_needs_entry", "goods_needs_entry", "market_activity_entry", "stockpile_entry", "common_market_entry", ] + + # Nested Array contains only NodePaths -const TABLE_ITEM_PATHS : Array[Array] = [ +const TABLE_ITEM_PATHS: Array[Array] = [ [^"./goods_type", ^"./value"], [^"./goods_type", ^"./value"], [^"./goods_type", ^"./value"], [^"./goods_type", ^"./activity", ^"./cost"], [^"./goods_type", ^"./value", ^"./change"], - [^"./goods_type", ^"./total", ^"./produce_change", ^"./exported"] + [^"./goods_type", ^"./total", ^"./produce_change", ^"./exported"], ] +const TABLE_UNSORTED: int = 255 +const TABLE_COLUMN_KEYS: Array[StringName] = [&"COLUMN_0", &"COLUMN_1", &"COLUMN_2", &"COLUMN_3"] +const SORT_DESCENDING: int = 0 +const SORT_ASCENDING: int = 1 -var _table_listboxes : Array[GUIListBox] +var _active: bool = false +var _trade_detail_good_index: int = -1 -const TABLE_UNSORTED : int = 255 -const TABLE_COLUMN_KEYS : Array[StringName] = [&"COLUMN_0", &"COLUMN_1", &"COLUMN_2", &"COLUMN_3"] -var _table_sort_columns : PackedByteArray -const SORT_DESCENDING : int = 0 -const SORT_ASCENDING : int = 1 -var _table_sort_directions : PackedByteArray +# Trade details +var _trade_detail_good_icon: GUIIcon +var _trade_detail_good_name_label: GUILabel +var _trade_detail_good_price_label: GUILabel +var _trade_detail_good_price_line_chart: GUILineChart +var _trade_detail_good_price_low_label: GUILabel +var _trade_detail_good_price_high_label: GUILabel +var _trade_detail_good_chart_time_label: GUILabel +var _trade_detail_automate_checkbox: GUIIconButton +var _trade_detail_buy_sell_stockpile_checkbox: GUIIconButton +var _trade_detail_buy_sell_stockpile_label: GUILabel +var _trade_detail_stockpile_slider_description_label: GUILabel +var _trade_detail_stockpile_slider_scrollbar: GUIScrollbar +var _trade_detail_stockpile_slider_amount_label: GUILabel +var _trade_detail_confirm_trade_button: GUIIconButton +var _trade_detail_government_good_needs_label: GUILabel +var _trade_detail_factory_good_needs_label: GUILabel +var _trade_detail_pop_good_needs_label: GUILabel +var _trade_detail_good_available_label: GUILabel +var _table_listboxes: Array[GUIListBox] +var _table_sort_columns: PackedByteArray +var _table_sort_directions: PackedByteArray + # Good entries -var _goods_entry_offset : Vector2 +var _goods_entry_offset: Vector2 + func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -73,7 +75,7 @@ func _ready() -> void: set_click_mask_from_nodepaths([^"./country_trade/main_bg"]) - var close_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/close_button") + var close_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/close_button") if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) @@ -81,72 +83,72 @@ func _ready() -> void: _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/government_needs_list")) _table_sort_columns.push_back(TABLE_UNSORTED) _table_sort_directions.push_back(SORT_DESCENDING) - var sort_government_needs_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_government_sort_by_goods") + var sort_government_needs_by_good_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_government_sort_by_goods") if sort_government_needs_by_good_button: sort_government_needs_by_good_button.pressed.connect(_change_table_sorting.bind(Table.GOVERNMENT_NEEDS, 0)) - var sort_government_needs_by_need_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_government_sort_by_value") + var sort_government_needs_by_need_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_government_sort_by_value") if sort_government_needs_by_need_button: sort_government_needs_by_need_button.pressed.connect(_change_table_sorting.bind(Table.GOVERNMENT_NEEDS, 1)) _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/factory_needs_list")) _table_sort_columns.push_back(TABLE_UNSORTED) _table_sort_directions.push_back(SORT_DESCENDING) - var sort_factory_needs_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_factories_sort_by_goods") + var sort_factory_needs_by_good_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_factories_sort_by_goods") if sort_factory_needs_by_good_button: sort_factory_needs_by_good_button.pressed.connect(_change_table_sorting.bind(Table.FACTORY_NEEDS, 0)) - var sort_factory_needs_by_need_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_factories_sort_by_value") + var sort_factory_needs_by_need_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_factories_sort_by_value") if sort_factory_needs_by_need_button: sort_factory_needs_by_need_button.pressed.connect(_change_table_sorting.bind(Table.FACTORY_NEEDS, 1)) _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/pop_needs_list")) _table_sort_columns.push_back(TABLE_UNSORTED) _table_sort_directions.push_back(SORT_DESCENDING) - var sort_pop_needs_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_pops_sort_by_goods") + var sort_pop_needs_by_good_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_pops_sort_by_goods") if sort_pop_needs_by_good_button: sort_pop_needs_by_good_button.pressed.connect(_change_table_sorting.bind(Table.POP_NEEDS, 0)) - var sort_pop_needs_by_need_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_pops_sort_by_value") + var sort_pop_needs_by_need_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/needs_pops_sort_by_value") if sort_pop_needs_by_need_button: sort_pop_needs_by_need_button.pressed.connect(_change_table_sorting.bind(Table.POP_NEEDS, 1)) _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/market_activity_list")) _table_sort_columns.push_back(TABLE_UNSORTED) _table_sort_directions.push_back(SORT_DESCENDING) - var sort_market_activity_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_goods") + var sort_market_activity_by_good_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_goods") if sort_market_activity_by_good_button: sort_market_activity_by_good_button.pressed.connect(_change_table_sorting.bind(Table.MARKET_ACTIVITY, 0)) - var sort_market_activity_by_activity_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_activity") + var sort_market_activity_by_activity_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_activity") if sort_market_activity_by_activity_button: sort_market_activity_by_activity_button.pressed.connect(_change_table_sorting.bind(Table.MARKET_ACTIVITY, 1)) - var sort_market_activity_by_cost_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_cost") + var sort_market_activity_by_cost_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/market_activity_sort_by_cost") if sort_market_activity_by_cost_button: sort_market_activity_by_cost_button.pressed.connect(_change_table_sorting.bind(Table.MARKET_ACTIVITY, 2)) _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/stockpile_list")) _table_sort_columns.push_back(TABLE_UNSORTED) _table_sort_directions.push_back(SORT_DESCENDING) - var sort_stockpile_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_goods") + var sort_stockpile_by_good_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_goods") if sort_stockpile_by_good_button: sort_stockpile_by_good_button.pressed.connect(_change_table_sorting.bind(Table.STOCKPILE, 0)) - var sort_stockpile_by_stock_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_value") + var sort_stockpile_by_stock_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_value") if sort_stockpile_by_stock_button: sort_stockpile_by_stock_button.pressed.connect(_change_table_sorting.bind(Table.STOCKPILE, 1)) - var sort_stockpile_by_increase_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_change") + var sort_stockpile_by_increase_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/stockpile_sort_by_change") if sort_stockpile_by_increase_button: sort_stockpile_by_increase_button.pressed.connect(_change_table_sorting.bind(Table.STOCKPILE, 2)) _table_listboxes.push_back(get_gui_listbox_from_nodepath(^"./country_trade/common_market_list")) _table_sort_columns.push_back(TABLE_UNSORTED) _table_sort_directions.push_back(SORT_DESCENDING) - var sort_common_market_by_good_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_goods") + var sort_common_market_by_good_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_goods") if sort_common_market_by_good_button: sort_common_market_by_good_button.pressed.connect(_change_table_sorting.bind(Table.COMMON_MARKET, 0)) - var sort_common_market_by_available_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_produced") + var sort_common_market_by_available_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_produced") if sort_common_market_by_available_button: sort_common_market_by_available_button.pressed.connect(_change_table_sorting.bind(Table.COMMON_MARKET, 1)) - var sort_common_market_by_increase_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_diff") + var sort_common_market_by_increase_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_diff") if sort_common_market_by_increase_button: sort_common_market_by_increase_button.pressed.connect(_change_table_sorting.bind(Table.COMMON_MARKET, 2)) - var sort_common_market_by_exported_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_exported") + var sort_common_market_by_exported_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/common_market_sort_by_exported") if sort_common_market_by_exported_button: sort_common_market_by_exported_button.pressed.connect(_change_table_sorting.bind(Table.COMMON_MARKET, 3)) @@ -166,13 +168,13 @@ func _ready() -> void: _trade_detail_good_chart_time_label = get_gui_label_from_nodepath(^"./country_trade/trade_details/price_chart_time") if _trade_detail_good_chart_time_label: _trade_detail_good_chart_time_label.set_text("PRICE_HISTORY_TIME_RANGE") - var trade_detail_automate_label : GUILabel = get_gui_label_from_nodepath(^"./country_trade/trade_details/automate_label") + var trade_detail_automate_label: GUILabel = get_gui_label_from_nodepath(^"./country_trade/trade_details/automate_label") if trade_detail_automate_label: trade_detail_automate_label.set_tooltip_string("AUTOMATE_TRADE_CHECK") _trade_detail_automate_checkbox = get_gui_icon_button_from_nodepath(^"./country_trade/trade_details/automate") if _trade_detail_automate_checkbox: _trade_detail_automate_checkbox.toggled.connect( - func(state : bool) -> void: + func(state: bool) -> void: PlayerSingleton.set_good_automated(_trade_detail_good_index, state) ) _trade_detail_automate_checkbox.set_tooltip_string("AUTOMATE_TRADE_CHECK") @@ -187,7 +189,7 @@ func _ready() -> void: _trade_detail_stockpile_slider_amount_label.set_auto_translate(false) if _trade_detail_stockpile_slider_scrollbar: _trade_detail_stockpile_slider_scrollbar.value_changed.connect( - func(value : int) -> void: + func(value: int) -> void: _update_stockpile_slider_amount_label(MenuSingleton.calculate_trade_menu_stockpile_cutoff_amount(_trade_detail_stockpile_slider_scrollbar)) ) _trade_detail_confirm_trade_button = get_gui_icon_button_from_nodepath(^"./country_trade/trade_details/confirm_trade") @@ -200,7 +202,7 @@ func _ready() -> void: _trade_detail_stockpile_slider_scrollbar ) ) - var good_details_button : GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/trade_details/goods_details") + var good_details_button: GUIIconButton = get_gui_icon_button_from_nodepath(^"./country_trade/trade_details/goods_details") if good_details_button: good_details_button.pressed.connect( func() -> void: @@ -229,39 +231,69 @@ func _ready() -> void: _update_info() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_TRANSLATION_CHANGED: _update_info() -func _on_update_active_nation_management_screen(active_screen : NationManagement.Screen) -> void: + +func _on_update_active_nation_management_screen(active_screen: NationManagement.Screen) -> void: _active = active_screen == _screen _update_info() + func _update_info() -> void: if _active: _update_trade_details() - const good_producers_tooltips_key : StringName = &"good_producers_tooltips" - const good_trading_yesterday_tooltips_key : StringName = &"good_trading_yesterday_tooltips" - const government_needs_key : StringName = &"government_needs" - const factory_needs_key : StringName = &"factory_needs" - const pop_needs_key : StringName = &"pop_needs" - const market_activity_key : StringName = &"market_activity" - const stockpile_key : StringName = &"stockpile" - const common_market_key : StringName = &"common_market" + const good_producers_tooltips_key: StringName = &"good_producers_tooltips" + const good_trading_yesterday_tooltips_key: StringName = &"good_trading_yesterday_tooltips" + const government_needs_key: StringName = &"government_needs" + const factory_needs_key: StringName = &"factory_needs" + const pop_needs_key: StringName = &"pop_needs" + const market_activity_key: StringName = &"market_activity" + const stockpile_key: StringName = &"stockpile" + const common_market_key: StringName = &"common_market" - var trade_info : Dictionary = MenuSingleton.get_trade_menu_tables_info() + var trade_info: Dictionary = MenuSingleton.get_trade_menu_tables_info() - var good_producers_tooltips : PackedStringArray = trade_info.get(good_producers_tooltips_key, [] as PackedStringArray) + var good_producers_tooltips: PackedStringArray = trade_info.get( + good_producers_tooltips_key, + [] as PackedStringArray, + ) - _generate_listbox(Table.GOVERNMENT_NEEDS, trade_info.get(government_needs_key, [] as PackedVector2Array), good_producers_tooltips) - _generate_listbox(Table.FACTORY_NEEDS, trade_info.get(factory_needs_key, [] as PackedVector2Array), good_producers_tooltips) - _generate_listbox(Table.POP_NEEDS, trade_info.get(pop_needs_key, [] as PackedVector2Array), good_producers_tooltips) - _generate_listbox(Table.MARKET_ACTIVITY, trade_info.get(market_activity_key, [] as PackedVector3Array), good_producers_tooltips) - _generate_listbox(Table.STOCKPILE, trade_info.get(stockpile_key, [] as PackedVector3Array), trade_info.get(good_trading_yesterday_tooltips_key, [] as PackedStringArray)) - _generate_listbox(Table.COMMON_MARKET, trade_info.get(common_market_key, [] as PackedVector4Array), good_producers_tooltips) + _generate_listbox( + Table.GOVERNMENT_NEEDS, + trade_info.get(government_needs_key, [] as PackedVector2Array), + good_producers_tooltips, + ) + _generate_listbox( + Table.FACTORY_NEEDS, + trade_info.get(factory_needs_key, [] as PackedVector2Array), + good_producers_tooltips, + ) + _generate_listbox( + Table.POP_NEEDS, + trade_info.get(pop_needs_key, [] as PackedVector2Array), + good_producers_tooltips, + ) + _generate_listbox( + Table.MARKET_ACTIVITY, + trade_info.get(market_activity_key, [] as PackedVector3Array), + good_producers_tooltips, + ) + _generate_listbox( + Table.STOCKPILE, + trade_info.get(stockpile_key, [] as PackedVector3Array), + trade_info.get(good_trading_yesterday_tooltips_key, [] as PackedStringArray), + ) + _generate_listbox( + Table.COMMON_MARKET, + trade_info.get(common_market_key, [] as PackedVector4Array), + good_producers_tooltips, + ) _generate_good_entries(good_producers_tooltips) @@ -269,12 +301,17 @@ func _update_info() -> void: else: hide() -# Set new_trade_detail_good_index to -1 if you're updating for any reason other than explicitly selecting a trade good to view the details of -func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: + +# Set new_trade_detail_good_index to -1 if you're updating for any reason other than explicitly +# selecting a trade good to view the details of + + +func _update_trade_details(new_trade_detail_good_index: int = -1) -> void: # If the new index isn't negative, update the current index to match it - # Even if the new index is the same as the current index, it indicates a forced refresh (including the trade order + # Even if the new index is the same as the current index, it indicates a forced refresh + # (including the trade order # buy/sell checkbox and stockpile cutoff slider, which otherwise wouldn't be refreshed) - var force_refresh : bool = new_trade_detail_good_index >= 0 + var force_refresh: bool = new_trade_detail_good_index >= 0 if force_refresh: _trade_detail_good_index = new_trade_detail_good_index elif _trade_detail_good_index < 0: @@ -284,26 +321,26 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: force_refresh = true # Trade details - const trade_detail_good_name_key : StringName = &"trade_detail_good_name" - const trade_detail_good_price_key : StringName = &"trade_detail_good_price" - const trade_detail_good_base_price_key : StringName = &"trade_detail_good_base_price" - const trade_detail_price_history_key : StringName = &"trade_detail_price_history" - const trade_detail_is_automated_key : StringName = &"trade_detail_is_automated" - const trade_detail_is_selling_key : StringName = &"trade_detail_is_selling" # or buying (false) - const trade_detail_slider_amount_key : StringName = &"trade_detail_slider_amount" # exponential good amount - const trade_detail_government_needs_key : StringName = &"trade_detail_government_needs" - const trade_detail_army_needs_key : StringName = &"trade_detail_army_needs" - const trade_detail_navy_needs_key : StringName = &"trade_detail_navy_needs" - const trade_detail_overseas_needs_key : StringName = &"trade_detail_overseas_needs" - const trade_detail_factory_needs_key : StringName = &"trade_detail_factory_needs" - const trade_detail_pop_needs_key : StringName = &"trade_detail_pop_needs" - const trade_detail_available_key : StringName = &"trade_detail_available" - - var trade_info : Dictionary = MenuSingleton.get_trade_menu_trade_details_info( + const trade_detail_good_name_key: StringName = &"trade_detail_good_name" + const trade_detail_good_price_key: StringName = &"trade_detail_good_price" + const trade_detail_good_base_price_key: StringName = &"trade_detail_good_base_price" + const trade_detail_price_history_key: StringName = &"trade_detail_price_history" + const trade_detail_is_automated_key: StringName = &"trade_detail_is_automated" + const trade_detail_is_selling_key: StringName = &"trade_detail_is_selling" # or buying (false) + const trade_detail_slider_amount_key: StringName = &"trade_detail_slider_amount" # exponential good amount + const trade_detail_government_needs_key: StringName = &"trade_detail_government_needs" + const trade_detail_army_needs_key: StringName = &"trade_detail_army_needs" + const trade_detail_navy_needs_key: StringName = &"trade_detail_navy_needs" + const trade_detail_overseas_needs_key: StringName = &"trade_detail_overseas_needs" + const trade_detail_factory_needs_key: StringName = &"trade_detail_factory_needs" + const trade_detail_pop_needs_key: StringName = &"trade_detail_pop_needs" + const trade_detail_available_key: StringName = &"trade_detail_available" + + var trade_info: Dictionary = MenuSingleton.get_trade_menu_trade_details_info( _trade_detail_good_index, _trade_detail_stockpile_slider_scrollbar if force_refresh else null ) - var trade_detail_good_name : String = trade_info.get(trade_detail_good_name_key, "") + var trade_detail_good_name: String = trade_info.get(trade_detail_good_name_key, "") if _trade_detail_good_icon: _trade_detail_good_icon.set_icon_index(_trade_detail_good_index + 2) @@ -315,14 +352,17 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: if _trade_detail_good_price_label: _trade_detail_good_price_label.set_text(GUINode.float_to_string_dp(trade_info.get(trade_detail_good_price_key, 0), 3) + "¤") - var price_history : PackedFloat32Array = trade_info.get(trade_detail_price_history_key, [] as PackedFloat32Array) + var price_history: PackedFloat32Array = trade_info.get( + trade_detail_price_history_key, + [] as PackedFloat32Array, + ) if price_history.size() == 1: # We cannot draw a line with just one point push_error("TradeMenu: Price history has only one point: ", price_history) price_history.clear() - var base_price : float = trade_info.get(trade_detail_good_base_price_key, 0) - var price_low : float = base_price - var price_high : float = base_price + var base_price: float = trade_info.get(trade_detail_good_base_price_key, 0) + var price_low: float = base_price + var price_high: float = base_price if _trade_detail_good_price_line_chart: if price_history.is_empty(): @@ -341,7 +381,7 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: if _trade_detail_good_chart_time_label: _trade_detail_good_chart_time_label.add_substitution("MONTHS", str(price_history.size())) - var is_automated : bool = trade_info.get(trade_detail_is_automated_key, false) + var is_automated: bool = trade_info.get(trade_detail_is_automated_key, false) if _trade_detail_automate_checkbox: # Investigate whether set_pressed_no_signal can/should be used here @@ -357,21 +397,24 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: _trade_detail_confirm_trade_button.set_disabled(is_automated) _trade_detail_confirm_trade_button.set_tooltip_string("TRADE_DISABLED_AUTOMATE" if is_automated else "TRADE_CONFIRM_DESC") - var factory_needs : float = trade_info.get(trade_detail_factory_needs_key, 0) - var factory_needs_string : String = GUINode.float_to_string_dp(factory_needs, 2) + var factory_needs: float = trade_info.get(trade_detail_factory_needs_key, 0) + var factory_needs_string: String = GUINode.float_to_string_dp(factory_needs, 2) if _trade_detail_government_good_needs_label: - _trade_detail_government_good_needs_label.add_substitution("VAL", GUINode.float_to_string_dp(trade_info.get(trade_detail_government_needs_key, 0), 2)) - var government_needs_tooltip : String - var army_needs : float = trade_info.get(trade_detail_army_needs_key, 0) + _trade_detail_government_good_needs_label.add_substitution( + "VAL", + GUINode.float_to_string_dp(trade_info.get(trade_detail_government_needs_key, 0), 2), + ) + var government_needs_tooltip: String + var army_needs: float = trade_info.get(trade_detail_army_needs_key, 0) if army_needs > 0: government_needs_tooltip = tr(&"TRADE_SUPPLY_NEED_A").replace("$VAL$", GUINode.float_to_string_dp(army_needs, 2)) - var navy_needs : float = trade_info.get(trade_detail_navy_needs_key, 0) + var navy_needs: float = trade_info.get(trade_detail_navy_needs_key, 0) if navy_needs > 0: government_needs_tooltip += tr(&"TRADE_SUPPLY_NEED_N").replace("$VAL$", GUINode.float_to_string_dp(navy_needs, 2)) if factory_needs > 0: government_needs_tooltip += tr(&"TRADE_TEMP_PROD_NEED").replace("$VAL$", factory_needs_string) - var overseas_needs : float = trade_info.get(trade_detail_overseas_needs_key, 0) + var overseas_needs: float = trade_info.get(trade_detail_overseas_needs_key, 0) if overseas_needs > 0: government_needs_tooltip += tr(&"TRADE_OVERSEAS_NEED").replace("$VAL$", GUINode.float_to_string_dp(overseas_needs, 2)) if not government_needs_tooltip.is_empty(): @@ -382,12 +425,19 @@ func _update_trade_details(new_trade_detail_good_index : int = -1) -> void: _trade_detail_factory_good_needs_label.add_substitution("VAL", factory_needs_string) if _trade_detail_pop_good_needs_label: - _trade_detail_pop_good_needs_label.add_substitution("VAL", GUINode.float_to_string_dp(trade_info.get(trade_detail_pop_needs_key, 0), 2)) + _trade_detail_pop_good_needs_label.add_substitution( + "VAL", + GUINode.float_to_string_dp(trade_info.get(trade_detail_pop_needs_key, 0), 2), + ) if _trade_detail_good_available_label: - _trade_detail_good_available_label.add_substitution("VAL", GUINode.float_to_string_dp(trade_info.get(trade_detail_available_key, 0), 2)) + _trade_detail_good_available_label.add_substitution( + "VAL", + GUINode.float_to_string_dp(trade_info.get(trade_detail_available_key, 0), 2), + ) + -func _update_trade_order_buy_sell(is_selling : bool) -> void: +func _update_trade_order_buy_sell(is_selling: bool) -> void: if _trade_detail_buy_sell_stockpile_checkbox: # Investigate whether set_pressed_no_signal can/should be used here _trade_detail_buy_sell_stockpile_checkbox.set_pressed(is_selling) @@ -398,10 +448,12 @@ func _update_trade_order_buy_sell(is_selling : bool) -> void: if _trade_detail_stockpile_slider_description_label: _trade_detail_stockpile_slider_description_label.set_text("MINIMUM_STOCKPILE_TARGET" if is_selling else "MAXIMUM_STOCKPILE_TARGET") -func _update_stockpile_slider_amount_label(slider_amount : float) -> void: + +func _update_stockpile_slider_amount_label(slider_amount: float) -> void: _trade_detail_stockpile_slider_amount_label.set_text(GUINode.float_to_string_dp(slider_amount, 3 if slider_amount < 10.0 else 2)) -func _change_table_sorting(table : Table, column : int) -> void: + +func _change_table_sorting(table: Table, column: int) -> void: if _table_sort_columns[table] != column: _table_sort_columns[table] = column _table_sort_directions[table] = SORT_DESCENDING @@ -410,26 +462,29 @@ func _change_table_sorting(table : Table, column : int) -> void: _sort_table(table) -func _sort_table(table : Table) -> void: - var column : int = _table_sort_columns[table] + +func _sort_table(table: Table) -> void: + var column: int = _table_sort_columns[table] if column == TABLE_UNSORTED: return - var sort_key : StringName = TABLE_COLUMN_KEYS[column] + var sort_key: StringName = TABLE_COLUMN_KEYS[column] _table_listboxes[table].sort_children( - (func(a : Node, b : Node) -> bool: return a.get_meta(sort_key) > b.get_meta(sort_key)) + (func(a: Node, b: Node) -> bool: return a.get_meta(sort_key) > b.get_meta(sort_key)) if _table_sort_directions[table] == SORT_DESCENDING else - (func(a : Node, b : Node) -> bool: return a.get_meta(sort_key) < b.get_meta(sort_key)) + (func(a: Node, b: Node) -> bool: return a.get_meta(sort_key) < b.get_meta(sort_key)) ) -func _float_to_string_suffixed_dp(value : float, decimals : int) -> String: + +func _float_to_string_suffixed_dp(value: float, decimals: int) -> String: if value < 1000: return GUINode.float_to_string_dp(value, decimals) else: return GUINode.float_to_string_dp(value / 1000, decimals) + "k" -func _float_to_string_suffixed_dp_dynamic(value : float) -> String: + +func _float_to_string_suffixed_dp_dynamic(value: float) -> String: if value < 2: return GUINode.float_to_string_dp(value, 3) elif value < 100: @@ -437,34 +492,40 @@ func _float_to_string_suffixed_dp_dynamic(value : float) -> String: else: return _float_to_string_suffixed_dp(value, 1) + # data is either a PackedVector2Array, PackedVector3Array or PackedVector4Array -func _generate_listbox(table : Table, data : Variant, good_tooltips : PackedStringArray) -> void: - var listbox : GUIListBox = _table_listboxes[table] + + +func _generate_listbox(table: Table, data: Variant, good_tooltips: PackedStringArray) -> void: + var listbox: GUIListBox = _table_listboxes[table] if not listbox: return - var entry_name : String = TABLE_ENTRY_NAMES[table] + var entry_name: String = TABLE_ENTRY_NAMES[table] listbox.clear_children(data.size()) while listbox.get_child_count() < data.size(): - var entry : Panel = GUINode.generate_gui_element(_gui_file, entry_name) + var entry: Panel = GUINode.generate_gui_element(_gui_file, entry_name) if not entry: break listbox.add_child(entry) - var item_paths : Array = TABLE_ITEM_PATHS[table] + var item_paths: Array = TABLE_ITEM_PATHS[table] for index in min(listbox.get_child_count(), data.size()): - var entry : Panel = listbox.get_child(index) + var entry: Panel = listbox.get_child(index) if not entry: break # entry_data is either a Vector2, Vector3 or Vector4 - var entry_data : Variant = data[index] - var good_index : int = int(entry_data.x) - var good_tooltip : String = good_tooltips[good_index] if good_index < good_tooltips.size() else "" + var entry_data: Variant = data[index] + var good_index: int = int(entry_data.x) + var good_tooltip: String = good_tooltips[good_index] if good_index < good_tooltips.size() else "" - var good_button : GUIIconButton = GUINode.get_gui_icon_button_from_node_and_path(entry, item_paths[0]) + var good_button: GUIIconButton = GUINode.get_gui_icon_button_from_node_and_path( + entry, + item_paths[0], + ) if good_button: good_button.set_icon_index(good_index + 2) good_button.set_tooltip_string(good_tooltip) @@ -474,9 +535,9 @@ func _generate_listbox(table : Table, data : Variant, good_tooltips : PackedStri ) entry.set_meta(TABLE_COLUMN_KEYS[0], good_index) - var set_tooltips : bool = table == Table.STOCKPILE + var set_tooltips: bool = table == Table.STOCKPILE - var second_column : GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[1]) + var second_column: GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[1]) if second_column: second_column.set_auto_translate(false) second_column.set_text( @@ -493,7 +554,7 @@ func _generate_listbox(table : Table, data : Variant, good_tooltips : PackedStri if item_paths.size() < 3: continue - var third_column : GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[2]) + var third_column: GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[2]) if third_column: third_column.set_auto_translate(false) third_column.set_text( @@ -510,7 +571,7 @@ func _generate_listbox(table : Table, data : Variant, good_tooltips : PackedStri if item_paths.size() < 4: continue - var fourth_column : GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[3]) + var fourth_column: GUILabel = GUINode.get_gui_label_from_node_and_path(entry, item_paths[3]) if fourth_column: fourth_column.set_auto_translate(false) fourth_column.set_text(_float_to_string_suffixed_dp(entry_data.w, 1)) @@ -520,27 +581,34 @@ func _generate_listbox(table : Table, data : Variant, good_tooltips : PackedStri _sort_table(table) -func _generate_good_entries(good_tooltips : PackedStringArray) -> void: - var good_categories_info : Dictionary = MenuSingleton.get_trade_menu_good_categories_info() - for good_category : String in good_categories_info: - var good_category_panel : Panel = get_panel_from_nodepath("./country_trade/group_%s" % good_category) +func _generate_good_entries(good_tooltips: PackedStringArray) -> void: + var good_categories_info: Dictionary = MenuSingleton.get_trade_menu_good_categories_info() + + for good_category: String in good_categories_info: + var good_category_panel: Panel = get_panel_from_nodepath("./country_trade/group_%s" % good_category) if not good_category_panel: continue # Fall back to 1 if panel width is too small to avoid division by zero - var max_items_per_row : int = max(floor(good_category_panel.get_size().x / _goods_entry_offset.x), 1) + var max_items_per_row: int = max( + floor(good_category_panel.get_size().x / _goods_entry_offset.x), + 1, + ) - var good_category_goods : Array[Dictionary] = good_categories_info[good_category] + var good_category_goods: Array[Dictionary] = good_categories_info[good_category] - var child_count : int = good_category_panel.get_child_count() + var child_count: int = good_category_panel.get_child_count() while child_count < good_category_goods.size(): - var good_entry_panel : Panel = generate_gui_element(_gui_file, "goods_entry") + var good_entry_panel: Panel = generate_gui_element(_gui_file, "goods_entry") if not good_entry_panel: break good_entry_panel.set_position( - _goods_entry_offset * Vector2(child_count % max_items_per_row, child_count / max_items_per_row) + _goods_entry_offset * Vector2( + child_count % max_items_per_row, + child_count / max_items_per_row, + ) ) good_category_panel.add_child(good_entry_panel) child_count += 1 @@ -549,24 +617,28 @@ func _generate_good_entries(good_tooltips : PackedStringArray) -> void: child_count -= 1 good_category_panel.remove_child(good_category_panel.get_child(child_count)) - for category_index : int in min(child_count, good_category_goods.size()): - var good_entry_panel : Panel = GUINode.get_panel_from_node(good_category_panel.get_child(category_index)) + for category_index: int in min(child_count, good_category_goods.size()): + var good_entry_panel: Panel = GUINode.get_panel_from_node(good_category_panel.get_child(category_index)) if not good_entry_panel: continue - const good_index_key : StringName = &"good_index" - const current_price_key : StringName = &"current_price" - const price_change_key : StringName = &"price_change" - const demand_tooltip_key : StringName = &"demand_tooltip" - const trade_settings_key : StringName = &"trade_settings" + const good_index_key: StringName = &"good_index" + const current_price_key: StringName = &"current_price" + const price_change_key: StringName = &"price_change" + const demand_tooltip_key: StringName = &"demand_tooltip" + const trade_settings_key: StringName = &"trade_settings" - var good_dict : Dictionary = good_category_goods[category_index] + var good_dict: Dictionary = good_category_goods[category_index] - var good_index : int = good_dict.get(good_index_key, 0) + var good_index: int = good_dict.get(good_index_key, 0) - var entry_button : GUIIconButton = get_gui_icon_button_from_node_and_path(good_entry_panel, ^"./entry_button") + var entry_button: GUIIconButton = get_gui_icon_button_from_node_and_path( + good_entry_panel, + ^"./entry_button", + ) if entry_button: - # Connecting this way ensures the Callable is always the same, preventing errors when good_index changes, + # Connecting this way ensures the Callable is always the same, preventing errors + # when good_index changes, # while still allowing updates by changing the meta value rather than the Callable entry_button.pressed.connect( func() -> void: @@ -575,34 +647,49 @@ func _generate_good_entries(good_tooltips : PackedStringArray) -> void: good_entry_panel.set_meta(TABLE_COLUMN_KEYS[0], good_index) entry_button.set_tooltip_string(good_dict.get(demand_tooltip_key, "")) - var good_button : GUIIconButton = get_gui_icon_button_from_node_and_path(good_entry_panel, ^"./goods_type") + var good_button: GUIIconButton = get_gui_icon_button_from_node_and_path( + good_entry_panel, + ^"./goods_type", + ) if good_button: good_button.set_icon_index(good_index + 2) good_button.set_tooltip_string(good_tooltips[good_index] if good_index < good_tooltips.size() else "") good_button.pressed.connect(func() -> void: print("Good button pressed with index ", good_index)) - var price_label : GUILabel = get_gui_label_from_node_and_path(good_entry_panel, ^"./price") + var price_label: GUILabel = get_gui_label_from_node_and_path( + good_entry_panel, + ^"./price", + ) if price_label: # TODO - change colour of text if price is very high or very low!!! price_label.set_auto_translate(false) price_label.set_text(GUINode.float_to_string_dp(good_dict.get(current_price_key, 0), 1) + "¤") - var trend_icon : GUIIcon = get_gui_icon_from_node_and_path(good_entry_panel, ^"./trend_indicator") + var trend_icon: GUIIcon = get_gui_icon_from_node_and_path( + good_entry_panel, + ^"./trend_indicator", + ) if trend_icon: - var price_change : float = good_dict.get(price_change_key, 0) + var price_change: float = good_dict.get(price_change_key, 0) trend_icon.set_icon_index(1 if price_change > 0 else 3 if price_change < 0 else 2) trend_icon.set_tooltip_string( tr(&"TRADE_PRICE_TREND").replace("$VALUE$", GUINode.float_to_string_dp(price_change, 4)) if price_change != 0.0 else "TRADE_PRICE_TREND_UNCHANGED" ) - var trade_settings : int = good_dict.get(trade_settings_key, 0) + var trade_settings: int = good_dict.get(trade_settings_key, 0) - var automated_icon : GUIIcon = get_gui_icon_from_node_and_path(good_entry_panel, ^"./automation_indicator") + var automated_icon: GUIIcon = get_gui_icon_from_node_and_path( + good_entry_panel, + ^"./automation_indicator", + ) if automated_icon: automated_icon.set_visible(trade_settings & MenuSingleton.TradeSettingBit.TRADE_SETTING_AUTOMATED) - var buy_sell_icon : GUIIcon = get_gui_icon_from_node_and_path(good_entry_panel, ^"./selling_indicator") + var buy_sell_icon: GUIIcon = get_gui_icon_from_node_and_path( + good_entry_panel, + ^"./selling_indicator", + ) if buy_sell_icon: if trade_settings & MenuSingleton.TradeSettingBit.TRADE_SETTING_BUYING: buy_sell_icon.set_icon_index(2) diff --git a/game/src/UI/Shared/MusicMenu/MusicMenu.gd b/game/src/UI/Shared/MusicMenu/MusicMenu.gd index 5110be62..328ba9fe 100644 --- a/game/src/UI/Shared/MusicMenu/MusicMenu.gd +++ b/game/src/UI/Shared/MusicMenu/MusicMenu.gd @@ -1,16 +1,17 @@ extends Control -@export var _song_selector_button : OptionButton -@export var _progress_slider : HSlider -@export var _previous_song_button : Button -@export var _play_pause_button : Button -@export var _next_song_button : Button -@export var _visibility_button : Button +@export var _song_selector_button: OptionButton +@export var _progress_slider: HSlider +@export var _previous_song_button: Button +@export var _play_pause_button: Button +@export var _next_song_button: Button +@export var _visibility_button: Button + +var _is_user_dragging_progress_slider: bool = false -var _is_user_dragging_progress_slider : bool = false func _ready() -> void: - for songName : String in MusicManager.get_all_song_names(): + for songName: String in MusicManager.get_all_song_names(): _song_selector_button.add_item(songName, _song_selector_button.item_count) _on_song_set(MusicManager.get_current_song_index()) MusicManager.song_started.connect(_on_song_set) @@ -18,43 +19,59 @@ func _ready() -> void: MusicManager.song_scrubbed.connect(_update_play_pause_button) _set_music_player_visible(MusicManager.is_music_player_visible) -func _on_song_set(track_id : int) -> void: + +func _process(_delta: float) -> void: + if not _is_user_dragging_progress_slider: + _progress_slider.value = MusicManager.get_current_song_progress_percentage() + + +func _on_song_set(track_id: int) -> void: _song_selector_button.selected = track_id _update_play_pause_button() -func _process(_delta : float) -> void: - if !_is_user_dragging_progress_slider: - _progress_slider.value = MusicManager.get_current_song_progress_percentage() -func _update_play_pause_button(_arg1 : Variant = null, _arg2 : Variant = null) -> void: +func _update_play_pause_button(_arg1: Variant = null, _arg2: Variant = null) -> void: _play_pause_button.text = "▶️" if MusicManager.is_paused() else "❚❚" + func _on_play_pause_button_pressed() -> void: MusicManager.toggle_play_pause() + # REQUIREMENTS # * UIFUN-93 + + func _on_next_song_button_pressed() -> void: MusicManager.select_next_song() + # REQUIREMENTS # * UIFUN-94 + + func _on_previous_song_button_pressed() -> void: MusicManager.select_previous_song() + # REQUIREMENTS # * UIFUN-95 -func _on_option_button_item_selected(index : int) -> void: + + +func _on_option_button_item_selected(index: int) -> void: MusicManager.start_song_by_index(index) + func _on_progress_slider_drag_started() -> void: _is_user_dragging_progress_slider = true -func _on_progress_slider_drag_ended(_value_changed : bool) -> void: + +func _on_progress_slider_drag_ended(_value_changed: bool) -> void: MusicManager.scrub_song_by_percentage(_progress_slider.value) _is_user_dragging_progress_slider = false -func _set_music_player_visible(is_player_visible : bool) -> void: + +func _set_music_player_visible(is_player_visible: bool) -> void: MusicManager.is_music_player_visible = is_player_visible _visibility_button.text = "⬆️" if is_player_visible else "⬇" _song_selector_button.visible = is_player_visible @@ -63,7 +80,10 @@ func _set_music_player_visible(is_player_visible : bool) -> void: _play_pause_button.visible = is_player_visible _next_song_button.visible = is_player_visible + # REQUIREMENTS # * UIFUN-91 + + func _on_music_ui_visibility_button_pressed() -> void: _set_music_player_visible(not MusicManager.is_music_player_visible) diff --git a/game/src/UI/Shared/MusicMenu/SongInfo.gd b/game/src/UI/Shared/MusicMenu/SongInfo.gd index 527f618e..58568955 100644 --- a/game/src/UI/Shared/MusicMenu/SongInfo.gd +++ b/game/src/UI/Shared/MusicMenu/SongInfo.gd @@ -1,18 +1,24 @@ -extends Resource class_name SongInfo +extends Resource + +var song_path: String = "" +var song_name: String = "" +var song_stream: AudioStream -var song_path : String = "" -var song_name : String = "" -var song_stream : AudioStream #Initialize from a file path -func init_file_path(dirname : String, fname : String) -> void: + + +func init_file_path(dirname: String, fname: String) -> void: song_path = dirname.path_join(fname) song_name = fname.get_basename().replace("_", " ") song_stream = load(song_path) + #Initialize from an audio stream -func init_stream(dirpath : String, name : String, stream : AudioStream) -> void: + + +func init_stream(dirpath: String, name: String, stream: AudioStream) -> void: song_path = dirpath song_name = name song_stream = stream diff --git a/game/src/UI/Shared/Theme/StyleBoxCombinedTexture.gd b/game/src/UI/Shared/Theme/StyleBoxCombinedTexture.gd index 1d68363b..40f51296 100644 --- a/game/src/UI/Shared/Theme/StyleBoxCombinedTexture.gd +++ b/game/src/UI/Shared/Theme/StyleBoxCombinedTexture.gd @@ -1,34 +1,22 @@ @tool -extends StyleBox class_name StyleBoxCombinedTexture +extends StyleBox @export -var texture_settings : Array[TextureSetting] = []: +var texture_settings: Array[TextureSetting] = []: get: return texture_settings.duplicate() set(v): texture_settings = v - for setting : TextureSetting in texture_settings: + for setting: TextureSetting in texture_settings: setting.changed.connect(emit_changed) emit_changed() -func _get_draw_rect(rect : Rect2) -> Rect2: - var combined_rect : Rect2 = Rect2() - for setting : TextureSetting in texture_settings: - if combined_rect.position.x > setting.expand_margin_left: - combined_rect.position.x = setting.expand_margin_left - if combined_rect.position.y > setting.expand_margin_top: - combined_rect.position.y = setting.expand_margin_top - if combined_rect.end.x < setting.expand_margin_right: - combined_rect.end.x = setting.expand_margin_right - if combined_rect.end.y < setting.expand_margin_bottom: - combined_rect.end.y = setting.expand_margin_bottom - return rect.grow_individual(combined_rect.position.x, combined_rect.position.y, combined_rect.end.x, combined_rect.end.y) -func _draw(to_canvas_item : RID, rect : Rect2) -> void: - for setting : TextureSetting in texture_settings: +func _draw(to_canvas_item: RID, rect: Rect2) -> void: + for setting: TextureSetting in texture_settings: if setting == null or setting.texture == null: continue - var inner_rect : Rect2 = rect + var inner_rect: Rect2 = rect inner_rect.position.x -= setting.expand_margin_left inner_rect.position.y -= setting.expand_margin_top inner_rect.size.x += setting.expand_margin_left + setting.expand_margin_right @@ -45,3 +33,22 @@ func _draw(to_canvas_item : RID, rect : Rect2) -> void: setting.draw_center, setting.modulate_color ) + + +func _get_draw_rect(rect: Rect2) -> Rect2: + var combined_rect: Rect2 = Rect2() + for setting: TextureSetting in texture_settings: + if combined_rect.position.x > setting.expand_margin_left: + combined_rect.position.x = setting.expand_margin_left + if combined_rect.position.y > setting.expand_margin_top: + combined_rect.position.y = setting.expand_margin_top + if combined_rect.end.x < setting.expand_margin_right: + combined_rect.end.x = setting.expand_margin_right + if combined_rect.end.y < setting.expand_margin_bottom: + combined_rect.end.y = setting.expand_margin_bottom + return rect.grow_individual( + combined_rect.position.x, + combined_rect.position.y, + combined_rect.end.x, + combined_rect.end.y, + ) diff --git a/game/src/UI/Shared/Theme/TextureSetting.gd b/game/src/UI/Shared/Theme/TextureSetting.gd index da9b1854..5dd48ae5 100644 --- a/game/src/UI/Shared/Theme/TextureSetting.gd +++ b/game/src/UI/Shared/Theme/TextureSetting.gd @@ -1,122 +1,116 @@ -extends Resource class_name TextureSetting +extends Resource @export -var texture : Texture2D: +var texture: Texture2D: get: return texture set(v): texture = v emit_changed() @export -var draw_center : bool = true: +var draw_center: bool = true: get: return draw_center set(v): draw_center = v emit_changed() - @export_group("Texture Margins", "texture_margin_") @export -var texture_margin_left : float = 0: +var texture_margin_left: float = 0: get: return texture_margin_left set(v): texture_margin_left = v emit_changed() @export -var texture_margin_top : float = 0: +var texture_margin_top: float = 0: get: return texture_margin_top set(v): texture_margin_top = v emit_changed() @export -var texture_margin_right : float = 0: +var texture_margin_right: float = 0: get: return texture_margin_right set(v): texture_margin_right = v emit_changed() @export -var texture_margin_bottom : float = 0: +var texture_margin_bottom: float = 0: get: return texture_margin_bottom set(v): texture_margin_bottom = v emit_changed() - @export_group("Expand Margins", "expand_margin_") @export -var expand_margin_left : float = 0: +var expand_margin_left: float = 0: get: return expand_margin_left set(v): expand_margin_left = v emit_changed() @export -var expand_margin_top : float = 0: +var expand_margin_top: float = 0: get: return expand_margin_top set(v): expand_margin_top = v emit_changed() @export -var expand_margin_right : float = 0: +var expand_margin_right: float = 0: get: return expand_margin_right set(v): expand_margin_right = v emit_changed() @export -var expand_margin_bottom : float = 0: +var expand_margin_bottom: float = 0: get: return expand_margin_bottom set(v): expand_margin_bottom = v emit_changed() - @export_group("Axis Stretch", "axis_stretch_") @export -var axis_stretch_horizontal : RenderingServer.NinePatchAxisMode = RenderingServer.NINE_PATCH_STRETCH: +var axis_stretch_horizontal: RenderingServer.NinePatchAxisMode = RenderingServer.NINE_PATCH_STRETCH: get: return axis_stretch_horizontal set(v): axis_stretch_horizontal = v emit_changed() @export -var axis_stretch_vertical : RenderingServer.NinePatchAxisMode = RenderingServer.NINE_PATCH_STRETCH: +var axis_stretch_vertical: RenderingServer.NinePatchAxisMode = RenderingServer.NINE_PATCH_STRETCH: get: return axis_stretch_vertical set(v): axis_stretch_vertical = v emit_changed() - @export_group("Sub-Region", "region_") @export -var region_rect : Rect2 = Rect2(0, 0, 0, 0): +var region_rect: Rect2 = Rect2(0, 0, 0, 0): get: return region_rect set(v): region_rect = v emit_changed() - @export_group("Modulate", "modulate_") @export -var modulate_color : Color = Color(1, 1, 1, 1): +var modulate_color: Color = Color(1, 1, 1, 1): get: return modulate_color set(v): modulate_color = v emit_changed() - @export_group("Content Margins", "content_margin_") @export -var content_margin_left : float = -1: +var content_margin_left: float = -1: get: return content_margin_left set(v): content_margin_left = v emit_changed() @export -var content_margin_top : float = -1: +var content_margin_top: float = -1: get: return content_margin_top set(v): content_margin_top = v emit_changed() @export -var content_margin_right : float = -1: +var content_margin_right: float = -1: get: return content_margin_right set(v): content_margin_right = v emit_changed() @export -var content_margin_bottom : float = -1: +var content_margin_bottom: float = -1: get: return content_margin_bottom set(v): content_margin_bottom = v diff --git a/game/src/UI/Shared/Tooltip.gd b/game/src/UI/Shared/Tooltip.gd index f079e4c5..dc2c8642 100644 --- a/game/src/UI/Shared/Tooltip.gd +++ b/game/src/UI/Shared/Tooltip.gd @@ -1,6 +1,7 @@ extends GUINode -var _tooltip_label : GUILabel +var _tooltip_label: GUILabel + func _ready() -> void: add_gui_element("core", "ToolTip") @@ -14,34 +15,37 @@ func _ready() -> void: hide() -func _notification(what : int) -> void: + +func _notification(what: int) -> void: match what: NOTIFICATION_RESIZED: _update_tooltip_max_size() + func _update_tooltip_max_size() -> void: if _tooltip_label: - var max_size : Vector2 = _tooltip_label.get_base_max_size() - var window_size : Vector2 = get_size() + var max_size: Vector2 = _tooltip_label.get_base_max_size() + var window_size: Vector2 = get_size() _tooltip_label.set_max_size(Vector2(min(max_size.x, window_size.x), window_size.y)) -func update_tooltip(text : String, substitution_dict : Dictionary, position : Vector2) -> void: + +func update_tooltip(text: String, substitution_dict: Dictionary, position: Vector2) -> void: if text and _tooltip_label: _tooltip_label.set_text(text) _tooltip_label.set_substitution_dict(substitution_dict) _tooltip_label.force_update_lines() - var adjusted_rect : Rect2 = _tooltip_label.get_adjusted_rect() + var adjusted_rect: Rect2 = _tooltip_label.get_adjusted_rect() # Shift position so that the tooltip doesn't go past the bottom or right sides of the window - var bottom_right : Vector2 = position + adjusted_rect.position + adjusted_rect.size - get_size() + var bottom_right: Vector2 = position + adjusted_rect.position + adjusted_rect.size - get_size() if bottom_right.x > 0: position.x -= bottom_right.x if bottom_right.y > 0: position.y -= bottom_right.y # Shift position so that the tooltip doesn't go past the top or left sides of the window - var top_left : Vector2 = position + adjusted_rect.position + var top_left: Vector2 = position + adjusted_rect.position if top_left.x < 0: position.x -= top_left.x if top_left.y < 0: diff --git a/game/src/Utilities/GameDebug.gd b/game/src/Utilities/GameDebug.gd index 0dc89e67..5f8e78a0 100644 --- a/game/src/Utilities/GameDebug.gd +++ b/game/src/Utilities/GameDebug.gd @@ -1,26 +1,29 @@ class_name GameDebug extends RefCounted -static var _singleton : GameDebug - -static var debug_mode : bool: +static var _singleton: GameDebug +static var debug_mode: bool: get = is_debug_mode, set = set_debug_mode -static func set_debug_mode(value : bool) -> void: + +static func set_debug_mode(value: bool) -> void: if _singleton == null: push_warning("Debug mode could not be set.") return _singleton._set_debug_mode(value) + static func is_debug_mode() -> bool: if _singleton == null: push_warning("Could not get debug mode, returning false.") return false return _singleton._is_debug_mode() -func _set_debug_mode(value : bool) -> void: + +func _set_debug_mode(value: bool) -> void: ArgumentParser.set_option_value(&"game-debug", value) print("Set debug mode to: ", value) + func _is_debug_mode() -> bool: return ArgumentParser.get_option_value(&"game-debug") diff --git a/game/src/Utilities/Localisation.gd b/game/src/Utilities/Localisation.gd index 25232ef6..e2dcfa4d 100644 --- a/game/src/Utilities/Localisation.gd +++ b/game/src/Utilities/Localisation.gd @@ -3,32 +3,41 @@ extends RefCounted # REQUIREMENTS # * SS-59, SS-60, SS-61 + + static func get_default_locale() -> String: - var locales : PackedStringArray = TranslationServer.get_loaded_locales() + var locales: PackedStringArray = TranslationServer.get_loaded_locales() var default_locale := OS.get_locale() if default_locale in locales: return default_locale var default_language := OS.get_locale_language() - for locale : String in locales: + for locale: String in locales: if locale.begins_with(default_language): return default_language return ProjectSettings.get_setting("internationalization/locale/fallback", "en_GB") -static func load_localisation(dir_path : String) -> void: + +static func load_localisation(dir_path: String) -> void: if LoadLocalisation.load_localisation_dir(dir_path) != OK: push_error("Error loading localisation directory: ", dir_path) - var loaded_locales : PackedStringArray = TranslationServer.get_loaded_locales() + var loaded_locales: PackedStringArray = TranslationServer.get_loaded_locales() print("Loaded ", loaded_locales.size(), " locales: ", loaded_locales) # REQUIREMENTS # * SS-57 # * FS-17 + + static func initialize() -> void: - var localisation_dir_path : String = ProjectSettings.get_setting("internationalization/locale/localisation_path", "") + var localisation_dir_path: String = ProjectSettings.get_setting( + "internationalization/locale/localisation_path", + "", + ) if localisation_dir_path.is_empty(): push_error("internationalization/locale/localisation_path setting is empty!") else: Localisation.load_localisation(localisation_dir_path) -static func tr_number(num : Variant) -> String: + +static func tr_number(num: Variant) -> String: return TextServerManager.get_primary_interface().format_number(str(num)) diff --git a/game/src/Utilities/NationManagement.gd b/game/src/Utilities/NationManagement.gd index 3b73bdee..b12e815c 100644 --- a/game/src/Utilities/NationManagement.gd +++ b/game/src/Utilities/NationManagement.gd @@ -9,5 +9,5 @@ enum Screen { POPULATION, TRADE, DIPLOMACY, - MILITARY + MILITARY, } diff --git a/game/src/Utilities/ShaderManager.gd b/game/src/Utilities/ShaderManager.gd index 7e409d3b..bad15352 100644 --- a/game/src/Utilities/ShaderManager.gd +++ b/game/src/Utilities/ShaderManager.gd @@ -1,48 +1,53 @@ class_name ShaderManagerClass extends RefCounted -const param_province_shape_tex : StringName = &"province_shape_tex" -const param_province_shape_subdivisions : StringName = &"province_shape_subdivisions" -const param_province_colour_tex : StringName = &"province_colour_tex" -const param_hover_index : StringName = &"hover_index" -const param_selected_index : StringName = &"selected_index" -const param_parchment_mix : StringName = &"parchment_mix" -const param_terrain_tex : StringName = &"terrain_tex" -const param_terrain_tile_factor : StringName = &"terrain_tile_factor" -const param_stripe_tex : StringName = &"stripe_tex" -const param_stripe_tile_factor : StringName = &"stripe_tile_factor" -const param_overlay_tex : StringName = &"overlay_tex" -const param_overlay_tile_factor : StringName = &"overlay_tile_factor" -const param_colormap_land_tex : StringName = &"colormap_land_tex" -const param_colormap_water_tex : StringName = &"colormap_water_tex" -const param_colormap_overlay_tex : StringName = &"colormap_overlay_tex" +const param_province_shape_tex: StringName = &"province_shape_tex" +const param_province_shape_subdivisions: StringName = &"province_shape_subdivisions" +const param_province_colour_tex: StringName = &"province_colour_tex" +const param_hover_index: StringName = &"hover_index" +const param_selected_index: StringName = &"selected_index" +const param_parchment_mix: StringName = &"parchment_mix" +const param_terrain_tex: StringName = &"terrain_tex" +const param_terrain_tile_factor: StringName = &"terrain_tile_factor" +const param_stripe_tex: StringName = &"stripe_tex" +const param_stripe_tile_factor: StringName = &"stripe_tile_factor" +const param_overlay_tex: StringName = &"overlay_tex" +const param_overlay_tile_factor: StringName = &"overlay_tile_factor" +const param_colormap_land_tex: StringName = &"colormap_land_tex" +const param_colormap_water_tex: StringName = &"colormap_water_tex" +const param_colormap_overlay_tex: StringName = &"colormap_overlay_tex" + func _set_shader_texture( - shader_material : ShaderMaterial, texture_param : StringName, texture : Texture, - tile_factor_param : StringName = &"", pixels_per_tile : float = 0.0 + shader_material: ShaderMaterial, texture_param: StringName, texture: Texture, + tile_factor_param: StringName = &"", pixels_per_tile: float = 0.0 ) -> Error: - var err : Error = OK + var err: Error = OK if texture != null: shader_material.set_shader_parameter(texture_param, texture) else: push_error("Invalid texture for shader parameter ", texture_param, " - null!") err = FAILED if tile_factor_param: - # Set to 1.0 / pixels_per_tile as the shader can multiply faster than it can divide, and it will not automatically - # optimise to multiplication by a reciprocal for fear of losing precision. As pixels_per_tile is often a power of two, + # Set to 1.0 / pixels_per_tile as the shader can multiply faster than it can divide, and it + # will not automatically + # optimise to multiplication by a reciprocal for fear of losing precision. As + # pixels_per_tile is often a power of two, # this doesn't actually lose any precision, and even if it did it would be insignificant. shader_material.set_shader_parameter(tile_factor_param, 1.0 / pixels_per_tile) return err + func _set_shader_asset_texture( - shader_material : ShaderMaterial, texture_param : StringName, texture_path : StringName, - tile_factor_param : StringName = &"", pixels_per_tile : float = 0.0 + shader_material: ShaderMaterial, texture_param: StringName, texture_path: StringName, + tile_factor_param: StringName = &"", pixels_per_tile: float = 0.0 ) -> Error: return _set_shader_texture( shader_material, texture_param, AssetManager.get_texture(texture_path), tile_factor_param, pixels_per_tile ) -func set_up_shader(material : Material, add_cosmetic_textures : bool) -> Error: + +func set_up_shader(material: Material, add_cosmetic_textures: bool) -> Error: # Shader Material if material == null: push_error("material is null!") @@ -50,31 +55,47 @@ func set_up_shader(material : Material, add_cosmetic_textures : bool) -> Error: if not material is ShaderMaterial: push_error("Invalid map mesh material class: ", material.get_class()) return FAILED - var shader_material : ShaderMaterial = material + var shader_material: ShaderMaterial = material - var ret : Error = OK + var ret: Error = OK # Province shape texture - if _set_shader_texture(shader_material, param_province_shape_tex, GameSingleton.get_province_shape_texture()) != OK: + if _set_shader_texture( + shader_material, + param_province_shape_tex, + GameSingleton.get_province_shape_texture(), + ) != OK: push_error("Failed to set province shape shader texture array!") ret = FAILED - var subdivisions : Vector2i = GameSingleton.get_province_shape_image_subdivisions() + var subdivisions: Vector2i = GameSingleton.get_province_shape_image_subdivisions() if subdivisions.x >= 1 and subdivisions.y >= 1: - shader_material.set_shader_parameter(param_province_shape_subdivisions, Vector2(subdivisions)) + shader_material.set_shader_parameter( + param_province_shape_subdivisions, + Vector2(subdivisions), + ) else: - push_error("Invalid province shape image subdivision: ", subdivisions.x, "x", subdivisions.y) + push_error( + "Invalid province shape image subdivision: ", + subdivisions.x, + "x", + subdivisions.y, + ) ret = FAILED if add_cosmetic_textures: # Province colour texture - if _set_shader_texture(shader_material, param_province_colour_tex, GameSingleton.get_province_colour_texture()) != OK: + if _set_shader_texture( + shader_material, + param_province_colour_tex, + GameSingleton.get_province_colour_texture(), + ) != OK: push_error("Failed to set province colour shader texture!") ret = FAILED # Terrain texture - const pixels_per_terrain_tile : float = 16.0 + const pixels_per_terrain_tile: float = 16.0 if _set_shader_texture( shader_material, param_terrain_tex, GameSingleton.get_terrain_texture(), @@ -84,7 +105,7 @@ func set_up_shader(material : Material, add_cosmetic_textures : bool) -> Error: ret = FAILED # Stripe texture - const pixels_per_stripe_tile : float = 8.0 + const pixels_per_stripe_tile: float = 8.0 if _set_shader_asset_texture( shader_material, param_stripe_tex, &"map/terrain/stripes.dds", @@ -94,7 +115,7 @@ func set_up_shader(material : Material, add_cosmetic_textures : bool) -> Error: ret = FAILED # Overlay texture - const pixels_per_overlay_tile : float = 512.0 + const pixels_per_overlay_tile: float = 512.0 if _set_shader_asset_texture( shader_material, param_overlay_tex, &"map/terrain/map_overlay_tile.dds", @@ -104,15 +125,27 @@ func set_up_shader(material : Material, add_cosmetic_textures : bool) -> Error: ret = FAILED # Land colormap - if _set_shader_asset_texture(shader_material, param_colormap_land_tex, &"map/terrain/colormap.dds") != OK: + if _set_shader_asset_texture( + shader_material, + param_colormap_land_tex, + &"map/terrain/colormap.dds", + ) != OK: push_error("Failed to set land colormap shader texture!") ret = FAILED # Water colormap - if _set_shader_asset_texture(shader_material, param_colormap_water_tex, &"map/terrain/colormap_water.dds") != OK: + if _set_shader_asset_texture( + shader_material, + param_colormap_water_tex, + &"map/terrain/colormap_water.dds", + ) != OK: push_error("Failed to set water colormap shader texture!") ret = FAILED # Overlay colormap - if _set_shader_asset_texture(shader_material, param_colormap_overlay_tex, &"map/terrain/colormap_political.dds") != OK: + if _set_shader_asset_texture( + shader_material, + param_colormap_overlay_tex, + &"map/terrain/colormap_political.dds", + ) != OK: push_error("Failed to set overlay colormap shader texture!") ret = FAILED diff --git a/gdstyle.toml b/gdstyle.toml new file mode 100644 index 00000000..da9bee15 --- /dev/null +++ b/gdstyle.toml @@ -0,0 +1,70 @@ +# gdstyle.toml: starter configuration +# +# Place this file as `gdstyle.toml` or `.gdstyle.toml` in your project root. +# gdstyle will search for it starting from the current directory and walking +# up the directory tree. + +# Maximum line length (default: 100) +max_line_length = 100 + +# Use tabs for indentation (default: true). +# Set to false if your project uses spaces. +use_tabs = true + +# Maximum function body length in lines (default: 50) +max_function_length = 50 + +# Maximum file length in lines (default: 1000) +max_file_length = 1000 + +# Maximum number of function parameters (default: 5) +max_parameters = 5 + +# File and directory patterns to exclude from linting. +# These are matched as glob patterns against file paths. +exclude = [".godot", "addons"] + +# Per-rule severity overrides. +# Values: "off" (disable), "warn" (warning), "error" (error) +# +# All rules are enabled with "warn" severity by default. +# Uncomment any line below to change its severity. +[rules] +# --- Naming --- +# "naming/class-name-pascal-case" = "warn" +# "naming/function-name-snake-case" = "warn" +# "naming/variable-name-snake-case" = "warn" +# "naming/constant-name-screaming-case" = "warn" +# "naming/signal-name-snake-case" = "warn" +# "naming/enum-name-pascal-case" = "warn" +# "naming/enum-member-screaming-case" = "warn" +# "naming/file-name-snake-case" = "warn" +# "naming/signal-past-tense" = "warn" +# "naming/private-underscore-prefix" = "warn" +# "naming/node-name-pascal-case" = "warn" + +# --- Formatting --- +# "format/max-line-length" = "warn" +# "format/trailing-whitespace" = "warn" +# "format/trailing-newline" = "warn" +# "format/no-tabs-as-spaces" = "warn" +# "format/boolean-operators" = "warn" +# "format/double-quotes" = "warn" +# "format/comment-spacing" = "warn" +# "format/no-unnecessary-parens" = "warn" +# "format/number-literals" = "warn" +# "format/one-statement-per-line" = "warn" +# "format/blank-lines" = "warn" +# "format/trailing-comma" = "warn" +# "format/operator-spacing" = "warn" +# "format/float-literal-zeros" = "warn" +# "format/large-number-underscores" = "warn" +# "format/enum-one-per-line" = "warn" + +# --- Ordering --- +# "order/class-member-order" = "warn" + +# --- Quality --- +# "quality/max-function-length" = "warn" +# "quality/max-file-length" = "warn" +# "quality/max-parameters" = "warn"