C++ Damage output with Separator

Ascuas Funkeln

Rakkedo Game
Joined
Apr 14, 2013
Messages
513
Solutions
30
Reaction score
265
Location
Poland
GitHub
AscuasFunkeln
Hello,
I have problem in my goldfish knowledge in the field of C++, and its create big number separator on dmg dealing.

C++:
        message.primary.value = damage.primary.value; // int32, when change manually to any number visible output of damage is the same as manually entered, so this is ?correct? line to edit :o
        message.secondary.value = damage.secondary.value;

I try make this like that

C++:
#include <iostream>
#include <locale>

std::cout.imbue(std::locale(""));
message.primary.value = std::cout << (damage.primary.value);
message.secondary.value = damage.secondary.value;

The compiler error is
Code:
can't convert std::basic_ostream<char,std::char_traits<char>>" into "int32_t"

Here online run of separator code.
Separator code

Anyone can help me to make this message output as number with separators? :)
C++:
bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage)
{
    const Position& targetPos = target->getPosition();
    if (damage.primary.value > 0) {
        if (target->getHealth() <= 0) {
            return false;
        }

        Player* attackerPlayer;
        if (attacker) {
            attackerPlayer = attacker->getPlayer();
        } else {
            attackerPlayer = nullptr;
        }

        Player* targetPlayer = target->getPlayer();
        if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
            return false;
        }

        if (damage.origin != ORIGIN_NONE) {
            const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE);
            if (!events.empty()) {
                for (CreatureEvent* creatureEvent : events) {
                    creatureEvent->executeHealthChange(target, attacker, damage);
                }
                damage.origin = ORIGIN_NONE;
                return combatChangeHealth(attacker, target, damage);
            }
        }

        int32_t realHealthChange = target->getHealth();
        target->gainHealth(attacker, damage.primary.value);
        realHealthChange = target->getHealth() - realHealthChange;

        if (realHealthChange > 0 && !target->isInGhostMode()) {
            std::string damageString = std::to_string(realHealthChange) + (realHealthChange != 1 ? " Vital Points." : " Vital Points.");

            std::string spectatorMessage;
            if (!attacker) {
                spectatorMessage += ucfirst(target->getNameDescription());
                spectatorMessage += " +" + damageString;
            } else {
                spectatorMessage += ucfirst(attacker->getNameDescription());
                spectatorMessage += " healed ";
                if (attacker == target) {
                    spectatorMessage += (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself" : "himself") : "itself");
                } else {
                    spectatorMessage += target->getNameDescription();
                }
                spectatorMessage += " +" + damageString;
            }

            TextMessage message;
            message.position = targetPos;
            message.primary.value = realHealthChange;
            message.primary.color = TEXTCOLOR_HEALING;

            SpectatorVec list;
            map.getSpectators(list, targetPos, false, true);
            for (Creature* spectator : list) {
                Player* tmpPlayer = spectator->getPlayer();
                if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
                    message.type = MESSAGE_HEALED;
                    message.text = "+" + target->getNameDescription() + " " + damageString;
                } else if (tmpPlayer == targetPlayer) {
                    message.type = MESSAGE_HEALED;
                    if (!attacker) {
                        message.text = "+" + damageString;
                    } else if (targetPlayer == attackerPlayer) {
                        message.text = "+" + damageString;
                    } else {
                        message.text = "+" + attacker->getNameDescription() + " " + damageString;
                    }
                } else {
                    message.type = MESSAGE_HEALED_OTHERS;
                    message.text = spectatorMessage;
                }
                tmpPlayer->sendTextMessage(message);
            }
        }
    } else {
        if (!target->isAttackable()) {
            if (!target->isInGhostMode()) {
                addMagicEffect(targetPos, CONST_ME_POFF);
            }
            return true;
        }

        Player* attackerPlayer;
        if (attacker) {
            attackerPlayer = attacker->getPlayer();
        } else {
            attackerPlayer = nullptr;
        }

        Player* targetPlayer = target->getPlayer();
        if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
            return false;
        }

        damage.primary.value = std::abs(damage.primary.value);
        damage.secondary.value = std::abs(damage.secondary.value);

        int32_t healthChange = damage.primary.value + damage.secondary.value;
        if (healthChange == 0) {
            return true;
        }

        TextMessage message;
        message.position = targetPos;

        SpectatorVec list;
        if (target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) {
            int32_t manaDamage = std::min<int32_t>(target->getMana(), healthChange);
            if (manaDamage != 0) {
                if (damage.origin != ORIGIN_NONE) {
                    const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
                    if (!events.empty()) {
                        for (CreatureEvent* creatureEvent : events) {
                            creatureEvent->executeManaChange(target, attacker, healthChange, damage.origin);
                        }
                        if (healthChange == 0) {
                            return true;
                        }
                        manaDamage = std::min<int32_t>(target->getMana(), healthChange);
                    }
                }

                target->drainMana(attacker, manaDamage);
                map.getSpectators(list, targetPos, true, true);
                addMagicEffect(list, targetPos, CONST_ME_LOSEENERGY);

                std::string damageString = std::to_string(manaDamage);
                std::string spectatorMessage = ucfirst(target->getNameDescription()) + " -" + damageString + " Magic Points";
                if (attacker) {
                    spectatorMessage += " due to ";
                    if (attacker == target) {
                        spectatorMessage += (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack") : "its own attack");
                    } else {
                        spectatorMessage += "an attack by " + attacker->getNameDescription();
                    }
                }
                spectatorMessage += '.';

                message.primary.value = manaDamage;
                message.primary.color = TEXTCOLOR_BLUE;

                for (Creature* spectator : list) {
                    Player* tmpPlayer = spectator->getPlayer();
                    if (tmpPlayer->getPosition().z != targetPos.z) {
                        continue;
                    }

                    if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
                        message.type = MESSAGE_DAMAGE_DEALT;
                        message.text = ucfirst(target->getNameDescription()) + " -" + damageString + " Magic Points due to your attack.";
                    } else if (tmpPlayer == targetPlayer) {
                        message.type = MESSAGE_DAMAGE_RECEIVED;
                        if (!attacker) {
                            message.text = "-" + damageString + " Magic Points.";
                        } else if (targetPlayer == attackerPlayer) {
                            message.text = "-" + damageString + " Magic Points due to your own attack.";
                        } else {
                            message.text = "-" + damageString + " Magic Points due to an attack by " + attacker->getNameDescription() + '.';
                        }
                    } else {
                        message.type = MESSAGE_DAMAGE_OTHERS;
                        message.text = spectatorMessage;
                    }
                    tmpPlayer->sendTextMessage(message);
                }

                damage.primary.value -= manaDamage;
                if (damage.primary.value < 0) {
                    damage.secondary.value = std::max<int32_t>(0, damage.secondary.value + damage.primary.value);
                    damage.primary.value = 0;
                }
            }
        }

        int32_t realDamage = damage.primary.value + damage.secondary.value;
        if (realDamage == 0) {
            return true;
        }

        if (damage.origin != ORIGIN_NONE) {
            const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE);
            if (!events.empty()) {
                for (CreatureEvent* creatureEvent : events) {
                    creatureEvent->executeHealthChange(target, attacker, damage);
                }
                damage.origin = ORIGIN_NONE;
                return combatChangeHealth(attacker, target, damage);
            }
        }

        int32_t targetHealth = target->getHealth();
        if (damage.primary.value >= targetHealth) {
            damage.primary.value = targetHealth;
            damage.secondary.value = 0;
        } else if (damage.secondary.value) {
            damage.secondary.value = std::min<int32_t>(damage.secondary.value, targetHealth - damage.primary.value);
        }

        realDamage = damage.primary.value + damage.secondary.value;
        if (realDamage == 0) {
            return true;
        } else if (realDamage >= targetHealth) {
            for (CreatureEvent* creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) {
                if (!creatureEvent->executeOnPrepareDeath(target, attacker)) {
                    return false;
                }
            }
        }

        target->drainHealth(attacker, realDamage);
        if (list.empty()) {
            map.getSpectators(list, targetPos, true, true);
        }
        addCreatureHealth(list, target);

        message.primary.value = damage.primary.value; // int32, when change manually to any number visible output of damage is the same as manually entered.
        message.secondary.value = damage.secondary.value;

        uint8_t hitEffect;
        if (message.primary.value) {
            combatGetTypeInfo(damage.primary.type, target, message.primary.color, hitEffect);
            if (hitEffect != CONST_ME_NONE) {
                addMagicEffect(list, targetPos, hitEffect);
            }
        }

        if (message.secondary.value) {
            combatGetTypeInfo(damage.secondary.type, target, message.secondary.color, hitEffect);
            if (hitEffect != CONST_ME_NONE) {
                addMagicEffect(list, targetPos, hitEffect);
            }
        }

        if (message.primary.color != TEXTCOLOR_NONE || message.secondary.color != TEXTCOLOR_NONE) {
            std::string damageString = std::to_string(realDamage) + (realDamage != 1 ? " Vital Points" : " Vital Point");
            std::string spectatorMessage = ucfirst(target->getNameDescription()) + " -" + damageString;
            if (attacker) {
                spectatorMessage += " due to ";
                if (attacker == target) {
                    spectatorMessage += (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack") : "its own attack");
                } else {
                    spectatorMessage += "an attack by " + attacker->getNameDescription();
                }
            }
            spectatorMessage += '.';

            for (Creature* spectator : list) {
                Player* tmpPlayer = spectator->getPlayer();
                if (tmpPlayer->getPosition().z != targetPos.z) {
                    continue;
                }

                if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
                    message.type = MESSAGE_DAMAGE_DEALT;
                    message.text = ucfirst(target->getNameDescription()) + " -" + damageString + " due to your attack.";
                } else if (tmpPlayer == targetPlayer) {
                    message.type = MESSAGE_DAMAGE_RECEIVED;
                    if (!attacker) {
                        message.text = "-" + damageString + ' ';
                    } else if (targetPlayer == attackerPlayer) {
                        message.text = "-" + damageString + " due to your own attack.";
                    } else {
                        message.text = "-" + damageString + " due to an attack by " + attacker->getNameDescription() + '.';
                    }
                } else {
                    message.type = MESSAGE_DAMAGE_OTHERS;
                    // TODO: Avoid copying spectatorMessage everytime we send to a spectator
                    message.text = spectatorMessage;
                }
                tmpPlayer->sendTextMessage(message);
            }
        }
    }

    return true;
}

