• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!
  • 2026 staff recruitment is open! Check it out and consider applying!

Elemental Damage Not Working on onManaChange

_M4G0_

Intermediate OT User
Joined
Feb 6, 2016
Messages
550
Solutions
17
Reaction score
108
Hello everyone! I'm trying to apply elemental damage to mana using onManaChange on my TFS 1.4 server, but it's not working.
onHealthChange works fine, but with onManaChange, the elemental damage is never applied.


LUA:
local onManaChangeDamage = CreatureEvent("onManaDamage")
function onManaChangeDamage.onManaChange(target, source, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    print("[DEBUG] onManaChange START")
    print("[DEBUG] Target:", target and target:getName() or "nil")
    print("[DEBUG] Source:", source and source:getName() or "nil")
    print("[DEBUG] Primary:", primaryDamage, primaryType, "Secondary:", secondaryDamage, secondaryType, "Origin:", origin)

    local weapon
    if source and source:isPlayer() then
        weapon = source:getSlotItem(CONST_SLOT_LEFT) or source:getSlotItem(CONST_SLOT_RIGHT)
    end
    print("[DEBUG] Equipped weapon:", weapon and weapon:getName() or "none")

    local elementTypes = {
        {combatType = COMBAT_FIREDAMAGE, storage = 977554},
        {combatType = COMBAT_ICEDAMAGE, storage = 977555},
        {combatType = COMBAT_ENERGYDAMAGE, storage = 977556},
        {combatType = COMBAT_EARTHDAMAGE, storage = 977557},
        {combatType = COMBAT_DEATHDAMAGE, storage = 977558},
        {combatType = COMBAT_WATERDAMAGE, storage = 977559},
        {combatType = COMBAT_HOLYDAMAGE, storage = 977560},
        {combatType = COMBAT_ARCANEDAMAGE, storage = 977561}
    }

    local totalElementPercent = 0
    local finalElementType = COMBAT_NONE

    if weapon then
        local itemType = weapon:getType()
        local weaponElement = itemType:getElementDamage() or 0
        local weaponElementType = itemType:getElementType() or COMBAT_NONE
        print("[DEBUG] Weapon element:", weaponElement, weaponElementType)

        if weaponElement > 0 and weaponElementType ~= COMBAT_NONE then
            totalElementPercent = weaponElement
            finalElementType = weaponElementType
            print("[DEBUG] Base element found:", finalElementType, "with", totalElementPercent, "%")

            for _, elem in ipairs(elementTypes) do
                if elem.combatType == weaponElementType then
                    local storageValue = source and source:getStorageValue(elem.storage) or 0
                    print("[DEBUG] Additional element storage:", storageValue)
                    if storageValue > 0 then
                        totalElementPercent = totalElementPercent + storageValue
                    end
                    break
                end
            end
        end
    end

    if totalElementPercent == 0 then
        print("[DEBUG] No weapon element, checking storages...")
        for _, elem in ipairs(elementTypes) do
            local storageValue = source and source:getStorageValue(elem.storage) or 0
            print("[DEBUG] Storage", elem.combatType, "=", storageValue)
            if storageValue > totalElementPercent then
                totalElementPercent = storageValue
                finalElementType = elem.combatType
            end
        end
    end

    print("[DEBUG] Total Element %:", totalElementPercent, "Final type:", finalElementType)

    if totalElementPercent > 0 and finalElementType ~= COMBAT_NONE then
        local mainValue = math.abs(primaryDamage)
        local elementalDamage = -math.floor(mainValue * totalElementPercent / 100)
        print("[DEBUG] Base value:", mainValue, "Calculated elemental damage:", elementalDamage)

        if target:isMonster() and monsterImmunities and monsterImmunities.isImmune then
            if monsterImmunities.isImmune(target, finalElementType) then
                print("[DEBUG] Immunity detected for final element:", finalElementType)
                target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT)
                elementalDamage = 0
            end
        end

        if secondaryType == finalElementType then
            secondaryDamage = elementalDamage
        else
            secondaryType = finalElementType
            secondaryDamage = (secondaryDamage < 0 and secondaryDamage or 0) + elementalDamage
        end
    end

    print("[DEBUG] Final return:", primaryDamage, primaryType, secondaryDamage, secondaryType)
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end
onManaChangeDamage:register()
 
