whoward69
DLL Minion
The aim of this tutorial is to show you how to communicate with the player from within the C++ code.
This tutorial assumes you are familiar with the techniques presented in the "Replacing hard-coded constants with database values" tutorial, specifically adding your own custom header file and the pre-processor techniques. If you haven't already done so, please read that tutorial first.
This tutorial also assumes that you have
Most of the time the DLL is executing silently - determining if a unit can enter a plot, calculating yields, sending events, responding to Lua requests, etc - but occasionally something happens within the C++ code that the player needs to be aware of - entering a new era, discovering a new tech, attrition on a unit, etc.
There are four ways to send a message to the player from with the C++ code
This tutorial looks at how to send each of these message types.
Placing a message at the top of the screen is the simplest, as we just need to call the correct method in the UI code
Colour and icon tags in the message text will display correctly, eg "You have gained [ICON_GOLD] [COLOR_YELLOW]+3 Gold[ENDCOLOR]"
There are three related methods AddCityMessage(), AddUnitMessage() and AddPlotMessage() which appear to be throw-backs to previous versions of the code base.
These four methods take several optional parameters, but these also seem to be left over from previous versions of the game core and aren't used within the active code base and don't seem to do anything anyway. Similarly the 1st, 3rd and 4th parameters appear to be redundant in the current code base.
Given all the redundant parameters it makes sense to create some macros to make using these methods more succinct.
(Note: ePlayer is the id of the player to receive the message and not necessarily the owner of the city, unit or plot.)
Typically we would already have a reference to pPlayer and be using a TXT_KEY_ to locate and format the message to display. So we can rewrite the code above as
If the message is related to an event at a plot, we can float the text up from the plot in question - similar to the healing/damage message for a unit. We need to take care that the active player has actually revealed the plot.
(Note: If you are sending lots of popup text for the same plot, to avoid the messages overlaying each other, increase the delay for the second and subsequent ones by GC.getPOST_COMBAT_TEXT_DELAY())
Again, this lends itself to a macro
(Note: The macro takes a specified player to receive the message and doesn't assume the active player - to fit the pattern of the other macros.)
The code now becomes
but we would typically already have ePlayer in hand.
With our five macros defined, it becomes a simple matter to add these methods to the associated Lua objects, such that we could write in Lua
Omitting (for brevity) the required LUAAPIEXTN() and Method() macro usage, the required code is
In CvLuaPlayer.cpp
In CvLuaCity.cpp
In CvLuaUnit.cpp
In CvLuaPlot.cpp
Sliding a notification down the right-hand side of the screen is also straight forward, providing the notification type you want to send already exists. There are several mods that permit new notification types to be defined and sent, but using those is beyond the scope of this tutorial.
The simplest notification type to send is the generic notification - which displays as an exclamation mark icon. Every notification has two pieces of text associated with it, the summary that is displayed to the side of the icon as the notification slides down the screen and the message that displays as the mouse hovers over the icon.
The four parameters after the text strings are used in different ways by different notifications. The first two are the x and y co-ordinates of the plot the notification is for. Not all notifications are related to a plot (eg a golden age starting or ending) in which case these should both be set to -1. The generic notification only uses these first two, moving the camera to look at the plot if the user clicks the notification.
The wonder completed notification uses the first two to pass the plot of the city the wonder was built in (if known to the player) and the third one to pass the building id of the completed wonder, whereas the war/peace notifications don't use the first two at all and only use the third and fourth ones to pass the player ids of those involved. Basically you need to know what the notification expects and pass the correct values!
If you're working with a team and need to send a notification to every player on the team, the CvTeam::AddNotification() method wraps the required functionality. We can add our own over-loaded wrapper method to the CvPlayer class to also make sending notifications to a player easier.
In CvPlayer.h
In CvPlayer.cpp
Which gives us the full range of simply sending notifications to the player
None of these three approaches actually force the player to acknowledge the message. If we want the player to sit up and take note, we need to display the message in such a way that the player must click a button before they can proceed. To do this we will put the message into the text popup dialog.
CvPopupInfo is defined in CvGameCoreDLLUtil\CvStructs.h and contains the data members that can be accessed from the Lua UI for popup dialogs, namely iData1, iData2, iData3, iFlags, bOption1, bOption2, eButtonPopupType and szText. All but szText can be set from the constructor. How iData1, iData2, iData3, iFlags, bOption1 and bOption2 are interpreted is up to the individual popup.
For example, the simple text popup dialog uses iData1 to set the wrap width (default 400), so if we have a lot of text in the message we can double the width of the dialog as follows
Care needs to be taken when displaying a popup that the player has to make a decision in that can require the player to take further actions, eg selecting a great person, in that if the player has already pressed the End Turn button they must be returned to their turn to take those further actions, eg moving/using the great person. If the popup dialog meets this criteria it is manadatory to add the following code after the pop-up is queued with the DLLUI->AddPopup() method
[END]
This tutorial assumes you are familiar with the techniques presented in the "Replacing hard-coded constants with database values" tutorial, specifically adding your own custom header file and the pre-processor techniques. If you haven't already done so, please read that tutorial first.
This tutorial also assumes that you have
- Knowledge of Lua and the Civ 5 objects, eg Player, City, Unit et al
- An understanding of the difference between (and significance of) the . and the : in Map.GetPlot() and pPlot:GetX()
- A basic knowledge of C programming and an understanding of object-orientated programming as implemented by C++ (if you don't know the significance of :: and the difference between -> and . then this tutorial is probably not for you)
- Successfully built, deployed and loaded the unaltered source as a custom DLL mod
- Created your own custom header file and added it to the CvGameCoreDLLPCH.h file
Most of the time the DLL is executing silently - determining if a unit can enter a plot, calculating yields, sending events, responding to Lua requests, etc - but occasionally something happens within the C++ code that the player needs to be aware of - entering a new era, discovering a new tech, attrition on a unit, etc.
There are four ways to send a message to the player from with the C++ code
- as a banner message centrally at the top of the map
- as a short piece of text floating up from a plot (similar to the healing/damage a unit receives)
- as a notification in the right hand notifications area
- as a popup dialog
This tutorial looks at how to send each of these message types.
Placing a message at the top of the screen is the simplest, as we just need to call the correct method in the UI code
Code:
// Add a message to the top of the screen for the current player
const PlayerTypes ePlayer = GC.getGame().getActivePlayer();
const char* sBannerMsg = "Hello World!";
DLLUI->AddMessage(0, ePlayer, false, GC.getEVENT_MESSAGE_TIME(), sBannerMsg);
Colour and icon tags in the message text will display correctly, eg "You have gained [ICON_GOLD] [COLOR_YELLOW]+3 Gold[ENDCOLOR]"
There are three related methods AddCityMessage(), AddUnitMessage() and AddPlotMessage() which appear to be throw-backs to previous versions of the code base.
These four methods take several optional parameters, but these also seem to be left over from previous versions of the game core and aren't used within the active code base and don't seem to do anything anyway. Similarly the 1st, 3rd and 4th parameters appear to be redundant in the current code base.
Given all the redundant parameters it makes sense to create some macros to make using these methods more succinct.
Code:
// Message wrappers
#define SHOW_PLAYER_MESSAGE(pPlayer, szMessage) DLLUI->AddMessage(0, pPlayer->GetID(), false, GC.getEVENT_MESSAGE_TIME(), szMessage)
#define SHOW_CITY_MESSAGE(pCity, ePlayer, szMessage) DLLUI->AddCityMessage(0, pCity->GetIDInfo(), ePlayer, false, GC.getEVENT_MESSAGE_TIME(), szMessage)
#define SHOW_UNIT_MESSAGE(pUnit, ePlayer, szMessage) DLLUI->AddUnitMessage(0, pUnit->GetIDInfo(), ePlayer, false, GC.getEVENT_MESSAGE_TIME(), szMessage)
#define SHOW_PLOT_MESSAGE(pPlot, ePlayer, szMessage) DLLUI->AddPlotMessage(0, pPlot->GetPlotIndex(), ePlayer, false, GC.getEVENT_MESSAGE_TIME(), szMessage)
(Note: ePlayer is the id of the player to receive the message and not necessarily the owner of the city, unit or plot.)
Typically we would already have a reference to pPlayer and be using a TXT_KEY_ to locate and format the message to display. So we can rewrite the code above as
Code:
SHOW_PLAYER_MESSAGE(pPlayer, Localization::Lookup("TXT_KEY_HELLO_WORLD").toUTF8());
If the message is related to an event at a plot, we can float the text up from the plot in question - similar to the healing/damage message for a unit. We need to take care that the active player has actually revealed the plot.
Code:
// Float a (short) message up from a plot, but only if the active player can see the plot
const char* sPopupMsg = "BOO!";
const float fPopupDelay = 0.0;
if (pPopupPlot->GetActiveFogOfWarMode() == FOGOFWARMODE_OFF) {
DLLUI->AddPopupText(pPopupPlot->getX(), pPopupPlot->getY(), sPopupMsg, fPopupDelay);
}
(Note: If you are sending lots of popup text for the same plot, to avoid the messages overlaying each other, increase the delay for the second and subsequent ones by GC.getPOST_COMBAT_TEXT_DELAY())
Again, this lends itself to a macro
Code:
#define SHOW_PLOT_POPUP(pPlot, ePlayer, szMessage, fDelay) if (pPlot->isVisible(GET_PLAYER(ePlayer).getTeam())) DLLUI->AddPopupText(pPlot->getX(), pPlot->getY(), szMessage, fDelay)
(Note: The macro takes a specified player to receive the message and doesn't assume the active player - to fit the pattern of the other macros.)
The code now becomes
Code:
const PlayerTypes ePlayer = GC.getGame().getActivePlayer();
SHOW_PLOT_POPUP(pPopupPlot, ePlayer, "BOO!", 0.0);
but we would typically already have ePlayer in hand.
With our five macros defined, it becomes a simple matter to add these methods to the associated Lua objects, such that we could write in Lua
Code:
pPlayer:AddMessage(sMessage)
pCity:AddMessage(sMessage) -- Default to the owner of the city
pCity:AddMessage(sMessage, iForPlayer)
pUnit:AddMessage(sMessage) -- Default to the owner of the unit
pUnit:AddMessage(sMessage, iForPlayer)
pPlot:AddMessage(sMessage) -- Default to the current player
pPlot:AddMessage(sMessage, iForPlayer)
pPlot:AddPopupMessage(sMessage) -- No delay and default to the current player
pPlot:AddPopupMessage(sMessage, fDelay) -- Default to the current player
pPlot:AddPopupMessage(sMessage, fDelay, iForPlayer)
Omitting (for brevity) the required LUAAPIEXTN() and Method() macro usage, the required code is
In CvLuaPlayer.cpp
Code:
#if defined(MOD_API_LUA_EXTENSIONS)
//------------------------------------------------------------------------------
int CvLuaPlayer::lAddMessage(lua_State* L)
{
SHOW_PLAYER_MESSAGE(GetInstance(L), lua_tostring(L, 2));
return 0;
}
#endif
In CvLuaCity.cpp
Code:
#if defined(MOD_API_LUA_EXTENSIONS)
//------------------------------------------------------------------------------
int CvLuaCity::lAddMessage(lua_State* L)
{
CvCity* pCity = GetInstance(L);
const char* szMessage = lua_tostring(L, 2);
const PlayerTypes ePlayer = (PlayerTypes) luaL_optinteger(L, 3, pCity->getOwner());
SHOW_CITY_MESSAGE(pCity, ePlayer, szMessage);
return 0;
}
#endif
In CvLuaUnit.cpp
Code:
#if defined(MOD_API_LUA_EXTENSIONS)
//------------------------------------------------------------------------------
int CvLuaUnit::lAddMessage(lua_State* L)
{
CvUnit* pUnit = GetInstance(L);
const char* szMessage = lua_tostring(L, 2);
const PlayerTypes ePlayer = (PlayerTypes) luaL_optinteger(L, 3, pUnit->getOwner());
SHOW_UNIT_MESSAGE(pUnit, ePlayer, szMessage);
return 0;
}
#endif
In CvLuaPlot.cpp
Code:
#if defined(MOD_API_LUA_EXTENSIONS)
//------------------------------------------------------------------------------
int CvLuaPlot::lAddMessage(lua_State* L)
{
CvPlot* pPlot = GetInstance(L);
const char* szMessage = lua_tostring(L, 2);
const PlayerTypes ePlayer = (PlayerTypes) luaL_optinteger(L, 3, GC.getGame().getActivePlayer());
SHOW_PLOT_MESSAGE(pPlot, ePlayer, szMessage);
return 0;
}
//------------------------------------------------------------------------------
int CvLuaPlot::lAddPopupMessage(lua_State* L)
{
CvPlot* pPlot = GetInstance(L);
const char* szMessage = lua_tostring(L, 2);
const float fDelay = (float) luaL_optnumber(L, 3, 0.0);
const PlayerTypes ePlayer = (PlayerTypes) luaL_optinteger(L, 4, GC.getGame().getActivePlayer());
SHOW_PLOT_POPUP(pPlot, ePlayer, szMessage, fDelay);
return 0;
}
#endif
Sliding a notification down the right-hand side of the screen is also straight forward, providing the notification type you want to send already exists. There are several mods that permit new notification types to be defined and sent, but using those is beyond the scope of this tutorial.
The simplest notification type to send is the generic notification - which displays as an exclamation mark icon. Every notification has two pieces of text associated with it, the summary that is displayed to the side of the icon as the notification slides down the screen and the message that displays as the mouse hovers over the icon.
Code:
// Send a generic notification to the active player
CvPlayer* pActivePlayer = GET_PLAYER(GC.getGame().getActivePlayer());
CvNotifications* pNotifications = pActivePlayer->GetNotifications();
if (pNotifications) {
// If this notification relates to a specific plot, set the x and y co-ordinates below
int iPlotX = -1;
int iPloyY = -1;
// Confusingly, the message comes before the summary in the parameter list!
pNotifications->Add(NOTIFICATION_GENERIC, sMessage, sSummary, iPlotX, iPloyY, -1, -1);
}
The four parameters after the text strings are used in different ways by different notifications. The first two are the x and y co-ordinates of the plot the notification is for. Not all notifications are related to a plot (eg a golden age starting or ending) in which case these should both be set to -1. The generic notification only uses these first two, moving the camera to look at the plot if the user clicks the notification.
The wonder completed notification uses the first two to pass the plot of the city the wonder was built in (if known to the player) and the third one to pass the building id of the completed wonder, whereas the war/peace notifications don't use the first two at all and only use the third and fourth ones to pass the player ids of those involved. Basically you need to know what the notification expects and pass the correct values!
If you're working with a team and need to send a notification to every player on the team, the CvTeam::AddNotification() method wraps the required functionality. We can add our own over-loaded wrapper method to the CvPlayer class to also make sending notifications to a player easier.
In CvPlayer.h
Code:
#if defined(MOD_API_EXTENSIONS)
int AddNotification(NotificationTypes eNotificationType, const char* sMessage, const char* sSummary, CvPlot* pPlot = NULL, int iGameDataIndex = -1, int iExtraGameData = -1);
int AddNotification(NotificationTypes eNotificationType, const char* sMessage, const char* sSummary, int iGameDataIndex, int iExtraGameData = -1);
#endif
In CvPlayer.cpp
Code:
#if defined(MOD_API_EXTENSIONS)
int CvPlayer::AddNotification(NotificationTypes eNotificationType, const char* sMessage, const char* sSummary, int iGameDataIndex, int iExtraGameData)
{
return AddNotification(eNotificationType, sMessage, sSummary, NULL, iGameDataIndex, iExtraGameData);
}
int CvPlayer::AddNotification(NotificationTypes eNotificationType, const char* sMessage, const char* sSummary, CvPlot* pPlot, int iGameDataIndex, int iExtraGameData)
{
int iNotification = -1;
CvNotifications* pNotifications = GetNotifications();
if (pNotifications) {
const int iPlotX = pPlot ? pPlot->getX() : -1;
const int iPlotY = pPlot ? pPlot->getY() : -1;
iNotification = pNotifications->Add(eNotificationType, sMessage, sSummary, iPlotX, iPlotY, iGameDataIndex, iExtraGameData);
}
return iNotification;
}
#endif
Which gives us the full range of simply sending notifications to the player
Code:
pPlayer->AddNotification(eNotification, sMessage, sSummary);
pPlayer->AddNotification(eNotification, sMessage, sSummary, pPlot);
pPlayer->AddNotification(eNotification, sMessage, sSummary, pPlot, iData1);
pPlayer->AddNotification(eNotification, sMessage, sSummary, pPlot, iData1, iData2);
pPlayer->AddNotification(eNotification, sMessage, sSummary, iData1);
pPlayer->AddNotification(eNotification, sMessage, sSummary, iData1, iData2);
None of these three approaches actually force the player to acknowledge the message. If we want the player to sit up and take note, we need to display the message in such a way that the player must click a button before they can proceed. To do this we will put the message into the text popup dialog.
Code:
// Popup a text dialog for the active player
CvPopupInfo kPopupInfo(BUTTONPOPUP_TEXT);
strcpy_s(kPopupInfo.szText, "Some dialog text");
DLLUI->AddPopup(kPopupInfo);
CvPopupInfo is defined in CvGameCoreDLLUtil\CvStructs.h and contains the data members that can be accessed from the Lua UI for popup dialogs, namely iData1, iData2, iData3, iFlags, bOption1, bOption2, eButtonPopupType and szText. All but szText can be set from the constructor. How iData1, iData2, iData3, iFlags, bOption1 and bOption2 are interpreted is up to the individual popup.
For example, the simple text popup dialog uses iData1 to set the wrap width (default 400), so if we have a lot of text in the message we can double the width of the dialog as follows
Code:
// Popup a text dialog for the active player
CvPopupInfo kPopupInfo(BUTTONPOPUP_TEXT, 800);
strcpy_s(kPopupInfo.szText, "Some extra wide text to display in the popup dialog");
DLLUI->AddPopup(kPopupInfo);
Care needs to be taken when displaying a popup that the player has to make a decision in that can require the player to take further actions, eg selecting a great person, in that if the player has already pressed the End Turn button they must be returned to their turn to take those further actions, eg moving/using the great person. If the popup dialog meets this criteria it is manadatory to add the following code after the pop-up is queued with the DLLUI->AddPopup() method
Code:
// If adding a popup that the player must make a choice in, make sure they are not in the end-turn phase.
CvPlayer* pActivePlayer = &GET_PLAYER(GC.getGame().getActivePlayer());
if (pActivePlayer.isLocalPlayer())
CancelActivePlayerEndTurn();
[END]