[DLL/C++] Communicating with the player

whoward69

DLL Minion
Joined
May 30, 2011
Messages
8,720
Location
Near Portsmouth, UK
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
  1. Knowledge of Lua and the Civ 5 objects, eg Player, City, Unit et al
  2. An understanding of the difference between (and significance of) the . and the : in Map.GetPlot() and pPlot:GetX()
  3. 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)
  4. Successfully built, deployed and loaded the unaltered source as a custom DLL mod
  5. 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]
 
Please use this thread in the SDK / Lua sub-forum for asking questions about this tutorial, I will then update/expand this thread as necessary.

This is NOT a tutorial on basic C/C++ programming, so please do not ask questions about the syntax, semantics or usage of the C++ language - there are many excellent resources for learning C++ available (most of which can be found in your local bookshop)

Also, please do NOT ask questions about specific issues for your own mods and/or ask for someone else to write your code for you on these threads - start a new thread either in the main C&C forum or the SDK / Lua sub-forum to ask your specific question/request, as that way you'll get specific answers ;) And finally, please note that I do NOT take requests for writing code for others' mods!
 
Back
Top Bottom