Alert message when unit takes terrain/distance damage

<Nexus>

Traveler of the Multiverse
Joined
Jan 23, 2014
Messages
5,554
Location
In a constant brainstorm...
So I'd like to continue this topic here and ask for @f1rpo 's help
This, for example, shows the "your Farm has been destroyed by a marauding Barbarian Warrior" message.

I am very noob for programming but tying to learn a thing or two :)

So I found these code parts that I consider relevant:
C++:
int CvPlot::getTerrainTurnDamage(CvSelectionGroup* pGroup) const
{
    if (!GC.getGameINLINE().isModderGameOption(MODDERGAMEOPTION_TERRAIN_DAMAGE))
    {
        return 0;
    }
    CLLNode<IDInfo>* pUnitNode = pGroup->headUnitNode();
    CvUnit* pLoopUnit;
    int        iMaxDamage = 0;

    FAssertMsg(pUnitNode != NULL, "headUnitNode() expected to be non-NULL");

    while (pUnitNode != NULL)
    {
        pLoopUnit = ::getUnit(pUnitNode->m_data);
        pUnitNode = pGroup->nextUnitNode(pUnitNode);

        int iDamage = getTerrainTurnDamage(pLoopUnit);

        if ( iDamage > iMaxDamage )
        {
            iMaxDamage = iDamage;
        }
    }

    return iMaxDamage;
}

