Tutorial:Component Context Objects (CCOs): Difference between revisions

From Total War Modding
 
(18 intermediate revisions by the same user not shown)
Line 31: Line 31:


=== Why Use CCOs? ===
=== Why Use CCOs? ===
So CCOs are objects embedded into UI components, but you're not interested in [[UI:Main Page|UI modding]], so why should you care? In short, despite being designed as part of the UI system, CCOs have scripting uses outside of the UI. There is game information exposed through CCOs that is not available through the [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/model_hierarchy.html model hierarchy], and there are some script actions you can take via CCO that don't have an equivalent within the [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/episodic_scripting.html episodic scripting interface]. You may also find that there comes a time in your modding journey where you need to pull game information from the current UI state, and CCOs are practically purpose-build for this task (example: getting a character CQI from an event feed message). For more specific use cases, you can skip ahead to Part 4 of this guide, but then please, do come back. We're just getting started.
So CCOs are objects embedded into UI components, but you're not interested in [[UI:Main Page|UI modding]], so why should you care? In short, despite being designed as part of the UI system, CCOs have scripting uses outside of the UI. There is game information exposed through CCOs that is not available through the [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/model_hierarchy.html model hierarchy], and there are some script actions you can take via CCO that don't have an equivalent within the [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/episodic_scripting.html episodic scripting interface]. You may also find that there comes a time in your modding journey where you need to pull game information from the current UI state, and CCOs are practically purpose-built for this task (example: getting a character CQI from an event feed message). For more specific use cases, you can skip ahead to Part 4 of this guide, but then please, do come back. We're just getting started.


== Part 2: CCOs in Context Viewer ==
== Part 2: CCOs in Context Viewer ==
[[Tutorial:Context Viewer (Warhammer 3)|Context Viewer]] is a wonderous tool bequeathed to us by CA that is essential for working with CCOs. If you aren't already familiar with it, stop here and take a moment to read the [[Tutorial:Context Viewer (Warhammer 3)|linked guide]] on this Wiki, which goes over how to turn it on and get it running.
[[Tutorial:Context Viewer (Warhammer 3)|Context Viewer]] is a wondrous tool bequeathed to us by CA that is essential for working with CCOs. If you aren't already familiar with it, stop here and take a moment to read the [[Tutorial:Context Viewer (Warhammer 3)|linked guide]] on this Wiki, which goes over how to turn it on and get it running.


Got Context Viewer up and running in your game? Great. Let's dive into some CCOs. Context Viewer's most useful feature is that it shows you exactly which CCOs are attached to which UI elements (themselves represented as CCOs here, a specific subtype of CCO called CcoComponent), and it will then show you which options you can run from these CCOs. '''All CCO options do one of two things: Either they extract information from the game and return it, or they send some instruction to the game, altering its state.''' These are referred to as '''Queries''' and '''Commands''' in Context Viewer, respectively.
Got Context Viewer up and running in your game? Great. Let's dive into some CCOs. Context Viewer's most useful feature is that it shows you exactly which CCOs are attached to which UI elements (themselves represented as CCOs here, a specific subtype of CCO called CcoComponent), and it will then show you which options you can run from these CCOs. '''All CCO options do one of two things: Either they extract information from the game and return it, or they send some instruction to the game, altering its state.''' These are referred to as '''Queries''' and '''Commands''' in Context Viewer, respectively.
Line 62: Line 62:
# Once you have your UIC, you can run its [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/uicomponent.html#function:uicomponent:GetContextObject GetContextObject method] to get the particular CCO you want to work with.
# Once you have your UIC, you can run its [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/uicomponent.html#function:uicomponent:GetContextObject GetContextObject method] to get the particular CCO you want to work with.
For example, let's return to the settlement_list component we were examining in Context Viewer in the previous section, but this time let's access those options via script. First, we will need to get the UIC for settlement_list:
For example, let's return to the settlement_list component we were examining in Context Viewer in the previous section, but this time let's access those options via script. First, we will need to get the UIC for settlement_list:
<br><br>
 
<code>local uic = find_uicomponent("settlement_list")</code>
local uic = find_uicomponent("settlement_list")
<br><br>
 
As you recall from our example with Context Viewer, this component has two other CCOs attached to it, one CcoCampaignSettlement for the currently selected settlement, and one CcoCampaignProvince for the province in which this settlement exists. Because there is only ever one of a given type of CCO connected to a component at any one time, we can retrieve the settlement CCO script interface simply:
As you recall from our example with Context Viewer, this component has two other CCOs attached to it, one CcoCampaignSettlement for the currently selected settlement, and one CcoCampaignProvince for the province in which this settlement exists. Because there is only ever one of a given type of CCO connected to a component at any one time, we can retrieve the settlement CCO script interface simply:
<br><br>
 
<code>local settlement_cco = uic:GetContextObject("CcoCampaignSettlement")</code>
local settlement_cco = uic:GetContextObject("CcoCampaignSettlement")
<br><br>


[[File:Cco scripting 1.png|thumb|Testing CCO scripting with Lua console]]
[[File:Cco scripting 1.png|thumb|Testing CCO scripting with Lua console]]
Line 115: Line 114:
  console_print(unit_rank)
  console_print(unit_rank)


Remember, this is just an example. You can use any of the queries or commands that you find in Context Viewer. There are some ''some'' limitations, however.
Remember, this is just an example. You can use any of the queries or commands that you find in Context Viewer. There are ''some'' limitations, however.
<br><br>
<br><br>


Line 166: Line 165:
*CcoBattleSelection
*CcoBattleSelection
*CcoBattleUnit
*CcoBattleUnit
If you attempt to use the cco function with a CCO that is not contained on the list above, it will fail. The ''only way'' to access these other CCOs is through the context system, i.e., via UI components.  
If you attempt to use the cco function with a CCO that is not contained on the list above, it will fail. The ''only way'' to access these other CCOs is through the context system, i.e., via UI components or other CCOs which link to the one you want.


It is also notable that the ObjectID requirement for the cco function is not 100% accurate. You may have noticed that there are asterisks next to CcoCampaignSettlement above. This is because '''the listed ObjectID for CcoCampaignSettlement does not work with the cco function'''. CcoCampaignSettlement interfaces ''can'' be accessed directly, but you need to replace the ObjectID found in Context Viewer with the settlement's CQI on the campaign map. Why is this? I have no idea; it was probably a mistake. Are there others like it? Not that I've found in my personal testing, but I have not gone through them all, so maybe. In any case, just be aware that the cco function was manually programmed to expect a particular data format for a particular set of CCO types, and there is always a chance that it won't match up precisely. You will never need to worry about this when accessing CCOs through the context system.
It is also notable that the ObjectID requirement for the cco function is not 100% accurate. You may have noticed that there are asterisks next to CcoCampaignSettlement above. This is because '''the listed ObjectID for CcoCampaignSettlement does not work with the cco function'''. CcoCampaignSettlement interfaces ''can'' be accessed directly, but you need to replace the ObjectID found in Context Viewer with the settlement's CQI on the campaign map. Why is this? I have no idea; it was probably a mistake. Are there others like it? Not that I've found in my personal testing, but I have not gone through them all, so maybe. In any case, just be aware that the cco function was manually programmed to expect a particular data format for a particular set of CCO types, and there is always a chance that it won't match up precisely. You will never need to worry about this when accessing CCOs through the context system.
Line 173: Line 172:


