Difference between revisions of "AI War 2:Journal Entries"
X4000Chris (talk | contribs) |
X4000Chris (talk | contribs) |
||
(13 intermediate revisions by the same user not shown) | |||
Line 2: | Line 2: | ||
'''The content that used to be on this page has moved to "[[AI War 2: Custom Text Messages To The Player | Custom Text Messages To The Player]]"''', as that's really a less-formal and chat-embedded type of journal entry. It also requires some custom coding. That was the only way to do things prior to us adding in the actual codified journal entries with their dedicated sidebar, etc. | '''The content that used to be on this page has moved to "[[AI War 2: Custom Text Messages To The Player | Custom Text Messages To The Player]]"''', as that's really a less-formal and chat-embedded type of journal entry. It also requires some custom coding. That was the only way to do things prior to us adding in the actual codified journal entries with their dedicated sidebar, etc. | ||
− | = | + | = The New Style Of Journal Entries = |
− | These were introduced in version 2. | + | These were introduced in version 2.040 of the game. |
− | + | = Creating New Journal Entries - Core Bits = | |
So, you want to write journal entry or five? Depending on your goals and how you want these to appear, there are various ways you can call them. But no matter what, the basics of how you create one is always the same. That's what we will cover here, field by field: | So, you want to write journal entry or five? Depending on your goals and how you want these to appear, there are various ways you can call them. But no matter what, the basics of how you create one is always the same. That's what we will cover here, field by field: | ||
− | == | + | == name == |
+ | |||
+ | Required field. Must be unique! | ||
This is a series of ASCII characters (a string), and it's the one thing stored in savegames. This entry is longer than most, but please read it. | This is a series of ASCII characters (a string), and it's the one thing stored in savegames. This entry is longer than most, but please read it. | ||
Line 16: | Line 18: | ||
* '''Uniqueness:''' | * '''Uniqueness:''' | ||
** This really does need to be unique! | ** This really does need to be unique! | ||
− | ** If you give your entry an obvious ID like | + | ** If you give your entry an obvious ID like name="GolemFound", then it's a pretty good bet that somebody else will do that also at some point. |
** If you're a modder, then generic IDs are a really great way to wind up with lots of errors when people try to load your mod and someone else's at the same time. | ** If you're a modder, then generic IDs are a really great way to wind up with lots of errors when people try to load your mod and someone else's at the same time. | ||
** Putting your initials or handle is certainly a good way to make it truly unique: CMP_x4000_FirstTime_HiveGolemName would be an example of something that someone else is unlikely to EVER accidentally reuse. | ** Putting your initials or handle is certainly a good way to make it truly unique: CMP_x4000_FirstTime_HiveGolemName would be an example of something that someone else is unlikely to EVER accidentally reuse. | ||
Line 28: | Line 30: | ||
Striking a balance is a good idea. "CP4FirHivGo" is even more brief, and still likely unique, but incomprehensible. | Striking a balance is a good idea. "CP4FirHivGo" is even more brief, and still likely unique, but incomprehensible. | ||
− | === optional_group_id | + | == sidebar_text == |
+ | |||
+ | Required field. [http://digitalnativestudios.com/textmeshpro/docs/rich-text/ Rich text] is supported. | ||
+ | |||
+ | This is the text that appears in the sidebar, on the journals tab, so that you can tell one entry from the next. The timestamp and everything like that is automated, but you have a bit of space where you might write something brief but intelligible like "Hive Golem" or "Hive Golem Discovered" or whatever fits and makes the most sense. Being brief is probably better if you are going for consistency, since some unit names can be incredibly lengthy. | ||
+ | |||
+ | This is just text that we use for display -- it isn't stored in the savegame, so it can be as long as comfortably fits in the sidebar. | ||
+ | |||
+ | == full_text == | ||
+ | |||
+ | Required field. [http://digitalnativestudios.com/textmeshpro/docs/rich-text/ Rich text] is supported. | ||
+ | |||
+ | This is the text that appears in the popup window after you click the sidebar entry. This can be pages and pages long, if you want. From a practical sense it is probably good idea if you keep it to something like a few thousand words, but you won't hurt game performance or savegame size or anything like that if it is very long. You'll just potentially wear out your reader. None of this gets stored in the savegame or transmitted over the network, so that's helpful. | ||
+ | |||
+ | == chat_text== | ||
+ | |||
+ | Optional field -- but frequently recommended. [http://digitalnativestudios.com/textmeshpro/docs/rich-text/ Rich text] is supported. | ||
+ | |||
+ | This is text that appears on the right-hand side of the screen (and chat log) when the journal entry appears. Essentially it is working like this -- [[AI War 2: Custom Text Messages To The Player | Custom Text Messages To The Player]] -- but without you having to do any custom code. | ||
+ | |||
+ | A little text entry saying you've found a new journal entry or what it is can be a great thing to go through chat, no question. So using this is often a good idea. But the length here DOES matter. For one thing, it gets saved into savegames until the chat log is long enough to push it out, and so that makes savegames larger if it is silly-long. Also, since this pops up on the screen during gameplay, if it is too long then it is very invasive and screen-blocking anyhow. | ||
+ | |||
+ | Please save most of your length for the actual full text! This is just a chat note, thematic or otherwise, to let people know a new journal entry has been logged. They may well not otherwise notice it, so it really is a very good idea to do. | ||
+ | |||
+ | = Creating New Journal Entries - Extended Bits = | ||
+ | |||
+ | You really don't have to use any of these, and in some respects it may even be better if you don't! But they provide flexibility for mods in particular to have very different styles of journals from what we envision with the main game. | ||
+ | |||
+ | == optional_group_id == | ||
You don't have to include this at all. | You don't have to include this at all. | ||
Line 39: | Line 69: | ||
** If you and another mod both come up with the same name, it won't be a problem per-se, no errors will be thrown, but when you say "show one of the FighterDiscovered entries," you'd potentially get one from the other mod and not yours. | ** If you and another mod both come up with the same name, it won't be a problem per-se, no errors will be thrown, but when you say "show one of the FighterDiscovered entries," you'd potentially get one from the other mod and not yours. | ||
− | == | + | == name_of_alternate_if_id_already_recorded == |
You don't have to include this at all. | You don't have to include this at all. | ||
Line 47: | Line 77: | ||
*** So if for instance there was a nice big entry that you had for when you first find Hive Golems, and you find three hive golems... only the first one would log anything at all. | *** So if for instance there was a nice big entry that you had for when you first find Hive Golems, and you find three hive golems... only the first one would log anything at all. | ||
*** This might be absolutely A-OK and something you are happy with, especially if it is written in a bestiary-style fashion. | *** This might be absolutely A-OK and something you are happy with, especially if it is written in a bestiary-style fashion. | ||
− | ** However, if you've already found (for instance) the first hive golem, but you still want it to say something else for each further one you find, you could use | + | ** However, if you've already found (for instance) the first hive golem, but you still want it to say something else for each further one you find, you could use name_of_alternate_if_id_already_recorded="AnotherHiveGolemFound" to point it to an entry that is just something like "hey, there's another one!" |
− | == can_record_another_copy_if_already_recorded_in_this_campaign | + | == can_record_another_copy_if_already_recorded_in_this_campaign == |
You don't have to include this at all. It defaults to false. | You don't have to include this at all. It defaults to false. | ||
* '''Purpose:''' If you want something to happen EVERY time some event happens, then this is how you'd set that up. | * '''Purpose:''' If you want something to happen EVERY time some event happens, then this is how you'd set that up. | ||
− | ** Most commonly you would pair this with | + | ** Most commonly you would pair this with name_of_alternate_if_id_already_recorded and have the first one be longer, and then the second one (for example "AnotherHiveGolemFound") be a lot more brief. |
+ | |||
+ | ===Why NOT To Use This=== | ||
It's really worth stating that the Journal is supposed to be interesting, and not a full list of capturables (that's what Intel is for! And Intel is far more informative and actually links to specific planets and entities when you click on them, etc). Intel also lets you control the priority of things you remember, and link over to the map, etc. It's the planner's tool. The journal is... flavor and background. | It's really worth stating that the Journal is supposed to be interesting, and not a full list of capturables (that's what Intel is for! And Intel is far more informative and actually links to specific planets and entities when you click on them, etc). Intel also lets you control the priority of things you remember, and link over to the map, etc. It's the planner's tool. The journal is... flavor and background. | ||
− | Filling up the journal with a bunch of entries like "we found another hive golem" sounds incredibly banal and cluttered, so we'd heartily suggest avoiding it. There's a good chance that there's no reason to use | + | Filling up the journal with a bunch of entries like "we found another hive golem" sounds incredibly banal and cluttered, so we'd heartily suggest avoiding it. There's a good chance that there's no reason to use name_of_alternate_if_id_already_recorded or can_record_another_copy_if_already_recorded_in_this_campaign, but we prefer to make flexible systems especially since you never know how a modder might use things. |
But a general rule of thumb is that if you give the player 1000 journal entries in a campaign, and dozens of them are basic duplicates, that journal tab is going to be laggy, irritating to thumb through, and generally something people find overwhelming to even open. Fewer entries that are more substantial is by far preferred. | But a general rule of thumb is that if you give the player 1000 journal entries in a campaign, and dozens of them are basic duplicates, that journal tab is going to be laggy, irritating to thumb through, and generally something people find overwhelming to even open. Fewer entries that are more substantial is by far preferred. | ||
+ | |||
+ | ===The Few Cases To Use This=== | ||
+ | |||
+ | There are things that don't really need unique text per event, but definitely need to be logged permanently, like the defeat of a faction. Given there might be multiple AIs that are defeated before the final AI, for example, they can all just use one journal entry noting their deaths and what that means, but not having unique text each time. | ||
+ | |||
+ | In other words, because these are major events that are the "same" but need to be noted every time. | ||
+ | |||
+ | There are also cases where the event may simply be so singularly important (defeated the final AI) that you want to leave this flag on just to be doubly sure that if somehow that is triggered twice, it gets logged twice. How might that sort of thing happen? Well, maybe there is some sort of mod that makes the game keep playing after all the AIs are gone, and that mod also allows for AIs to come back to life or new AIs to be added. So killing the final AI maybe happens more than once in this mod, contrary to everything we otherwise thought about the game. | ||
+ | |||
+ | In other words, basically paranoia. Mods and future expansions can do all sorts of crazy things we don't expect, so if this is such an important event that logging it should never ever be missed, setting this flags certainly won't hurt anything. | ||
+ | |||
+ | == author_line == | ||
+ | |||
+ | You don't have to include this, and the game doesn't do anything with this. But sometimes folks like to leave a little remark saying what they contributed. If it's a file that is of your own making, then just putting an xml comment at the top of the file (using the format | ||
+ | |||
+ | <pre><!-- comment in here --> </pre> | ||
+ | |||
+ | like anywhere else) is probably the better way to go. You can include any sort of contact info and name info without it being on every entry in your file. Up to you, though. We had noticed in the past some people using author_line="John Wrote This" in other areas, so we've kind of kept it as a convention. | ||
+ | |||
+ | = Creating New Journal Entries - Text Replacement = | ||
+ | |||
+ | Journal entries have a VERY limited ability to do some text replacement, as defined below. If there are multiple events of the same sort, such as defeating multiple AIs as one example, then using text replacement can clarify what the heck is being discussed. If the journal entry is related to a specific planet, then clicking it would take you to the planet in question, as well. | ||
+ | |||
+ | == text_replacement_related_faction == | ||
+ | |||
+ | You don't have to include this at all. | ||
+ | |||
+ | * '''Purpose:''' If you are wanting to explicitly include the name of a faction, for instance to differentiate between multiple factions of the same sort that exist, then use this. | ||
+ | ** In all of the text fields related to this journal entry, any instances of {FactionName} will be replaced with the actual name of the faction, and its colorization. | ||
+ | |||
+ | * '''Mitigating Factors:''' This doesn't make savegames much larger. It's one integer, which is nice. | ||
+ | |||
+ | == text_replacement_related_faction_color == | ||
+ | |||
+ | You don't have to include this at all. | ||
+ | |||
+ | * '''Purpose:''' If you are wanting to use the color of a faction, but use a different name, then use this. This might be for purposes of something like the Imperial Spire in the Fallen Spire Campaign, where they are literally a different sub-faction but don't have their own faction entry.. | ||
+ | ** In all of the text fields related to this journal entry, any instances of {FactionColor} will be replaced with the actual name of the faction, and its colorization. | ||
+ | ** You MUST later close the xml tag, which would usually be with code like this: <code>{FactionColor}Imperial Spire Fleet</color> Arrived</code> | ||
+ | *** However, in xml attributes you can't directly use the greater than or less than signs. So you need to use this type of text: <code>{FactionColor}Imperial Spire Fleet&lt;/color&gt; Arrived</code> | ||
+ | |||
+ | * '''Mitigating Factors:''' This doesn't make savegames much larger. It's one integer, which is nice. | ||
+ | |||
+ | == text_replacement_related_planet == | ||
+ | |||
+ | You don't have to include this at all. | ||
+ | |||
+ | * '''Purpose:''' If you are wanting to explicitly include the name of a planet, for instance to specify where something happened, then use this. | ||
+ | ** In all of the text fields related to this journal entry, any instances of {PlanetName} will be replaced with the actual name of the planet. | ||
+ | |||
+ | * '''Mitigating Factors:''' Almost all journal entries are naturally linked to planets, which means the data is already being saved in the first place, and so this makes no difference to size. | ||
+ | ** You also don't have to actually use the replacement tag or this xml attribute in order to make the "click this to view planet" work. | ||
+ | |||
+ | == text_replacement_related_unit_type == | ||
+ | |||
+ | You don't have to include this at all. | ||
+ | |||
+ | * '''Purpose:''' If you are wanting to explicitly include the name of a type of unit (which may change, or multiple unit types may use one journal entry), for instance to specify what is taking some action or has been discovered, then use this. | ||
+ | ** In all of the text fields related to this journal entry, any instances of {UnitTypeName} will be replaced with the actual name of the unit type. | ||
+ | |||
+ | * '''Mitigating Factors:''' Almost all journal entries are naturally linked to a unit type, which means the data is already being saved in the first place, and so this makes no difference to size. | ||
+ | ** If a journal entry is linked to a planet and a unit type, or a planet and unit type and faction, then when clicking it it will try to center on a unit of the appropriate type at the planet in question, if one still exists. | ||
+ | *** You can't link directly to a specific unit, because it might move planets or die before the end of the campaign, but journal entries are there until the very end. So the clicking behavior is based on whatever the current state of the campaign is. | ||
+ | |||
+ | = Q&A On How To Log Journal Entries From Custom Code = | ||
+ | |||
+ | '''Q:''' Do you have sample code for how to emit different journal entries? For example, I'd like a permanent journal entry at game start when you have allied marauders that basically explain how marauders work, and that they can build Raiders if they have their own planets. | ||
+ | |||
+ | '''A:''' Sure -- it's super easy, thankfully, and similar to the old way of sending custom text messages. | ||
+ | |||
+ | First off, bear in mind that the journal entries themselves CAN send custom text messages themselves while they also write to the permanent journal. That is up to the actual journal entry design, and not something that your calling code would know anything about. My general advice is to trust the journal author to handle the chat message if they want one. | ||
+ | |||
+ | == Example Code For AI Death == | ||
+ | Some example code for when we are noticing that an AI master controller phase 2 has just died: | ||
+ | |||
+ | GameEntity_Squad entity = [the AI master controller phase 2] | ||
+ | bool undefeatedAIsRemaining = [calculated in other code above]; | ||
+ | if ( undefeatedAIsRemaining ) | ||
+ | World_AIW2.Instance.QueueLogJournalEntryToSidebar( "CP_AIDef_More", string.Empty, entity, Context ); | ||
+ | else | ||
+ | World_AIW2.Instance.QueueLogJournalEntryToSidebar( "CP_AIDef_Final", string.Empty, entity, Context ); | ||
+ | |||
+ | Essentially, the CP_AIDef_More is a journal entry that is set up for when there is at least one AI faction still alive, while CP_AIDef_Final is used for when there is not. They both have can_record_another_copy_if_already_recorded_in_this_campaign set to true, as an aside, the first because it is needed and the second out of paranoia for future mods or expansions. | ||
+ | |||
+ | == The Various Method Overloads And How To Use Them == | ||
+ | |||
+ | There are several different overloads to World_AIW2.Instance.QueueLogJournalEntryToSidebar(), and all of them are safe to be called from any thread on any machine. Please DO bear in mind that these will generate a GameCommand that then gets executed on everyone's machine. So if you are in sim code that happens for everybody in multiplayer, you really want to wrapper it like this: | ||
+ | |||
+ | if ( Engine_Universal.GetIsHostMode() ) | ||
+ | { | ||
+ | //your code in here | ||
+ | } | ||
+ | |||
+ | You have no idea how many clients there are, but there is always one host. In single player there is one host and no clients. And all code definitely runs on the host, but not all code runs on clients (some code, like the long-term planning threads, is host-only). If you DON'T wrapper it in this way, then you'll get multiple copies of the journal entry -- one per player in the game at the moment. | ||
+ | |||
+ | ===Overrides include=== | ||
+ | |||
+ | * void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, ArcenSimContextBase ContextCanBeNull ) | ||
+ | ** UniqueID_ToUseIfGroupIDNotPresent | ||
+ | *** If you don't want to use it, send in string.Empty. It is more efficient than "" in RAM (look it up). Don't send in null. | ||
+ | *** If this is is blank, then it's going to need to find a journal entry by group ID. If this is present, it will ignore the group ID. | ||
+ | ** OptionalGroupID | ||
+ | *** If you don't want to use it, send in string.Empty. It is more efficient than "" in RAM (look it up). Don't send in null. | ||
+ | *** If this is is blank, then it's going to need to find a journal entry by the specific unique name. If it can't find either, it will just do nothing. | ||
+ | ** ContextCanBeNull | ||
+ | *** You can pass in a null, but it's better if you pass in an ArcenSimContext. | ||
+ | *** In the event that maybe you are on some sort of background thread, passing in an ArcenSimContext will ensure that your command gets properly queued. | ||
+ | *** Worst case, you pass in a null and are on a background thread, and potentially your main-thread sim queue gets a little scrambled or even throws an error. | ||
+ | |||
+ | * void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, ArcenSimContext ContextCanBeNull ) | ||
+ | ** This is identical to the method above, but uses ArcenSimContext rather than ArcenSimContextBase. If you're reading this and not in the core codebase (ArcenUniversal), then you'll automatically wind up using this version. But it really doesn't matter. | ||
+ | ** The purpose of having two methods is so that some of our stuff that is game-agnostic in ArcenUniversal can still request journal entries, like voice clips. | ||
+ | |||
+ | * void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, GameEntity_Squad RelatedEntityOrNull, ArcenSimContext Context ) | ||
+ | ** RelatedEntityOrNull | ||
+ | *** This is provided as a convenience, because we can pull the faction, unit type, and planet all off this one entity. | ||
+ | *** As noted above in the documentation, we don't store references to specific entities, ever, in journals -- any given entity might come or go, but journal entries are forever. | ||
+ | *** Probably this is a pretty rare unit, with one per planet. Since this will give a planet AND a unit type AND a faction, then it will center on the first unit of that type for that faction it finds at the planet. | ||
+ | |||
+ | * void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, Planet RelatedPlanetOrNull, ArcenSimContext Context ) | ||
+ | ** RelatedPlanetOrNull | ||
+ | *** Assuming that you are using the xml flag that allows for planet text replacement, this is the planet that it will show the name of. However, even if you don't use the xml flag for text replacement, having this planet linked in means that when you right-click the journal entry in the sidebar, it will take you to this planet. | ||
+ | |||
+ | * void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, Faction RelatedFactionOrNull, GameEntityTypeData RelatedUnitTypeOrNull, Planet RelatedPlanetOrNull, ArcenSimContext Context ) | ||
+ | ** RelatedFactionOrNull | ||
+ | *** Assuming that you are using the xml flag that allows for faction text replacement, this is the faction that it will show the name of. | ||
+ | ** RelatedUnitTypeOrNull | ||
+ | *** Assuming that you are using the xml flag that allows for unit type text replacement, this is the unit type that it will show the name of. | ||
+ | ** Additionally, if you give a planet AND a unit type, then it will center on the first unit of that type it finds at the planet. If there is also a faction specified, then it will center on the first unit of that type for that faction. | ||
+ | |||
+ | = Code Hooks For Journals = | ||
+ | |||
+ | You really should read [[AI War 2: Code Hooks]] to understand what those are for in a generalized sense. Once you know how those work, the following are defined for use with journals: | ||
+ | |||
+ | == OnJournalFirstQueued == | ||
+ | |||
+ | <pre><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" /></pre> | ||
+ | |||
+ | == OnJournalQueued == | ||
+ | |||
+ | <pre><hook name="OnJournalQueued" | ||
+ | description="Executed whenever a journal entry is queued. Can occur multiple times per campaign, but only if the journal can occur multiple times." | ||
+ | mainobject_is="JournalEntryInCampaign" secondaryobject_is="JournalEntry" additionalobjects_are="null" context_is="Usable" /></pre> | ||
+ | |||
+ | == OnJournalOpened == | ||
+ | |||
+ | <pre><hook name="OnJournalOpened" | ||
+ | description="Executed whenever the journal is opened to be read by the player, just before the text is displayed." | ||
+ | mainobject_is="JournalEntryInCampaign" secondaryobject_is="JournalEntry" additionalobjects_are="null" context_is="Usable" /></pre> | ||
+ | |||
+ | == OnJournalClosed == | ||
+ | |||
+ | <pre><hook name="OnJournalClosed" | ||
+ | description="Executed whenever the journal entry is closed." | ||
+ | mainobject_is="JournalEntryInCampaign" secondaryobject_is="JournalEntry" additionalobjects_are="null" context_is="Usable" /></pre> |
Latest revision as of 19:25, 19 May 2020
Contents
Hey!
The content that used to be on this page has moved to " Custom Text Messages To The Player", as that's really a less-formal and chat-embedded type of journal entry. It also requires some custom coding. That was the only way to do things prior to us adding in the actual codified journal entries with their dedicated sidebar, etc.
The New Style Of Journal Entries
These were introduced in version 2.040 of the game.
Creating New Journal Entries - Core Bits
So, you want to write journal entry or five? Depending on your goals and how you want these to appear, there are various ways you can call them. But no matter what, the basics of how you create one is always the same. That's what we will cover here, field by field:
name
Required field. Must be unique!
This is a series of ASCII characters (a string), and it's the one thing stored in savegames. This entry is longer than most, but please read it.
- Uniqueness:
- This really does need to be unique!
- If you give your entry an obvious ID like name="GolemFound", then it's a pretty good bet that somebody else will do that also at some point.
- If you're a modder, then generic IDs are a really great way to wind up with lots of errors when people try to load your mod and someone else's at the same time.
- Putting your initials or handle is certainly a good way to make it truly unique: CMP_x4000_FirstTime_HiveGolemName would be an example of something that someone else is unlikely to EVER accidentally reuse.
- Brevity:
- "CMP_x4000_FirstTime_HiveGolem" is fine from a uniqueness standpoint, but at 29 characters it's awful from a brevity standpoint.
- Each character in the name is saved into the savegame, raw, at a rate of 1 byte per character.
- How many journal entries will we really be saving? Probably not that many, compared to something like ships. But let's be reasonable everywhere we can.
- Adopting something much more brief like "CMP4_1st_HiveGol" is 16 characters, but just as readable to me, and just as likely to be unique.
Striking a balance is a good idea. "CP4FirHivGo" is even more brief, and still likely unique, but incomprehensible.
sidebar_text
Required field. Rich text is supported.
This is the text that appears in the sidebar, on the journals tab, so that you can tell one entry from the next. The timestamp and everything like that is automated, but you have a bit of space where you might write something brief but intelligible like "Hive Golem" or "Hive Golem Discovered" or whatever fits and makes the most sense. Being brief is probably better if you are going for consistency, since some unit names can be incredibly lengthy.
This is just text that we use for display -- it isn't stored in the savegame, so it can be as long as comfortably fits in the sidebar.
full_text
Required field. Rich text is supported.
This is the text that appears in the popup window after you click the sidebar entry. This can be pages and pages long, if you want. From a practical sense it is probably good idea if you keep it to something like a few thousand words, but you won't hurt game performance or savegame size or anything like that if it is very long. You'll just potentially wear out your reader. None of this gets stored in the savegame or transmitted over the network, so that's helpful.
chat_text
Optional field -- but frequently recommended. Rich text is supported.
This is text that appears on the right-hand side of the screen (and chat log) when the journal entry appears. Essentially it is working like this -- Custom Text Messages To The Player -- but without you having to do any custom code.
A little text entry saying you've found a new journal entry or what it is can be a great thing to go through chat, no question. So using this is often a good idea. But the length here DOES matter. For one thing, it gets saved into savegames until the chat log is long enough to push it out, and so that makes savegames larger if it is silly-long. Also, since this pops up on the screen during gameplay, if it is too long then it is very invasive and screen-blocking anyhow.
Please save most of your length for the actual full text! This is just a chat note, thematic or otherwise, to let people know a new journal entry has been logged. They may well not otherwise notice it, so it really is a very good idea to do.
Creating New Journal Entries - Extended Bits
You really don't have to use any of these, and in some respects it may even be better if you don't! But they provide flexibility for mods in particular to have very different styles of journals from what we envision with the main game.
optional_group_id
You don't have to include this at all.
- Purpose: Sometimes you will want to have "one at random out of a group of possible entries" logged to the game, and this lets you do that.
- All of the entries that are in the group need to have a matching ID, so for instance putting four entries into "HiveGolemAppears" would work just fine.
- Mitigating Factors: These are NOT saved in savegames, so don't need to be particularly brief.
- These do need to be somewhat unique, in the sense that if your mod has a really generic event name in it like "FighterDiscovered" may or may not clash with someone else's mod who has an event they name the same thing.
- If you and another mod both come up with the same name, it won't be a problem per-se, no errors will be thrown, but when you say "show one of the FighterDiscovered entries," you'd potentially get one from the other mod and not yours.
name_of_alternate_if_id_already_recorded
You don't have to include this at all.
- Purpose: If you are NOT using group_ids, this is another way to have something progressive happen when normally it would try to log the same entry multiple times.
- Each journal entry will only be entered once per campaign, normally.
- So if for instance there was a nice big entry that you had for when you first find Hive Golems, and you find three hive golems... only the first one would log anything at all.
- This might be absolutely A-OK and something you are happy with, especially if it is written in a bestiary-style fashion.
- However, if you've already found (for instance) the first hive golem, but you still want it to say something else for each further one you find, you could use name_of_alternate_if_id_already_recorded="AnotherHiveGolemFound" to point it to an entry that is just something like "hey, there's another one!"
- Each journal entry will only be entered once per campaign, normally.
can_record_another_copy_if_already_recorded_in_this_campaign
You don't have to include this at all. It defaults to false.
- Purpose: If you want something to happen EVERY time some event happens, then this is how you'd set that up.
- Most commonly you would pair this with name_of_alternate_if_id_already_recorded and have the first one be longer, and then the second one (for example "AnotherHiveGolemFound") be a lot more brief.
Why NOT To Use This
It's really worth stating that the Journal is supposed to be interesting, and not a full list of capturables (that's what Intel is for! And Intel is far more informative and actually links to specific planets and entities when you click on them, etc). Intel also lets you control the priority of things you remember, and link over to the map, etc. It's the planner's tool. The journal is... flavor and background.
Filling up the journal with a bunch of entries like "we found another hive golem" sounds incredibly banal and cluttered, so we'd heartily suggest avoiding it. There's a good chance that there's no reason to use name_of_alternate_if_id_already_recorded or can_record_another_copy_if_already_recorded_in_this_campaign, but we prefer to make flexible systems especially since you never know how a modder might use things.
But a general rule of thumb is that if you give the player 1000 journal entries in a campaign, and dozens of them are basic duplicates, that journal tab is going to be laggy, irritating to thumb through, and generally something people find overwhelming to even open. Fewer entries that are more substantial is by far preferred.
The Few Cases To Use This
There are things that don't really need unique text per event, but definitely need to be logged permanently, like the defeat of a faction. Given there might be multiple AIs that are defeated before the final AI, for example, they can all just use one journal entry noting their deaths and what that means, but not having unique text each time.
In other words, because these are major events that are the "same" but need to be noted every time.
There are also cases where the event may simply be so singularly important (defeated the final AI) that you want to leave this flag on just to be doubly sure that if somehow that is triggered twice, it gets logged twice. How might that sort of thing happen? Well, maybe there is some sort of mod that makes the game keep playing after all the AIs are gone, and that mod also allows for AIs to come back to life or new AIs to be added. So killing the final AI maybe happens more than once in this mod, contrary to everything we otherwise thought about the game.
In other words, basically paranoia. Mods and future expansions can do all sorts of crazy things we don't expect, so if this is such an important event that logging it should never ever be missed, setting this flags certainly won't hurt anything.
author_line
You don't have to include this, and the game doesn't do anything with this. But sometimes folks like to leave a little remark saying what they contributed. If it's a file that is of your own making, then just putting an xml comment at the top of the file (using the format
<!-- comment in here -->
like anywhere else) is probably the better way to go. You can include any sort of contact info and name info without it being on every entry in your file. Up to you, though. We had noticed in the past some people using author_line="John Wrote This" in other areas, so we've kind of kept it as a convention.
Creating New Journal Entries - Text Replacement
Journal entries have a VERY limited ability to do some text replacement, as defined below. If there are multiple events of the same sort, such as defeating multiple AIs as one example, then using text replacement can clarify what the heck is being discussed. If the journal entry is related to a specific planet, then clicking it would take you to the planet in question, as well.
You don't have to include this at all.
- Purpose: If you are wanting to explicitly include the name of a faction, for instance to differentiate between multiple factions of the same sort that exist, then use this.
- In all of the text fields related to this journal entry, any instances of {FactionName} will be replaced with the actual name of the faction, and its colorization.
- Mitigating Factors: This doesn't make savegames much larger. It's one integer, which is nice.
You don't have to include this at all.
- Purpose: If you are wanting to use the color of a faction, but use a different name, then use this. This might be for purposes of something like the Imperial Spire in the Fallen Spire Campaign, where they are literally a different sub-faction but don't have their own faction entry..
- In all of the text fields related to this journal entry, any instances of {FactionColor} will be replaced with the actual name of the faction, and its colorization.
- You MUST later close the xml tag, which would usually be with code like this:
{FactionColor}Imperial Spire Fleet</color> Arrived
- However, in xml attributes you can't directly use the greater than or less than signs. So you need to use this type of text:
{FactionColor}Imperial Spire Fleet</color> Arrived
- However, in xml attributes you can't directly use the greater than or less than signs. So you need to use this type of text:
- Mitigating Factors: This doesn't make savegames much larger. It's one integer, which is nice.
You don't have to include this at all.
- Purpose: If you are wanting to explicitly include the name of a planet, for instance to specify where something happened, then use this.
- In all of the text fields related to this journal entry, any instances of {PlanetName} will be replaced with the actual name of the planet.
- Mitigating Factors: Almost all journal entries are naturally linked to planets, which means the data is already being saved in the first place, and so this makes no difference to size.
- You also don't have to actually use the replacement tag or this xml attribute in order to make the "click this to view planet" work.
You don't have to include this at all.
- Purpose: If you are wanting to explicitly include the name of a type of unit (which may change, or multiple unit types may use one journal entry), for instance to specify what is taking some action or has been discovered, then use this.
- In all of the text fields related to this journal entry, any instances of {UnitTypeName} will be replaced with the actual name of the unit type.
- Mitigating Factors: Almost all journal entries are naturally linked to a unit type, which means the data is already being saved in the first place, and so this makes no difference to size.
- If a journal entry is linked to a planet and a unit type, or a planet and unit type and faction, then when clicking it it will try to center on a unit of the appropriate type at the planet in question, if one still exists.
- You can't link directly to a specific unit, because it might move planets or die before the end of the campaign, but journal entries are there until the very end. So the clicking behavior is based on whatever the current state of the campaign is.
- If a journal entry is linked to a planet and a unit type, or a planet and unit type and faction, then when clicking it it will try to center on a unit of the appropriate type at the planet in question, if one still exists.
Q&A On How To Log Journal Entries From Custom Code
Q: Do you have sample code for how to emit different journal entries? For example, I'd like a permanent journal entry at game start when you have allied marauders that basically explain how marauders work, and that they can build Raiders if they have their own planets.
A: Sure -- it's super easy, thankfully, and similar to the old way of sending custom text messages.
First off, bear in mind that the journal entries themselves CAN send custom text messages themselves while they also write to the permanent journal. That is up to the actual journal entry design, and not something that your calling code would know anything about. My general advice is to trust the journal author to handle the chat message if they want one.
Example Code For AI Death
Some example code for when we are noticing that an AI master controller phase 2 has just died:
GameEntity_Squad entity = [the AI master controller phase 2] bool undefeatedAIsRemaining = [calculated in other code above]; if ( undefeatedAIsRemaining ) World_AIW2.Instance.QueueLogJournalEntryToSidebar( "CP_AIDef_More", string.Empty, entity, Context ); else World_AIW2.Instance.QueueLogJournalEntryToSidebar( "CP_AIDef_Final", string.Empty, entity, Context );
Essentially, the CP_AIDef_More is a journal entry that is set up for when there is at least one AI faction still alive, while CP_AIDef_Final is used for when there is not. They both have can_record_another_copy_if_already_recorded_in_this_campaign set to true, as an aside, the first because it is needed and the second out of paranoia for future mods or expansions.
The Various Method Overloads And How To Use Them
There are several different overloads to World_AIW2.Instance.QueueLogJournalEntryToSidebar(), and all of them are safe to be called from any thread on any machine. Please DO bear in mind that these will generate a GameCommand that then gets executed on everyone's machine. So if you are in sim code that happens for everybody in multiplayer, you really want to wrapper it like this:
if ( Engine_Universal.GetIsHostMode() ) { //your code in here }
You have no idea how many clients there are, but there is always one host. In single player there is one host and no clients. And all code definitely runs on the host, but not all code runs on clients (some code, like the long-term planning threads, is host-only). If you DON'T wrapper it in this way, then you'll get multiple copies of the journal entry -- one per player in the game at the moment.
Overrides include
- void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, ArcenSimContextBase ContextCanBeNull )
- UniqueID_ToUseIfGroupIDNotPresent
- If you don't want to use it, send in string.Empty. It is more efficient than "" in RAM (look it up). Don't send in null.
- If this is is blank, then it's going to need to find a journal entry by group ID. If this is present, it will ignore the group ID.
- OptionalGroupID
- If you don't want to use it, send in string.Empty. It is more efficient than "" in RAM (look it up). Don't send in null.
- If this is is blank, then it's going to need to find a journal entry by the specific unique name. If it can't find either, it will just do nothing.
- ContextCanBeNull
- You can pass in a null, but it's better if you pass in an ArcenSimContext.
- In the event that maybe you are on some sort of background thread, passing in an ArcenSimContext will ensure that your command gets properly queued.
- Worst case, you pass in a null and are on a background thread, and potentially your main-thread sim queue gets a little scrambled or even throws an error.
- UniqueID_ToUseIfGroupIDNotPresent
- void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, ArcenSimContext ContextCanBeNull )
- This is identical to the method above, but uses ArcenSimContext rather than ArcenSimContextBase. If you're reading this and not in the core codebase (ArcenUniversal), then you'll automatically wind up using this version. But it really doesn't matter.
- The purpose of having two methods is so that some of our stuff that is game-agnostic in ArcenUniversal can still request journal entries, like voice clips.
- void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, GameEntity_Squad RelatedEntityOrNull, ArcenSimContext Context )
- RelatedEntityOrNull
- This is provided as a convenience, because we can pull the faction, unit type, and planet all off this one entity.
- As noted above in the documentation, we don't store references to specific entities, ever, in journals -- any given entity might come or go, but journal entries are forever.
- Probably this is a pretty rare unit, with one per planet. Since this will give a planet AND a unit type AND a faction, then it will center on the first unit of that type for that faction it finds at the planet.
- RelatedEntityOrNull
- void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, Planet RelatedPlanetOrNull, ArcenSimContext Context )
- RelatedPlanetOrNull
- Assuming that you are using the xml flag that allows for planet text replacement, this is the planet that it will show the name of. However, even if you don't use the xml flag for text replacement, having this planet linked in means that when you right-click the journal entry in the sidebar, it will take you to this planet.
- RelatedPlanetOrNull
- void QueueLogJournalEntryToSidebar( string UniqueID_ToUseIfGroupIDNotPresent, string OptionalGroupID, Faction RelatedFactionOrNull, GameEntityTypeData RelatedUnitTypeOrNull, Planet RelatedPlanetOrNull, ArcenSimContext Context )
- RelatedFactionOrNull
- Assuming that you are using the xml flag that allows for faction text replacement, this is the faction that it will show the name of.
- RelatedUnitTypeOrNull
- Assuming that you are using the xml flag that allows for unit type text replacement, this is the unit type that it will show the name of.
- Additionally, if you give a planet AND a unit type, then it will center on the first unit of that type it finds at the planet. If there is also a faction specified, then it will center on the first unit of that type for that faction.
- RelatedFactionOrNull
Code Hooks For Journals
You really should read AI War 2: Code Hooks to understand what those are for in a generalized sense. Once you know how those work, the following are defined for use with journals:
OnJournalFirstQueued
<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" />
OnJournalQueued
<hook name="OnJournalQueued" description="Executed whenever a journal entry is queued. Can occur multiple times per campaign, but only if the journal can occur multiple times." mainobject_is="JournalEntryInCampaign" secondaryobject_is="JournalEntry" additionalobjects_are="null" context_is="Usable" />
OnJournalOpened
<hook name="OnJournalOpened" description="Executed whenever the journal is opened to be read by the player, just before the text is displayed." mainobject_is="JournalEntryInCampaign" secondaryobject_is="JournalEntry" additionalobjects_are="null" context_is="Usable" />
OnJournalClosed
<hook name="OnJournalClosed" description="Executed whenever the journal entry is closed." mainobject_is="JournalEntryInCampaign" secondaryobject_is="JournalEntry" additionalobjects_are="null" context_is="Usable" />