int CvPlot::getTerrainTurnDamage(CvUnit* pUnit) const
{
    //PROFILE_FUNC();

    int iDamagePercent = -GC.getTerrainInfo(getTerrainType()).getHealthPercent();
// Movement Limits by 45deg - START
    if (pUnit != NULL)
    {
        if (GC.getGameINLINE().isOption(GAMEOPTION_MOVEMENT_LIMITS))
        {
            if (pUnit->isOutsideMovementLimits(this))
            {
                iDamagePercent += 25;
            }
        }
    }
// Movement Limits by 45deg - END

and this:

C++:
int CvPlot::getTotalTurnDamage(CvUnit* pUnit) const
{
    return getTerrainTurnDamage(pUnit) + getFeatureTurnDamage();
}

I think this second part is the place to insert the code to print an alert. Maybe something like this:

C++:
int CvPlot::getTotalTurnDamage(CvUnit* pUnit) const
{
    return getTerrainTurnDamage(pUnit) + getFeatureTurnDamage()
        
                {
                    szBuffer = gDLL->getText("TXT_KEY_MISC_IMP_DESTROYED", GC.getImprovementInfo(pPlot->getImprovementType()).getTextKeyWide(), getNameKey(), getVisualCivAdjective(pPlot->getTeam()));
                    gDLL->getInterfaceIFace()->addMessage(pPlot->getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGED", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true);
                }
}

Am I at least on the right track? :mischief:

CvPlot.cpp attached too.
 

Attachments

  • CvPlot.cpp.txt
    440.8 KB · Views: 16
The message should ideally be shown when the damage gets applied, not already when the damage gets calculated, because the calculation could also happen for UI or AI purposes. In fact, a full-text search for getTotalTurnDamage calls shows that this (AND) function is currently used only for AI decisions.

getTerrainTurnDamage gets called for applying damage in CvUnit::doTurn, and feature damage is also handled there (by essentially re-implementing CvPlot::getFeatureDamage – but let's not make that redundancy our problem). I'd put the message at the start of the if block (i.e. just before the #ifdef MULTI_FEATURE_MOD); this ensures that the unit is still alive, so it'll be safe to obtain its name or any other info needed for the message.

I assume that you want to show a single message for the total damage taken, and it seems that getTotalTurnDamage can be used for this. The message could say e.g. "Your Spearman has taken 15 damage from exposure." Not sure how else to state the damage source; could be either feature or being too far away (out of supply I guess). "From the environment" would work too.

AND uses a wrapper function named AddDLLMessage (global function in CvGameCoreUtils) for showing interface messages. (There's also AddMessage, but AddDLLMessage seems to be more widely used. No comments to indicate which one should be preferred.) So, untested code:
Code:
{
    int iTotalEnvirDmg = plot()->getTotalTurnDamage(this);
    if (iTotalEnvirDmg != 0)
    {
        CvWString szBuffer = gDLL->getText("TXT_KEY_ENVIR_DAMAGE_TAKEN",
                getNameKey(), iTotalEnvirDmg);
        AddDLLMessage(getOwner(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer,
                NULL, MESSAGE_TYPE_INFO, getButton(),
                (ColorTypes)GC.getInfoTypeForString("COLOR_RED"),
                pPlot->getX(), pPlot->getY(), true, true);
    }
}
Notes: The iTotalEnvirDmg variable is mainly there to avoid calling getTotalTurnDamage twice. Performance isn't a concern here, so this just a stylistic choice. And the outer curly braces are there to limit the lifetime of that variable, i.e. to make clear that it will have no relevancy later on in the function; also a stylistic choice. Could also write
Code:
if (plot()->getTotalTurnDamage(this) != 0)
{
    CvWString szBuffer = gDLL->getText("TXT_KEY_ENVIR_DAMAGE_TAKEN",
            getNameKey(), plot()->getTotalTurnDamage(this));
    AddDLLMessage(/* etc. */);
}
The getTotalTurnDamage call is a bit clunky with the this parameter. The person who implemented that function probably should've put it at CvUnit, in which case the call would simply be getTotalTurnDamage(). The szBuffer variable is needed because AddDLLMessage expects a CvWString, whereas getText returns a C-style string (wchar pointer). In the code you had copied, the variable was declared at some earlier point. (The original BtS code declares variables only at the start of a function – a convention that was already widely discouraged at the time, iirc. Most modders with experience in C++ have not stuck to that convention.)

The parameters of AddDLLMessage are:
Code:
    PlayerTypes ePlayer, bool bForce, int iLength, CvWString szString, LPCTSTR pszSound,
    InterfaceMessageTypes eType, LPCSTR pszIcon, ColorTypes eFlashColor,
    int iFlashX, int iFlashY, bool bShowOffScreenArrows, bool bShowOnScreenArrows
So the message is sent to the unit owner (getOwnerINLINE is marginally faster than getOwner, but, if that ever matters, one can simply inline getOwner as well; won't do any harm so long as we're unable to recompile the EXE; same for getX/Y_INLINE), it's not "forced", meaning that it'll appear at the start of the owner's turn rather than instantly, but, since CvUnit::doTurn happens at the start of a turn, that should really make no difference. Well, I guess forcing the message would eliminate the delay in between messages. The display duration iLength is always either EVENT_MESSAGE_TIME (10 s) or EVENT_MESSAGE_TIME_LONG (20 s) from GlobalDefines. NULL for the sound means that none is played. Don't know which one would be appropriate and it's of course potentially annoying. Message types for this sort of message are INFO, MINOR_EVENT, MAJOR_EVENT or DISPLAY_ONLY (cf. CvEnums.h). The message type determines how long the message will be kept in the turn log (Ctrl+Tab). For the balloon icon, I use the unit button graphic (same as for the pillage message). The "flash" color sets the text color (i.e. there's no point in trying to color the message through XML) and I think also the color of the balloon. The coordinates are for the balloon, but also allow jumping to the plot from the turn log. Not sure about the exact effect of the arrow flags off the top of my head.

TXT_KEY_ENVIR_DAMAGE_TAKEN is still to be defined in XML, with a %s1 parameter for the unit name and %d2 for the amount of damage. Apart from that, have I already done all the work? Maybe not. One can put a lot of effort into making such messages as concise, informative and context-sensitive as possible. If the unit dies, then the message should arguably say that, instead of showing the amount of damage. Maybe other players should get a message too if the unit is visible to them. (This could avoid confusion about rival units just disappearing.) What if a large stack takes damage? That could flood the screen and log with messages. "Your Spearman and 7 other units have each taken 15 damage" might be nice (though this doesn't spell out whether any units have died); not trivial to implement as doTurn gets called for each unit individually. For a start, one could show a message only for the center unit (CvPlot::getCenterUnit). Though that's not going to work correctly when the center unit is a Spy or some other non-combat unit. Could also state whether the unit is taking damage for being out of supply (MOVEMENT_LIMITS) or whether it's taking feature damage. (I don't think I'd bother with that.)

Oh, and if you want to make those messages optional (if they're well done, then probably they don't need to be), that will also take some extra work. I wouldn't literally implement the messages as Civ4lerts (i.e. fully in Python - I don't think that would even be possible), but the option checkbox could still go on the Alerts tab of the BUG menu.

Edit: clarity
 
Last edited:
I would suggest you change the text to "Your [unit] has taken [number] damage from being outside of supply range." Mostly because that would actually tell people what the problem is.
 
"Terrain damage" is a somewhat misleading name for this effect (even though there are some exception based on terrain and features). The respective game option is named "Movement Limits" fwiw.
Feature damage is sensibly named and really its own thing. So perhaps a separate message should be shown for that – "Your %s1_UnitName has taken %d2 damage from %s3_FeatureName."
 
"Terrain damage" is a somewhat misleading name for this effect (even though there are some exception based on terrain and features). The respective game option is named "Movement Limits" fwiw.
Feature damage is sensibly named and really its own thing. So perhaps a separate message should be shown for that – "Your %s1_UnitName has taken %d2 damage from %s3_FeatureName."
In his mod units have a limited range of movement in the early game. The idea being to stop early game exploration and expansion. And this thread is about that.
 
If the Movement Limits option is enabled, yes. I've rewritten part of that option in the AND/ Chronicles DLL, so, in a way, I know it well. On the other hand, I haven't really played with it, so I'm not the ideal person to suggest what the message text should be. About feature damage, let's see, AND uses that for Storms, Plagues, Fallout, Biogas. OK, I thought it might be used for Jungle or so; that could've been confusing when the player gets messages about the out-of-limits damage while units also take damage from hostile features, without explanation. As it is (assuming that it's the same in Chronicles), I guess it's best to consider feature damage a separate issue and only worry about the so-called terrain damage here.
 
Thank you for your thorough explanation f1rpo. I'll reread it tomorrow with a clear (er) mind but now it's late here.

I want a simple message without percentages: "Your X unit has taken damage due to the harsh weather and/or lack of supplies. " Than the playercan click on the message on Messages screen to focus onto the unit.
 
Thank you for your thorough explanation f1rpo. I'll reread it tomorrow with a clear (er) mind but now it's late here.

I want a simple message without percentages: "Your X unit has taken damage due to the harsh weather and/or lack of supplies. " Than the playercan click on the message on Messages screen to focus onto the unit.
Have you considered that a player seeing that would think it was a random event of some sort and not the movement limit game mechanic?

I am asking because the main reason this was started was us players pointing out that the mechanic is not really well explained anywhere to the point that we don't even know it exists until our units start vanishing for no reason and we come to complain about a bug. And so I figure I might suggest that the message you show actually explain what is going on.

PS. I am not saying the mechanic is not a good idea. Indeed it is one of the better ideas in the mod. But like it's just not really explained to the player at all.
 
Last edited:
Top Bottom