bool Game::combatChangeMana(Creature* attacker, Creature* target, int32_t manaChange, CombatOrigin origin)
{
    if (manaChange > 0) {
        if (attacker) {
            const Player* attackerPlayer = attacker->getPlayer();
            if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && target->getPlayer() && attackerPlayer->getSkullClient(target) == SKULL_NONE) {
                return false;
            }
        }

        if (origin != ORIGIN_NONE) {
            const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
            if (!events.empty()) {
                for (CreatureEvent* creatureEvent : events) {
                    creatureEvent->executeManaChange(target, attacker, manaChange, origin);
                }
                return combatChangeMana(attacker, target, manaChange, ORIGIN_NONE);
            }
        }

        target->changeMana(manaChange);
    } else {
        const Position& targetPos = target->getPosition();
        if (!target->isAttackable()) {
            if (!target->isInGhostMode()) {
                addMagicEffect(targetPos, CONST_ME_POFF);
            }
            return false;
        }

        Player* attackerPlayer;
        if (attacker) {
            attackerPlayer = attacker->getPlayer();
        } else {
            attackerPlayer = nullptr;
        }

        Player* targetPlayer = target->getPlayer();
        if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
            return false;
        }

        int32_t manaLoss = std::min<int32_t>(target->getMana(), -manaChange);
        BlockType_t blockType = target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss);
        if (blockType != BLOCK_NONE) {
            addMagicEffect(targetPos, CONST_ME_POFF);
            return false;
        }

        if (manaLoss <= 0) {
            return true;
        }

        if (origin != ORIGIN_NONE) {
            const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
            if (!events.empty()) {
                for (CreatureEvent* creatureEvent : events) {
                    creatureEvent->executeManaChange(target, attacker, manaChange, origin);
                }
                return combatChangeMana(attacker, target, manaChange, ORIGIN_NONE);
            }
        }

        target->drainMana(attacker, manaLoss);

        std::string damageString = std::to_string(manaLoss);
        std::string spectatorMessage = ucfirst(target->getNameDescription()) + " loses " + damageString + " Magic Points";
        if (attacker) {
            spectatorMessage += " due to ";
            if (attacker == target) {
                spectatorMessage += (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack") : "its own attack");
            } else {
                spectatorMessage += "an attack by " + attacker->getNameDescription();
            }
        }
        spectatorMessage += '.';

        TextMessage message;
        message.position = targetPos;
        message.primary.value = manaLoss;
        message.primary.color = TEXTCOLOR_BLUE;

        SpectatorVec list;
        map.getSpectators(list, targetPos, false, true);
        for (Creature* spectator : list) {
            Player* tmpPlayer = spectator->getPlayer();
            if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
                message.type = MESSAGE_DAMAGE_DEALT;
                message.text = ucfirst(target->getNameDescription()) + " loses " + damageString + " Magic Points due to your attack.";
            } else if (tmpPlayer == targetPlayer) {
                message.type = MESSAGE_DAMAGE_RECEIVED;
                if (!attacker) {
                    message.text = "You lose " + damageString + " Magic Points.";
                } else if (targetPlayer == attackerPlayer) {
                    message.text = "You lose " + damageString + " Magic Points due to your own attack.";
                } else {
                    message.text = "You lose " + damageString + " Magic Points due to an attack by " + attacker->getNameDescription() + '.';
                }
            } else {
                message.type = MESSAGE_DAMAGE_OTHERS;
                message.text = spectatorMessage;
            }
            tmpPlayer->sendTextMessage(message);
        }
    }

    return true;
}
 
