whoward69
DLL Minion
(Requires v81 or later of "DLL - Various Mod Components", and probably of more interest to Total Conversion modders.)
Background
City State quests (clear a barbarian camp, bully another city state, discover the most techs, etc) are all hard-coded in the DLL.
Adding one is not possible (without modding the DLL), which for a Total Conversion means you're stuck with the standard quests, which may not be relevant for the milieu.
Custom quests, such as rescuing a maiden held captive by a dragon, finding all the "keys" to a magic box, or just sending a bard to entertain the king, are either not possible, or have to be faked with a lot of Lua using the standard quests as a framework.
V81 of "DLL - Various Mod Components" makes all of these (and many other) quests possible - via XML/SQL and Lua GameEvents.
Overview
A City State quest comprises
This information is now obtained by the quest logic in the DLL either from the Quests database table or via GameEvents.
The definition of the new Quests table is
and the new GameEvents are
Additional quest related API methods are also available
And also an API to permit control of the AIs economic and military strategy
We'll cover (most of) these in detail in the following three example quests.
Types of Quests
There are three categories of City State quests - Global (eg build a World Wonder), Global Race (eg gain the most techs) and Personal (eg bully another City State or discover a Natural Wonder).
Not all database columns and GameEvents are relevant to each type of quest, but every custom quest will have an entry in the Quests table and implement one or more quest GameEvent handlers.
Example 1: Global Quest - Circumnavigate the Globe
The aim of this quest is to be the first to circumnavigate the globe.
The Quests database table entry is
The unique Type for this quest is MINOR_CIV_QUEST_CIRCUMNAVIGATE, a unique id will be automatically assigned by the database, when you need it, use GameInfoTypes.MINOR_CIV_QUEST_CIRCUMNAVIGATE as you usually would.
This quest is only awarded once by a City State, and if it's already active, other players can join in. This makes it a global quest, so we set Global to true. Also, we need more than one player who could complete the quest, so we set MinPlayers to 2 to indicate that we want at least two players (human or AI) to participate.
By default, if a player bullies a City State after a quest has be handed out, that quest will be revoked. As we don't want that behaviour, we override it by setting RevokeOnBully to false.
The player that completes the quest typically gains a friendship boost with the City State, for this quest that boost will be 50 and this is set by the Friendship value.
As this is an exploration/trading type quest, we'll increase the liklihood that the quest will be handed out by a maritime or mercantile City State. The default biases are 100, so 300 means 3 times more likely, while 50 is half as likely. Biases cannot be negative. A bias of 0 means that the quest will never be handed out by a City State with the associated type or personality.
When the City State hands out (starts) the quest, a notification is generated. A notification comprises two text strings - the message and the summary. If these messages are simple strings, we can place their TXT_KEY entries directly into the StartSummary and StartMessage values for the quest. However, if the strings are not simple, omitting the entries will cause a GameEvent to be generated, in which we can format the strings as desired and generate the notification from Lua.
The start message and summary for this quest are simple, but as this is an example, we'll omit them to show how to generate the notification via Lua.
When the quest is complete, the player has either won (they finished the quest) or lost (someone else finished it, so the quest for them has been cancelled). Again, a notification is sent, and we either put the TXT_KEYs directly into the quest entry in the database table or omit them to use the GameEvent. As we're already showing how to generate the start notification from Lua, we'll use TXT_KEYs for these four entries.
That's it as far as the quest logic in the DLL is concerned, but the UI needs three additional pieces of information. For the UI, the quest needs an icon, a tooltip and a relative place in a list of quests to be displayed.
The Priority entry is where in a list of quests this quest appears - see the entries for the standard City State quests in the Quests table to work out about where you want your custom quest to appear in the list - 150 is after the "Build a Road" quest.
The Icon entry is either an [ICON_XYZ] string or the name of a Lua function to determine the icon. If the entry starts with a square bracket ([), this is a specific icon, otherwise it's the name of a function.
Similarly, the Tooltip entry is either a TXT_KEY or the name of a Lua function to determine the tooltip. If the entry starts with the eight characters TXT_KEY_, this is a simple text string, otherwise it's the name of a function.
For this quest we could have used a simple icon and tooltip entry, but as this is an example, we'll use Lua functions to generate the icon and tooltip.
As we've just mentioned them, we'll look at the functions to get the icon and tooltip for the UI.
To use these you'll need to include the modded version of the CityStateStatusHelper.lua file into your mod. If you don't override this core file, nothing bad will happen, you just won't see any custom quests in the City State popups (or other mods that use the helpers, eg the City States screen of my "UI - Trade Opportunities" mod).
The modded CityStateStatusHelper.lua file includes all files that match the pattern "CityStateQuests_Helpers_{something}.lua", so we need to create a "CityStateQuests_Helpers_Circumnavigate.lua" file and set it to be VFS=true (do NOT add it as an InGameUIAddin).
Each function receives five parameters that we could use to return a different icon (see the standard GetReligionQuestIcon function in CityStateStatusHelper.lua file) or format the tooltip (see the GetTourismContestQuestTooltip function below)
Our TXT_KEY is very simple
As we're looking at TXT_KEYs the ones for the won (finished) and lost (cancelled) notifications are
Note that we get the City State name passed as a parameter/substition ({1_MinorName:textkey}) for these TXT_KEYs, while the finished pair also receive the amount of friendship/influence gained ({2_InfluenceReward}).
And now for the GameEvent handlers.
Every custom quest must handle the QuestIsAvailable GameEvent - this event is used to ascertain if the quest can be started by a specific player for a specific City State. We are going to keep this example simple, so our only condition for starting the quest is that the globe must not have already been circumnavigated and that the player must be in the Medieval Era
We'll worry about how iPlayerCircumnavigating gets set later, but it records which player, if anyone, was the first to circumnavigate the globe.
As we didn't specify values in the Quests entry for StartSummary and StartMessage, we need to handle the QuestSendNotification GameEvent to generate the notification to the player that the City State has issued the challenge
Note that the same GameEvent is used for all three notifications (if the TXT_KEYs aren't in the Quests table), you differentiate the notification to send with the bStart and bFinish parameters (these will NEVER both be true).
Also note the new API method on the Player object pPlayer:AddQuestNotification(iCS, sMsg, sSum, iX, iY, bNewQuest) for sending the notification. While you can use the standard pPlayer:AddNotification() method, it is NOT recommended as it will not produce a notification that behaves the same as the standard City State quests.
This custom quest must also handle the QuestIsCompleted and QuestIsExpired GameEvents. The former returns true if the player has won, the latter returns true if the player has lost, return false means that the quest is ongoing. Our handlers just need to ascertain if the player who actually circumnavigated the globe is the current player.
So the City State issues the challenge and waits for someone to circumnavigate the globe. Presumably the player will build a ship and start exploring ... but what about the AI?
The DLL sends a QuestStart GameEvent at the commencement of the quest, so we can hook this and add some logic to give the AI a nudge in the right direction. We could check its units for some ships capable of exploration and if not locate a coastal city or three and add an exploration type ship to the build queue and hope they get built and then explore. Alternatively, we can just make sure the AI strategy to recon the sea is active (via a new API method).
Finally, we need to look at how the iPlayerCircumnavigating variable gets set. We use the CircumnavigatedGlobe GameEvent along with some simple data persistence
The complete code is in the "Quests - Circumnavigate" mod
Background
City State quests (clear a barbarian camp, bully another city state, discover the most techs, etc) are all hard-coded in the DLL.
Adding one is not possible (without modding the DLL), which for a Total Conversion means you're stuck with the standard quests, which may not be relevant for the milieu.
Custom quests, such as rescuing a maiden held captive by a dragon, finding all the "keys" to a magic box, or just sending a bard to entertain the king, are either not possible, or have to be faked with a lot of Lua using the standard quests as a framework.
V81 of "DLL - Various Mod Components" makes all of these (and many other) quests possible - via XML/SQL and Lua GameEvents.
Overview
A City State quest comprises
- Conditions that have to be met for the quest to be a candidate for handing out
- Biases that determine which of the City State types (mercantile, religious, etc) and personalities (friendly, hostile, etc) are most/least likely to hand out the quest
- Static information about the quest (duration, friendship gained, minimum number of participants, etc)
- Dynamic information about the active quest (location of the camp to be cleared, other City State to be bullied, etc)
- Conditions for the quest (once active) to be completed (won), cancelled (lost) or revoked (eg if the CS handing out the quest is bullied by the player)
- Messages to display when one of the above conditions are met
- An icon and tooltip to display for the quest (used only by the UI)
This information is now obtained by the quest logic in the DLL either from the Quests database table or via GameEvents.
Spoiler Quests table and GameEvents :
The definition of the new Quests table is
Code:
<Table name="Quests">
<Column name="ID" type="integer" primarykey="true" autoincrement="true"/>
<Column name="Type" type="text" unique="true" notnull="true"/>
<!-- Set to true if quest is backed by C++ code in the DLL -->
<Column name="Internal" type="boolean" default="false"/>
<!-- Set to false to disable this type of event -->
<Column name="Enabled" type="boolean" default="true"/>
<!-- Game option (if any) to disable this event for, eg GAMEOPTION_NO_POLICIES, GAMEOPTION_NO_RELIGION, GAMEOPTION_NO_SCIENCE -->
<Column name="DisabledOnOption" type="text" default="NULL"/>
<!-- Set to true for global (one quest applies to all players), false for personal (one quest per player) quests -->
<Column name="Global" type="boolean" default="false"/>
<!-- Set to false to override the default behaviour of revoking the quest in the player bullies the CS -->
<Column name="RevokeOnBully" type="boolean" default="true"/>
<!-- Set to true if this is a contest type quest (eg most culture, science, etc) - must also hook the QuestContestValue event -->
<Column name="Contest" type="boolean" default="false"/>
<!-- Mininimum number of majors the CS must have met before the quest can be considered, typically used for contest quests -->
<Column name="MinPlayers" type="integer" default="1"/>
<!-- Duration in turns this quest runs for; will be automatically adjusted for non-standard game speeds -->
<Column name="Duration" type="integer" default="-1"/>
<!-- Friendship/Influence gained on completion of the quest -->
<Column name="Friendship" type="integer" default="0"/>
<!-- Notification text pairs, set to NULL to force a QuestSendNotification event -->
<Column name="StartSummary" type="text" default="NULL" reference="Language_en_US(Tag)"/>
<Column name="StartMessage" type="text" default="NULL" reference="Language_en_US(Tag)"/>
<Column name="FinishSummary" type="text" default="NULL" reference="Language_en_US(Tag)"/>
<Column name="FinishMessage" type="text" default="NULL" reference="Language_en_US(Tag)"/>
<Column name="CancelSummary" type="text" default="NULL" reference="Language_en_US(Tag)"/>
<Column name="CancelMessage" type="text" default="NULL" reference="Language_en_US(Tag)"/>
<!-- Personality biases, only one of these will ever apply -->
<Column name="BiasFriendly" type="integer" default="100"/>
<Column name="BiasHostile" type="integer" default="100"/>
<Column name="BiasNeutral" type="integer" default="100"/>
<Column name="BiasIrrational" type="integer" default="100"/>
<!-- Trait biases, only one of these will ever apply -->
<Column name="BiasMaritime" type="integer" default="100"/>
<Column name="BiasMercantile" type="integer" default="100"/>
<Column name="BiasCultured" type="integer" default="100"/>
<Column name="BiasMilitaristic" type="integer" default="100"/>
<Column name="BiasReligious" type="integer" default="100"/>
<!-- UI related data, not processed by the DLL in any way -->
<!-- The priority for displaying the quest,
there are "holes" in the standard quest sequence to permit custom quests to be inserted if needed -->
<Column name="Priority" type="integer" default="100"/>
<!-- The icon associated with the quest, or the function name to determine the icon -->
<Column name="Icon" type="text" default="[ICON_TEAM_1]"/>
<!-- The TXT_KEY associated with the quest (can use {1_TargetName:textkey} if there is a target for the quest (bully, find, etc)),
or the function name to determine the tooltip -->
<Column name="Tooltip" type="text" default="TXT_KEY_CITY_STATE_QUEST_GENERIC_FORMAL"/>
</Table>
Code:
<!-- Events sent by City State quests (v81) -->
<!-- ASSUMPTION: There is only one active quest of any given MinorCivQuestTypes per major, that is, (iPlayer, iCS, iQuest) is unique -->
<!-- GameEvents.QuestIsAvailable.Add(function(iPlayer, iCS, iQuest, bNewQuest, iData1, iData2) return false end) -->
<!-- GameEvents.QuestIsCompleted.Add(function(iPlayer, iCS, iQuest, bLastTurn) return false end) -->
<!-- GameEvents.QuestIsRevoked.Add(function(iPlayer, iCS, iQuest) return false end) -->
<!-- GameEvents.QuestIsExpired.Add(function(iPlayer, iCS, iQuest) return false end) -->
<!-- GameEvents.QuestStart.Add(function(iPlayer, iCS, iQuest, bNewQuest, iStartTurn, iData1, iData2) end) -->
<!-- GameEvents.QuestGetData.Add(function(iPlayer, iCS, iQuest, bData1) return 0 end) -->
<!-- GameEvents.QuestSendNotification.Add(function(iPlayer, iCS, iQuest, iStartTurn, iEndTurn, iData1, iData2, bStarted, bFinished, sNames) end) -->
<!-- GameEvents.QuestContestValue.Add(function(iPlayer, iCS, iQuest) return 0 end) -->
Code:
LUAAPIEXTN(DoMinorCivStartQuestForPlayer, void, iMajor, iQuest);
LUAAPIEXTN(GetQuestTurnsDuration, int, iMajor, iQuest);
LUAAPIEXTN(IsEverBulliedByMajor, bool, iPlayer);
LUAAPIEXTN(IsRecentlyBulliedByMajor, bool, iPlayer);
LUAAPIEXTN(AddQuestNotification, void, iCS, sMessage, sSummary, iPlotX, iPlotY, bNewQuest);
Code:
LUAAPIEXTN(GetActiveEconomicStrategies, table);
LUAAPIEXTN(IsActiveEconomicStrategy, bool, iStrategy);
LUAAPIEXTN(ActivateEconomicStrategy, void, iStrategy);
LUAAPIEXTN(DeactivateEconomicStrategy, void, iStrategy);
LUAAPIEXTN(GetActiveMilitaryStrategies, table);
LUAAPIEXTN(IsActiveMilitaryStrategy, bool, iStrategy);
LUAAPIEXTN(ActivateMilitaryStrategy, void, iStrategy);
LUAAPIEXTN(DeactivateMilitaryStrategy, void, iStrategy);
Types of Quests
There are three categories of City State quests - Global (eg build a World Wonder), Global Race (eg gain the most techs) and Personal (eg bully another City State or discover a Natural Wonder).
Not all database columns and GameEvents are relevant to each type of quest, but every custom quest will have an entry in the Quests table and implement one or more quest GameEvent handlers.
Example 1: Global Quest - Circumnavigate the Globe
The aim of this quest is to be the first to circumnavigate the globe.
The Quests database table entry is
Code:
<Quests>
<Row>
<Type>MINOR_CIV_QUEST_CIRCUMNAVIGATE</Type>
<Global>true</Global>
<MinPlayers>2</MinPlayers>
<RevokeOnBully>false</RevokeOnBully>
<Friendship>50</Friendship>
<BiasMaritime>300</BiasMaritime>
<BiasMercantile>200</BiasMercantile>
<!-- While we could easily use simple TXT_KEYs here, we'll use the Lua GameEvent (as this is an example!)
<StartSummary>TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_START_S</StartSummary>
<StartMessage>TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_START</StartMessage -->
<FinishSummary>TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_FINISH_S</FinishSummary>
<FinishMessage>TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_FINISH</FinishMessage>
<CancelSummary>TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_CANCEL_S</CancelSummary>
<CancelMessage>TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_CANCEL</CancelMessage>
<Priority>150</Priority>
<!-- To prove a point, we'll get the icon and tooltip via Lua functions - see CityStateQuests_Helpers_Circumnavigate.lua -->
<!--Icon>[ICON_TRADE]</Icon-->
<Icon>GetCircumnavigateQuestIcon</Icon>
<!--Tooltip>TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_FORMAL</Tooltip-->
<Tooltip>GetCircumnavigateQuestTooltip</Tooltip>
</Row>
</Quests>
The unique Type for this quest is MINOR_CIV_QUEST_CIRCUMNAVIGATE, a unique id will be automatically assigned by the database, when you need it, use GameInfoTypes.MINOR_CIV_QUEST_CIRCUMNAVIGATE as you usually would.
This quest is only awarded once by a City State, and if it's already active, other players can join in. This makes it a global quest, so we set Global to true. Also, we need more than one player who could complete the quest, so we set MinPlayers to 2 to indicate that we want at least two players (human or AI) to participate.
By default, if a player bullies a City State after a quest has be handed out, that quest will be revoked. As we don't want that behaviour, we override it by setting RevokeOnBully to false.
The player that completes the quest typically gains a friendship boost with the City State, for this quest that boost will be 50 and this is set by the Friendship value.
As this is an exploration/trading type quest, we'll increase the liklihood that the quest will be handed out by a maritime or mercantile City State. The default biases are 100, so 300 means 3 times more likely, while 50 is half as likely. Biases cannot be negative. A bias of 0 means that the quest will never be handed out by a City State with the associated type or personality.
When the City State hands out (starts) the quest, a notification is generated. A notification comprises two text strings - the message and the summary. If these messages are simple strings, we can place their TXT_KEY entries directly into the StartSummary and StartMessage values for the quest. However, if the strings are not simple, omitting the entries will cause a GameEvent to be generated, in which we can format the strings as desired and generate the notification from Lua.
The start message and summary for this quest are simple, but as this is an example, we'll omit them to show how to generate the notification via Lua.
When the quest is complete, the player has either won (they finished the quest) or lost (someone else finished it, so the quest for them has been cancelled). Again, a notification is sent, and we either put the TXT_KEYs directly into the quest entry in the database table or omit them to use the GameEvent. As we're already showing how to generate the start notification from Lua, we'll use TXT_KEYs for these four entries.
That's it as far as the quest logic in the DLL is concerned, but the UI needs three additional pieces of information. For the UI, the quest needs an icon, a tooltip and a relative place in a list of quests to be displayed.
The Priority entry is where in a list of quests this quest appears - see the entries for the standard City State quests in the Quests table to work out about where you want your custom quest to appear in the list - 150 is after the "Build a Road" quest.
The Icon entry is either an [ICON_XYZ] string or the name of a Lua function to determine the icon. If the entry starts with a square bracket ([), this is a specific icon, otherwise it's the name of a function.
Similarly, the Tooltip entry is either a TXT_KEY or the name of a Lua function to determine the tooltip. If the entry starts with the eight characters TXT_KEY_, this is a simple text string, otherwise it's the name of a function.
For this quest we could have used a simple icon and tooltip entry, but as this is an example, we'll use Lua functions to generate the icon and tooltip.
As we've just mentioned them, we'll look at the functions to get the icon and tooltip for the UI.
To use these you'll need to include the modded version of the CityStateStatusHelper.lua file into your mod. If you don't override this core file, nothing bad will happen, you just won't see any custom quests in the City State popups (or other mods that use the helpers, eg the City States screen of my "UI - Trade Opportunities" mod).
The modded CityStateStatusHelper.lua file includes all files that match the pattern "CityStateQuests_Helpers_{something}.lua", so we need to create a "CityStateQuests_Helpers_Circumnavigate.lua" file and set it to be VFS=true (do NOT add it as an InGameUIAddin).
Each function receives five parameters that we could use to return a different icon (see the standard GetReligionQuestIcon function in CityStateStatusHelper.lua file) or format the tooltip (see the GetTourismContestQuestTooltip function below)
Code:
--
-- This file MUST follow the naming pattern of "CityStateQuests_Helpers_{something}.lua"
--
function GetCircumnavigateQuestIcon(iMajor, iMinor, iQuest, iData1, iData2)
return "[ICON_TRADE]"
end
function GetCircumnavigateQuestTooltip(iMajor, iMinor, iQuest, iData1, iData2)
return Locale.Lookup("TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_FORMAL")
end
Our TXT_KEY is very simple
Code:
<Language_en_US>
<Row Tag="TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_FORMAL">
<Text>They want the map circumnavigated.</Text>
</Row>
</Language_en_US>
As we're looking at TXT_KEYs the ones for the won (finished) and lost (cancelled) notifications are
Code:
<Language_en_US>
<!-- We can ONLY use the {1_MinorName:textkey} replacement, and {2_InfluenceReward} for _FINISH, no others are available -->
<Row Tag="TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_FINISH">
<Text>You have successfully circumnavigated the map, much to the delight of {1_MinorName:textkey}! Your [ICON_INFLUENCE] Influence over them has increased by [COLOR_POSITIVE_TEXT]{2_InfluenceReward}[ENDCOLOR].</Text>
</Row>
<Row Tag="TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_FINISH_S">
<Text>Map circumnavigated for {1_MinorName:textkey}!</Text>
</Row>
<Row Tag="TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_CANCEL">
<Text>The map has been circumnavigated by someone else!</Text>
</Row>
<Row Tag="TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_CANCEL_S">
<Text>Someone Else Circumnavigated For {1_MinorName:textkey}</Text>
</Row>
</Language_en_US>
Note that we get the City State name passed as a parameter/substition ({1_MinorName:textkey}) for these TXT_KEYs, while the finished pair also receive the amount of friendship/influence gained ({2_InfluenceReward}).
And now for the GameEvent handlers.
Every custom quest must handle the QuestIsAvailable GameEvent - this event is used to ascertain if the quest can be started by a specific player for a specific City State. We are going to keep this example simple, so our only condition for starting the quest is that the globe must not have already been circumnavigated and that the player must be in the Medieval Era
Code:
local iThisQuest = GameInfoTypes.MINOR_CIV_QUEST_CIRCUMNAVIGATE
local iEraMinimum = GameInfoTypes.ERA_MEDIEVAL
function OnQuestIsAvailable(iPlayer, iCS, iQuest, bNewQuest, iData1, iData2)
if (iQuest == iThisQuest) then
-- If the map has already been circumnavigated, this quest is no longer available
if (iPlayerCircumnavigating == -1) then
-- Only let the player join in if they are in (or past) the appropriate era
-- We could do something more complex here like checking for the ability to cross oceans (but that assumes a map with at least some water)
-- or we could check the width of the map being greater than a certain value and WrapX being true
return Players[iPlayer]:GetCurrentEra() >= iEraMinimum
end
end
return false
end
GameEvents.QuestIsAvailable.Add(OnQuestIsAvailable)
We'll worry about how iPlayerCircumnavigating gets set later, but it records which player, if anyone, was the first to circumnavigate the globe.
As we didn't specify values in the Quests entry for StartSummary and StartMessage, we need to handle the QuestSendNotification GameEvent to generate the notification to the player that the City State has issued the challenge
Code:
function OnQuestSendNotification(iPlayer, iCS, iQuest, iStartTurn, iEndTurn, iData1, iData2, bStart, bFinish, sNames)
if (iQuest == iThisQuest) then
if (bStart) then
-- Notify the player the quest has started
-- If both StartMessage and StartSummary are present in the quest definition, this will never occur
-- While we could handle this with simple TXT_KEYs, we'll manually send the notification here as an example
local sMinor = Players[iCS]:GetName()
local sMessage, sSummary
if (Map.IsWrapX()) then
-- Map wraps, so we'll ask for circumnavigation
sMessage = "TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_START"
sSummary = "TXT_KEY_MINOR_CIV_QUEST_CIRCUMNAVIGATE_START_S"
else
-- Map is a single continent, so we'll ask the player to cross it
-- To implement this, needs MUCH more work
sMessage = "TXT_KEY_MINOR_CIV_QUEST_TRANSCONTINENT_START"
sSummary = "TXT_KEY_MINOR_CIV_QUEST_TRANSCONTINENT_START_S"
end
Players[iPlayer]:AddQuestNotification(iCS, Locale.ConvertTextKey(sMessage, sMinor), Locale.ConvertTextKey(sSummary, sMinor), -1, -1, true)
end
end
end
GameEvents.QuestSendNotification.Add(OnQuestSendNotification)
Note that the same GameEvent is used for all three notifications (if the TXT_KEYs aren't in the Quests table), you differentiate the notification to send with the bStart and bFinish parameters (these will NEVER both be true).
Also note the new API method on the Player object pPlayer:AddQuestNotification(iCS, sMsg, sSum, iX, iY, bNewQuest) for sending the notification. While you can use the standard pPlayer:AddNotification() method, it is NOT recommended as it will not produce a notification that behaves the same as the standard City State quests.
This custom quest must also handle the QuestIsCompleted and QuestIsExpired GameEvents. The former returns true if the player has won, the latter returns true if the player has lost, return false means that the quest is ongoing. Our handlers just need to ascertain if the player who actually circumnavigated the globe is the current player.
Code:
function OnQuestIsCompleted(iPlayer, iCS, iQuest, bLastTurn)
if (iQuest == iThisQuest) then
-- Have we completed the quest by being the first player to circumnavigate the map?
return (iPlayer == iPlayerCircumnavigating)
end
return false
end
GameEvents.QuestIsCompleted.Add(OnQuestIsCompleted)
function OnQuestIsExpired(iPlayer, iCS, iQuest)
if (iQuest == iThisQuest) then
-- Did someone beat us to it?
return (iPlayerCircumnavigating ~= -1 and iPlayerCircumnavigating ~= iPlayer)
end
return false
end
GameEvents.QuestIsExpired.Add(OnQuestIsExpired)
So the City State issues the challenge and waits for someone to circumnavigate the globe. Presumably the player will build a ship and start exploring ... but what about the AI?
The DLL sends a QuestStart GameEvent at the commencement of the quest, so we can hook this and add some logic to give the AI a nudge in the right direction. We could check its units for some ships capable of exploration and if not locate a coastal city or three and add an exploration type ship to the build queue and hope they get built and then explore. Alternatively, we can just make sure the AI strategy to recon the sea is active (via a new API method).
Code:
function OnQuestStart(iPlayer, iCS, iQuest, bNewQuest, iStartTurn, iData1, iData2)
if (iQuest == iThisQuest) then
if (not Players[iPlayer]:IsHuman()) then
-- Direct the AI to attempt to circumnavigate by activating the strategy to recon at sea
Players[iPlayer]:ActivateEconomicStrategy(GameInfoTypes.ECONOMICAISTRATEGY_NEED_RECON_SEA)
end
end
end
GameEvents.QuestStart.Add(OnQuestStart)
Finally, we need to look at how the iPlayerCircumnavigating variable gets set. We use the CircumnavigatedGlobe GameEvent along with some simple data persistence
Code:
--
-- House-keeping of who circumnavigated first
--
local modDB = Modding.OpenSaveData()
local iPlayerCircumnavigating = modDB.GetValue("MINOR_CIV_QUEST_CIRCUMNAVIGATE_PLAYER") or -1
function OnCircumnavigatedGlobe(iTeam)
-- We get the team that circumnavigated the globe, so we'll give the win to the team leader
-- As an example this is acceptable, a production mod may want to give the win to all team members,
-- or track the current player and just give it to them
iPlayerCircumnavigating = Players[Teams[iTeam]:GetLeaderID()]:GetID()
modDB.SetValue("MINOR_CIV_QUEST_CIRCUMNAVIGATE_PLAYER", iPlayerCircumnavigating)
GameEvents.CircumnavigatedGlobe.Remove(OnCircumnavigatedGlobe)
end
if (iPlayerCircumnavigating == -1) then
GameEvents.CircumnavigatedGlobe.Add(OnCircumnavigatedGlobe)
end
The complete code is in the "Quests - Circumnavigate" mod