Solution
So your issue seems to be only visual because the elemental damage (secondary) is applied
int32_t manaChange = damage.primary.value + damage.secondary.value

in both healthChange and manaChange, nothing custom in blockHit as well.

i assume you are having conflict due to visually conflicting behaviours.

1) Health dmg MESSAGE behaviour:
message.primary.value = damage.primary.value;
message.secondary.value = damage.secondary.value;

2) Mana dmg MESSAGE behaviour
int32_t realManaChange = target->getMana();
target->changeMana(damage.primary.value+damage.secondary.value);
realManaChange = target->getMana() - realManaChange;
message.position = targetPos;
message.primary.value = realManaChange;
message.primary.color =...
1755411565450.webp1755444154438.webp

I noticed that the damage seems to be applied, but it is not the same as the health change that displays the element and physical damage, the damage is watery in the mana change, could someone help with this, how to display the owner the same as the health change
 
share the functions from game.cpp and player.cpp

Player::BlockHit
Game::combatChangeHealth
Game::combatChangeMana
 
Last edited:
share the functions from game.cpp and player.cpp

Player::BlockHit
Game::combatChangeHealth
Game::combatChangeMana
BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage,
bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/, bool ignoreResistances /* = false*/)
{
BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field, ignoreResistances);

if (attacker) {
sendCreatureSquare(attacker, SQ_COLOR_BLACK);
}

if (blockType != BLOCK_NONE) {
return blockType;
}

if (damage <= 0) {
damage = 0;
return BLOCK_ARMOR;
}

if (!ignoreResistances) {
for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_BADGE; ++slot) {
if (!isItemAbilityEnabled(static_cast<slots_t>(slot))) {
continue;
}

Item* item = inventory[slot];
if (!item) {
continue;
}

const ItemType& it = Item::items[item->getID()];
if (!it.abilities) {
if (damage <= 0) {
damage = 0;
return BLOCK_ARMOR;
}

continue;
}

const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)];
if (absorbPercent != 0) {
damage -= std::round(damage * (absorbPercent / 100.));

uint16_t charges = item->getCharges();
if (charges != 0) {
g_game.transformItem(item, item->getID(), charges - 1);
}
}

if (field) {
const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)];
if (fieldAbsorbPercent != 0) {
damage -= std::round(damage * (fieldAbsorbPercent / 100.));

uint16_t charges = item->getCharges();
if (charges != 0) {
g_game.transformItem(item, item->getID(), charges - 1);
}
}
}
}
}

if (damage <= 0) {
damage = 0;
blockType = BLOCK_ARMOR;
}
return blockType;
}
Game::combatChangeHealth
Game::combatChangeMana

 
Last edited:
So your issue seems to be only visual because the elemental damage (secondary) is applied
int32_t manaChange = damage.primary.value + damage.secondary.value

in both healthChange and manaChange, nothing custom in blockHit as well.

i assume you are having conflict due to visually conflicting behaviours.

1) Health dmg MESSAGE behaviour:
message.primary.value = damage.primary.value;
message.secondary.value = damage.secondary.value;

2) Mana dmg MESSAGE behaviour
int32_t realManaChange = target->getMana();
target->changeMana(damage.primary.value+damage.secondary.value);
realManaChange = target->getMana() - realManaChange;
message.position = targetPos;
message.primary.value = realManaChange;
message.primary.color = TEXTCOLOR_MAYABLUE;

which means the following:
1) In HP the dmg Message & effect of primary and secondary (normal dmg & elemental) appears indiviually.
2) in MP the dmg message & effect of primary and secondary (normal dmg & elemental) are merged into realManaChange and sent ONCE <-- considering the concept that manaDamage is always same color could easily be seperated by appling values in message.secondary.type and message.secondary.value

View attachment 94351View attachment 94352

I noticed that the damage seems to be applied, but it is not the same as the health change that displays the element and physical damage, the damage is watery in the mana change, could someone help with this, how to display the owner the same as the health change
Confirming that is the value of the damage in HP it was 162 and 68 fire (primary 162 & secondary 68) = 230 Total Damage (Average)

