diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index db664508..a9f8e79f 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,3 +4,6 @@ updates:
directory: "/"
schedule:
interval: "daily"
+ allow:
+ - dependency-name: "extension/deps/openvic-simulation"
+ - dependency-name: "scripts"
diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation
index 12157ce8..e594a7e4 160000
--- a/extension/deps/openvic-simulation
+++ b/extension/deps/openvic-simulation
@@ -1 +1 @@
-Subproject commit 12157ce86d6a1a1637f9ef5f7feabe5650b7a993
+Subproject commit e594a7e4954b33df714f17b857757478b074d054
diff --git a/extension/doc_classes/MapItemSingleton.xml b/extension/doc_classes/MapItemSingleton.xml
index 1f72849e..27c55502 100644
--- a/extension/doc_classes/MapItemSingleton.xml
+++ b/extension/doc_classes/MapItemSingleton.xml
@@ -12,11 +12,6 @@
-
-
-
-
-
@@ -27,6 +22,11 @@
+
+
+
+
+
diff --git a/extension/src/openvic-extension/singletons/MapItemSingleton.cpp b/extension/src/openvic-extension/singletons/MapItemSingleton.cpp
index b96b5786..d1145d6e 100644
--- a/extension/src/openvic-extension/singletons/MapItemSingleton.cpp
+++ b/extension/src/openvic-extension/singletons/MapItemSingleton.cpp
@@ -24,7 +24,7 @@ using namespace OpenVic;
void MapItemSingleton::_bind_methods() {
OV_BIND_METHOD(MapItemSingleton::get_billboards);
OV_BIND_METHOD(MapItemSingleton::get_province_positions);
- OV_BIND_METHOD(MapItemSingleton::get_capital_count);
+ OV_BIND_METHOD(MapItemSingleton::get_max_capital_count);
OV_BIND_METHOD(MapItemSingleton::get_capital_positions);
OV_BIND_METHOD(MapItemSingleton::get_crime_icons);
OV_BIND_METHOD(MapItemSingleton::get_rgo_icons);
@@ -46,7 +46,6 @@ MapItemSingleton::~MapItemSingleton() {
}
// Get the billboard object from the loaded objects
-
GFX::Billboard const* MapItemSingleton::get_billboard(std::string_view name, bool error_on_fail) const {
GameSingleton const* game_singleton = GameSingleton::get_singleton();
ERR_FAIL_NULL_V(game_singleton, nullptr);
@@ -55,7 +54,9 @@ GFX::Billboard const* MapItemSingleton::get_billboard(std::string_view name, boo
game_singleton->get_definition_manager().get_ui_manager().get_cast_object_by_identifier(name);
if (error_on_fail) {
- ERR_FAIL_NULL_V_MSG(billboard, nullptr, vformat("Failed to find billboard \"%s\"", Utilities::std_to_godot_string(name)));
+ ERR_FAIL_NULL_V_MSG(
+ billboard, nullptr, vformat("Failed to find billboard \"%s\"", Utilities::std_to_godot_string(name))
+ );
}
return billboard;
@@ -69,11 +70,9 @@ bool MapItemSingleton::add_billboard_dict(std::string_view name, TypedArray MapItemSingleton::get_billboards() const {
GameSingleton const* game_singleton = GameSingleton::get_singleton();
@@ -100,30 +98,41 @@ TypedArray MapItemSingleton::get_billboards() const {
add_billboard_dict(obj->get_name(), ret);
}
}
-
+
return ret;
}
+// We assume GameSingleton isn't null when this is being called
+static Vector2 get_billboard_pos(ProvinceDefinition const& province) {
+ return Utilities::to_godot_fvec2(province.get_city_position()) / GameSingleton::get_singleton()->get_map_dims();
+}
+
PackedVector2Array MapItemSingleton::get_province_positions() const {
GameSingleton const* game_singleton = GameSingleton::get_singleton();
- ERR_FAIL_NULL_V(game_singleton, PackedVector2Array());
+ ERR_FAIL_NULL_V(game_singleton, {});
+
+ MapDefinition const& map_definition = game_singleton->get_definition_manager().get_map_definition();
PackedVector2Array billboard_pos {};
-
- for(ProvinceDefinition const& prov : game_singleton->get_definition_manager().get_map_definition().get_province_definitions()){
- if(prov.is_water()) continue; //billboards dont appear over water, skip
- fvec2_t city_pos = prov.get_city_position();
- Vector2 pos = Utilities::to_godot_fvec2(city_pos) / game_singleton->get_map_dims();
- billboard_pos.push_back(pos);
+ billboard_pos.resize(map_definition.get_land_province_count());
+ int64_t index = 0;
+
+ for (ProvinceDefinition const& prov : map_definition.get_province_definitions()) {
+ if (prov.is_water()) {
+ // billboards dont appear over water, skip
+ continue;
+ }
+
+ billboard_pos[index++] = get_billboard_pos(prov);
}
-
+
return billboard_pos;
}
//includes non-existent countries, used for setting the billboard buffer size
-int32_t MapItemSingleton::get_capital_count() const {
+int32_t MapItemSingleton::get_max_capital_count() const {
GameSingleton const* game_singleton = GameSingleton::get_singleton();
ERR_FAIL_NULL_V(game_singleton, 0);
@@ -132,68 +141,84 @@ int32_t MapItemSingleton::get_capital_count() const {
PackedVector2Array MapItemSingleton::get_capital_positions() const {
GameSingleton const* game_singleton = GameSingleton::get_singleton();
- ERR_FAIL_NULL_V(game_singleton, PackedVector2Array());
+ ERR_FAIL_NULL_V(game_singleton, {});
InstanceManager const* instance_manager = game_singleton->get_instance_manager();
- ERR_FAIL_NULL_V(instance_manager, PackedVector2Array());
+ ERR_FAIL_NULL_V(instance_manager, {});
+
+ CountryInstanceManager const& country_instance_manager = instance_manager->get_country_instance_manager();
PackedVector2Array billboard_pos {};
- for(CountryInstance const& country : instance_manager->get_country_instance_manager().get_country_instances()){
- if(!country.exists()) continue; //skip non-existant countries
+ billboard_pos.resize(country_instance_manager.get_country_instance_count());
- fvec2_t city_pos = country.get_capital()->get_province_definition().get_city_position();
- Vector2 pos = Utilities::to_godot_fvec2(city_pos) / game_singleton->get_map_dims();
- billboard_pos.push_back(pos);
+ int64_t index = 0;
+ for (CountryInstance const& country : country_instance_manager.get_country_instances()) {
+ if (!country.exists() || country.get_capital() == nullptr) {
+ //skip non-existent or capital-less countries
+ continue;
+ }
+
+ billboard_pos[index++] = get_billboard_pos(country.get_capital()->get_province_definition());
}
+ billboard_pos.resize(index);
+
return billboard_pos;
}
PackedByteArray MapItemSingleton::get_crime_icons() const {
GameSingleton const* game_singleton = GameSingleton::get_singleton();
- ERR_FAIL_NULL_V(game_singleton, PackedByteArray());
+ ERR_FAIL_NULL_V(game_singleton, {});
InstanceManager const* instance_manager = game_singleton->get_instance_manager();
- ERR_FAIL_NULL_V(instance_manager, PackedByteArray());
+ ERR_FAIL_NULL_V(instance_manager, {});
+
+ MapInstance const& map_instance = instance_manager->get_map_instance();
PackedByteArray icons {};
- for(ProvinceInstance const& prov_inst : instance_manager->get_map_instance().get_province_instances()){
- if (prov_inst.get_province_definition().is_water()) continue; //billboards dont appear over water, skip
+ icons.resize(map_instance.get_map_definition().get_land_province_count());
- if(prov_inst.get_crime() == nullptr){
- icons.push_back(0); //no crime on the province
- }
- else {
- icons.push_back(prov_inst.get_crime()->get_icon());
+ int64_t index = 0;
+
+ for (ProvinceInstance const& prov_inst : map_instance.get_province_instances()) {
+ if (prov_inst.get_province_definition().is_water()) {
+ // billboards dont appear over water, skip
+ continue;
}
-
+
+ Crime const* crime = prov_inst.get_crime();
+ icons[index++] = crime != nullptr ? crime->get_icon() : 0; // 0 if no crime in the province
}
return icons;
-
}
PackedByteArray MapItemSingleton::get_rgo_icons() const {
GameSingleton const* game_singleton = GameSingleton::get_singleton();
- ERR_FAIL_NULL_V(game_singleton, PackedByteArray());
+ ERR_FAIL_NULL_V(game_singleton, {});
InstanceManager const* instance_manager = game_singleton->get_instance_manager();
- ERR_FAIL_NULL_V(instance_manager, PackedByteArray());
+ ERR_FAIL_NULL_V(instance_manager, {});
+
+ MapInstance const& map_instance = instance_manager->get_map_instance();
PackedByteArray icons {};
- for(ProvinceInstance const& prov_inst : instance_manager->get_map_instance().get_province_instances()){
- if (prov_inst.get_province_definition().is_water()) continue; //billboards dont appear over water, skip
+ icons.resize(map_instance.get_map_definition().get_land_province_count());
- if(prov_inst.get_rgo_good() == nullptr){
- icons.push_back(0); //no good on the province
- }
- else{
- icons.push_back(prov_inst.get_rgo_good()->get_index()+1);
+ int64_t index = 0;
+
+ for (ProvinceInstance const& prov_inst : map_instance.get_province_instances()) {
+ if (prov_inst.get_province_definition().is_water()) {
+ // billboards dont appear over water, skip
+ continue;
}
+
+ GoodDefinition const* rgo_good = prov_inst.get_rgo_good();
+ icons[index++] = rgo_good != nullptr ? rgo_good->get_index() + 1 : 0; // 0 if no rgo good in the province
}
return icons;
@@ -210,28 +235,28 @@ TODO: National focus isn't implemented yet. It could be done at the country inst
PackedByteArray MapItemSingleton::get_national_focus_icons() const {
GameSingleton const* game_singleton = GameSingleton::get_singleton();
- ERR_FAIL_NULL_V(game_singleton, PackedByteArray());
+ ERR_FAIL_NULL_V(game_singleton, {});
InstanceManager const* instance_manager = game_singleton->get_instance_manager();
- ERR_FAIL_NULL_V(instance_manager, PackedByteArray());
+ ERR_FAIL_NULL_V(instance_manager, {});
+
+ MapInstance const& map_instance = instance_manager->get_map_instance();
PackedByteArray icons {};
- for(ProvinceInstance const& prov_inst : instance_manager->get_map_instance().get_province_instances()){
- if (prov_inst.get_province_definition().is_water()) continue; //billboards dont appear over water, skip
+ icons.resize(map_instance.get_map_definition().get_land_province_count());
- State const* state = prov_inst.get_state();
- if (state == nullptr) {
- icons.push_back(0);
- UtilityFunctions::push_warning(
- "State for province ", Utilities::std_to_godot_string(prov_inst.get_identifier()), " was null"
- );
- } else if (&prov_inst == state->get_capital()) {
- icons.push_back(1);
- } else {
- icons.push_back(0);
+ int64_t index = 0;
+
+ for (ProvinceInstance const& prov_inst : map_instance.get_province_instances()) {
+ if (prov_inst.get_province_definition().is_water()) {
+ // billboards dont appear over water, skip
+ continue;
}
+
+ State const* state = prov_inst.get_state();
+ icons[index++] = state != nullptr && &prov_inst == state->get_capital() ? 1 : 0;
}
return icons;
-}
\ No newline at end of file
+}
diff --git a/extension/src/openvic-extension/singletons/MapItemSingleton.hpp b/extension/src/openvic-extension/singletons/MapItemSingleton.hpp
index 479e9cec..867ac8d3 100644
--- a/extension/src/openvic-extension/singletons/MapItemSingleton.hpp
+++ b/extension/src/openvic-extension/singletons/MapItemSingleton.hpp
@@ -28,13 +28,11 @@ namespace OpenVic {
bool add_billboard_dict(std::string_view name, godot::TypedArray& billboard_dict_array) const;
godot::TypedArray get_billboards() const;
godot::PackedVector2Array get_province_positions() const;
- int32_t get_capital_count() const;
+ int32_t get_max_capital_count() const;
godot::PackedVector2Array get_capital_positions() const;
godot::PackedByteArray get_crime_icons() const;
godot::PackedByteArray get_rgo_icons() const;
godot::PackedByteArray get_national_focus_icons() const;
-
};
-
-}
\ No newline at end of file
+}
diff --git a/extension/src/openvic-extension/singletons/PopulationMenu.cpp b/extension/src/openvic-extension/singletons/PopulationMenu.cpp
index 29f30aca..e2db7c7b 100644
--- a/extension/src/openvic-extension/singletons/PopulationMenu.cpp
+++ b/extension/src/openvic-extension/singletons/PopulationMenu.cpp
@@ -397,10 +397,10 @@ Error MenuSingleton::_population_menu_update_filtered_pops() {
population_menu.workforce_distribution[pop->get_type()] += pop_size;
population_menu.religion_distribution[&pop->get_religion()] += pop_size;
- population_menu.ideology_distribution += pop->get_ideologies() * pop_size;
+ population_menu.ideology_distribution += pop->get_ideology_distribution() * pop_size;
population_menu.culture_distribution[&pop->get_culture()] += pop_size;
- population_menu.issue_distribution += pop->get_issues() * pop_size;
- population_menu.vote_distribution += pop->get_votes() * pop_size;
+ population_menu.issue_distribution += pop->get_issue_distribution() * pop_size;
+ population_menu.vote_distribution += pop->get_vote_distribution() * pop_size;
}
normalise_fixed_point_map(population_menu.workforce_distribution);
@@ -455,11 +455,11 @@ MenuSingleton::sort_func_t MenuSingleton::_get_population_menu_sort_func(PopSort
};
case SORT_IDEOLOGY:
return [](Pop const* a, Pop const* b) -> bool {
- return sorted_indexed_map_less_than(a->get_ideologies(), b->get_ideologies());
+ return sorted_indexed_map_less_than(a->get_ideology_distribution(), b->get_ideology_distribution());
};
case SORT_ISSUES:
return [](Pop const* a, Pop const* b) -> bool {
- return sorted_fixed_map_less_than(a->get_issues(), b->get_issues());
+ return sorted_fixed_map_less_than(a->get_issue_distribution(), b->get_issue_distribution());
};
case SORT_UNEMPLOYMENT:
return [](Pop const* a, Pop const* b) -> bool {
@@ -684,8 +684,8 @@ TypedArray MenuSingleton::get_population_menu_pop_rows(int32_t start
}
pop_dict[pop_militancy_key] = pop->get_militancy().to_float();
pop_dict[pop_consciousness_key] = pop->get_consciousness().to_float();
- pop_dict[pop_ideology_key] = GFXPieChartTexture::distribution_to_slices_array(pop->get_ideologies());
- pop_dict[pop_issues_key] = GFXPieChartTexture::distribution_to_slices_array(pop->get_issues());
+ pop_dict[pop_ideology_key] = GFXPieChartTexture::distribution_to_slices_array(pop->get_ideology_distribution());
+ pop_dict[pop_issues_key] = GFXPieChartTexture::distribution_to_slices_array(pop->get_issue_distribution());
pop_dict[pop_unemployment_key] = pop->get_unemployment().to_float();
pop_dict[pop_cash_key] = pop->get_cash().to_float();
pop_dict[pop_life_needs_key] = pop->get_life_needs_fulfilled().to_float();
diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp
index 0dd17678..544ea989 100644
--- a/extension/src/openvic-extension/utility/UITools.cpp
+++ b/extension/src/openvic-extension/utility/UITools.cpp
@@ -1,14 +1,23 @@
#include "UITools.hpp"
+
#include
+#include
#include
+#include
+#include
+#include
+#include
#include
#include
#include
+#include
#include
#include
#include
+#include
#include
+#include
#include "openvic-extension/classes/GUIButton.hpp"
#include "openvic-extension/classes/GUIIcon.hpp"
@@ -24,10 +33,6 @@
#include "openvic-extension/singletons/AssetManager.hpp"
#include "openvic-extension/singletons/GameSingleton.hpp"
#include "openvic-extension/utility/Utilities.hpp"
-#include "godot_cpp/classes/global_constants.hpp"
-#include "godot_cpp/classes/input_event_key.hpp"
-#include "godot_cpp/classes/shortcut.hpp"
-#include "godot_cpp/core/error_macros.hpp"
using namespace godot;
using namespace OpenVic;
@@ -68,7 +73,7 @@ GUI::Position const* UITools::get_gui_position(String const& gui_scene, String c
static Array get_events_from_shortcut_key(String const& key) {
Array events;
- if (key.length() == 0) {
+ if (key.is_empty()) {
return events;
}
@@ -123,6 +128,69 @@ static Array get_events_from_shortcut_key(String const& key) {
return events;
}
+static Error try_create_shortcut_action_for_button(
+ GUIButton* gui_button, String const& shortcut_key_name, String const& shortcut_hotkey_name = ""
+) {
+ if (shortcut_key_name.is_empty()) {
+ return OK;
+ }
+
+ Array event_array = get_events_from_shortcut_key(shortcut_key_name);
+
+ ERR_FAIL_COND_V_MSG(
+ event_array.is_empty(), ERR_INVALID_PARAMETER,
+ vformat("Unknown shortcut key '%s' for GUI button %s", shortcut_key_name, gui_button->get_name())
+ );
+
+ InputMap* const im = InputMap::get_singleton();
+ String action_name;
+ if (shortcut_hotkey_name.is_empty()) {
+ action_name = //
+ vformat("button_%s_hotkey", gui_button->get_name().to_lower().replace("button", "").replace("hotkey", ""))
+ .replace("__", "_");
+ } else {
+ action_name = vformat("button_%s_hotkey", shortcut_hotkey_name);
+ }
+ Ref action_event;
+ action_event.instantiate();
+ action_event->set_action(action_name);
+ action_event->set_pressed(true);
+
+ if (im->has_action(action_name)) {
+ TypedArray events = im->action_get_events(action_name);
+ bool should_warn = events.size() != event_array.size();
+ if (!should_warn) {
+ for (std::size_t index = 0; index < events.size(); index++) {
+ if (!event_array.has(events[index])) {
+ should_warn = true;
+ break;
+ }
+ }
+ }
+
+ if (should_warn) {
+ WARN_PRINT(vformat("'%s' already found in InputMap with different values, reusing hotkey", action_name));
+ }
+ } else {
+ im->add_action(action_name);
+ for (std::size_t index = 0; index < event_array.size(); index++) {
+ Ref event = event_array[index];
+ ERR_CONTINUE(event.is_null());
+ im->action_add_event(action_name, event);
+ }
+ }
+
+ Array shortcut_array;
+ shortcut_array.push_back(action_event);
+
+ Ref shortcut;
+ shortcut.instantiate();
+ shortcut->set_events(shortcut_array);
+ gui_button->set_shortcut(shortcut);
+
+ return OK;
+}
+
/* GUI::Element tree -> godot::Control tree conversion code below: */
namespace OpenVic {
@@ -283,12 +351,7 @@ static bool generate_button(generate_gui_args_t&& args) {
// TODO - clicksound, rotation (?)
const String button_name = Utilities::std_to_godot_string(button.get_name());
const String shortcut_key_name = Utilities::std_to_godot_string(button.get_shortcut());
- Array event_array = get_events_from_shortcut_key(shortcut_key_name);
- ERR_FAIL_COND_V_MSG(
- shortcut_key_name.length() != 0 && event_array.size() == 0, false,
- vformat("Unknown shortcut key '%s' for GUI button %s", shortcut_key_name, button_name)
- );
ERR_FAIL_NULL_V_MSG(button.get_sprite(), false, vformat("Null sprite for GUI button %s", button_name));
GUIButton* gui_button = nullptr;
@@ -335,11 +398,8 @@ static bool generate_button(generate_gui_args_t&& args) {
ret &= gui_button->set_gfx_font(button.get_font()) == OK;
}
- if (shortcut_key_name.length() != 0) {
- Ref shortcut;
- shortcut.instantiate();
- shortcut->set_events(event_array);
- gui_button->set_shortcut(shortcut);
+ if (try_create_shortcut_action_for_button(gui_button, shortcut_key_name) != OK) {
+ WARN_PRINT(vformat("Failed to create shortcut for GUI button '%s'", button_name));
}
gui_button->set_shortcut_feedback(false);
@@ -354,12 +414,7 @@ static bool generate_checkbox(generate_gui_args_t&& args) {
const String checkbox_name = Utilities::std_to_godot_string(checkbox.get_name());
const String shortcut_key_name = Utilities::std_to_godot_string(checkbox.get_shortcut());
- Array event_array = get_events_from_shortcut_key(shortcut_key_name);
- ERR_FAIL_COND_V_MSG(
- shortcut_key_name.length() != 0 && event_array.size() == 0, false,
- vformat("Unknown shortcut key '%s' for GUI checkbox %s", shortcut_key_name, checkbox_name)
- );
ERR_FAIL_NULL_V_MSG(checkbox.get_sprite(), false, vformat("Null sprite for GUI checkbox %s", checkbox_name));
GFX::IconTextureSprite const* texture_sprite = checkbox.get_sprite()->cast_to();
@@ -390,11 +445,8 @@ static bool generate_checkbox(generate_gui_args_t&& args) {
ret &= gui_icon_button->set_gfx_font(checkbox.get_font()) == OK;
}
- if (shortcut_key_name.length() != 0) {
- Ref shortcut;
- shortcut.instantiate();
- shortcut->set_events(event_array);
- gui_icon_button->set_shortcut(shortcut);
+ if (try_create_shortcut_action_for_button(gui_icon_button, shortcut_key_name) != OK) {
+ WARN_PRINT(vformat("Failed to create shortcut hotkey for GUI checkbox '%s'", checkbox_name));
}
gui_icon_button->set_shortcut_feedback(false);
diff --git a/game/addons/keychain/Keychain.gd b/game/addons/keychain/Keychain.gd
index 2288107b..99611894 100644
--- a/game/addons/keychain/Keychain.gd
+++ b/game/addons/keychain/Keychain.gd
@@ -2,6 +2,8 @@ extends Node
const PROFILES_PATH := "user://shortcut_profiles"
+signal reload_keychain()
+
## [Array] of [ShortcutProfile]s.
var profiles: Array[ShortcutProfile] = [preload("profiles/default.tres")]
var selected_profile := profiles[0] ## The currently selected [ShortcutProfile].
@@ -22,9 +24,12 @@ var ignore_ui_actions := true
## and the fourth for [InputEventJoypadMotion]s.
var changeable_types: PackedByteArray = [true, true, true, true]
## The file path of the [code]config_file[/code].
-var config_path := "user://cache.ini"
+var config_path := "user://config.ini"
## Used to store the settings to the filesystem.
var config_file: ConfigFile
+## Used to check if unused binding check should be ignored for action
+var keep_binding_check : Callable = func(action_name : StringName) -> bool:
+ return false
class InputAction:
@@ -48,6 +53,10 @@ class InputGroup:
folded = _folded
+func _init() -> void:
+ for locale in TranslationServer.get_loaded_locales():
+ load_translation(locale)
+
func _ready() -> void:
if !config_file:
config_file = ConfigFile.new()
@@ -58,11 +67,11 @@ func _ready() -> void:
DirAccess.make_dir_recursive_absolute(PROFILES_PATH)
var profile_dir := DirAccess.open(PROFILES_PATH)
profile_dir.list_dir_begin()
- var file_name = profile_dir.get_next()
+ var file_name := profile_dir.get_next()
while file_name != "":
if !profile_dir.current_is_dir():
if file_name.get_extension() == "tres":
- var file = load(PROFILES_PATH.path_join(file_name))
+ var file := load(PROFILES_PATH.path_join(file_name))
if file is ShortcutProfile:
profiles.append(file)
file_name = profile_dir.get_next()
@@ -76,12 +85,15 @@ func _ready() -> void:
if saved:
profiles.append(profile)
+ initialize_profiles()
+
+func initialize_profiles() -> void:
for profile in profiles:
profile.fill_bindings()
profile_index = config_file.get_value("shortcuts", "shortcuts_profile", 0)
change_profile(profile_index)
-
+ Keychain.reload_keychain.emit()
func change_profile(index: int) -> void:
if index >= profiles.size():
@@ -89,6 +101,7 @@ func change_profile(index: int) -> void:
profile_index = index
selected_profile = profiles[index]
for action in selected_profile.bindings:
+ if not InputMap.has_action(action): continue
action_erase_events(action)
for event in selected_profile.bindings[action]:
action_add_event(action, event)
@@ -107,7 +120,10 @@ func action_erase_events(action: StringName) -> void:
func load_translation(locale: String) -> void:
- var translation = load("res://addons/keychain/translations".path_join(locale + ".po"))
+ var translation_file_path := "res://addons/keychain/translations".path_join(locale + ".po")
+ if not ResourceLoader.exists(translation_file_path, "Translation"):
+ return
+ var translation := load(translation_file_path)
if is_instance_valid(translation) and translation is Translation:
TranslationServer.add_translation(translation)
diff --git a/game/addons/keychain/ShortcutEdit.gd b/game/addons/keychain/ShortcutEdit.gd
index 207ddec1..40a4ee49 100644
--- a/game/addons/keychain/ShortcutEdit.gd
+++ b/game/addons/keychain/ShortcutEdit.gd
@@ -10,8 +10,8 @@ const MOUSE_BUTTON_NAMES: PackedStringArray = [
"Wheel Down Button",
"Wheel Left Button",
"Wheel Right Button",
- "X Button 1",
- "X Button 2",
+ "Mouse Thumb Button 1",
+ "Mouse Thumb Button 2",
]
const JOY_BUTTON_NAMES: PackedStringArray = [
@@ -110,6 +110,10 @@ func _ready() -> void:
if OS.get_name() == "Web":
$VBoxContainer/HBoxContainer/OpenProfileFolder.queue_free()
+ Keychain.reload_keychain.connect(_on_reload_keychain)
+
+func _on_reload_keychain() -> void:
+ _on_ProfileOptionButton_item_selected(Keychain.profile_index)
func _construct_tree() -> void:
var buttons_disabled := false if Keychain.selected_profile.customizable else true
@@ -271,7 +275,7 @@ func _on_shortcut_tree_button_clicked(item: TreeItem, _column: int, id: int, _mb
rect.position.y += 42 - tree.get_scroll().y
rect.position += global_position
rect.size = Vector2(110, 23 * shortcut_type_menu.get_item_count())
- shortcut_type_menu.popup(rect)
+ shortcut_type_menu.popup_on_parent(rect)
elif id == 1: # Delete
Keychain.action_erase_events(action)
Keychain.selected_profile.change_action(action)
diff --git a/game/addons/keychain/ShortcutEdit.tscn b/game/addons/keychain/ShortcutEdit.tscn
index 57bfd46c..2e46a0e5 100644
--- a/game/addons/keychain/ShortcutEdit.tscn
+++ b/game/addons/keychain/ShortcutEdit.tscn
@@ -35,25 +35,30 @@ text = "Shortcut profile:"
[node name="ProfileOptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
+size_flags_horizontal = 3
mouse_default_cursor_shape = 2
[node name="NewProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
+size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "New"
[node name="RenameProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
+size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "Rename"
[node name="DeleteProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
+size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "Delete"
[node name="OpenProfileFolder" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
+size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "Open Folder"
diff --git a/game/addons/keychain/ShortcutProfile.gd b/game/addons/keychain/ShortcutProfile.gd
index 0fb269a1..e7935a3b 100644
--- a/game/addons/keychain/ShortcutProfile.gd
+++ b/game/addons/keychain/ShortcutProfile.gd
@@ -17,6 +17,9 @@ func fill_bindings() -> void:
bindings[action] = InputMap.action_get_events(action)
unnecessary_actions.erase(action)
for action in unnecessary_actions:
+ if Keychain.keep_binding_check.is_valid() and Keychain.keep_binding_check.call(action):
+ unnecessary_actions.erase(action)
+ continue
bindings.erase(action)
save()
diff --git a/game/addons/keychain/assets/add.svg b/game/addons/keychain/assets/add.svg
index afad08a2..b0fdf8a3 100644
--- a/game/addons/keychain/assets/add.svg
+++ b/game/addons/keychain/assets/add.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/game/addons/keychain/assets/close.svg b/game/addons/keychain/assets/close.svg
index 331727ab..fc587082 100644
--- a/game/addons/keychain/assets/close.svg
+++ b/game/addons/keychain/assets/close.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/game/addons/keychain/assets/edit.svg b/game/addons/keychain/assets/edit.svg
index 6fc7ae01..925f7930 100644
--- a/game/addons/keychain/assets/edit.svg
+++ b/game/addons/keychain/assets/edit.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/game/addons/keychain/assets/folder.svg b/game/addons/keychain/assets/folder.svg
index c2def257..904f94b2 100644
--- a/game/addons/keychain/assets/folder.svg
+++ b/game/addons/keychain/assets/folder.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/game/addons/keychain/assets/keyboard.svg b/game/addons/keychain/assets/keyboard.svg
index b9dfab71..ab6755c8 100644
--- a/game/addons/keychain/assets/keyboard.svg
+++ b/game/addons/keychain/assets/keyboard.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/game/addons/keychain/assets/keyboard_physical.svg b/game/addons/keychain/assets/keyboard_physical.svg
index 4364e0b4..f12cd461 100644
--- a/game/addons/keychain/assets/keyboard_physical.svg
+++ b/game/addons/keychain/assets/keyboard_physical.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/game/addons/keychain/assets/mouse.svg b/game/addons/keychain/assets/mouse.svg
index 21751208..2dcc03e1 100644
--- a/game/addons/keychain/assets/mouse.svg
+++ b/game/addons/keychain/assets/mouse.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/game/addons/keychain/assets/shortcut.svg b/game/addons/keychain/assets/shortcut.svg
index 4ef16f04..87b1a40e 100644
--- a/game/addons/keychain/assets/shortcut.svg
+++ b/game/addons/keychain/assets/shortcut.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/game/addons/keychain/translations/Translations.pot b/game/addons/keychain/translations/Translations.pot
index 1f10c772..752a8c8c 100644
--- a/game/addons/keychain/translations/Translations.pot
+++ b/game/addons/keychain/translations/Translations.pot
@@ -103,10 +103,10 @@ msgstr ""
msgid "Wheel Right Button"
msgstr ""
-msgid "X Button 1"
+msgid "Mouse Thumb Button 1"
msgstr ""
-msgid "X Button 2"
+msgid "Mouse Thumb Button 2"
msgstr ""
msgid "DualShock Cross, Xbox A, Nintendo B"
diff --git a/game/addons/keychain/translations/el_GR.po b/game/addons/keychain/translations/el_GR.po
index 4b6ce8bb..1ac87482 100644
--- a/game/addons/keychain/translations/el_GR.po
+++ b/game/addons/keychain/translations/el_GR.po
@@ -109,11 +109,11 @@ msgstr "Τρόχος αριστερά"
msgid "Wheel Right Button"
msgstr "Τρόχος δεξιά"
-msgid "X Button 1"
-msgstr "Κουμπί X 1"
+msgid "Mouse Thumb Button 1"
+msgstr "Κουμπί αντίχειρα ποντικού 1"
-msgid "X Button 2"
-msgstr "Κουμπί X 2"
+msgid "Mouse Thumb Button 2"
+msgstr "Κουμπί αντίχειρα ποντικιού 2"
msgid "DualShock Cross, Xbox A, Nintendo B"
msgstr "DualShock Σταυρός, Xbox A, Nintendo B"
diff --git a/game/assets/localisation/locales/helpers.csv b/game/assets/localisation/locales/helpers.csv
new file mode 100644
index 00000000..76853cc3
--- /dev/null
+++ b/game/assets/localisation/locales/helpers.csv
@@ -0,0 +1,2 @@
+keys;en;fr;de;es
+Mapmode %s;Mapmode %s;Mode De Carte %s;Kartenmodus %s;Modo Mapa %s
\ No newline at end of file
diff --git a/game/assets/localisation/locales/helpers.csv.import b/game/assets/localisation/locales/helpers.csv.import
new file mode 100644
index 00000000..2a5dbb39
--- /dev/null
+++ b/game/assets/localisation/locales/helpers.csv.import
@@ -0,0 +1,17 @@
+[remap]
+
+importer="csv_translation"
+type="Translation"
+uid="uid://dfro7ae4a6gkc"
+
+[deps]
+
+files=["res://assets/localisation/locales/helpers.en.translation", "res://assets/localisation/locales/helpers.fr.translation", "res://assets/localisation/locales/helpers.de.translation", "res://assets/localisation/locales/helpers.es.translation"]
+
+source_file="res://assets/localisation/locales/helpers.csv"
+dest_files=["res://assets/localisation/locales/helpers.en.translation", "res://assets/localisation/locales/helpers.fr.translation", "res://assets/localisation/locales/helpers.de.translation", "res://assets/localisation/locales/helpers.es.translation"]
+
+[params]
+
+compress=false
+delimiter=1
diff --git a/game/project.godot b/game/project.godot
index 13883395..cd3e6641 100644
--- a/game/project.godot
+++ b/game/project.godot
@@ -95,13 +95,13 @@ map_west={
map_zoom_in={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":8,"position":Vector2(174, 17),"global_position":Vector2(180, 80),"factor":1.0,"button_index":4,"canceled":false,"pressed":true,"double_click":false,"script":null)
-, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":81,"physical_keycode":0,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":81,"physical_keycode":0,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
]
}
map_zoom_out={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":16,"position":Vector2(325, 24),"global_position":Vector2(331, 87),"factor":1.0,"button_index":5,"canceled":false,"pressed":true,"double_click":false,"script":null)
-, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":69,"physical_keycode":0,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":69,"physical_keycode":0,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
map_drag={
@@ -145,6 +145,7 @@ menu_pause={
[internationalization]
locale/translation_remaps={}
+locale/translations=PackedStringArray("res://assets/localisation/locales/helpers.de.translation", "res://assets/localisation/locales/helpers.en.translation", "res://assets/localisation/locales/helpers.es.translation", "res://assets/localisation/locales/helpers.fr.translation")
locale/fallback="en_GB"
locale/locale_filter_mode=0
locale/country_short_name={
diff --git a/game/src/Game/GameSession/BillboardManager.gd b/game/src/Game/GameSession/BillboardManager.gd
index 8b317715..f14986b8 100644
--- a/game/src/Game/GameSession/BillboardManager.gd
+++ b/game/src/Game/GameSession/BillboardManager.gd
@@ -1,32 +1,48 @@
extends MultiMeshInstance3D
-#given a name: get the index for the texture in the shader
-# this is to reduce the number of magic indeces in the code
-# to get the proper billboard image
-var billboard_names : Dictionary = {}
-
@export var _map_view : MapView
-const SCALE_FACTOR : float = 1.0/96.0
-
-enum ProvinceBillboards { NONE, INVISIBLE, RGO, CRIME, NATIONAL_FOCUS }
+const SCALE_FACTOR : float = 1.0 / 96.0
+
+enum BillboardType { NONE, RGO, CRIME, NATIONAL_FOCUS, CAPITAL }
+const BILLBOARD_NAMES : Dictionary = {
+ BillboardType.RGO: &"tradegoods",
+ BillboardType.CRIME: &"crimes",
+ BillboardType.NATIONAL_FOCUS: &"national_focus",
+ BillboardType.CAPITAL: &"capital"
+}
+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)
+}
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
-var textures : Array[Texture2D] = []
-var frames : Array[int] = []
-var scales : Array[float] = []
+# Given a BillboardType, get the index for the texture in the shader.
+# This is to reduce the number of magic indeces in the code
+# to get the proper billboard image
+var billboard_type_to_index : Dictionary
+
+var textures : Array[Texture2D]
+var frames : PackedByteArray
+var scales : PackedVector2Array
-var current_province_billboard : ProvinceBillboards = ProvinceBillboards.NONE
+var current_province_billboard : BillboardType = BillboardType.NONE
+var province_billboards_visible : bool = true
# ============== Billboards =============
# Billboards are displayed using a multimesh (batch drawn mesh)
# with positions set to every province and every nation capital.
# 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
+# 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
# multimesh only lets us send custom data to the shader as a single float vec4/Color variable
@@ -38,125 +54,141 @@ var current_province_billboard : ProvinceBillboards = ProvinceBillboards.NONE
# w: unused
# "Province billboards" refer to billboards positioned on every province
-# for map modes such as RGO output, while "Capital billboards" refers to the
+# 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 noOfFrames_key : StringName = &"noFrames"
-
- var billboards : Array[Dictionary] = MapItemSingleton.get_billboards()
- for j : int in billboards.size():
- var billboard : Dictionary = billboards[j]
-
- var billboard_name : String = billboard[name_key]
- var texture_name : String = billboard[texture_key]
+ const no_of_frames_key : StringName = &"noFrames"
+
+ 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:
+ if billboard_name == BILLBOARD_NAMES[key]:
+ billboard_type = key
+ break
+
+ if billboard_type == BillboardType.NONE:
+ continue
+
+ var texture_name : StringName = billboard[texture_key]
var billboard_scale : float = billboard[scale_key]
- var noFrames : int = billboard[noOfFrames_key]
-
+ var no_of_frames : int = billboard[no_of_frames_key]
+
#fix the alpha edges of the billboard textures
- var texture : Texture2D = 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
var image : Image = texture.get_image()
image.fix_alpha_edges()
texture.set_image(image)
-
+
+ # We use the texture array size (which will be the same as frames and scales' sizes)
+ # rather than billboard_index as the former only counts billboards we're actually using,
+ # while the latter counts all billboards defined in the game's GFX files
+ billboard_type_to_index[billboard_type] = textures.size()
+
textures.push_back(texture)
- frames.push_back(noFrames)
- scales.push_back(billboard_scale*SCALE_FACTOR)
- billboard_names[billboard_name] = j
-
+ 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)
if material == null:
push_error("ShaderMaterial for billboards was null")
return
-
- material.set_shader_parameter("billboards",textures)
- material.set_shader_parameter("numframes",frames)
- material.set_shader_parameter("sizes",scales)
- multimesh.mesh.surface_set_material(0,material)
-
+
+ material.set_shader_parameter(&"billboards", textures)
+ material.set_shader_parameter(&"numframes", frames)
+ material.set_shader_parameter(&"sizes", scales)
+ multimesh.mesh.surface_set_material(0, material)
+
var positions : PackedVector2Array = MapItemSingleton.get_province_positions()
provinces_size = positions.size()
- total_capitals_size = MapItemSingleton.get_capital_count()
-
+ total_capitals_size = MapItemSingleton.get_max_capital_count()
+
# 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
multimesh.instance_count = provinces_size + total_capitals_size
- multimesh.visible_instance_count = provinces_size + total_capitals_size
-
- set_capitals()
- var map_positions : PackedVector3Array = to_map_coords(positions)
+ if _map_view == null:
+ push_error("MapView export varible for BillboardManager must be set!")
+ return
- for i : int in positions.size():
- multimesh.set_instance_transform(i + total_capitals_size, Transform3D(Basis(),
- map_positions[i]
- ))
+ 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]))
+ )
+ # These signals will trigger and update capitals and province icons right
+ # at the beginning of (as well as later throughout) the game session
GameSingleton.mapmode_changed.connect(_on_map_mode_changed)
GameSingleton.gamestate_updated.connect(_on_game_state_changed)
-#TODO: Get rid of the vertical stretch, proper capitals placement
-
-#fetch the nation capitals and setup billboards for them
+# Fetch the nation capitals and setup billboards for them
func set_capitals() -> void:
- var positions : PackedVector2Array = MapItemSingleton.get_capital_positions()
- var capital_positions : PackedVector3Array = to_map_coords(positions)
- var image_index : int = billboard_names["capital"]
-
- #multimesh.visible_instance_count = capitals_begin_index + capital_positions.size()
- for i : int in capital_positions.size():
- multimesh.set_instance_transform(i,Transform3D(Basis(),
- capital_positions[i]
- ))
-
- #capital image, frame=1 ,2x unused
- #frame=1 because frame=0 would cause capitals not to show
- #and as an index its fine, since the shader UVs will wrap around
+ 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:
+ multimesh.set_instance_transform(
+ capital_index,
+ Transform3D(Basis(), _map_view._map_to_world_coords(capital_positions[capital_index]))
+ )
+
+ # capital image, frame=1, 2x unused
+ # frame=1 because frame=0 would cause capitals not to show
+ # and as an index its fine, since the shader UVs will wrap around
# 1.0 to 2.0 --> 0.0 to 1.0 so the capital image is preserved
- multimesh.set_instance_custom_data(i,Color(#capital_index,Color(
- image_index,1.0,0,0
- ))
+ multimesh.set_instance_custom_data(
+ capital_index,
+ Color(image_index, 1.0, 0.0, 0.0)
+ )
+
# For every country that doesn't exist, make the capital invisible
- for i : int in total_capitals_size - capital_positions.size():
- multimesh.set_instance_custom_data(capital_positions.size() + i, Color(
- image_index,0.0,0,0
- ))
-
-# should provinces display RGO, crime, ..., or no billboard
-func set_province_billboards(display : ProvinceBillboards = ProvinceBillboards.INVISIBLE) -> void:
- var image_index : int = 0
- var icons : PackedByteArray = PackedByteArray()
- icons.resize(provinces_size)
- icons.fill(0) #by default, display nothing (invisible)
- match display:
- ProvinceBillboards.RGO:
- image_index = billboard_names["tradegoods"]
- icons = MapItemSingleton.get_rgo_icons()
- current_province_billboard = display
- ProvinceBillboards.CRIME:
- image_index = billboard_names["crimes"]
- icons = MapItemSingleton.get_crime_icons()
- current_province_billboard = display
- ProvinceBillboards.NATIONAL_FOCUS:
- image_index = billboard_names["national_focus"]
- icons = MapItemSingleton.get_national_focus_icons()
- current_province_billboard = display
- ProvinceBillboards.NONE:
- current_province_billboard = display
- _: #display nothing, but keep the current billboard setting
- pass
- # capitals are first in the array, so start iterating after them
- for i : int in provinces_size:
- multimesh.set_instance_custom_data(i + total_capitals_size,Color(
- image_index,icons[i],0,0
- ))
+ 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)
+ if not province_billboards_visible or image_index < 0:
+ multimesh.visible_instance_count = total_capitals_size
+ else:
+ var icons : PackedByteArray
+ match current_province_billboard:
+ BillboardType.RGO:
+ icons = MapItemSingleton.get_rgo_icons()
+ BillboardType.CRIME:
+ icons = MapItemSingleton.get_crime_icons()
+ BillboardType.NATIONAL_FOCUS:
+ icons = MapItemSingleton.get_national_focus_icons()
+ _:
+ push_error("Invalid province billboard type: ", current_province_billboard)
+ return
+
+ # Capitals are first in the array, so start iterating after them
+ 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)
+ )
+
+ multimesh.visible_instance_count = total_capitals_size + provinces_size
func _on_game_state_changed() -> void:
- set_province_billboards(current_province_billboard)
+ update_province_billboards()
set_capitals()
# There are essentially 3 visibility states we can be in
@@ -166,33 +198,20 @@ func _on_game_state_changed() -> void:
# So set_visible here is essentially to toggle the visibility of capitals
func detailed_map(visible : bool) -> void:
- if visible:
- set_visible(true)
- set_province_billboards(current_province_billboard)
- else:
- set_visible(true)
- set_province_billboards()
+ province_billboards_visible = visible
+ update_province_billboards()
func parchment_view(is_parchment : bool) -> void:
- if is_parchment:
- set_visible(false)
- else:
- detailed_map(false)
+ set_visible(not is_parchment)
func _on_map_mode_changed(map_mode : int) -> void:
match map_mode:
MapModes.INFRASTRUCTURE, MapModes.COLONIAL, MapModes.RGO_OUTPUT:
- set_province_billboards(ProvinceBillboards.RGO)
+ current_province_billboard = BillboardType.RGO
MapModes.REVOLT_RISK:
- set_province_billboards(ProvinceBillboards.CRIME)
+ current_province_billboard = BillboardType.CRIME
MapModes.NATIONAL_FOCUS:
- set_province_billboards(ProvinceBillboards.NATIONAL_FOCUS)
+ current_province_billboard = BillboardType.NATIONAL_FOCUS
_:
- set_province_billboards(ProvinceBillboards.NONE)
-
-func to_map_coords(positions : PackedVector2Array) -> PackedVector3Array:
- var map_positions : PackedVector3Array = PackedVector3Array()
- for pos_in : Vector2 in positions:
- var pos : Vector3 = _map_view._map_to_world_coords(pos_in)
- map_positions.push_back(pos)
- return map_positions
+ current_province_billboard = BillboardType.NONE
+ update_province_billboards()
diff --git a/game/src/Game/GameSession/GameSession.gd b/game/src/Game/GameSession/GameSession.gd
index 9d07fd6a..1fac9d5c 100644
--- a/game/src/Game/GameSession/GameSession.gd
+++ b/game/src/Game/GameSession/GameSession.gd
@@ -13,6 +13,8 @@ func _ready() -> void:
MusicConductor.generate_playlist()
MusicConductor.select_next_song()
+ Keychain.initialize_profiles()
+
func _process(_delta : float) -> void:
GameSingleton.update_clock()
diff --git a/game/src/Game/GameSession/billboard.gdshader b/game/src/Game/GameSession/billboard.gdshader
index 417cedf4..bf15d2cd 100644
--- a/game/src/Game/GameSession/billboard.gdshader
+++ b/game/src/Game/GameSession/billboard.gdshader
@@ -4,11 +4,13 @@ shader_type spatial;
//3d space.
render_mode unshaded, depth_test_disabled;
-//vic2 only ever loads a max of 12 BillboardType
-//Of these, only 4 are actually used
-uniform sampler2D billboards[12] : source_color;
-uniform uint numframes[12];
-uniform float sizes[12];
+// Vic2 only ever loads a max of 12 BillboardType
+// Of these, only 4 are actually used
+const uint BILLBOARD_COUNT = 4u;
+
+uniform sampler2D billboards[BILLBOARD_COUNT] : source_color;
+uniform uint numframes[BILLBOARD_COUNT];
+uniform vec2 sizes[BILLBOARD_COUNT];
//COLOR/INSTANCE_CUSTOM is our custom data, used as follows:
// x=image index
@@ -17,8 +19,8 @@ uniform float sizes[12];
void vertex() {
COLOR = INSTANCE_CUSTOM; //send instance_custom info to fragment
- float size = sizes[uint(COLOR.x + 0.5)];
- VERTEX = (vec4(VERTEX * size, 1.0) * VIEW_MATRIX).xyz;
+ VERTEX.xy *= sizes[uint(COLOR.x + 0.5)];
+ VERTEX = (vec4(VERTEX, 1.0) * VIEW_MATRIX).xyz;
}
void fragment() {
diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd
index 837e7127..760da7a5 100644
--- a/game/src/Game/GameStart.gd
+++ b/game/src/Game/GameStart.gd
@@ -15,7 +15,37 @@ const GameMenuScene := preload("res://src/Game/GameMenu.tscn")
var _settings_base_path : String = ""
var _compatibility_path_list : PackedStringArray = []
+func _enter_tree() -> void:
+ 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
+ &"map_north": Keychain.InputAction.new("Move North", "Map", true),
+ &"map_east": Keychain.InputAction.new("Move East", "Map", true),
+ &"map_south": Keychain.InputAction.new("Move South", "Map", true),
+ &"map_west": Keychain.InputAction.new("Move West", "Map", true),
+ &"map_zoom_in": Keychain.InputAction.new("Zoom In", "Map", true),
+ &"map_zoom_out": Keychain.InputAction.new("Zoom Out", "Map", true),
+ &"map_drag": Keychain.InputAction.new("Mouse Drag", "Map", true),
+ &"map_click": Keychain.InputAction.new("Mouse Click", "Map", true),
+ &"map_right_click": Keychain.InputAction.new("Mouse Right Click", "Map", true),
+ # Time Group
+ &"time_pause": Keychain.InputAction.new("Pause", "Time", true),
+ &"time_speed_increase": Keychain.InputAction.new("Speed Increase", "Time", true),
+ &"time_speed_decrease": Keychain.InputAction.new("Speed Decrease", "Time", true),
+ # UI Group
+ &"menu_pause": Keychain.InputAction.new("Open Pause Menu", "UI", true),
+ }
+
+ Keychain.groups = {
+ "Map": Keychain.InputGroup.new("", false),
+ "Time": Keychain.InputGroup.new("", false),
+ "UI": Keychain.InputGroup.new("", false),
+ "Hotkeys": Keychain.InputGroup.new("UI")
+ }
+
Localisation.initialize()
if ArgumentParser.get_argument(&"help"):
ArgumentParser._print_help()
diff --git a/game/src/Game/Menu/OptionMenu/OptionsMenu.gd b/game/src/Game/Menu/OptionMenu/OptionsMenu.gd
index 2b70d1ca..c487fb46 100644
--- a/game/src/Game/Menu/OptionMenu/OptionsMenu.gd
+++ b/game/src/Game/Menu/OptionMenu/OptionsMenu.gd
@@ -14,6 +14,29 @@ func _ready() -> void:
_tab_container.set_tab_title(3, "OPTIONS_CONTROLS")
_tab_container.set_tab_title(4, "OPTIONS_OTHER")
+ # Setup Keychain for Hotkeys
+ for action : StringName in InputMap.get_actions():
+ if not Keychain.keep_binding_check.call(action):
+ continue
+ 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.profiles[0].bindings[action] = InputMap.action_get_events(action)
+
# Prepare options menu before loading user settings
var tab_bar : TabBar = _tab_container.get_child(0, true)
diff --git a/game/src/Game/Model/XACLoader.gd b/game/src/Game/Model/XACLoader.gd
index c16d1cdf..423c6222 100644
--- a/game/src/Game/Model/XACLoader.gd
+++ b/game/src/Game/Model/XACLoader.gd
@@ -142,13 +142,6 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D:
push_warning("Skipping unused mesh \"", mesh_chunk_name, "\" in model \"", node.name, "\"")
continue
- # polySurface97 corresponds to the "arab_infantry_helmet", and needs to be removed often
- # but only in cases where it isn't an attachment
- const INVALID_IF_NOT_ONLY_MESH : PackedStringArray = ["polySurface97"]
- 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")
- break
-
var mesh : ArrayMesh = null
var verts : PackedVector3Array
var normals : PackedVector3Array
@@ -200,6 +193,16 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D:
skinning_chunk_ind = 1
applyVertexWeights = false
+ # polySurface97 corresponds to the "arab_infantry_helmet", and needs to be removed often
+ # 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"]
+ 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")
+ break
+
var meshInstance : MeshInstance3D = MeshInstance3D.new()
node.add_child(meshInstance)
meshInstance.owner = node