== Part 4: Practical Use Cases for CCOs ==
== Part 4: Practical Use Cases for CCOs ==
'''SECTION NOT FINISHED, ONLY OUTLINE/NOTES BELOW'''
"That's all very interesting," I hear you pondering, "but how is this practically useful for me, the modder?" I'm glad you asked. In this section, I will go over just that: practical uses for CCOs in script mods.
* The above is neat and all, but where does this stuff really get useful?
 
* One of the primary use cases of CCOs is getting information from the game database “on the fly” within your scripts, which is not otherwise possible. The context system contains a ton of (although by no means all of the) DB information in the form of Cco___Record objects, which are kind of like sub-objects to the main CcoCampaign____ objectsAgain, you can find what is available here via Context Viewer, and it will generally follow logically from a UI element representing whatever it is you’re seeking. There is also a list of all available database table records on the left panel of Context Viewer (“context menu”), allowing for direct lookups.
=== Retrieving Game Database Information ===
** Example: Finding out a unit’s class through its unit record
 
* CCOs are also useful for getting campaign information that isn’t easily accessible – or even possibly accessible – using standard model hierarchy queries. Again, all of the possibilities will be contained within Context Viewer. One example that is currently used in CBFM is getting information regarding “initiatives,” which covers things like Ogre Big Names (character initiatives) or Chaos Gifts for Warriors of Chaos factions (faction initiatives). You can use this information to give additional bonuses to a WoC faction that has a certain Chaos Gift active, for example.
The CCO_____Record category of CCO includes a vast array of information replicated from game database (DB) entries, a concept with which you must already be familiar if you've made it far enough in Total War modding that you clicked on a page about CCOs. By no means is the entire DB replicated in Record CCOs, but almost all of the most useful stuff is, and the CCO interface provides a method (the only method!) for a modder to retrieve this information from within a script at runtime.
** Example: Getting a list of active Chaos Gifts for a WoC faction   
 
* Most CCO commands (not queries) accomplish things that could just as easily be done through episodic scripting, which makes them rather redundant to the modder. However, there are exceptions. Some examples: forcibly disbanding a unit (via CcoCampaignUnit), automatically making a dilemma choice (via CcoCampaignEventDilemma).
Within the context system, you will typically find these Record CCOs attached to their relevant campaign entity CCOs. For example, when examining a CcoCampaignUnit, you will find that one of its included queries is "UnitRecordContext"; this query will return the CcoMainUnitRecord for this unit. Within ''this'' CCO interface, you will find queries that provide information from the DB, such as the unit's DB key, its upkeep cost, or its tier level. While it is true that some of this information could be obtained via the [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/model_hierarchy.html model hierarchy], some of it (like tier level) cannot. As always, a full list of available queries can be found in Context Viewer.
** Example: Disbanding a unit
 
Because CcoMainUnitRecord is one of the CCOs listed above that is supported for direct access via script, you don't actually need to navigate through the context system to first find an applicable unit on the campaign map before accessing this record information. If you just need DB information for a particular unit type, you can use the cco function:
 
local unit_record_cco = cco("CcoMainUnitRecord","wh_main_emp_inf_swordsmen")
local unit_class = unit_record_cco:Call("ClassName")
 