Damage in MP using same chars and everything is same 100% i can see the same average 233 which means the formula is min/max are just in action and in mana dmg the primary and secondary message is merged.
 
Solution
So your issue seems to be only visual because the elemental damage (secondary) is applied
int32_t manaChange = damage.primary.value + damage.secondary.value

in both healthChange and manaChange, nothing custom in blockHit as well.

i assume you are having conflict due to visually conflicting behaviours.

1) Health dmg MESSAGE behaviour:


2) Mana dmg MESSAGE behaviour


which means the following:
1) In HP the dmg Message & effect of primary and secondary (normal dmg & elemental) appears indiviually.
2) in MP the dmg message & effect of primary and secondary (normal dmg & elemental) are merged into realManaChange and sent ONCE <-- considering the concept that manaDamage is always same color could easily be seperated by appling values in message.secondary.type and message.secondary.value


Confirming that is the value of the damage in HP it was 162 and 68 fire (primary 162 & secondary 68) = 230 Total Damage (Average)

Damage in MP using same chars and everything is same 100% i can see the same average 233 which means the formula is min/max are just in action and in mana dmg the primary and secondary message is merged.
Exactly, it's just the visual aspect. I made a similar change to what you suggested
let's say it worked😅😅😅 , but I don't think that's the correct modification

1755553834840.webp


C++:
targetPlayer->drainMana(attacker, manaDamage);
                map.getSpectators(spectators, targetPos, true, true);
                addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY);

                std::string spectatorMessage;
                    
                    message.primary.value = damage.primary.value;
                    message.secondary.value = damage.secondary.value;
                // message.primary.value = manaDamage;
                // message.primary.color = TEXTCOLOR_BLUE;

C++:
int32_t realManaChange = targetPlayer->getMana();
        targetPlayer->changeMana(manaChange);
        realManaChange = targetPlayer->getMana() - realManaChange;

        if (realManaChange > 0 && !targetPlayer->isInGhostMode()) {
            TextMessage message(MESSAGE_HEALED, "You gained " + std::to_string(realManaChange) + " mana.");
            message.position = target->getPosition();
            message.primary.value = damage.primary.value;
            message.secondary.value = damage.secondary.value;
            targetPlayer->sendTextMessage(message);
        }
Post automatically merged:

C++:
targetPlayer->drainMana(attacker, manaLoss);

        std::string spectatorMessage;

        TextMessage message;
        message.position = targetPos;
        message.secondary.value = damage.secondary.value; // Secondary damage
        message.primary.color = TEXTCOLOR_MAYABLUE;
        message.secondary.color = TEXTCOLOR_LIGHTBLUE;
 
Last edited:
I think that's it, if there's something wrong, please help me fix it.
thanks for helping :)
C++:
if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) {
            int32_t manaDamage = std::min<int32_t>(targetPlayer->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, damage);
                        }
                        healthChange = damage.primary.value + damage.secondary.value;
                        if (healthChange == 0) {
                            return true;
                        }
                        manaDamage = std::min<int32_t>(targetPlayer->getMana(), healthChange);
                    }
                }

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

                std::string spectatorMessage;

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

                uint8_t hitEffect;

                message.primary.color = TEXTCOLOR_BLUE;

                if (message.secondary.value > 0) {
                    combatGetTypeInfo(damage.secondary.type, target, message.secondary.color, hitEffect);
                }


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

                    if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
                        message.type = MESSAGE_DAMAGE_DEALT;
                        message.text = fmt::format("{:s} loses {:d} mana due to your attack.", target->getNameDescription(), manaDamage);
                        message.text[0] = std::toupper(message.text[0]);
                    } else if (tmpPlayer == targetPlayer) {
                        message.type = MESSAGE_DAMAGE_RECEIVED;
                        if (!attacker) {
                            message.text = fmt::format("You lose {:d} mana.", manaDamage);
                        } else if (targetPlayer == attackerPlayer) {
                            message.text = fmt::format("You lose {:d} mana due to your own attack.", manaDamage);
                        } else {
                            message.text = fmt::format("You lose {:d} mana due to an attack by {:s}.", manaDamage, attacker->getNameDescription());
                        }
                    } else {
                        message.type = MESSAGE_DAMAGE_OTHERS;
                        if (spectatorMessage.empty()) {
                            if (!attacker) {
                                spectatorMessage = fmt::format("{:s} loses {:d} mana.", target->getNameDescription(), manaDamage);
                            } else if (attacker == target) {
                                spectatorMessage = fmt::format("{:s} loses {:d} mana due to {:s} own attack.", target->getNameDescription(), manaDamage, targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his");
                            } else {
                                spectatorMessage = fmt::format("{:s} loses {:d} mana due to an attack by {:s}.", target->getNameDescription(), manaDamage, attacker->getNameDescription());
                            }
                            spectatorMessage[0] = std::toupper(spectatorMessage[0]);
                        }
                        message.text = spectatorMessage;
                    }
                    tmpPlayer->sendTextMessage(message);
                }

                int32_t primaryAbsorbed = std::min(damage.primary.value, manaDamage);
                int32_t secondaryAbsorbed = std::min(damage.secondary.value, manaDamage - primaryAbsorbed);

                damage.primary.value -= primaryAbsorbed;
                damage.secondary.value -= secondaryAbsorbed;

                damage.primary.value = std::max<int32_t>(0, damage.primary.value);
                damage.secondary.value = std::max<int32_t>(0, damage.secondary.value);
            }
        }
 
