Tutorial:Pooled Resource Unit Costs: Difference between revisions

From Total War Modding
No edit summary
No edit summary
Line 57: Line 57:
== ✋ Handling the UI ==
== ✋ Handling the UI ==
To make the UI components work we need listeners!
To make the UI components work we need listeners!
<blockquote>''One listener to rule them all, one listener to find them, one listener to bring them all, and with a function bind them!''</blockquote>
Actually we need one more listener as well.


'''IMPORTANT''': Make sure you change the '''listener and saved value names''' from the examples to avoid incompatibilities.
'''IMPORTANT''': Make sure you change the '''listener and saved value names''' from the examples to avoid incompatibilities.
Line 96: Line 94:
  )</code>
  )</code>


The functions <code>initialise_uics(recruitment_type)</code> and <code>handle_pr_uics</code> will be explained later.
Within that listener there are 2 functions, <code>initialise_uics(recruitment_type)</code> and <code>handle_pr_uics</code>.


Next we need to use <code>ComponentLClickUp</code> and <code>UITrigger</code>. These listeners act as a pair <code>ComponentLClickUp</code>sends a message which is received by <code>UITrigger</code>. This is required for [https://tw-modding.com/wiki/Multiplayer_Desyncs multiplayer compatibility].


The <code>ComponentLClickUp</code> listener contains 2 parts. The first part is to account for a refresh in the UI when the army's stance is changed.


Lastly we need to use <code>PanelClosedCampaign</code> to reset our saved values when the whole <code>"units_panel"</code> closes.
Lastly we need to use <code>PanelClosedCampaign</code> to reset our saved values when the whole <code>"units_panel"</code> closes.


== 💰 Handling the Cost ==
== 💰 Handling the Cost ==
Next we need to use <code>ComponentLClickUp</code> and <code>UITrigger</code>. These listeners act as a pair <code>ComponentLClickUp</code>sends a message which is received by <code>UITrigger</code>. This is required for [https://tw-modding.com/wiki/Multiplayer_Desyncs multiplayer compatibility].
The <code>ComponentLClickUp</code> listener contains 2 parts. The first part is to account for a refresh in the UI when the army's stance is changed.

Revision as of 08:57, 10 July 2023

Originally written by Pear

💬 Let's Talk Pooled Resources

Pooled Resources are cool. The problem is they're often not used for very much. This tutorial aims to fix that by explaining how to use Pooled Resources as costs for units in the main recruitment panel and providing an example mod for reference. The theory used here can be applied to a range of other things such as RoRs, lords, agents, buildings, technologies, or anything really!

This guide is also my submission for Mod Jam #7, along with an example mod for making units cost Pooled Resources in the main recruitment panel.

🧑‍🎓 The Theory

If you're not familiar with Lua UI modding, UI components (UICs), or scripting in general then the UI Tutorial Series is a good place to start. This Pooled Resource Costs guide won't build the script for you but it will give you the bricks and tools you need to put the script together. However! feel free to contact me on the Modding Den Discord if you're stuck.

The main things this guide will explain are:

  1. Building the UI: This involves finding existing UICs, creating / copying UICs, and repositioning them.
  2. Handling the UI: This involves handling setting up the UI, accounting for refreshes, making sure UICs are enabled / disabled, and have the right text, tooltips, and icons at the right time.
  3. Handling the Cost: This involves taking away / returning the Pooled Resource to the player in a multiplayer-friendly way.

You'll need my example mod as a reference, and I'd recommend using Groove Wizard's Visual Studio development environment for your scripting, and Groove's Modding Development Tools: Lua Console to test the code in game (yes this man is a machine).

👷 Building the UI

The first step in building the UI is figuring out where you want your Pooled Resource cost to appear. Do you want to keep the vanilla treasury recruitment cost? Do you want several Pooled Resource costs? Do you want it below the unit card like normal treasury costs or on the side of the unit card like the Wood Elves' Amber resource?

If you don't want a recruitment cost and only want one Pooled Resource cost then you can technically skip most of this section and just reuse the vanilla recruitment cost UIC, however I would highly recommend still reading this section to better understand the process.

For the purpose of this guide we will be keeping the vanilla treasury costs and making one new Pooled Resource cost which will go below the unit card.

🔍 Finding UI Components:

Recruitment cost component.png

In order to modify a part of the UI you need to find where it's located. To do this we can use the Context Viewer. To find a UIC, open the Context Viewer, position your mouse on the screen where you want your UIC to go, and click with your mouse wheel. This will take you most, if not all, of the way to the path of your UIC. So click on the unit card then expand the unit card UIC in the Context Viewer until you find the UIC for recruitment cost. Once you've found it, paste CopyFullPathToClipboard() in the expression tester box. This copies the path to that UIC which is how we find it in the script.

When copied it will look like this: ":root:units_panel:main_units_panel:..." and so on.

Referencing my example mod, to find the recruitment cost UIC with a script you write it like this:

local recruitment_uic = find_uicomponent(core:get_ui_root(), "units_panel", "main_units_panel", "recruitment_docker", "recruitment_options", "recruitment_listbox", "local1", "unit_list", "listview", "list_clip", "list_box", "wh_main_emp_inf_swordsmen_recruitable", "unit_icon", "RecruitmentCost") 

It is fine as it is however it can be split into smaller, easier-to-access parts like this:

local recruitment_uic = find_uicomponent(core:get_ui_root(), "units_panel", "main_units_panel", "recruitment_docker", "recruitment_options", "recruitment_listbox", recruitment_type)
local listview_uic = find_uicomponent(recruitment_uic, "unit_list", "listview")
local unit_uic = find_uicomponent(listview_uic, "list_clip", "list_box", "wh_main_emp_inf_swordsmen_recruitable")
local recruitment_cost_uic = find_uicomponent(unit_uic, "unit_icon", "RecruitmentCost")

📷 Creating / Copying Components:

Next up you need to copy the recruitment cost UIC. To do that you need to use uicomponent:CopyComponent() and name the new UIC e.g. "prestige cost" like this:

UIComponent(recruitment_cost_uic:CopyComponent("prestige_cost"))

Using the paths we've previously established, the path for this new "prestige_cost" UIC we've created is therefore:

local prestige_cost_parent_uic = find_uicomponent(unit_uic, "unit_icon", "prestige_cost")

⬇️ Repositioning Components:

This part is a bit more tedious. You will need to use the console (shift + f3) from Groove's Modding Dev Tools to test where the new "prestige_cost" UIC we've created should be repositioned. To do that you need to use uicomponent:SetDockOffset() and your own x and y coordinates, e.g. x = 0, y = 5 like this:

local prestige_cost_parent_uic = find_uicomponent(unit_uic, "unit_icon", "prestige_cost")
prestige_cost_parent_uic:SetDockOffset(x, y)

If you really want to make sure it's in the right place take a screenshot and do a bit of pixel peeping in a photo editor and count the pixels between e.g. the recruitment cost UIC and the upkeep cost UIC and that's how many pixels your new component should be below the recruitment cost UIC. The correct coordinates for the normal recruitment panel are:

prestige_cost_parent_uic:SetDockOffset(8, 19)

this may change depending on the faction or panel e.g. RoRs or lords.

✋ Handling the UI

To make the UI components work we need listeners!

IMPORTANT: Make sure you change the listener and saved value names from the examples to avoid incompatibilities.

Setting up the UI

The first listener we need is PanelOpenedCampaign. We can use this to tell when the main recruitment panel "units_recruitment" is open so we can start setting up our UI. To find the name of the panel you're adding Pooled Resource costs to you can check the log produced by Groove's Modding Dev Tools. In the log it will look something like this:

[ui] <97.6s>    Panel opened units_panel
[ui] <98.8s>    Panel opened recruitment_options
[ui] <98.8s>    Panel opened units_recruitment

For this listener to work for both local and global recruitment we need a for loop to run the set up for both types of recruitment which will use this table:

local recruitment_types = {
   {recruitment_type = "local1"},
   {recruitment_type = "global"}
} 

We also need some saved values to make sure the listener doesn't continually resize the parent UI components. Together the listener looks like this:

core:add_listener(
   "example_panel_opened_listener",
   "PanelOpenedCampaign",
   function(context)
       return context.string == "units_recruitment"
   end,
   function()
       for i = 1, #recruitment_types do
           local recruitment_type = recruitment_types[i].recruitment_type
           if cm:get_saved_value("example_init_1") ~= true then
               cm:set_saved_value("example_init_1", true)
               initialise_uics(recruitment_type)
           else
               handle_pr_uics()
               return
           end
       end
   end,
   true
)

Within that listener there are 2 functions, initialise_uics(recruitment_type) and handle_pr_uics.


Lastly we need to use PanelClosedCampaign to reset our saved values when the whole "units_panel" closes.

💰 Handling the Cost

Next we need to use ComponentLClickUp and UITrigger. These listeners act as a pair ComponentLClickUpsends a message which is received by UITrigger. This is required for multiplayer compatibility.

The ComponentLClickUp listener contains 2 parts. The first part is to account for a refresh in the UI when the army's stance is changed.