The above code will return the unit class for bog-standard Empire Swordsmen, which in this case is "Melee Infantry." You may have noticed that the ObjectID that CcoMainUnitRecord accepts is a string -- not just any string, mind you, but an exact match for this unit's DB key in the <code>main_units</code> table. This is not an accident. '''For Record CCOs that support direct script access, the ObjectID typically matches the relevant key in the DB,''' allowing for quick and easy access to these CCOs without the need to look up the ObjectID in Context Viewer.
 
=== Retrieving Campaign Information ===
 
Campaign CCOs can be used as an alternative to the [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/model_hierarchy.html model hierarchy] for retrieving dynamic information about an active campaign, and in some cases, it's the easier option. One example of this that is currently used as part of [https://steamcommunity.com/sharedfiles/filedetails/?id=2856936614 Community Bugfix Mod] is getting information about "faction initiatives," which is the system that drives the "Gifts of Chaos" feature added with the Champions of Chaos DLC. This system extended the "character initiatives" system, which is what is used to track things like Ogre Big Names. Character initiatives, being older, have documented interfaces in the model hierarchy, but faction initiatives do not. Luckily, we can use CCOs instead:
 
-- CcoCampaignFaction has direct access support, and the ObjectID matches the faction key
local faction_cco = cco("CcoCampaignFaction","wh_main_chs_chaos")
local active_undivided_gifts = faction_cco:Call("InitiativeSetList.FirstContext(InitiativeSetContext.Key == 'wh3_dlc20_faction_initiative_set_chs_undivided').ActiveInitiatives.Size")
 
The above code starts by directly accessing the faction CCO for Archaon's faction (OG Chaos), which is simple enough. It then calls a [[Tutorial:Component Context Objects (CCOs)#Context_Expressions|context expression]] from this CCO interface, which is essentially a series of queries, the result of which is the number of active Undivided initiatives (Gifts of Chaos) that Archaon is currently rolling with. Don't worry if the context expression part of this seems confusing; I will go into [[Tutorial:Component Context Objects (CCOs)#Context_Expressions|greater detail]] on these in the last section of this guide.
 
=== Using CCO Commands for Campaign Scripting ===
 
Generally speaking, when you want to make changes to the game (as opposed to queries from the game) via script, the [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/episodic_scripting.html episodic scripting interface] is your best bet. It's straightforward and quite robust, certainly more robust than what is available through CCO commands. Yet there are a few situations where CCOs can do things that episodic scripting cannot, and when you're in such a situation, it's nice to have the added options.
 
[[File:Cco practical 1.png|thumb|Using CCOs to disband a unit, starting from a character]]
[[File:Cco practical 2.png|thumb|Forsaken has been forsaken!]]
 
One such instance is the scripted disbanding of a unit from an army. If you have the ObjectID of the unit you wish to disband, you can use the cco function to get its CcoCampaignUnit directly and simply call the "Disband" command from there. More often, you'll be starting from the lord leading the army, which has a CcoCampaignCharacter we can access. For this example, I will disband the first unit of an army. The only information I need to start out with is the CQI of the lord, which is something we can easily get through the model hierarchy.
 
Because CcoCampaignCharacter is one of the direct access-enabled CCOs, we can get it using the cco function. Conveniently, the ObjectID for a CcoCampaignCharacter matches that character's CQI, so once we have that, we can just plug it in:
 
local character_cco = cco("CcoCampaignCharacter",9436)
 
To get from this Character CCO to the unit CCO that we want (the first unit in this lord's army that isn't the lord itself), we need to go through a few steps:
# We need to get from our Character CCO to its attached CcoCampaignMilitaryForce, which is the CCO for the army. Context Viewer shows us that this can be accomplished using the "MilitaryForceContext" query. (Side note: CcoCampaignMilitaryForce is ''not'' on the list of direct access CCOs, so we could ''not'' have started from here using the cco function).
# From the Military Force CCO, we need to get a list of units in the army. Context Viewer shows us that this can be accomplished using the "UnitList" query.
# From the UnitList, we want to select the second (first non-lord) unit, which is represented by [1] because context expressions use [https://en.wikipedia.org/wiki/Zero-based_numbering zero-based indexing] (CCO lists will be discussed in [[Tutorial:Component Context Objects (CCOs)#CCO_Lists|more detail]] in the final section of this guide).
# Finally, having arrived at the Unit CCO (CcoCampaignUnit), we send the "Disband" CCO command, which immediately disbands the unit.
As a context expression, this is represented as such:
 
  character_cco:Call("MilitaryForceContext.UnitList[1].Disband")
 
<br>
Any command you find in Context Viewer can be executed in much the same way. Once you find an entry point into the context system, be that from a character CCO, a faction CCO, or back to basics with a UI component, you can nearly always find a way to string a series of queries together that gets you to the command (or final query) you want.
 
=== Applying CCOs to UI Components ===
 
I'm not really going to get into this in this guide, as this lies squarely within the realm of advanced [[UI:Main Page|UI modding]], but I will just mention that you can, if you wish, manually attach CCOs to UI Components. This can be done through the UIComponent interface, using the [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/uicomponent.html#function:uicomponent:SetContextObject SetContextObject method]. You would want to do this if you were setting up your own UI elements and wanted to tie them into the context system. If aren't already knowledgeable with TW_UI and Context Callbacks, however, I would advise caution: You can cause all kinds of weird behavior with this command.
 
== Part 5: CCO Shortcuts and Advanced Techniques ==
If you've made it this far into the guide, you should already have a pretty firm grasp on what CCOs are, how to access them, and how to use them both to get information from and send commands to the game. This section will provide you with some additional tools to make working with CCOs more efficient, and then finally we will go through some advanced examples so that you can leverage CCOs to their full potential.
 
=== Common Interface CCO shortcuts ===
 
The [https://chadvandy.github.io/tw_modding_resources/WH3/campaign/common.html common interface] provides a couple of helper functions to speed up the direct access of CCOs as described in Part 3. These are <code>common.get_context_value</code>, which is designed to return information from a CCO query, and <code>common.call_context_command</code>, which is designed to (you guessed it) call CCO commands. Their usage is quite simple:
 
return_value = common.get_context_value(TypeID,ObjectID,CallString) -- returns result of the CCO interface call to return_value
common.call_context_command(TypeID,ObjectID,CallString) -- makes CCO interface call, returns nothing
 
All these are, really, is a combination of the cco function we used previously followed automatically by the resultant CCO interface's Call method. It turns two lines of code into one line. Of these two functions, <code>common.get_context_value</code> is the only one you really need; both functions will call the specified query or command, but only get_context_value will return something when applicable. This can be a useful shortcut, and if you look through official scripts you will see that CA has used these functions quite a bit, but keep in mind that there is nothing special about them: Using these functions is fundamentally no different than using the cco function plus Call method described earlier in this guide; they come with the same advantages and limitations.
 
=== CCO Reference Documentation ===
 
As you may or may not be aware, CA includes some mostly auto-generated reference documentation for CCOs with WH3. I have been holding out on talking about this until now because there isn't much this documentation provides that can't be found by exploring Context Viewer, and by exploring Context Viewer first, I think you get a much better sense of how the various components of the context system link together. Context Viewer requires you to be in the game, though, and while it does include a search function, you can't search through it for raw text strings quite as effectively as you can with a giant HTML document.
 
You can find this documentation in your own game files using a .pack viewer (i.e., RPFM) under documentation > ui > documentation.html. I have also copied the latest version as of writing (from WH3 v. 3.1) below for your convenience. Just about all CCOs along with their respective queries and commands are listed here. It's a massive list, but you can take comfort in the knowledge that you'll never have much use for the vast majority of them.
 
[[File:CCO Documentation.zip]]
 
=== CCO Lists ===
 
Remember how I said there can never be more than one of a given CCO type attached to a given UI component? That's true, but other (non-UI, i.e., non-CcoComponent) CCOs often come with queries that return lists of CCOs that all share the same type which are related to the parent CCO in some way. For example, a CcoCampaignMilitaryForce comes with a UnitList query, and that query returns a list of CcoCampaignUnit objects for each individual unit within that military force. The form that this list takes is itself a CCO, a CcoContextList, and there are queries and commands common to this list format that will work for CCO lists regardless of the type of CCOs contained within (you can find these list queries and commands in the aforementioned documentation; just do a search for "CcoContextList").
 
In working with CCOs, you will find that these lists come up quite a bit, so learning how to navigate them is a good idea. Here are the basics:
* To select a particular element in the list, simply follow the name of the list with a number in brackets (just as you would do for a table with numeric keys in Lua, with the important caveat that indexing here is zero-based, not one-based). If you want to select the third unit in a unit list, for example, you would write <code>UnitList[2]</code>
* To get a count of the number of elements contained in the list, use the "Size" query: <code>UnitList.Size</code> will return a count of the number of items present in this CcoContextList.
 
You can accomplish just about anything involving lists using just those two options combined with regular Lua scripting. For example, let's say I want to find out the total number of individual entities (single models) in an army. Each CcoCampaignUnit object has a query "NumEntities" that returns this value for that unit, but we need to add this up for all of the units in the military force. Here's how we could do this, assuming our lord's CQI was 17, as an example:
 
local total_entities = 0 -- starting value
local lord_cco = cco("CcoCampaignCharacter",17) -- get the CCO interface for our lord
local num_units = lord_cco:Call("MilitaryForceContext.UnitList.Size") -- pulls the military force CCO from our lord, then queries that CcoCampaignMilitaryForce for its unit list and counts the number of units returned
for i = 0, (num_units - 1) do
    -- loop through UnitList, running the NumEntities query for each unit
    local unit_entities = lord_cco:Call("MilitaryForceContext.UnitList[" .. i .. "].NumEntities")
    -- add the number of entities for each unit to our total
    total_entities = total_entities + unit_entities
end
 
At the end of our example, our variable <code>total_entities</code> will contain the total number of individual entities in this force.
 
=== Context Expressions ===
 
At its simplest, the Call method of a CCO script interface is used to call a single query or command for that CCO. As we've already seen at various places throughout this guide, however, it ''can'' do more than that. What you are really providing as an argument for <code>my_cco:Call</code> (or as the third argument for <code>common.get_context_value</code>) is a '''context expression''', and while this can be a single query or command, it can also chain multiple queries together, some of which can take additional arguments themselves, weaving through the context system from CCO to CCO in order to get to the end point you desire. Once again, we can use Context Viewer as our guide here: Any series of query buttons you click through in Context Viewer can be converted into a context expression, a series of instructions for your script to execute which mirrors that series of clicks.
 
How does it work? It looks complicated at first, but there's a logic to it. You start with a normal CCO query, one which returns another CCO, and then you can select a query or command from ''that'' CCO, delineating these two commands with a period (mimicking "syntax sugar" patterns from Lua). As long as you keep adding queries which return other CCOs, you can continue to do this as many times as you need. Let's return to an [[Tutorial:Component Context Objects (CCOs)#Retrieving_Campaign_Information|earlier example]] for getting the number of active Undivided Gifts of Chaos for Archaon's faction, only this time, let's break down the context expression:
 
  faction_cco:Call("InitiativeSetList.FirstContext(InitiativeSetContext.Key == 'wh3_dlc20_faction_initiative_set_chs_undivided').ActiveInitiatives.Size")
 
# We're starting with the CcoCampaignFaction interface for Archaon's faction, which we have assigned to the "faction_cco" variable.
# From here, we call <code>InitiativeSetList</code>, which returns a CcoContextList containing all of the CcoCampaignInitiativeSet objects for this faction (refer to the subsection on CCO Lists, just above, for more information on these). The period after "InitiativeSetList" indicates that the next query (or command) will be for the CCO that this returns, which in this case is our list.
# Now at our list, we run the <code>FirstContext</code> query, which is a query common to all CCOs of type CcoContextList that takes a secondary context expression as an argument to test each item in the list. It returns the first CCO ("first context") that causes the secondary context expression (<code>InitiativeSetContext.Key == 'wh3_dlc20_faction_initiative_set_chs_undivided'</code> in this instance) to return true. Let's break this down further:
## The secondary context expression is run on each object in the list. In this case, all of these are CcoCampaignInitiativeSet objects.
## CcoCampaignInitiativeSet objects have a query <code>InitiativeSetContext</code>, which returns another CCO, a CcoInitiativeSet'''Record''' (as you recall, CCOs ending in "Record" indicate that they contain database information).
## We call the query <code>Key</code> from the CcoInitiativeSetRecord object, which returns the string key from the DB for this particular initiative set. We compare this key to the one we are looking for, "wh3_dlc20_faction_initiative_set_chs_undivided", and if it matches, the test is passed: <code>FirstContext</code> will return this CcoCampaignInitiativeSet object.
# Now that we've found the right CcoCampaignInitiativeSet, we can run its <code>ActiveInitiatives</code> query.
# This query returns ''another'' CcoContextList, this time one that contains CcoCampaignInitiative objects (these represent the individual Gifts of Chaos from the Undivided set)
# Since we only care about the number of active initiatives in this example, we simply run the <code>Size</code> query on this list, which finally returns what we were after, the number of active Undivided initiatives.
 
This was a bit of a complex example, but I hope it demonstrates just how robust context expression can be (if you want). If you want to practice with context expressions, I have good news: Context Viewer provides a space for just that. The text entry field at the bottom, which was referred to as the "test field" earlier in this document, is built to interpret context expressions and will immediately show you the result (or an error if you get something wrong). Keep in mind that the starting point for these context expressions is whatever page you are currently on in Context Viewer. To test the above example using Context Viewer, for example, you would want to start on the CcoCampaignFaction page for Warhost of the Apocalypse.
 
== Closing Thoughts ==


== Part 5: CCO Shortcuts and Tricks ==
CCOs seem quite strange and esoteric at first glance, but I've found that the more I work with them, the more logical and elegant they seem. You certainly won't need to use them all the time, but they are a good tool to add to your scripting toolbox. This guide is by no means an exhaustive account of everything you can do with them, but my hope is that it will provide a solid foundation on the topic for anyone who has been interested. If you have any questions, feel free to reach out to me in Da Modding Den. - Dux
'''SECTION NOT FINISHED, ONLY OUTLINE/NOTES BELOW'''
* Using common context commands to quickly get the information (or send the command) you want.
** You will note that ObjectIDs for many of the common database record CCOs tend to match their DB key entries (e.g., all of the ObjectIDs for CcoMainUnitRecord objects match their corresponding unit key from the DB), which makes looking them up directly particularly easy.
** ObjectIDs are not 100% reliable! The ObjectID does not work for direct access of CcoCampaignSettlement CCOs, for example. The cco function requires a CQI in this case instead, which was probably a programming error, but it is what it is.
** common.get_context_value is really the only function you need; common.call_context_command is exactly the same except it never returns anything
** These commands are no different than running the cco function followed by the Call method; this is just a shortcut.
* Using the CCO documentation that was included with your game so that you don’t need to hunt through Context Viewer all the time.
** I saved this for last because the information contained in this documentation is immense and risks being overwhelming if not understood within the context of the context system.
** Two important sections:
*** CcoContextList
*** Global functions, which can be used anywhere and even called via common commands without arguments.
**** Example: RootComponent
* Interacting with Cco lists via script (indexing, etc.)
** Advanced example: Determining how many units of a certain tier are contained within a particular army

Latest revision as of 11:29, 12 July 2023

Welcome to the wonderful world of modding with CCOs. This guide seeks to consolidate and expand upon information that has already been provided by community heavyweights Groove Wizard (in the UI section of this wiki) and DrunkFlamingo (in the Eldritch Knowledge channel on Da Modding Den Discord), dropping everything we know about these mysterious interfaces in one place. This guide will start with foundational topics like "what are these things?" and "why should I care?," moving through the basics and onto some semi-advanced usage examples.

It should be noted that while CCOs were evidently developed by CA primarily as a tool for building user interfaces, this guide will focus more on their applications outside of that primary role, in particular how they can be a useful asset for script-based mods.

Prerequisites

Before going any further with this guide, you really need to have a basic working knowledge of Lua scripting in Total War. If you're completely new to the world of scripting, I highly recommend beginning with Groove Wizard's excellent Lua tutorial series first. Once you're confident in the basics, come on back.

Although it isn't absolutely necessary, strictly speaking, I also highly recommend getting yourself a Lua console to try out the examples in this guide. This will allow you to execute Lua code while the game is running, which is much, much more efficient for testing purposes than continuously changing lines in an external script and restarting the game each time. Conveniently enough, Groove Wizard has also made one of these for us (where would we be without him?), and this is the console that I will be using in my examples.

Part 1: What is a CCO?

What, you don't know? It's in the title, silly. CCOs are "Component Context Objects." There. Section done.

Just kidding. I also had no idea what that was supposed to mean when I started working with these things. So let's unpack it. First, "Component" in this case refers specifically to a UI component. If you have ever delved into the world of UI scripting, you may already be familiar with this concept. If not, just think of everything you can see in the Total War user interface -- the minimap in the corner, the unit panel, the building browser, etc. -- as a component. Many (although not all) of these UI components are part of the "context system," which provides objects (context objects!) containing game information that is meant to be displayed (or at least contained) in some way within the UI component. In the words of CA, from their documentation on the Context Viewer tool (which we will be using frequently later in the guide):

The TW UI (user interface) framework is primarily driven by what we call the ‘context system’.  We expose game objects that we want to represent in the UI as contexts (which we refer to cco for short, you will notice the cco prefix at the start of all context type names).
So a context is basically a type of object that exists in the game.  Some simple examples:
- A battle unit is exposed as a CcoBattleUnit
- A campaign character is exposed as a CcoCampaignCharacter
- A campaign settlement is exposed as a CcoCampaignSettlement
- A special ability is exposed as a CcoUnitAbility
Each context has an interface that we can call both queries on (to get information) as well as commands on (to change game state). We combine these with different behaviours in our UI system to basically build the UI.

Essentially, you can think of CCOs as potentially handy interface points between the UI and the game. They provide a way to extract game info from a UI element or send commands to the game via a UI element. Because the UI state is always changing (when opening new panels, etc.), the interface points that are available are variable (you might even say they are contextual in nature). That said, you can also access many (though certainly not all) CCOs directly, regardless of UI state, and this is generally how CCOs can really shine in script mods -- but more on that later.

CCO Categories

Although there is nothing fundamental that differentiates one CCO from another -- they can all be used in essentially the same way -- CA has seen fit to label some CCOs in a manner that groups them into useful categories to indicate for what purpose they should be used. The important categories are as follows:

  • CcoCampaign_____: This group is for CCOs dealing with entities on the campaign map, e.g., armies, factions, settlements, agents, units, etc.
  • Cco_____Record: This group is for CCOs dealing with database information for various parts of the game. That's right, using CCOs, you can retrieve (some) database information via script.
  • CcoBattle_____: This group is for CCOs dealing with entities on the battle map, e.g., units, capture points, buildings, alliances, etc.
  • CcoComponent: This is not a group but one particular type of CCO, the one that corresponds directly to UI components. You can think of CcoComponent objects as the root objects for the entire context system; they're rather important.

Everything that isn't categorized into one of the above can be considered miscellaneous. They have various functions, but you won't necessarily get any hints in the name. Luckily, 90%+ of what you'll want to use CCOs for will probably come from the first two categories.

Why Use CCOs?

So CCOs are objects embedded into UI components, but you're not interested in UI modding, so why should you care? In short, despite being designed as part of the UI system, CCOs have scripting uses outside of the UI. There is game information exposed through CCOs that is not available through the model hierarchy, and there are some script actions you can take via CCO that don't have an equivalent within the episodic scripting interface. You may also find that there comes a time in your modding journey where you need to pull game information from the current UI state, and CCOs are practically purpose-built for this task (example: getting a character CQI from an event feed message). For more specific use cases, you can skip ahead to Part 4 of this guide, but then please, do come back. We're just getting started.

Part 2: CCOs in Context Viewer

Context Viewer is a wondrous tool bequeathed to us by CA that is essential for working with CCOs. If you aren't already familiar with it, stop here and take a moment to read the linked guide on this Wiki, which goes over how to turn it on and get it running.

Got Context Viewer up and running in your game? Great. Let's dive into some CCOs. Context Viewer's most useful feature is that it shows you exactly which CCOs are attached to which UI elements (themselves represented as CCOs here, a specific subtype of CCO called CcoComponent), and it will then show you which options you can run from these CCOs. All CCO options do one of two things: Either they extract information from the game and return it, or they send some instruction to the game, altering its state. These are referred to as Queries and Commands in Context Viewer, respectively.

Using Context Viewer, you can mess around with all of this. Queries will automatically show you what they would return (unless additional arguments are required, in which case you would need to provide them in the context expression test field at the bottom), and commands can be clicked to induce the effect immediately (again, unless additional arguments are required). This is great for rapid experimentation.

Navigating to the settlement_list UI component

As an example, open any campaign, then open Context Viewer, then select any settlement anywhere on the map. I will be using my current Nurgle campaign, but any settlement in any campaign will work just as well.

The screenshot to the right shows how to use Context Viewer to inspect a particular UI element for possible CCO options contained within it. In this case, that element is the settlement_list component, which is a child component of settlement_panel, the main component for all of the settlement info UI elements.

With the settlement_list component selected in the component tree, you will see that the main panel now shows all of the options that are contained within this component. There are various queries and commands that can be called on the UI component itself (CcoComponent), and one such query -- the one we really care about for this example -- is "ContextsList." This lists all of the other contexts (i.e., other CCOs) attached to this component. You will see that there are two: One CcoCampaignProvince, which is a CCO containing queries and commands for the whole province, and one CcoCampaignSettlement, which is a CCO containing queries and commands for the currently selected settlement. Clicking on either option in ContextsList will open the query and command options for that CCO, which you are encouraged to explore.

It should be pointed out that there can never be more than one CCO of a given type attached to a given component at any one time. In this example, the CcoCampaignSettlement attached to the settlement_list UI component is for Dragon Fang Mount, but this result is contextual; it depends on the settlement that is currently selected. Click on a different settlement (or use a different UI component when we get to script), and this result will change.

The important point about all this is that all of the options we just explored, as well as anything else you find in Context Viewer, can be made available to you in script, as we will see in the next section. Think of Context Viewer as your guide for what is possible with CCOs in scripting.

Part 3: Utilizing CCOs Within Scripts

Of course, you’re not here just to play around with Context Viewer. Obviously, for any of this to be useful, you need to be able to utilize it within your mod scripts. There are a few ways of doing this, the most reliable of which is through UIComponent script objects. Using this method, you will be able to access any CCO you can find through Context Viewer.

Accessing CCOs via UIComponent Script Objects

General UI scripting is not the focus of this guide (you can find more on that here), but for our purposes here there are a few things you need to know:

  1. A "UIComponent script object" is another script interface designed specifically for script-based manipulation of UI elements themselves. It is separate from but related to CCOs in that both interfaces deal with the UI.
  2. The simplest way to access any UIComponent is through the find_uicomponent function, which allows you to input a search path through the UI hierarchy (identical to the component tree seen in Context Viewer, which you should use as your guide) and get back the UIComponent (or "UIC" for short) for the UI element that you want.
  3. Once you have your UIC, you can run its GetContextObject method to get the particular CCO you want to work with.

For example, let's return to the settlement_list component we were examining in Context Viewer in the previous section, but this time let's access those options via script. First, we will need to get the UIC for settlement_list:

local uic = find_uicomponent("settlement_list")

As you recall from our example with Context Viewer, this component has two other CCOs attached to it, one CcoCampaignSettlement for the currently selected settlement, and one CcoCampaignProvince for the province in which this settlement exists. Because there is only ever one of a given type of CCO connected to a component at any one time, we can retrieve the settlement CCO script interface simply:

local settlement_cco = uic:GetContextObject("CcoCampaignSettlement")
Testing CCO scripting with Lua console
Result of our test
Same code, new context, different result

Now we have our settlement CCO retrieved as a CCO script interface, which has been assigned to the local variable "settlement_cco." You may wonder why we didn't simply use "cco" since this is a local variable; the answer is that CA has already assigned "cco" as an important global function that we will touch on later in this section, so don't use "cco" as your variable name for CCO script interfaces.

All CCO script interfaces contain the method "Call", which allows you to run any query or command that you saw in your experimentation with Context Viewer previously. For example, let's run both a query and a command using our Lua console so that we can test it "live."

The code we will use is as follows:

local uic = find_uicomponent("settlement_list")
local settlement_cco = uic:GetContextObject("CcoCampaignSettlement")
local name = settlement_cco:Call("Name")
settlement_cco:Call("ZoomTo")

This will first find our UIC. Then, it will get the CcoCampaignSettlement CCO attached to it and save it as a CCO script interface. Finally, through our CCO interface, this code calls the query "Name" and the command "ZoomTo," both of which were shown as available options for this CCO in Context Viewer. The effect of "ZoomTo" can be seen immediately, but in order to use the string that is returned from the "Name" query, we will need to save it to a variable and then print it, which for this particular console can be accomplished using console_print(name). The screenshots to the right demonstrate the whole process and result.

Remember what the second "C" in CCO stands for? That's right: Context. To get a sense of why the context system can be so useful with UI systems, let's try a quick follow-up test. Without changing a single line of code in our Lua console, we're going to click on a different settlement and then run it again.

Notice what happened? The code didn't change, but the context did, so the result is now completely different. This is one of the ways in which CCOs can be especially useful, particularly if you do intend to work on UI scripting.

Directly Accessing CCOs Within Scripts

If you were to run a third test using the setup above, one where instead of clicking a new settlement, you selected no settlements, your new result would be a script error, and this illustrates the major drawback of using this method for accessing CCOs in scripts: It requires the UI component from which we are going to get our desired CCO to already exist. In our example, we are using the "settlement_list" component, but this component is only created when the player has selected a settlement. Sure, you could create a UI listener that is triggered by the player clicking on a settlement (running your CCO code only after that happens), and that may be what you want in some circumstances, but often you will find that you want access to the queries and commands contained within a certain CCO without waiting for connected UI elements to pop into existence first. You want a context-less CCO, a C-O, if you will. Thankfully, this, too, is possible -- at least for many of the most commonly useful CCOs.

The cco Function

Remember when I said that "cco" should not be used as a variable name for your CCO script interfaces? This subsection will explain why. As it turns out, cco(TypeID,ObjectID) is a global function that can be called from anywhere within Total War script. It has two arguments: "TypeID," which is your CCO type (e.g., CcoCampaignUnit), and "ObjectID," which is the specific identifier for that particular CCO (either a number or a string, depending on the CCO type). We never needed an ObjectID in our previous examples because we could rely on UI context; providing the CCO type for that UI component was enough information, but now that we're ignoring the UI, we need additional identity information. But where can we find this information? In Context Viewer, of course! Where else?

TypeID and ObjectID for any CCO provided by Context Viewer
Directly accessing a unit CCO to get its rank level

As you may have already noticed, Context Viewer provides the TypeID and ObjectID of any CCO that you mouse over in its main window. Using this information in conjunction with the cco function, we can directly retrieve a CCO script interface without needing to go through any UI components first. As an example, let's directly access the CcoCampaignUnit interface for this unit of Forsaken in the screenshot to the left. Here's how:

local unit_cco = cco("CcoCampaignUnit",32821)

That's it! Just as before, you can now use the "Call" method on this CCO script interface to run any query or command available for that CCO. The difference now is that because we accessed this CCO directly, the current state of the UI doesn't make a difference; we can use this combination of TypeId and ObjectID to access this CCO from anywhere at any time.

As a quick example, let's use our Lua console to directly access the CcoCampaignUnit CCO for this unit of Forsaken and send a query to get its current rank (the query for this is "ExperienceLevel," as shown in Context Viewer). Now that we don't need to worry about UICs, the code is dead simple:

local unit_cco = cco("CcoCampaignUnit",32821)
local unit_rank = unit_cco:Call("ExperienceLevel")

console_print(unit_rank)

Remember, this is just an example. You can use any of the queries or commands that you find in Context Viewer. There are some limitations, however.

Direct Access Limitations

At this point, you may be wondering, "Why even bother with the UIComponent method? This cco function is so much more convenient!" Well, there are two reasons. First and foremost, the CCO function does not work for all CCOs. In fact, it only works for about 45 of them, although these include some of the most useful. Below is the full list of CCOs that have direct script access support (AFAIK):

  • CcoCampaignCharacter
  • CcoCampaignFaction
  • CcoCampaignRoot
  • CcoCampaignSettlement**
  • CcoCampaignTechnology
  • CcoCampaignUnit
  • CcoAncillaryRecord
  • CcoArmoryItemSetRecord
  • CcoBattlePurchasableGroupRecord
  • CcoBattlePurchasableItemsJunctionRecord
  • CcoBattlePurchasableItemsUiCategoryRecord
  • CcoBattleRecord
  • CcoBuildingLevelRecord
  • CcoCampaignGroupRecord
  • CcoCampaignMapPlayableAreaRecord
  • CcoCampaignPayloadRecord
  • CcoCampaignPayloadResourceTransactionRecord
  • CcoCampaignPayloadUnitComponentRecord
  • CcoCultureRecord
  • CcoFactionRecord
  • CcoMainUnitRecord
  • CcoMultiplayerCampaignSettingRecord
  • CcoOwnershipProductRecord
  • CcoRitualChainRecord
  • CcoRitualPayloadRecord
  • CcoSubcultureRecord
  • CcoTimeOfLegendsCampaignRecord
  • CcoUiMainThemeOptionRecord
  • CcoUnitPortholeCameraSettingRecord
  • CcoUnitSetToMpUnitCapRecord
  • CcoUnitsCustomBattleTypeCategoryRecord
  • CcoUnitsCustomBattleTypeRecord
  • CcoEffectBundle
  • CcoScriptObject
  • CcoCustomBattleMap
  • CcoFrontendRoot
  • CcoCampaignBasePlague
  • CcoCampaignPlagueIngredient
  • CcoCampaignBuildingSlot
  • CcoCampaignFactionInteraction
  • CcoCampaignPendingActionNotificationQueue
  • CcoComponent
  • CcoBattleRoot
  • CcoBattleSelection
  • CcoBattleUnit

If you attempt to use the cco function with a CCO that is not contained on the list above, it will fail. The only way to access these other CCOs is through the context system, i.e., via UI components or other CCOs which link to the one you want.

It is also notable that the ObjectID requirement for the cco function is not 100% accurate. You may have noticed that there are asterisks next to CcoCampaignSettlement above. This is because the listed ObjectID for CcoCampaignSettlement does not work with the cco function. CcoCampaignSettlement interfaces can be accessed directly, but you need to replace the ObjectID found in Context Viewer with the settlement's CQI on the campaign map. Why is this? I have no idea; it was probably a mistake. Are there others like it? Not that I've found in my personal testing, but I have not gone through them all, so maybe. In any case, just be aware that the cco function was manually programmed to expect a particular data format for a particular set of CCO types, and there is always a chance that it won't match up precisely. You will never need to worry about this when accessing CCOs through the context system.

The second limitation to direct access of CCOs is that by accessing them directly, you are setting aside the contextual aspect of their design that can make them so useful in the UI. This isn't a problem if you just want information from a unit record, for example, but if you are intending to use CCOs in the UI scripting realm, you will find that the contextual relationship with UI components is one of their best features.

Part 4: Practical Use Cases for CCOs

"That's all very interesting," I hear you pondering, "but how is this practically useful for me, the modder?" I'm glad you asked. In this section, I will go over just that: practical uses for CCOs in script mods.

Retrieving Game Database Information

The CCO_____Record category of CCO includes a vast array of information replicated from game database (DB) entries, a concept with which you must already be familiar if you've made it far enough in Total War modding that you clicked on a page about CCOs. By no means is the entire DB replicated in Record CCOs, but almost all of the most useful stuff is, and the CCO interface provides a method (the only method!) for a modder to retrieve this information from within a script at runtime.

Within the context system, you will typically find these Record CCOs attached to their relevant campaign entity CCOs. For example, when examining a CcoCampaignUnit, you will find that one of its included queries is "UnitRecordContext"; this query will return the CcoMainUnitRecord for this unit. Within this CCO interface, you will find queries that provide information from the DB, such as the unit's DB key, its upkeep cost, or its tier level. While it is true that some of this information could be obtained via the model hierarchy, some of it (like tier level) cannot. As always, a full list of available queries can be found in Context Viewer.

Because CcoMainUnitRecord is one of the CCOs listed above that is supported for direct access via script, you don't actually need to navigate through the context system to first find an applicable unit on the campaign map before accessing this record information. If you just need DB information for a particular unit type, you can use the cco function:

local unit_record_cco = cco("CcoMainUnitRecord","wh_main_emp_inf_swordsmen")
local unit_class = unit_record_cco:Call("ClassName")

The above code will return the unit class for bog-standard Empire Swordsmen, which in this case is "Melee Infantry." You may have noticed that the ObjectID that CcoMainUnitRecord accepts is a string -- not just any string, mind you, but an exact match for this unit's DB key in the main_units table. This is not an accident. For Record CCOs that support direct script access, the ObjectID typically matches the relevant key in the DB, allowing for quick and easy access to these CCOs without the need to look up the ObjectID in Context Viewer.

Retrieving Campaign Information

Campaign CCOs can be used as an alternative to the model hierarchy for retrieving dynamic information about an active campaign, and in some cases, it's the easier option. One example of this that is currently used as part of Community Bugfix Mod is getting information about "faction initiatives," which is the system that drives the "Gifts of Chaos" feature added with the Champions of Chaos DLC. This system extended the "character initiatives" system, which is what is used to track things like Ogre Big Names. Character initiatives, being older, have documented interfaces in the model hierarchy, but faction initiatives do not. Luckily, we can use CCOs instead:

-- CcoCampaignFaction has direct access support, and the ObjectID matches the faction key
local faction_cco = cco("CcoCampaignFaction","wh_main_chs_chaos")

local active_undivided_gifts = faction_cco:Call("InitiativeSetList.FirstContext(InitiativeSetContext.Key == 'wh3_dlc20_faction_initiative_set_chs_undivided').ActiveInitiatives.Size")

The above code starts by directly accessing the faction CCO for Archaon's faction (OG Chaos), which is simple enough. It then calls a context expression from this CCO interface, which is essentially a series of queries, the result of which is the number of active Undivided initiatives (Gifts of Chaos) that Archaon is currently rolling with. Don't worry if the context expression part of this seems confusing; I will go into greater detail on these in the last section of this guide.

Using CCO Commands for Campaign Scripting

Generally speaking, when you want to make changes to the game (as opposed to queries from the game) via script, the episodic scripting interface is your best bet. It's straightforward and quite robust, certainly more robust than what is available through CCO commands. Yet there are a few situations where CCOs can do things that episodic scripting cannot, and when you're in such a situation, it's nice to have the added options.

Using CCOs to disband a unit, starting from a character
Forsaken has been forsaken!

One such instance is the scripted disbanding of a unit from an army. If you have the ObjectID of the unit you wish to disband, you can use the cco function to get its CcoCampaignUnit directly and simply call the "Disband" command from there. More often, you'll be starting from the lord leading the army, which has a CcoCampaignCharacter we can access. For this example, I will disband the first unit of an army. The only information I need to start out with is the CQI of the lord, which is something we can easily get through the model hierarchy.

Because CcoCampaignCharacter is one of the direct access-enabled CCOs, we can get it using the cco function. Conveniently, the ObjectID for a CcoCampaignCharacter matches that character's CQI, so once we have that, we can just plug it in:

local character_cco = cco("CcoCampaignCharacter",9436)

To get from this Character CCO to the unit CCO that we want (the first unit in this lord's army that isn't the lord itself), we need to go through a few steps:

  1. We need to get from our Character CCO to its attached CcoCampaignMilitaryForce, which is the CCO for the army. Context Viewer shows us that this can be accomplished using the "MilitaryForceContext" query. (Side note: CcoCampaignMilitaryForce is not on the list of direct access CCOs, so we could not have started from here using the cco function).
  2. From the Military Force CCO, we need to get a list of units in the army. Context Viewer shows us that this can be accomplished using the "UnitList" query.
  3. From the UnitList, we want to select the second (first non-lord) unit, which is represented by [1] because context expressions use zero-based indexing (CCO lists will be discussed in more detail in the final section of this guide).
  4. Finally, having arrived at the Unit CCO (CcoCampaignUnit), we send the "Disband" CCO command, which immediately disbands the unit.

As a context expression, this is represented as such:

character_cco:Call("MilitaryForceContext.UnitList[1].Disband")


Any command you find in Context Viewer can be executed in much the same way. Once you find an entry point into the context system, be that from a character CCO, a faction CCO, or back to basics with a UI component, you can nearly always find a way to string a series of queries together that gets you to the command (or final query) you want.

Applying CCOs to UI Components

I'm not really going to get into this in this guide, as this lies squarely within the realm of advanced UI modding, but I will just mention that you can, if you wish, manually attach CCOs to UI Components. This can be done through the UIComponent interface, using the SetContextObject method. You would want to do this if you were setting up your own UI elements and wanted to tie them into the context system. If aren't already knowledgeable with TW_UI and Context Callbacks, however, I would advise caution: You can cause all kinds of weird behavior with this command.

Part 5: CCO Shortcuts and Advanced Techniques

If you've made it this far into the guide, you should already have a pretty firm grasp on what CCOs are, how to access them, and how to use them both to get information from and send commands to the game. This section will provide you with some additional tools to make working with CCOs more efficient, and then finally we will go through some advanced examples so that you can leverage CCOs to their full potential.

Common Interface CCO shortcuts

The common interface provides a couple of helper functions to speed up the direct access of CCOs as described in Part 3. These are common.get_context_value, which is designed to return information from a CCO query, and common.call_context_command, which is designed to (you guessed it) call CCO commands. Their usage is quite simple:

return_value = common.get_context_value(TypeID,ObjectID,CallString) -- returns result of the CCO interface call to return_value
common.call_context_command(TypeID,ObjectID,CallString) -- makes CCO interface call, returns nothing

All these are, really, is a combination of the cco function we used previously followed automatically by the resultant CCO interface's Call method. It turns two lines of code into one line. Of these two functions, common.get_context_value is the only one you really need; both functions will call the specified query or command, but only get_context_value will return something when applicable. This can be a useful shortcut, and if you look through official scripts you will see that CA has used these functions quite a bit, but keep in mind that there is nothing special about them: Using these functions is fundamentally no different than using the cco function plus Call method described earlier in this guide; they come with the same advantages and limitations.

CCO Reference Documentation

As you may or may not be aware, CA includes some mostly auto-generated reference documentation for CCOs with WH3. I have been holding out on talking about this until now because there isn't much this documentation provides that can't be found by exploring Context Viewer, and by exploring Context Viewer first, I think you get a much better sense of how the various components of the context system link together. Context Viewer requires you to be in the game, though, and while it does include a search function, you can't search through it for raw text strings quite as effectively as you can with a giant HTML document.

You can find this documentation in your own game files using a .pack viewer (i.e., RPFM) under documentation > ui > documentation.html. I have also copied the latest version as of writing (from WH3 v. 3.1) below for your convenience. Just about all CCOs along with their respective queries and commands are listed here. It's a massive list, but you can take comfort in the knowledge that you'll never have much use for the vast majority of them.

File:CCO Documentation.zip

CCO Lists

Remember how I said there can never be more than one of a given CCO type attached to a given UI component? That's true, but other (non-UI, i.e., non-CcoComponent) CCOs often come with queries that return lists of CCOs that all share the same type which are related to the parent CCO in some way. For example, a CcoCampaignMilitaryForce comes with a UnitList query, and that query returns a list of CcoCampaignUnit objects for each individual unit within that military force. The form that this list takes is itself a CCO, a CcoContextList, and there are queries and commands common to this list format that will work for CCO lists regardless of the type of CCOs contained within (you can find these list queries and commands in the aforementioned documentation; just do a search for "CcoContextList").

In working with CCOs, you will find that these lists come up quite a bit, so learning how to navigate them is a good idea. Here are the basics:

  • To select a particular element in the list, simply follow the name of the list with a number in brackets (just as you would do for a table with numeric keys in Lua, with the important caveat that indexing here is zero-based, not one-based). If you want to select the third unit in a unit list, for example, you would write UnitList[2]
  • To get a count of the number of elements contained in the list, use the "Size" query: UnitList.Size will return a count of the number of items present in this CcoContextList.

You can accomplish just about anything involving lists using just those two options combined with regular Lua scripting. For example, let's say I want to find out the total number of individual entities (single models) in an army. Each CcoCampaignUnit object has a query "NumEntities" that returns this value for that unit, but we need to add this up for all of the units in the military force. Here's how we could do this, assuming our lord's CQI was 17, as an example:

local total_entities = 0 -- starting value
local lord_cco = cco("CcoCampaignCharacter",17) -- get the CCO interface for our lord
local num_units = lord_cco:Call("MilitaryForceContext.UnitList.Size") -- pulls the military force CCO from our lord, then queries that CcoCampaignMilitaryForce for its unit list and counts the number of units returned

for i = 0, (num_units - 1) do
   -- loop through UnitList, running the NumEntities query for each unit
   local unit_entities = lord_cco:Call("MilitaryForceContext.UnitList[" .. i .. "].NumEntities")

   -- add the number of entities for each unit to our total
   total_entities = total_entities + unit_entities 
end

At the end of our example, our variable total_entities will contain the total number of individual entities in this force.

Context Expressions

At its simplest, the Call method of a CCO script interface is used to call a single query or command for that CCO. As we've already seen at various places throughout this guide, however, it can do more than that. What you are really providing as an argument for my_cco:Call (or as the third argument for common.get_context_value) is a context expression, and while this can be a single query or command, it can also chain multiple queries together, some of which can take additional arguments themselves, weaving through the context system from CCO to CCO in order to get to the end point you desire. Once again, we can use Context Viewer as our guide here: Any series of query buttons you click through in Context Viewer can be converted into a context expression, a series of instructions for your script to execute which mirrors that series of clicks.

How does it work? It looks complicated at first, but there's a logic to it. You start with a normal CCO query, one which returns another CCO, and then you can select a query or command from that CCO, delineating these two commands with a period (mimicking "syntax sugar" patterns from Lua). As long as you keep adding queries which return other CCOs, you can continue to do this as many times as you need. Let's return to an earlier example for getting the number of active Undivided Gifts of Chaos for Archaon's faction, only this time, let's break down the context expression:

faction_cco:Call("InitiativeSetList.FirstContext(InitiativeSetContext.Key == 'wh3_dlc20_faction_initiative_set_chs_undivided').ActiveInitiatives.Size")
  1. We're starting with the CcoCampaignFaction interface for Archaon's faction, which we have assigned to the "faction_cco" variable.
  2. From here, we call InitiativeSetList, which returns a CcoContextList containing all of the CcoCampaignInitiativeSet objects for this faction (refer to the subsection on CCO Lists, just above, for more information on these). The period after "InitiativeSetList" indicates that the next query (or command) will be for the CCO that this returns, which in this case is our list.
  3. Now at our list, we run the FirstContext query, which is a query common to all CCOs of type CcoContextList that takes a secondary context expression as an argument to test each item in the list. It returns the first CCO ("first context") that causes the secondary context expression (InitiativeSetContext.Key == 'wh3_dlc20_faction_initiative_set_chs_undivided' in this instance) to return true. Let's break this down further:
    1. The secondary context expression is run on each object in the list. In this case, all of these are CcoCampaignInitiativeSet objects.
    2. CcoCampaignInitiativeSet objects have a query InitiativeSetContext, which returns another CCO, a CcoInitiativeSetRecord (as you recall, CCOs ending in "Record" indicate that they contain database information).
    3. We call the query Key from the CcoInitiativeSetRecord object, which returns the string key from the DB for this particular initiative set. We compare this key to the one we are looking for, "wh3_dlc20_faction_initiative_set_chs_undivided", and if it matches, the test is passed: FirstContext will return this CcoCampaignInitiativeSet object.
  4. Now that we've found the right CcoCampaignInitiativeSet, we can run its ActiveInitiatives query.
  5. This query returns another CcoContextList, this time one that contains CcoCampaignInitiative objects (these represent the individual Gifts of Chaos from the Undivided set)
  6. Since we only care about the number of active initiatives in this example, we simply run the Size query on this list, which finally returns what we were after, the number of active Undivided initiatives.

This was a bit of a complex example, but I hope it demonstrates just how robust context expression can be (if you want). If you want to practice with context expressions, I have good news: Context Viewer provides a space for just that. The text entry field at the bottom, which was referred to as the "test field" earlier in this document, is built to interpret context expressions and will immediately show you the result (or an error if you get something wrong). Keep in mind that the starting point for these context expressions is whatever page you are currently on in Context Viewer. To test the above example using Context Viewer, for example, you would want to start on the CcoCampaignFaction page for Warhost of the Apocalypse.

Closing Thoughts

CCOs seem quite strange and esoteric at first glance, but I've found that the more I work with them, the more logical and elegant they seem. You certainly won't need to use them all the time, but they are a good tool to add to your scripting toolbox. This guide is by no means an exhaustive account of everything you can do with them, but my hope is that it will provide a solid foundation on the topic for anyone who has been interested. If you have any questions, feel free to reach out to me in Da Modding Den. - Dux