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 ? " hitpoints." : " hitpoint.");
std::string spectatorMessage;
if (!attacker) {
spectatorMessage += ucfirst(target->getNameDescription());
spectatorMessage += " was healed for " + 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 += " for " + damageString;
}
TextMessage message;
std::ostringstream strHealthChange;
strHealthChange << realHealthChange;
addAnimatedText(strHealthChange.str(), targetPos, TEXTCOLOR_MAYABLUE);
SpectatorVec list;
map.getSpectators(list, targetPos, false, true);
for (Creature* spectator : list) {
Player* tmpPlayer = spectator->getPlayer();
if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
message.type = MESSAGE_STATUS_DEFAULT;
message.text = "You heal " + target->getNameDescription() + " for " + damageString;
} else if (tmpPlayer == targetPlayer) {
message.type = MESSAGE_STATUS_DEFAULT;
if (!attacker) {
message.text = "You were healed for " + damageString;
} else if (targetPlayer == attackerPlayer) {
message.text = "You heal yourself for " + damageString;
} else {
message.text = "You were healed by " + attacker->getNameDescription() + " for " + damageString;
}
} else {
message.type = MESSAGE_STATUS_DEFAULT;
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;
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()) + " loses " + damageString + " mana";
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 += '.';
std::ostringstream strManaDamage;
strManaDamage << manaDamage;
addAnimatedText(strManaDamage.str(), targetPos, 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_STATUS_DEFAULT;
message.text = ucfirst(target->getNameDescription()) + " loses " + damageString + " mana due to your attack.";
} else if (tmpPlayer == targetPlayer) {
message.type = MESSAGE_STATUS_DEFAULT;
if (!attacker) {
message.text = "You lose " + damageString + " mana.";
} else if (targetPlayer == attackerPlayer) {
message.text = "You lose " + damageString + " mana due to your own attack.";
} else {
message.text = "You lose " + damageString + " mana due to an attack by " + attacker->getNameDescription() + '.';
}
} else {
message.type = MESSAGE_STATUS_DEFAULT;
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;
message.secondary.value = damage.secondary.value;
uint8_t hitEffect;
if (message.primary.value) {
if (attackerPlayer && targetPlayer) {
message.primary.color = TEXTCOLOR_BLUE;
hitEffect = CONST_ME_MAGIC_BLUE;
} else {
combatGetTypeInfo(damage.primary.type, target, message.primary.color, hitEffect);
}
if (hitEffect != CONST_ME_NONE) {
addMagicEffect(list, targetPos, hitEffect);
}
if (message.primary.color != TEXTCOLOR_NONE) {
std::ostringstream strPrimaryDamage;
strPrimaryDamage << message.primary.value;
addAnimatedText(strPrimaryDamage.str(), targetPos, message.primary.color);
}
}
if (message.secondary.value) {
if (attackerPlayer && targetPlayer) {
message.primary.color = TEXTCOLOR_BLUE;
hitEffect = CONST_ME_MAGIC_BLUE;
} else {
combatGetTypeInfo(damage.primary.type, target, message.primary.color, hitEffect);
}
if (hitEffect != CONST_ME_NONE) {
addMagicEffect(list, targetPos, hitEffect);
}
if (message.secondary.color != TEXTCOLOR_NONE) {
std::ostringstream strSecondaryDamage;
strSecondaryDamage << message.secondary.value;
addAnimatedText(strSecondaryDamage.str(), targetPos, message.secondary.color);
}
}
if (message.primary.color != TEXTCOLOR_NONE || message.secondary.color != TEXTCOLOR_NONE) {
std::string damageString = std::to_string(realDamage) + (realDamage != 1 ? " hitpoints" : " hitpoint");
std::string spectatorMessage = ucfirst(target->getNameDescription()) + " loses " + 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_STATUS_DEFAULT;
message.text = ucfirst(target->getNameDescription()) + " loses " + damageString + " due to your attack.";
} else if (tmpPlayer == targetPlayer) {
message.type = MESSAGE_STATUS_DEFAULT;
if (!attacker) {
message.text = "You lose " + damageString + '.';
} else if (targetPlayer == attackerPlayer) {
message.text = "You lose " + damageString + " due to your own attack.";
} else {
message.text = "You lose " + damageString + " due to an attack by " + attacker->getNameDescription() + '.';
}
} else {
message.type = MESSAGE_STATUS_DEFAULT;
// TODO: Avoid copying spectatorMessage everytime we send to a spectator
message.text = spectatorMessage;
}
tmpPlayer->sendTextMessage(message);
}
}
}
return true;
}