Tutorial:Component Context Objects (CCOs)

From Total War Modding
Revision as of 22:14, 8 July 2023 by Dux (talk | contribs)

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.

Why Use CCOs?

So CCOs are objects embedded into UI components, but I'm not interested in UI modding, so why should I 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-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.

Part 2: CCOs in Context Viewer

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 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 commands you can run from these CCOs. All CCO commands do one of two things: Either they extract information from the game and return it (this is what most of them do), 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 these options. 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 in the screenshots below, but any settlement in any campaign will work just as well.

The screenshot on 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. 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, and this result will change. Clicking on either option in ContextsList will open the query and command options for that CCO.

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.

Part 3: Utilizing CCOs Within Scripts

SECTION NOT FINISHED, ONLY OUTLINE/NOTES BELOW

  • 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.
    • General UI scripting is not the focus of this guide (you can find more on that here), but its basic concepts and a couple of specific commands are required here.
    • 1. Use find_uicomponent to get a UIComponent Lua object for the UI element you want (reference Context Viewer to find it)
    • 2. Once you have your UIComponent object, run the GetContextObject method to get the required CCO interface.
    • 3. Every CCO interface has a “Call” method which you can then use to run queries or commands applicable to that specific CCO.
    • Example: Get info about a settlement and zoom to it. Then click another one, change nothing, and see the magic of context.

Directly accessing CCOs within scripts

  • The obvious limitation of accessing CCOs through UIComponent objects is that you need to create them from a live UI element, which means that UI element needs to currently exist for any of this to work. Let’s say you want information about a unit in a particular force, but that force isn’t currently selected by the player. We can’t create a UIComponent object for the units panel in this case because the units panel only exists when an army is selected. Sure, you could create a listener that waits until the unit panel for the army you want is opened and then proceed from there, but that’s rather cumbersome if you just want some info from CcoMainUnitRecord, for example. Thankfully, you can skip all of the UIComponent stuff and directly access some CCOs from anywhere in script. This will not work for all CCOs, but it does work for many of the most useful, allowing you to circumvent the “context” part of these CCOs altogether.
  • The CCO function
    • Once you find something you want to use in Context Viewer, all you need to use it in a script is its CCO type (TypeID) and unique ID (ObjectID), both of which Context Viewer provides. You can then use the cco function, which can be called from anywhere in Total War script, to get this CCO as a Lua interface which you can then utilize in script. The cco function is very simple: It takes the TypeID and ObjectID as arguments, and then it returns a CCO Lua interface object.
    • Just as we did before, we can now use the Call method within our CCO interface to run queries or commands. These can even be chained together.
    • Example: Check a unit’s current rank via CcoCampaignUnit

Part 4: Practical Use Cases for CCOs

SECTION NOT FINISHED, ONLY OUTLINE/NOTES BELOW

  • 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____ objects. Again, 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.
    • 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.
    • 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).
    • Example: Disbanding a unit

Part 5: CCO Shortcuts and Tricks

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