Solution
As far as I'm aware you can't. The variable you're trying to write to is an integer (a whole number).

First you'd need to make this change (changing it from an integer to a string) as close to the protocol as possible (so that it doesn't affect anything else, like computations).

Disclaimer: I don't know whether that would work, because I don't have the protocol memorised by heart. My assumption however would be that it expects an integer to be sent over the wire.

If that's the case (I'd first check that it indeed expects an integer before you waste time writing code) then the only way you could do it is by modifying the client to format how the damage points are displayed in the client. If you're using the original Tibia client...

Ziker

Well-Known Member
Joined
May 7, 2017
Messages
198
Solutions
7
Reaction score
64
Location
Edinburgh
As far as I'm aware you can't. The variable you're trying to write to is an integer (a whole number).

First you'd need to make this change (changing it from an integer to a string) as close to the protocol as possible (so that it doesn't affect anything else, like computations).

Disclaimer: I don't know whether that would work, because I don't have the protocol memorised by heart. My assumption however would be that it expects an integer to be sent over the wire.

If that's the case (I'd first check that it indeed expects an integer before you waste time writing code) then the only way you could do it is by modifying the client to format how the damage points are displayed in the client. If you're using the original Tibia client you're out of luck. Otherwise look at your OTC source code, find where this message gets printed onto the screen (as far from the protocol as possible this time) and format it there.

