Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f283b08
Scriptable buttons visibility
SneakBug8 Jul 4, 2025
81e4b4a
Scriptable buttons visibility
SneakBug8 Jul 4, 2025
7aa6816
Extract scriptable buttons into DCON
SneakBug8 Jul 5, 2025
5f4fe33
Fix visibiility trigger
SneakBug8 Jul 5, 2025
fc738c1
AI to take national scripted interactions
SneakBug8 Jul 5, 2025
82b2c41
Comment behavior
SneakBug8 Jul 5, 2025
c5dbf28
Docs
SneakBug8 Jul 6, 2025
7e5a3e8
Docs
SneakBug8 Jul 6, 2025
356de4b
Toggleable scriptable windows
SneakBug8 Jul 7, 2025
d5ef8f8
Scripted elements with datamodel field for dynamic frames
SneakBug8 Jul 7, 2025
4d7203d
Scriptable state religion icon with a datamodel
SneakBug8 Jul 7, 2025
a27c712
trade_routes_attraction national modifier
SneakBug8 Jul 12, 2025
313591d
Global variables
SneakBug8 Jul 12, 2025
0fc2cfc
Check global variable trigger
SneakBug8 Jul 12, 2025
7252cb2
synonim for capital_scope
SneakBug8 Jul 15, 2025
584e9a4
sub_unit effect
SneakBug8 Jul 15, 2025
e031a54
Visible and allow triggers on UI scripted buttons
SneakBug8 Jul 15, 2025
248e745
UI variable toggle button scripting
SneakBug8 Jul 16, 2025
95201ba
Extract into new files
SneakBug8 Jul 16, 2025
6f707c2
Fix global vars
SneakBug8 Jul 16, 2025
4deaf80
Expand on the datamodels
SneakBug8 Jul 16, 2025
e27eb6c
Docs
SneakBug8 Jul 16, 2025
3e07341
State def names
SneakBug8 Jul 18, 2025
4c6b4b3
Visibility trigger tooltips
SneakBug8 Jul 18, 2025
0aa1709
Option for tiled textures
SneakBug8 Jul 20, 2025
27bcc79
Fix regression
SneakBug8 Jul 20, 2025
fc3785e
Merge remote-tracking branch 'origin/main' into feature/scripting3
SneakBug8 Jul 20, 2025
a0222cc
Adjust
SneakBug8 Jul 20, 2025
8e9ce76
Merge remote-tracking branch 'origin/main' into feature/scripting4
SneakBug8 Jul 21, 2025
e9c9ca1
Reorganize code & imports
SneakBug8 Jul 21, 2025
ed4c6b7
Merge branch 'feature/scripting3' into feature/scripting4
SneakBug8 Jul 21, 2025
5fd7f95
Merge remote-tracking branch 'origin/main' into feature/scripting4
SneakBug8 Aug 19, 2025
b9b5bc4
AI weighting scripted interactions
SneakBug8 Aug 19, 2025
00b34cb
Fixes
SneakBug8 Aug 19, 2025
4a32d1c
Adjust US numbering
SneakBug8 Aug 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ list(APPEND ALICE_INCREMENTAL_SOURCES_LIST
"src/gui/gui_leader_select.cpp"
"src/gui/gui_leader_tooltip.cpp"
"src/gui/gui_modifier_tooltips.cpp"
"src/gui/gui_scripted_elements.cpp"
"src/gui/province_tiles/province_tiles.cpp"
"src/gui/gui_province_window.cpp"
"src/gui/gui_trigger_tooltips.cpp"
Expand Down
4 changes: 4 additions & 0 deletions assets/localisation/en-US/alice.csv
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ make_accepted_culture;$text$ becomes an accepted culture
primary_culture_changes_to;Primary culture changes to ?Y$text$?W
union_culture_accepted;All cultures in the nation's primary culture group become accepted
remove_accepted_culture;$text$ stops being an accepted culture
set_national_variable_to;Set national variable $text$ to $value$
set_global_variable_to;Set global ?Y$text$?! to ?Y$value$?!
increase_global_variable_by;Increase global ?Y$text$?! by ?Y$value$?!
decrease_global_variable_by;Decrease global ?Y$text$?! by ?Y$value$?!
increase_national_variable_by;Increase nation's ?Y$text$?W by ?Y$value$?W
decrease_national_variable_by;Decrease nation's ?Y$text$?W by ?Y$value$?W
life_rating;Life rating
Expand Down
52 changes: 52 additions & 0 deletions assets/shaders/glsl/ui_f_shader.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,57 @@ vec4 frame_stretch(vec2 tc) {
yout = border_size / tsize.y + (1.0 - 2.0 * border_size / tsize.y) * (realy - border_size) / (d_rect.w * 2.0 * border_size);
return texture(texture_sampler, vec2(xout, yout));
}

vec4 frame_repeat(vec2 tc) {
float realx = tc.x * d_rect.z;
float realy = tc.y * d_rect.w;
vec2 tsize = textureSize(texture_sampler, 0);
float xout = 0.0;
float yout = 0.0;

// Calculate inner texture dimensions (center portion)
float inner_tex_w = tsize.x - 2.0 * border_size;
float inner_tex_h = tsize.y - 2.0 * border_size;
float inner_rect_w = d_rect.z - 2.0 * border_size;
float inner_rect_h = d_rect.w - 2.0 * border_size;

// Handle X-axis
if(realx <= border_size) {
xout = realx / tsize.x;
}
else if(realx >= (d_rect.z - border_size)) {
xout = (1.0 - border_size / tsize.x) + (border_size - (d_rect.z - realx)) / tsize.x;
}
else {
// Tile center horizontally
if(inner_tex_w > 0.0) {
float offset = mod(realx - border_size, inner_tex_w);
xout = (border_size + offset) / tsize.x;
} else {
xout = border_size / tsize.x;
}
}

// Handle Y-axis
if(realy <= border_size) {
yout = realy / tsize.y;
}
else if(realy >= (d_rect.w - border_size)) {
yout = (1.0 - border_size / tsize.y) + (border_size - (d_rect.w - realy)) / tsize.y;
}
else {
// Tile center vertically
if(inner_tex_h > 0.0) {
float offset = mod(realy - border_size, inner_tex_h);
yout = (border_size + offset) / tsize.y;
} else {
yout = border_size / tsize.y;
}
}

return texture(texture_sampler, vec2(xout, yout));
}

//layout(index = 9) subroutine(font_function_class)
vec4 piechart(vec2 tc) {
if(((tc.x - 0.5) * (tc.x - 0.5) + (tc.y - 0.5) * (tc.y - 0.5)) > 0.25)
Expand Down Expand Up @@ -181,6 +232,7 @@ case 20: return alpha_color(tc);
case 21: return subsprite_c(tc);
case 22: return linegraph_acolor(tc);
case 23: return stripchart(tc);
case 24: return frame_repeat(tc);

default: break;
}
Expand Down
24 changes: 3 additions & 21 deletions docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Additionally, triggers such as technology triggers no longer suffer from having
- `increment_variable = ...`: Shorthand to increment by 1
- `decrement_variable = ...`: Shorthand to decrement by 1
- `set_variable_to_zero = ...`: Shorthand to set a variable to 0
- `set_global_variable = ...`: Change global variable
- `change_global_varible = ...`: Change global variable
- `ruling_party_ideology = THIS/FROM`: Appoints the ruling party with an ideology of `THIS` or `FROM`
- `add_accepted_culture = THIS/FROM`: Now with `THIS/FROM` adds the PRIMARY culture of `THIS/FROM` to the nation in scope
- `add_accepted_culture = this_union/from_union`: Adds the culture union of the primary culture of `THIS/FROM` as accepted to the nation in scope
Expand Down Expand Up @@ -430,27 +432,7 @@ This control will then automatically be inserted into the window named `province

### Scriptable buttons

Of course, adding new buttons wouldn't mean much if you couldn't make them do things. To allow you to add custom button effects to the game, we have introduced two new ui element types: `provinceScriptButtonType` and `nationScriptButtonType`. These buttons are defined in the same way as a `guiButtonType`, except that they can be given additional `allow` and `effect` parameters. For example:
```
provinceScriptButtonType = {
name = "wololo_button"
extends = "province_view_header"
position = { x= 146 y = 3 }
quadTextureSprite = "GFX_wololo"
allow = {
owner = { tag = FROM }
}
effect = {
assimilate = "yes please"
}
}
```

A province script button has its main and THIS slots filled with the province that the containing window is about, with FROM the player's nation. A nation script button has its main and THIS slots filled with the nation that the containing window is about, if there is one, or the player's nation if there is not, and has FROM populated with the player's nation.

The allow trigger condition is optional and is used to determine when the button is enabled. If the allow condition is omitted, the button will always be enabled.

The tooltip for these scriptable buttons will always display the relevant allow condition and the effect. You may also optionally add a custom description to the tooltip by adding a localization key that is the name of the button followed by `_tooltip`. In the case of the button above, for example, the tooltip is defined as `wololo_button_tooltip;Wololo $PROVINCE$`. The following three variables can be used in the tooltip: `$PROVINCE$`, which will resolve to the targeted province, `$NATION$`, which will resolve to the targeted nation or the owner of the targeted province, and `$PLAYER$`, which will always resolve to the player's own nation.
[See Scripting](features/scripting.md)

### Abbreviated `.gui` syntax

Expand Down
115 changes: 115 additions & 0 deletions docs/features/Scripting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Scripting

## US27. Scriptable buttons

Adding new buttons wouldn't mean much if you couldn't make them do things. To allow you to add custom button effects to the game, we have introduced two new ui element types: `provinceScriptButtonType` and `nationScriptButtonType`. These buttons are defined in the same way as a `guiButtonType`, except that they can be given additional `allow` and `effect` parameters. For example:

```
provinceScriptButtonType = {
name = "wololo_button"
extends = "province_view_header"
position = { x= 146 y = 3 }
quadTextureSprite = "GFX_wololo"
visible = {
tag = USA
}
allow = {
owner = { tag = FROM }
}
effect = {
assimilate = "yes please"
}
}

nationScriptButtonType = {
name = "wololo_button"
extends = "province_view_header"
position = { x= 146 y = 3 }
quadTextureSprite = "GFX_wololo"
visible = {
tag = USA
}
allow = {
owner = { tag = FROM }
}
effect = {
assimilate = "yes please"
}
ai_will_do = {
always = yes
}
}
```

How does it work:
- A province script button has its main and THIS slots filled with the province that the containing window is about, with FROM the player's nation.
- A nation script button has its main and THIS slots filled with the nation that the containing window is about, if there is one, or the player's nation if there is not, and has FROM populated with the player's nation.
- The `visible` trigger condition is optional and is used to determine when the button is rendered. If the allow condition is omitted, the button will always be enabled.
- The `allow` trigger condition is optional and is used to determine when the button is enabled. If the allow condition is omitted, the button will always be enabled.
- The tooltip for these scriptable buttons will always display the relevant allow condition and the effect. You may also optionally add a custom description to the tooltip by adding a localization key that is the name of the button followed by `_tooltip`. In the case of the button above, for example, the tooltip is defined as `wololo_button_tooltip;Wololo $PROVINCE$`. The following three variables can be used in the tooltip: `$PROVINCE$`, which will resolve to the targeted province, `$NATION$`, which will resolve to the targeted nation or the owner of the targeted province, and `$PLAYER$`, which will always resolve to the player's own nation.
- AI evaluates national scripted interactions once a month in a similar way to decisions.
- AI doesn't use province scripted interactions.

Recent changes:

- SneakBug8: Moved links to triggers & effects away from UI element definition into a special `scripted_interaction` DCON table. This reduces memory usage (there is over 8k UI elements in the basegame each having 6 bytes for these links) and increases calculations during UI update (find the interaction ID from GUI element ID).
- SneakBug8: AI can now use scripted buttons that have `ai_will_do` evaluation defined by iterating over a limited number of scripted interactions along with decisions.

**As a Modder,**
**I want to mod scripted buttons,**
**So that I add extra interactions to the game.**

**Acceptance Criteria:**
| AC1 | Allow trigger is parsed |
| AC2 | `Visible` trigger is parsed |
| AC3 | Effect is parsed |
| AC4 | Ai_will_do block is parsed |
| AC5 | AI takes national interactions |

**Definition of Done:**
- [X] All acceptance criteria are met.
- [X] Code is reviewed and approved.
- [ ] Necessary tests are written and pass.
- [X] Documentation is updated, if applicable.
- [x] Feature is available in release versions of PA.

## US28. Toggleable windows

**As a Modder,**
**I want to mod buttons that toggle windows visibility,**
**So that I add extra windows to the game.**

**Acceptance Criteria:**
| AC1 | `uiscriptbuttontype` elements can have `toggle_ui_key` with a name of UI variable |
| AC2 | when clicking `uiscriptbuttontype` the associated UI variable is toggled True/False |
| AC3 | `windowType` elements can have `visible_ui_key` with a name of UI variable |
| AC4 | Window is shown only when UI variable in `visible_ui_key` is set to True |
| AC5 | `uiscriptbuttontype` can have `visible` triggers |
| AC6 | `uiscriptbuttontype` can have `allow` triggers |

**Definition of Done:**
- [X] All acceptance criteria are met.
- [X] Code is reviewed and approved.
- [ ] Necessary tests are written and pass.
- [X] Documentation is updated, if applicable.
- [x] Feature is available in release versions of PA.

## US29. Scriptable images with dynamic frames through datamodels

**As a Modder,**
**I want to mod in icons that have dynamic frames,**
**So that I add extra consistency to the UI.**

**Acceptance Criteria:**
| AC1 | GUI elements can have `datamodel` with one of the datamodel options |
| AC2 | Datamodel can be `state_religion` |
| AC3 | When a supper element has `datamodel="state_religion"`, it always displays the state religion of the player |
| AC4 | `uiscriptbuttontype` can have a datamodel |
| AC5 | `iconType` can have a datamodel |

**Definition of Done:**
- [X] All acceptance criteria are met.
- [X] Code is reviewed and approved.
- [ ] Necessary tests are written and pass.
- [X] Documentation is updated, if applicable.
- [x] Feature is available in release versions of PA.
1 change: 1 addition & 0 deletions docs/features/trade.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
| AC19 | Local and precious goods cannot be traded. |
| AC20 | Not yet unlocked commodities aren't traded. |
| AC21 | Increase of volume reduces the transport cost. |
| AC22 | National `trade_routes_attraction` modifier changes appeal of its states for trade routes |

**Definition of Done:**
- [X] All acceptance criteria are met.
Expand Down
1 change: 1 addition & 0 deletions docs/modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ MOD_LIST_ELEMENT(126, aristocrat_reinvestment, true, modifier_display_type::perc
| farmers_savings | Increases the % of incomes this pop type deposits into the national bank |
| disallow_naval_trade = 1 | If >0.0f, then the nation can't trade with other countries by sea. Passing zero or negative values will lead to unexpected results. |
| disallow_land_trade = 1 | If >0.0f, then the nation can't trade with other countries by land. Passing zero or negative values will lead to unexpected results. |
| trade_routes_attraction | Increases attraction of trade routes |
1 change: 1 addition & 0 deletions docs/triggers.md
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ Not an exhaustive list of triggers, be wary the trigger codes **may** have flags
| 0x02DB | has_building_bank | 0 | |
| 0x02DC | has_building_university | 0 | |
| 0x02DD | test | 1 | Forwards `THIS`, `FROM` and primary arguments, for more information see Scripted Triggers on the modding extensions |
| 0x02E6 | check_global_variable | 3 | Checks global variable value |

### Optimizations

Expand Down
103 changes: 103 additions & 0 deletions src/ai/ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
namespace ai {

void take_ai_decisions(sys::state& state) {
// Assumption: AI_WILL_DO in decisions returns either 0 or 1. When it's zero, then the AI will not enact the decision. Weighting the decisions, therefore, is not implemented.

using decision_nation_pair = std::pair<dcon::decision_id, dcon::nation_id>;
concurrency::combinable<std::vector<decision_nation_pair, dcon::cache_aligned_allocator<decision_nation_pair>>> decisions_taken;

Expand All @@ -42,6 +44,7 @@ void take_ai_decisions(sys::state& state) {
&& state.world.nation_get_owned_province_count(ids) != 0;
if(ve::compress_mask(filter_a).v != 0) {
// empty allow assumed to be an "always = yes"
// empty potential assumed to be an "always = yes"
ve::mask_vector filter_b = potential
? filter_a && (trigger::evaluate(state, potential, trigger::to_generic(ids), trigger::to_generic(ids), 0))
: filter_a;
Expand Down Expand Up @@ -83,12 +86,112 @@ void take_ai_decisions(sys::state& state) {
auto n = v.second;
auto d = v.first;
auto e = state.world.decision_get_effect(d);
// The effect of a prior decision once taken may invalidate the conditions that enabled another copy of the decision in the simultaneous evaluation to be taken
if(command::can_take_decision(state, n, d)) {
nations::take_decision(state, n, d);
}
}
}

void take_ai_scripted_interactions(sys::state& state) {
// US27AC5 National level interactions first
using element_nation_pair = std::pair<dcon::scripted_interaction_id, dcon::nation_id>;
struct item_to_sort {
element_nation_pair pair; float weight = NAN;
};
concurrency::combinable<std::vector<item_to_sort, dcon::cache_aligned_allocator<item_to_sort>>> interactions_taken;

// execute in staggered blocks
uint32_t d_block_size = state.world.decision_size() / 32;
uint32_t block_index = 0;
auto d_block_end = state.world.decision_size();
concurrency::parallel_for(d_block_size * block_index, d_block_end, [&](uint32_t i) {
auto sel = dcon::scripted_interaction_id{ dcon::scripted_interaction_id::value_base_t(i) };
auto e = state.world.scripted_interaction_get_effect(sel);
if(e) {
auto potential = state.world.scripted_interaction_get_visible(sel);
auto allow = state.world.scripted_interaction_get_allow(sel);
auto ai_will_do = state.world.scripted_interaction_get_ai_will_do(sel);
ve::execute_serial_fast<dcon::nation_id>(state.world.nation_size(), [&](auto ids) {
// AI-only, not dead nations
ve::mask_vector filter_a = !state.world.nation_get_is_player_controlled(ids) && state.world.nation_get_owned_province_count(ids) != 0;
if(ve::compress_mask(filter_a).v != 0) {
// empty allow assumed to be an "always = yes"
// empty visible assumed to be an "always = yes"
ve::mask_vector filter_b = potential ? filter_a && (trigger::evaluate(state, potential, trigger::to_generic(ids), trigger::to_generic(ids), 0)) : filter_a;
if(ve::compress_mask(filter_b).v != 0) {
ve::mask_vector filter_c = allow ? filter_b && (trigger::evaluate(state, allow, trigger::to_generic(ids), trigger::to_generic(ids), 0)) : filter_b;
if(ve::compress_mask(filter_c).v != 0) {
ve::mask_vector filter_d = ai_will_do ? filter_c && (trigger::evaluate_multiplicative_modifier(state, ai_will_do, trigger::to_generic(ids), trigger::to_generic(ids), 0) > 0.0f) : filter_c;
ve::apply([&](dcon::nation_id n, bool passed_filter) {
if(passed_filter) {
interactions_taken.local().push_back({ element_nation_pair(sel, n), NAN });
}
}, ids, filter_d);
}
}
}
});
}
});
// combination and final execution
auto total_vector = interactions_taken.combine([](auto& a, auto& b) {
std::vector<item_to_sort, dcon::cache_aligned_allocator<item_to_sort>> result(a.begin(), a.end());
result.insert(result.end(), b.begin(), b.end());
return result;
});

// Order interactions
std::sort(total_vector.begin(), total_vector.end(), [&](auto a, auto b) {
auto na = a.pair.second;
auto nb = b.pair.second;
auto interactiona = a.pair.first;
auto interactionb = b.pair.first;
auto& weight_a = a.weight;
auto& weight_b = b.weight;
if(weight_a == NAN) {
auto& ai_will_do_a = state.world.scripted_interaction_get_ai_will_do(interactiona);
if(ai_will_do_a) {
weight_a = trigger::evaluate_multiplicative_modifier(state, ai_will_do_a, trigger::to_generic(na), trigger::to_generic(na), 0);
}
else {
weight_a = 1;
}
}
if(weight_b == NAN) {
auto& ai_will_do_b = state.world.scripted_interaction_get_ai_will_do(interactionb);
if (ai_will_do_b) {
weight_b = trigger::evaluate_multiplicative_modifier(state, ai_will_do_b, trigger::to_generic(nb), trigger::to_generic(nb), 0);
} else {
weight_a = 1;
}
}
if(na != nb)
return na.index() < nb.index();
if(weight_a != weight_b)
return weight_a > weight_b;

auto random_a = rng::get_random(state, uint32_t(interactiona.index()) << 5 ^ uint32_t(na.index()));
auto random_b = rng::get_random(state, uint32_t(interactionb.index()) << 5 ^ uint32_t(nb.index()));
return random_a < random_b;
});
// assumption 1: no duplicate pair of <n, d>
for(const auto& v : total_vector) {
auto nation = v.pair.second;
auto interaction = v.pair.first;
// The effect of a prior interaction once taken may invalidate the conditions that enabled another copy of the interaction in the simultaneous evaluation to be taken
/*if(command::can_use_nation_button(state, nation, interaction, nation)) {
command::execute_use_nation_button(state, nation, interaction, nation);
} */
auto potential = state.world.scripted_interaction_get_visible(interaction);
auto e = state.world.scripted_interaction_get_effect(interaction);
if((!potential || trigger::evaluate(state, potential, trigger::to_generic(nation), trigger::to_generic(nation), 0))
&& trigger::evaluate(state, state.world.scripted_interaction_get_allow(interaction), trigger::to_generic(nation), trigger::to_generic(nation), 0)) {
effect::execute(state, e, trigger::to_generic(nation), trigger::to_generic(nation), 0, uint32_t(state.current_date.value), uint32_t(nation.index() << 4 ^ interaction.index()));
}
}
}

float estimate_pop_party_support(sys::state& state, dcon::nation_id n, dcon::political_party_id pid) {
auto iid = state.world.political_party_get_ideology(pid);
/*float v = 0.f;
Expand Down
1 change: 1 addition & 0 deletions src/ai/ai.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct ai_path_length {
};

void take_ai_decisions(sys::state& state);
void take_ai_scripted_interactions(sys::state& state);
void update_ai_ruling_party(sys::state& state);
void update_ai_colonial_investment(sys::state& state);
void update_ai_colony_starting(sys::state& state);
Expand Down
Loading
Loading