I think that's it, if there's something wrong, please help me fix it.
thanks for helping :)
C++:
if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) {
            int32_t manaDamage = std::min<int32_t>(targetPlayer->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, damage);
                        }
                        healthChange = damage.primary.value + damage.secondary.value;
                        if (healthChange == 0) {
                            return true;
                        }
                        manaDamage = std::min<int32_t>(targetPlayer->getMana(), healthChange);
                    }
                }

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

                std::string spectatorMessage;

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

                uint8_t hitEffect;

                message.primary.color = TEXTCOLOR_BLUE;

                if (message.secondary.value > 0) {
                    combatGetTypeInfo(damage.secondary.type, target, message.secondary.color, hitEffect);
                }


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

                    if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
                        message.type = MESSAGE_DAMAGE_DEALT;
                        message.text = fmt::format("{:s} loses {:d} mana due to your attack.", target->getNameDescription(), manaDamage);
                        message.text[0] = std::toupper(message.text[0]);
                    } else if (tmpPlayer == targetPlayer) {
                        message.type = MESSAGE_DAMAGE_RECEIVED;
                        if (!attacker) {
                            message.text = fmt::format("You lose {:d} mana.", manaDamage);
                        } else if (targetPlayer == attackerPlayer) {
                            message.text = fmt::format("You lose {:d} mana due to your own attack.", manaDamage);
                        } else {
                            message.text = fmt::format("You lose {:d} mana due to an attack by {:s}.", manaDamage, attacker->getNameDescription());
                        }
                    } else {
                        message.type = MESSAGE_DAMAGE_OTHERS;
                        if (spectatorMessage.empty()) {
                            if (!attacker) {
                                spectatorMessage = fmt::format("{:s} loses {:d} mana.", target->getNameDescription(), manaDamage);
                            } else if (attacker == target) {
                                spectatorMessage = fmt::format("{:s} loses {:d} mana due to {:s} own attack.", target->getNameDescription(), manaDamage, targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his");
                            } else {
                                spectatorMessage = fmt::format("{:s} loses {:d} mana due to an attack by {:s}.", target->getNameDescription(), manaDamage, attacker->getNameDescription());
                            }
                            spectatorMessage[0] = std::toupper(spectatorMessage[0]);
                        }
                        message.text = spectatorMessage;
                    }
                    tmpPlayer->sendTextMessage(message);
                }

                int32_t primaryAbsorbed = std::min(damage.primary.value, manaDamage);
                int32_t secondaryAbsorbed = std::min(damage.secondary.value, manaDamage - primaryAbsorbed);

                damage.primary.value -= primaryAbsorbed;
                damage.secondary.value -= secondaryAbsorbed;

                damage.primary.value = std::max<int32_t>(0, damage.primary.value);
                damage.secondary.value = std::max<int32_t>(0, damage.secondary.value);
            }
        }

welldone! everything seems fine
 
Back
Top