@EDIT
I did the initial check for you, see otland/forgottenserver (https://github.com/otland/forgottenserver/blob/master/src/protocolgame.cpp#L1442). The NetworkMessage instance expects an unsigned 32-bit integer and so the client will only be able to parse an unsigned 32-bit integer. I'd suggest making the necessary changes on the client side, that'd be the easiest.

@EDIT 2
In case you're lucky to use OTClient, you'll find what you need here: edubart/otclient (https://github.com/edubart/otclient/blob/b3d947d4c3121bc6fa866a629ad62de50c12e8e0/src/client/protocolgameparse.cpp#L1685).

You can convert the integer to a string and then place your separators accordingly before populating the AnimatedTextPtr with the text.
 
Last edited:
Solution
OP
Ascuas Funkeln

Ascuas Funkeln

Rakkedo Game
Joined
Apr 14, 2013
Messages
513
Solutions
30
Reaction score
265
Location
Poland
GitHub
AscuasFunkeln
As far as I'm aware you can't. The variable you're trying to write to is an integer (a whole number).

First you'd need to make this change (changing it from an integer to a string) as close to the protocol as possible (so that it doesn't affect anything else, like computations).

Disclaimer: I don't know whether that would work, because I don't have the protocol memorised by heart. My assumption however would be that it expects an integer to be sent over the wire.

If that's the case (I'd first check that it indeed expects an integer before you waste time writing code) then the only way you could do it is by modifying the client to format how the damage points are displayed in the client. If you're using the original Tibia client you're out of luck. Otherwise look at your OTC source code, find where this message gets printed onto the screen (as far from the protocol as possible this time) and format it there.

