AI War 2:Code Hooks

From Arcen Wiki
Revision as of 18:22, 19 May 2020 by X4000Chris (talk | contribs) (→‎Example C# Hook Handler Code)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

As of version 2.044, this is a major new framework to to support modders in particular being able to subscribe to various events that might happen.

Where To Find Hooks

Each hook is defined in GameData\Configuration\ExternalCodeHook, or optionally in equivalent folders for expansions or mods themselves.

As we did with the expansion external data, however, we might wind up keeping the expansion ones with the base game for simplicity. That may keep mods in particular as compatible as possible.

Hook Attributes

Example:

<hook name="OnJournalFirstQueued"
        description="Executed the first time a specific final journal entry is queued. Will only ever execute once, even if the journal can occur multiple times."
        mainobject_is="JournalEntryInCampaign" secondaryobject_is="JournalEntry" additionalobjects_are="null" context_is="Usable" />

That example is something that our code would call any handlers for every time the conditions are right (in this case, every time any journal entry is queued for the first time in a campaign).

name

What event handlers later need to reference in their "hook" field.

description

Explains what the heck it is for, in terms that a modder or another developer will hopefully understand. These are not ever seen by players. Actually, none of this is ever seen by player.

mainobject_is

Defines for the modder what type the "MainObject" parameter will be (so they can cast to that from type object).

secondaryobject_is

Defines for the modder what type the "SecondaryObject" parameter will be (so they can cast to that from type object).

additionalobjects_are

Defines for the modder what the heck is in the "AdditionObjects" array parameter (this can be an array of unlike type objects, or a list of multiple of the same type of objects, or more commonly just a null array).

context_is

Defines for the modder if there is anything unusual to know about the ArcenSimContextBase that is being passed in.

Generally speaking there is nothing to say, but sometimes it will always be null, for instance. It will also generally be something you want to cast to ArcenSimContext from ArcenSimContextBase, but we define it as ArcenSimContextBase so that we can call this even from the ArcenUniversal dll.

Where To Find Hook Handlers

Event handlers are defined in GameData\Configuration\ExternalCodeHookHandler, or the equivalent folder for expansions or mods.

  • These would typically be what people are actually modding in, rather than adding actual hooks (unless their mod is that huge).
  • These just define the dll and class names of the handler, and then the hook that they are attached to, and a name that needs to be unique but doesn't get used for anything.

Example

<handler name="ChrisTestFirstQueued" hook="OnJournalFirstQueued"
        dll_name="AIWarExternalCode" type_name="Arcen.AIW2.External.ExampleHandler_AnyJournalEvent" />

This one would cause the HandleExternalHook( object MainObject, object SecondaryObject, object[] AdditionalObjects, ArcenExternalCodeHook Hook, ArcenSimContextBase Context ) method to be called on an object instantiated from the Arcen.AIW2.External.ExampleHandler_AnyJournalEvent class in the AIWarExternalCode dll from GameData/ModdableLogicDLLs.

That class needs to inherit from the interface IArcenExternalCodeHookHandler.

In this particular example, every time the base game decides that the "OnJournalFirstQueued" hook needs to call all its handlers, it will do so. Multiple mods may all be reacting to "OnJournalFirstQueued" all at once (in random but sequential order), but as an individual modder you have no way of knowing what else someone else is doing.

It's also possible that for whatever reason, we may choose to use a hook and a handler in our own internal code, and so that would run in probably a random order compared to whatever mods or expansions. Usually for our own code, we define interface methods that we call across dlls, so it's hard to say why we'd need to do this sort of approach for internal code, but there's always the chance.

Hook Handlers Instantiation

It's worth noting that every time a hook handler object is defined, it instantiates a new copy of it that object. So if four different handler entries in xml all reference the same class, there would be four objects of the type of that class. You CAN store permanent data on those, but it's not recommended in most cases.

You also can do something like define a static Instance variable to try to have a singleton pattern, but if more than one xml entry calls the same class, you'd have three classes not properly referenced, and then the static Instance pointing to the last-instantiated version. So a static List<> or similar would be better.

Example C# Hook Handler Code

Hopefully self-explanatory, but you can also find this and the related xml in the actual game files.

using Arcen.Universal;
using Arcen.AIW2.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;

namespace Arcen.AIW2.External
{
    public class ExampleHandler_AnyJournalEvent : IArcenExternalCodeHookHandler
    {
        public void HandleExternalHook( object MainObject, object SecondaryObject, object[] AdditionalObjects, ArcenExternalCodeHook Hook, ArcenSimContextBase Context )
        {
            JournalEntryInCampaign campaignEntry = MainObject as JournalEntryInCampaign;
            JournalEntry entry = SecondaryObject as JournalEntry;

            //you can switch on Hook.InternalName to handle multiple event types with one IArcenExternalCodeHookHandler, if you want

            ArcenDebugging.ArcenDebugLog( Hook.InternalName + ":\n" + entry.DoLocalTextReplacements( campaignEntry, entry.FullText ), Verbosity.ShowAsInfo );
        }
    }
}