@EDIT
I did the initial check for you, see otland/forgottenserver (https://github.com/otland/forgottenserver/blob/master/src/protocolgame.cpp#L1442). The NetworkMessage instance expects an unsigned 32-bit integer and so the client will only be able to parse an unsigned 32-bit integer. I'd suggest making the necessary changes on the client side, that'd be the easiest.

@EDIT 2
In case you're lucky to use OTClient, you'll find what you need here: edubart/otclient (https://github.com/edubart/otclient/blob/b3d947d4c3121bc6fa866a629ad62de50c12e8e0/src/client/protocolgameparse.cpp#L1685).

You can convert the integer to a string and then place your separators accordingly before populating the AnimatedTextPtr with the text.
Hmm... Look like i go forward but im to retarded to fix it.

C++:
                animatedText->setText(stdext::to_string(value[i]));
So, actually this dont covert int to string?

This dont work:
C++:
            for(int i=0;i<2;++i) {
                if(value[i] == 0)
                    continue;
                int dmg = value[i];
                std::cout.imbue(std::locale(""));
                std::string DamageValue = std::cout << dmg; // this dont work -  cannot convert from 'std::basic_ostream<char,std::char_traits<char>>' to 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
                AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText);
                animatedText->setColor(color[i]);
                animatedText->setText(stdext::to_string(DamageValue));
                g_map.addThing(animatedText, pos);
            }
            break;
        }
This dont make any errors, but there is without std::cout
C++:
            for(int i=0;i<2;++i) {
                if(value[i] == 0)
                    continue;
                int dmg = value[i];
                std::cout.imbue(std::locale(""));
                std::string dmgval = std::to_string(dmg);
                AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText);
                animatedText->setColor(color[i]);
                animatedText->setText(stdext::to_string(dmgval));
                g_map.addThing(animatedText, pos);
            }
            break;
        }
And with this std::cout starting problems
std::string dmgval = std::to_string(dmg); So i got the nubmer in string, now idk how to put this fk commas here :|
Look like this is not level for me :\

@edit

Ok i made this :D
With this code (requires en_US.UTF-8 locale in system) instead of std::cout
C++:
                std::ostringstream ss;
                ss.imbue(std::locale("en_US.UTF-8"));
 
Last edited:

Ziker

Well-Known Member
Joined
May 7, 2017
Messages
198
Solutions
7
Reaction score
64
Location
Edinburgh
Hmm... Look like i go forward but im to retarded to fix it.

C++:
                animatedText->setText(stdext::to_string(value[i]));
So, actually this dont covert int to string?

This dont work:
C++:
            for(int i=0;i<2;++i) {
                if(value[i] == 0)
                    continue;
                int dmg = value[i];
                std::cout.imbue(std::locale(""));
                std::string DamageValue = std::cout << dmg; // this dont work -  cannot convert from 'std::basic_ostream<char,std::char_traits<char>>' to 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
                AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText);
                animatedText->setColor(color[i]);
                animatedText->setText(stdext::to_string(DamageValue));
                g_map.addThing(animatedText, pos);
            }
            break;
        }
This dont make any errors, but there is without std::cout
C++:
            for(int i=0;i<2;++i) {
                if(value[i] == 0)
                    continue;
                int dmg = value[i];
                std::cout.imbue(std::locale(""));
                std::string dmgval = std::to_string(dmg);
                AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText);
                animatedText->setColor(color[i]);
                animatedText->setText(stdext::to_string(dmgval));
                g_map.addThing(animatedText, pos);
            }
            break;
        }
And with this std::cout starting problems
std::string dmgval = std::to_string(dmg); So i got the nubmer in string, now idk how to put this fk commas here :|
Look like this is not level for me :\

@edit

Ok i made this :D
With this code (requires en_US.UTF-8 locale in system) instead of std::cout
C++:
                std::ostringstream ss;
                ss.imbue(std::locale("en_US.UTF-8"));
Glad you managed to figure it out on your own. I should have been more clear earlier. "to_string" does convert it to a string. You just need to assign it to a variable and modify rather than passing to "setText" immediately.

I don't know what would be the most idiomatic way to format it, but I'd go for fmt rather than using streams.

Since you've got it working yourself I'll try to find a couple of minutes to write it my way and you can choose whichever you prefer.

@Edit
Seems like OTClient doesn't use fmtlib at all, so I'm gonna leave it at what you've done. You may want to tidy it up, so please post all your changes and I'll take a look at them instead.
 
Last edited:
Top