////////////////////////////////////////////////////////////////////////
// OpenTibia - an opensource roleplaying game
////////////////////////////////////////////////////////////////////////
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
////////////////////////////////////////////////////////////////////////
#include "otpch.h"
#include "const.h"
#include "combat.h"
#include "tools.h"
#include "game.h"
#include "configmanager.h"
#include "creature.h"
#include "player.h"
#include "weapons.h"
extern Game g_game;
extern Weapons* g_weapons;
extern ConfigManager g_config;
Combat::Combat()
{
params.valueCallback = NULL;
params.tileCallback = NULL;
params.targetCallback = NULL;
area = NULL;
formulaType = FORMULA_UNDEFINED;
mina = minb = maxa = maxb = 0.0;
}
Combat::~Combat()
{
for(std::list::iterator it = params.conditionList.begin(); it != params.conditionList.end(); ++it)
delete (*it);
params.conditionList.clear();
delete params.valueCallback;
delete params.tileCallback;
delete params.targetCallback;
delete area;
}
bool Combat::getMinMaxValues(Creature* creature, Creature* target, int32_t& min, int32_t& max) const
{
if(!creature)
return false;
if(creature->getCombatValues(min, max))
return true;
if(Player* player = creature->getPlayer())
{
if(params.valueCallback)
{
params.valueCallback->getMinMaxValues(player, min, max, params.useCharges);
return true;
}
switch(formulaType)
{
case FORMULA_LEVELMAGIC:
{
min = (int32_t)((player->getLevel() + player->getMagicLevel() * 4) * 1. * mina + minb);
max = (int32_t)((player->getLevel() + player->getMagicLevel() * 4) * 1. * maxa + maxb);
Vocation* vocation = player->getVocation();
float multiplier = 1.0f;
if(max > 0)
multiplier = vocation->getMultiplier(MULTIPLIER_MAGICHEALING);
else
multiplier = vocation->getMultiplier(MULTIPLIER_MAGIC);
min = (int32_t)(min * multiplier);
max = (int32_t)(max * multiplier);
return true;
}
case FORMULA_SKILL:
{
min = (int32_t)minb;
Item* tool = player->getWeapon();
if(const Weapon* weapon = g_weapons->getWeapon(tool))
{
max = (int32_t)(weapon->getWeaponDamage(player, target, tool, true) * maxa + maxb);
if(params.useCharges && tool->hasCharges() && g_config.getBool(ConfigManager::REMOVE_WEAPON_CHARGES))
g_game.transformItem(tool, tool->getID(), std::max((int32_t)0, ((int32_t)tool->getCharges()) - 1));
}
else
max = (int32_t)maxb;
return true;
}
case FORMULA_VALUE:
{
min = (int32_t)mina;
max = (int32_t)maxa;
return true;
}
default:
min = max = 0;
break;
}
return false;
}
if(formulaType == FORMULA_VALUE)
{
min = (int32_t)mina;
max = (int32_t)maxa;
return true;
}
return false;
}
void Combat::getCombatArea(const Position& centerPos, const Position& targetPos, const AreaCombat* area, std::list& list)
{
uint16_t tmpX = targetPos.x, tmpY = targetPos.y;
if(area)
area->getList(centerPos, targetPos, list);
else if(targetPos.z < MAP_MAX_LAYERS)
{
Tile* tile = g_game.getTile(tmpX, tmpY, targetPos.z);
if(!tile)
{
tile = new Tile(tmpX, tmpY, targetPos.z);
g_game.setTile(tile);
}
list.push_back(tile);
}
}
CombatType_t Combat::ConditionToDamageType(ConditionType_t type)
{
switch(type)
{
case CONDITION_FIRE:
return COMBAT_FIREDAMAGE;
case CONDITION_ENERGY:
return COMBAT_ENERGYDAMAGE;
case CONDITION_POISON:
return COMBAT_EARTHDAMAGE;
case CONDITION_FREEZING:
return COMBAT_ICEDAMAGE;
case CONDITION_DAZZLED:
return COMBAT_HOLYDAMAGE;
case CONDITION_CURSED:
return COMBAT_DEATHDAMAGE;
case CONDITION_DROWN:
return COMBAT_DROWNDAMAGE;
case CONDITION_PHYSICAL:
return COMBAT_PHYSICALDAMAGE;
default:
break;
}
return COMBAT_NONE;
}
ConditionType_t Combat::DamageToConditionType(CombatType_t type)
{
switch(type)
{
case COMBAT_FIREDAMAGE:
return CONDITION_FIRE;
case COMBAT_ENERGYDAMAGE:
return CONDITION_ENERGY;
case COMBAT_EARTHDAMAGE:
return CONDITION_POISON;
case COMBAT_ICEDAMAGE:
return CONDITION_FREEZING;
case COMBAT_HOLYDAMAGE:
return CONDITION_DAZZLED;
case COMBAT_DEATHDAMAGE:
return CONDITION_CURSED;
case COMBAT_PHYSICALDAMAGE:
return CONDITION_PHYSICAL;
default:
break;
}
return CONDITION_NONE;
}
ReturnValue Combat::canDoCombat(const Creature* caster, const Tile* tile, bool isAggressive)
{
if(tile->hasProperty(BLOCKPROJECTILE) || tile->floorChange() || tile->getTeleportItem())
return RET_NOTENOUGHROOM;
if(caster)
{
bool success = true;
CreatureEventList combatAreaEvents = const_cast(caster)->getCreatureEvents(CREATURE_EVENT_COMBAT_AREA);
for(CreatureEventList::iterator it = combatAreaEvents.begin(); it != combatAreaEvents.end(); ++it)
{
if(!(*it)->executeCombatArea(const_cast(caster), const_cast(tile), isAggressive) && success)
success = false;
}
if(!success)
return RET_NOTPOSSIBLE;
if(caster->getPosition().z < tile->getPosition().z)
return RET_FIRSTGODOWNSTAIRS;
if(caster->getPosition().z > tile->getPosition().z)
return RET_FIRSTGOUPSTAIRS;
if(!isAggressive)
return RET_NOERROR;
const Player* player = caster->getPlayer();
if(player && player->hasFlag(PlayerFlag_IgnoreProtectionZone))
return RET_NOERROR;
}
return isAggressive && tile->hasFlag(TILESTATE_PROTECTIONZONE) ?
RET_ACTIONNOTPERMITTEDINPROTECTIONZONE : RET_NOERROR;
}
ReturnValue Combat::canDoCombat(const Creature* attacker, const Creature* target)
{
if(!attacker)
return RET_NOERROR;
bool success = true;
CreatureEventList combatEvents = const_cast(attacker)->getCreatureEvents(CREATURE_EVENT_COMBAT);
for(CreatureEventList::iterator it = combatEvents.begin(); it != combatEvents.end(); ++it)
{
if(!(*it)->executeCombat(const_cast(attacker), const_cast(target)) && success)
success = false;
}
if(!success)
return RET_NOTPOSSIBLE;
bool checkZones = false;
if(const Player* targetPlayer = target->getPlayer())
{
if(!targetPlayer->isAttackable())
return RET_YOUMAYNOTATTACKTHISPLAYER;
const Player* attackerPlayer = NULL;
if((attackerPlayer = attacker->getPlayer()) || (attacker->getMaster()
&& (attackerPlayer = attacker->getMaster()->getPlayer())))
{
checkZones = true;
if((g_game.getWorldType() == WORLD_TYPE_NO_PVP && !Combat::isInPvpZone(attacker, target)) ||
isProtected(const_cast(attackerPlayer), const_cast(targetPlayer))
|| (g_config.getBool(ConfigManager::CANNOT_ATTACK_SAME_LOOKFEET) &&
attackerPlayer->getDefaultOutfit().lookFeet == targetPlayer->getDefaultOutfit().lookFeet)
|| (targetPlayer->isInGhostMode() && !attackerPlayer->canSeeGhost(targetPlayer)))
return RET_YOUMAYNOTATTACKTHISPLAYER;
}
}
else if(target->getMonster())
{
if(!target->isAttackable())
return RET_YOUMAYNOTATTACKTHISCREATURE;
const Player* attackerPlayer = NULL;
if((attackerPlayer = attacker->getPlayer()) || (attacker->getMaster()
&& (attackerPlayer = attacker->getMaster()->getPlayer())))
{
if(attackerPlayer->hasFlag(PlayerFlag_CannotAttackMonster))
return RET_YOUMAYNOTATTACKTHISCREATURE;
if(target->getMaster() && target->getMaster()->getPlayer())
{
checkZones = true;
if(g_game.getWorldType() == WORLD_TYPE_NO_PVP && !Combat::isInPvpZone(attacker, target))
return RET_YOUMAYNOTATTACKTHISCREATURE;
}
}
}
if(checkZones)
{
if(target->getTile()->hasFlag(TILESTATE_NOPVPZONE) || (attacker->getTile()->hasFlag(TILESTATE_NOPVPZONE) &&
!target->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !target->getTile()->hasFlag(TILESTATE_PROTECTIONZONE)))
return RET_ACTIONNOTPERMITTEDINANOPVPZONE;
}
return RET_NOERROR;
}
ReturnValue Combat::canTargetCreature(const Player* player, const Creature* target)
{
if(player == target)
return RET_YOUMAYNOTATTACKTHISPLAYER;
Player* tmpPlayer = const_cast(player);
CreatureEventList targetEvents = tmpPlayer->getCreatureEvents(CREATURE_EVENT_TARGET);
bool deny = false;
for(CreatureEventList::iterator it = targetEvents.begin(); it != targetEvents.end(); ++it)
{
if(!(*it)->executeTarget(tmpPlayer, const_cast(target)))
deny = true;
}
if(deny)
return RET_DONTSHOWMESSAGE;
if(!player->hasFlag(PlayerFlag_IgnoreProtectionZone))
{
if(player->getZone() == ZONE_PROTECTION)
return RET_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE;
if(target->getZone() == ZONE_PROTECTION)
return RET_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE;
if(target->getPlayer() || (target->getMaster() && target->getMaster()->getPlayer()))
{
if(player->getZone() == ZONE_NOPVP)
return RET_ACTIONNOTPERMITTEDINANOPVPZONE;
if(target->getZone() == ZONE_NOPVP)
return RET_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE;
}
}
if(player->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable())
{
if(target->getPlayer())
return RET_YOUMAYNOTATTACKTHISPLAYER;
return RET_YOUMAYNOTATTACKTHISCREATURE;
}
if(target->getPlayer() && player->getSecureMode() == SECUREMODE_ON && !Combat::isInPvpZone(player, target)
&& player->getSkullClient(target->getPlayer()) == SKULL_NONE)
return RET_TURNSECUREMODETOATTACKUNMARKEDPLAYERS;
return Combat::canDoCombat(player, target);
}
bool Combat::isInPvpZone(const Creature* attacker, const Creature* target)
{
return attacker->getZone() == ZONE_PVP && target->getZone() == ZONE_PVP;
}
bool Combat::isProtected(Player* attacker, Player* target)
{
if(attacker->hasFlag(PlayerFlag_CannotAttackPlayer) || !target->isAttackable())
return true;
if(attacker->getZone() == ZONE_PVP && target->getZone() == ZONE_PVP && g_config.getBool(ConfigManager::PVP_TILE_IGNORE_PROTECTION))
return false;
if(attacker->hasCustomFlag(PlayerCustomFlag_IsProtected) || target->hasCustomFlag(PlayerCustomFlag_IsProtected))
return true;
uint32_t protectionLevel = g_config.getNumber(ConfigManager::PROTECTION_LEVEL);
if(target->getLevel() < protectionLevel || attacker->getLevel() < protectionLevel)
return true;
if(!attacker->getVocation()->isAttackable() || !target->getVocation()->isAttackable())
return true;
return attacker->checkLoginDelay(target->getID());
}
void Combat::setPlayerCombatValues(formulaType_t _type, double _mina, double _minb, double _maxa, double _maxb)
{
formulaType = _type;
mina = _mina;
minb = _minb;
maxa = _maxa;
maxb = _maxb;
}
bool Combat::setParam(CombatParam_t param, uint32_t value)
{
switch(param)
{
case COMBATPARAM_COMBATTYPE:
params.combatType = (CombatType_t)value;
return true;
case COMBATPARAM_EFFECT:
params.impactEffect = value;
return true;
case COMBATPARAM_DISTANCEEFFECT:
params.distanceEffect = value;
return true;
case COMBATPARAM_BLOCKEDBYARMOR:
params.blockedByArmor = (value != 0);
return true;
case COMBATPARAM_BLOCKEDBYSHIELD:
params.blockedByShield = (value != 0);
return true;
case COMBATPARAM_TARGETCASTERORTOPMOST:
params.targetCasterOrTopMost = (value != 0);
return true;
case COMBATPARAM_TARGETPLAYERSORSUMMONS:
params.targetPlayersOrSummons = (value != 0);
return true;
case COMBATPARAM_CREATEITEM:
params.itemId = value;
return true;
case COMBATPARAM_AGGRESSIVE:
params.isAggressive = (value != 0);
return true;
case COMBATPARAM_DISPEL:
params.dispelType = (ConditionType_t)value;
return true;
case COMBATPARAM_USECHARGES:
params.useCharges = (value != 0);
return true;
default:
break;
}
return false;
}
bool Combat::setCallback(CallBackParam_t key)
{
switch(key)
{
case CALLBACKPARAM_LEVELMAGICVALUE:
{
delete params.valueCallback;
params.valueCallback = new ValueCallback(FORMULA_LEVELMAGIC);
return true;
}
case CALLBACKPARAM_SKILLVALUE:
{
delete params.valueCallback;
params.valueCallback = new ValueCallback(FORMULA_SKILL);
return true;
}
case CALLBACKPARAM_TARGETTILECALLBACK:
{
delete params.tileCallback;
params.tileCallback = new TileCallback();
break;
}
case CALLBACKPARAM_TARGETCREATURECALLBACK:
{
delete params.targetCallback;
params.targetCallback = new TargetCallback();
break;
}
default:
std::cout << "Combat::setCallback - Unknown callback type: " << (uint32_t)key << std::endl;
break;
}
return false;
}
CallBack* Combat::getCallback(CallBackParam_t key)
{
switch(key)
{
case CALLBACKPARAM_LEVELMAGICVALUE:
case CALLBACKPARAM_SKILLVALUE:
return params.valueCallback;
case CALLBACKPARAM_TARGETTILECALLBACK:
return params.tileCallback;
case CALLBACKPARAM_TARGETCREATURECALLBACK:
return params.targetCallback;
default:
break;
}
return NULL;
}
bool Combat::CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, void* data)
{
Combat2Var* var = (Combat2Var*)data;
int32_t healthChange = random_range(var->minChange, var->maxChange, DISTRO_NORMAL);
if(g_game.combatBlockHit(params.combatType, caster, target, healthChange, params.blockedByShield, params.blockedByArmor))
return false;
if(healthChange < 0)
{
if(caster && caster->getPlayer() && target->getPlayer())
healthChange = healthChange / 2;
}
if(!g_game.combatChangeHealth(params.combatType, caster, target, healthChange))
return false;
CombatConditionFunc(caster, target, params, NULL);
CombatDispelFunc(caster, target, params, NULL);
return true;
}
bool Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, void* data)
{
Combat2Var* var = (Combat2Var*)data;
int32_t manaChange = random_range(var->minChange, var->maxChange, DISTRO_NORMAL);
if(manaChange < 0)
{
if(caster && caster->getPlayer() && target->getPlayer())
manaChange = manaChange / 2;
}
if(!g_game.combatChangeMana(caster, target, manaChange))
return false;
CombatConditionFunc(caster, target, params, NULL);
CombatDispelFunc(caster, target, params, NULL);
return true;
}
bool Combat::CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, void* data)
{
if(params.conditionList.empty())
return false;
bool result = true;
for(std::list::const_iterator it = params.conditionList.begin(); it != params.conditionList.end(); ++it)
{
if(caster == target || !target->isImmune((*it)->getType()))
{
Condition* tmp = (*it)->clone();
if(caster)
tmp->setParam(CONDITIONPARAM_OWNER, caster->getID());
//TODO: infight condition until all aggressive conditions has ended
if(!target->addCombatCondition(tmp) && result)
result = false;
}
}
return result;
}
bool Combat::CombatDispelFunc(Creature* caster, Creature* target, const CombatParams& params, void* data)
{
if(!target->hasCondition(params.dispelType))
return false;
target->removeCondition(caster, params.dispelType);
return true;
}
bool Combat::CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, void* data)
{
CombatConditionFunc(caster, target, params, NULL);
CombatDispelFunc(caster, target, params, NULL);
return true;
}
void Combat::combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params)
{
if(params.itemId != 0)
{
Player* player = NULL;
if(caster)
{
if(caster->getPlayer())
player = caster->getPlayer();
else if(caster->isSummon() && caster->getMaster()->getPlayer())
player = caster->getMaster()->getPlayer();
}
uint32_t itemId = params.itemId;
if(player)
{
bool pzLock = false;
if(g_game.getWorldType() == WORLD_TYPE_NO_PVP || tile->hasFlag(TILESTATE_NOPVPZONE))
{
switch(itemId)
{
case ITEM_FIREFIELD_PVP:
itemId = ITEM_FIREFIELD_NOPVP;
break;
case ITEM_POISONFIELD_PVP:
itemId = ITEM_POISONFIELD_NOPVP;
break;
case ITEM_ENERGYFIELD_PVP:
itemId = ITEM_ENERGYFIELD_NOPVP;
break;
}
}
else if(params.isAggressive && !Item::items[itemId].blockSolid)
pzLock = true;
player->addInFightTicks(pzLock);
}
if(Item* item = Item::CreateItem(itemId))
{
if(caster)
item->setOwner(caster->getID());
if(g_game.internalAddItem(caster, tile, item) == RET_NOERROR)
g_game.startDecay(item);
else
delete item;
}
}
if(params.tileCallback)
params.tileCallback->onTileCombat(caster, tile);
if(params.impactEffect != NM_ME_NONE)
g_game.addMagicEffect(list, tile->getPosition(), params.impactEffect);
}
void Combat::postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params)
{
if(caster && params.distanceEffect != NM_ME_NONE)
addDistanceEffect(caster, caster->getPosition(), pos, params.distanceEffect);
}
void Combat::addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect)
{
uint8_t distanceEffect = effect;
if(distanceEffect == NM_SHOOT_WEAPONTYPE)
{
switch(caster->getWeaponType())
{
case WEAPON_AXE:
distanceEffect = NM_SHOOT_WHIRLWINDAXE;
break;
case WEAPON_SWORD:
distanceEffect = NM_SHOOT_WHIRLWINDSWORD;
break;
case WEAPON_CLUB:
distanceEffect = NM_SHOOT_WHIRLWINDCLUB;
break;
case WEAPON_FIST:
distanceEffect = NM_SHOOT_LARGEROCK;
break;
default:
distanceEffect = NM_ME_NONE;
break;
}
}
if(caster && distanceEffect != NM_ME_NONE)
g_game.addDistanceEffect(fromPos, toPos, distanceEffect);
}
void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area,
const CombatParams& params, COMBATFUNC func, void* data)
{
std::list tileList;
if(caster)
getCombatArea(caster->getPosition(), pos, area, tileList);
else
getCombatArea(pos, pos, area, tileList);
uint32_t maxX = 0, maxY = 0, diff;
//calculate the max viewable range
for(std::list::iterator it = tileList.begin(); it != tileList.end(); ++it)
{
diff = std::abs((*it)->getPosition().x - pos.x);
if(diff > maxX)
maxX = diff;
diff = std::abs((*it)->getPosition().y - pos.y);
if(diff > maxY)
maxY = diff;
}
SpectatorVec list;
g_game.getSpectators(list, pos, false, true, maxX + Map::maxViewportX, maxX + Map::maxViewportX,
maxY + Map::maxViewportY, maxY + Map::maxViewportY);
for(std::list::iterator it = tileList.begin(); it != tileList.end(); ++it)
{
if(canDoCombat(caster, (*it), params.isAggressive) == RET_NOERROR)
{
bool skip = true;
if((*it)->creatures)
{
for(CreatureVector::iterator cit = (*it)->creatures->begin(); skip && cit != (*it)->creatures->end(); ++cit)
{
if(params.targetPlayersOrSummons && !(*cit)->getPlayer() && (!(*cit)->getMaster() || !(*cit)->getMaster()->getPlayer()))
continue;
if(params.targetCasterOrTopMost)
{
if(caster && caster->getTile() == (*it))
{
if((*cit) == caster)
skip = false;
}
else if((*cit) == (*it)->getTopCreature())
skip = false;
if(skip)
continue;
}
if(!params.isAggressive || (caster != (*cit) && Combat::canDoCombat(caster, (*cit)) == RET_NOERROR))
{
func(caster, (*cit), params, data);
if(params.targetCallback)
params.targetCallback->onTargetCombat(caster, (*cit));
}
}
}
combatTileEffects(list, caster, (*it), params);
}
}
postCombatEffects(caster, pos, params);
}
void Combat::doCombat(Creature* caster, Creature* target) const
{
//target combat callback function
if(params.combatType != COMBAT_NONE)
{
int32_t minChange = 0, maxChange = 0;
getMinMaxValues(caster, target, minChange, maxChange);
if(params.combatType != COMBAT_MANADRAIN)
doCombatHealth(caster, target, minChange, maxChange, params);
else
doCombatMana(caster, target, minChange, maxChange, params);
}
else
doCombatDefault(caster, target, params);
}
void Combat::doCombat(Creature* caster, const Position& pos) const
{
//area combat callback function
if(params.combatType != COMBAT_NONE)
{
int32_t minChange = 0, maxChange = 0;
getMinMaxValues(caster, NULL, minChange, maxChange);
if(params.combatType != COMBAT_MANADRAIN)
doCombatHealth(caster, pos, area, minChange, maxChange, params);
else
doCombatMana(caster, pos, area, minChange, maxChange, params);
}
else
CombatFunc(caster, pos, area, params, CombatNullFunc, NULL);
}
void Combat::doCombatHealth(Creature* caster, Creature* target, int32_t minChange, int32_t maxChange, const CombatParams& params)
{
if(!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR))
{
Combat2Var var;
var.minChange = minChange;
var.maxChange = maxChange;
CombatHealthFunc(caster, target, params, (void*)&var);
if(params.impactEffect != NM_ME_NONE)
g_game.addMagicEffect(target->getPosition(), params.impactEffect);
if(caster && params.distanceEffect != NM_ME_NONE)
addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect);
}
}
void Combat::doCombatHealth(Creature* caster, const Position& pos, const AreaCombat* area,
int32_t minChange, int32_t maxChange, const CombatParams& params)
{
Combat2Var var;
var.minChange = minChange;
var.maxChange = maxChange;
CombatFunc(caster, pos, area, params, CombatHealthFunc, (void*)&var);
}
void Combat::doCombatMana(Creature* caster, Creature* target, int32_t minChange, int32_t maxChange, const CombatParams& params)
{
if(!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR))
{
Combat2Var var;
var.minChange = minChange;
var.maxChange = maxChange;
CombatManaFunc(caster, target, params, (void*)&var);
if(params.targetCallback)
params.targetCallback->onTargetCombat(caster, target);
if(params.impactEffect != NM_ME_NONE)
g_game.addMagicEffect(target->getPosition(), params.impactEffect);
if(caster && params.distanceEffect != NM_ME_NONE)
addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect);
}
}
void Combat::doCombatMana(Creature* caster, const Position& pos, const AreaCombat* area,
int32_t minChange, int32_t maxChange, const CombatParams& params)
{
Combat2Var var;
var.minChange = minChange;
var.maxChange = maxChange;
CombatFunc(caster, pos, area, params, CombatManaFunc, (void*)&var);
}
void Combat::doCombatCondition(Creature* caster, const Position& pos, const AreaCombat* area,
const CombatParams& params)
{
CombatFunc(caster, pos, area, params, CombatConditionFunc, NULL);
}
void Combat::doCombatCondition(Creature* caster, Creature* target, const CombatParams& params)
{
if(!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR))
{
CombatConditionFunc(caster, target, params, NULL);
if(params.targetCallback)
params.targetCallback->onTargetCombat(caster, target);
if(params.impactEffect != NM_ME_NONE)
g_game.addMagicEffect(target->getPosition(), params.impactEffect);
if(caster && params.distanceEffect != NM_ME_NONE)
addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect);
}
}
void Combat::doCombatDispel(Creature* caster, const Position& pos, const AreaCombat* area,
const CombatParams& params)
{
CombatFunc(caster, pos, area, params, CombatDispelFunc, NULL);
}
void Combat::doCombatDispel(Creature* caster, Creature* target, const CombatParams& params)
{
if(!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR))
{
CombatDispelFunc(caster, target, params, NULL);
if(params.targetCallback)
params.targetCallback->onTargetCombat(caster, target);
if(params.impactEffect != NM_ME_NONE)
g_game.addMagicEffect(target->getPosition(), params.impactEffect);
if(caster && params.distanceEffect != NM_ME_NONE)
addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect);
}
}
void Combat::doCombatDefault(Creature* caster, Creature* target, const CombatParams& params)
{
if(!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR))
{
const SpectatorVec& list = g_game.getSpectators(target->getTile()->getPosition());
CombatNullFunc(caster, target, params, NULL);
combatTileEffects(list, caster, target->getTile(), params);
if(params.targetCallback)
params.targetCallback->onTargetCombat(caster, target);
if(params.impactEffect != NM_ME_NONE)
g_game.addMagicEffect(target->getPosition(), params.impactEffect);
if(caster && params.distanceEffect != NM_ME_NONE)
addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect);
}
}
//**********************************************************
void ValueCallback::getMinMaxValues(Player* player, int32_t& min, int32_t& max, bool useCharges) const
{
//"onGetPlayerMinMaxValues"(cid, ...)
if(m_scriptInterface->reserveScriptEnv())
{
ScriptEnviroment* env = m_scriptInterface->getScriptEnv();
if(!env->setCallbackId(m_scriptId, m_scriptInterface))
return;
m_scriptInterface->pushFunction(m_scriptId);
lua_State* L = m_scriptInterface->getLuaState();
lua_pushnumber(L, env->addThing(player));
int32_t parameters = 1;
switch(type)
{
case FORMULA_LEVELMAGIC:
{
//"onGetPlayerMinMaxValues"(cid, level, maglevel)
lua_pushnumber(L, player->getLevel());
lua_pushnumber(L, player->getMagicLevel());
parameters += 2;
break;
}
case FORMULA_SKILL:
{
//"onGetPlayerMinMaxValues"(cid, attackSkill, attackValue, attackFactor)
Item* tool = player->getWeapon();
lua_pushnumber(L, player->getWeaponSkill(tool));
int32_t attackValue = 7;
if(tool)
{
attackValue = tool->getAttack();
if(useCharges && tool->hasCharges() && g_config.getBool(ConfigManager::REMOVE_WEAPON_CHARGES))
g_game.transformItem(tool, tool->getID(), std::max(0, tool->getCharges() - 1));
}
lua_pushnumber(L, attackValue);
lua_pushnumber(L, (float)player->getAttackFactor());
parameters += 3;
break;
}
default:
{
std::cout << "[Warning - ValueCallback::getMinMaxValues] Unknown callback type" << std::endl;
return;
}
}
int32_t params = lua_gettop(L);
if(!lua_pcall(L, parameters, 2, 0))
{
max = LuaScriptInterface::popNumber(L);
min = LuaScriptInterface::popNumber(L);
Vocation* vocation = player->getVocation();
float multiplier = 1.0;
if(max > 0)
multiplier = vocation->getMultiplier(MULTIPLIER_MAGICHEALING);
else
multiplier = vocation->getMultiplier(MULTIPLIER_MAGIC);
min = (int32_t)(min * multiplier);
max = (int32_t)(max * multiplier);
}
else
LuaScriptInterface::reportError(NULL, std::string(LuaScriptInterface::popString(L)));
if((lua_gettop(L) + parameters + 1) != params)
LuaScriptInterface::reportError(NULL, "Stack size changed!");
env->resetCallback();
m_scriptInterface->releaseScriptEnv();
}
else
std::cout << "[Error - ValueCallback::getMinMaxValues] Call stack overflow." << std::endl;
}
//**********************************************************
void TileCallback::onTileCombat(Creature* creature, Tile* tile) const
{
//"onTileCombat"(cid, pos)
if(m_scriptInterface->reserveScriptEnv())
{
ScriptEnviroment* env = m_scriptInterface->getScriptEnv();
if(!env->setCallbackId(m_scriptId, m_scriptInterface))
return;
m_scriptInterface->pushFunction(m_scriptId);
lua_State* L = m_scriptInterface->getLuaState();
lua_pushnumber(L, creature ? env->addThing(creature) : 0);
m_scriptInterface->pushPosition(L, tile->getPosition(), 0);
m_scriptInterface->callFunction(2);
env->resetCallback();
m_scriptInterface->releaseScriptEnv();
}
else
std::cout << "[Error] Call stack overflow. TileCallback::onTileCombat" << std::endl;
}
//**********************************************************
void TargetCallback::onTargetCombat(Creature* creature, Creature* target) const
{
//"onTargetCombat"(cid, target)
if(m_scriptInterface->reserveScriptEnv())
{
ScriptEnviroment* env = m_scriptInterface->getScriptEnv();
if(!env->setCallbackId(m_scriptId, m_scriptInterface))
return;
uint32_t cid = 0;
if(creature)
cid = env->addThing(creature);
m_scriptInterface->pushFunction(m_scriptId);
lua_State* L = m_scriptInterface->getLuaState();
lua_pushnumber(L, cid);
lua_pushnumber(L, env->addThing(target));
int32_t size = lua_gettop(L);
if(lua_pcall(L, 2, 0 /*nReturnValues*/, 0) != 0)
LuaScriptInterface::reportError(NULL, std::string(LuaScriptInterface::popString(L)));
if((lua_gettop(L) + 2 /*nParams*/ + 1) != size)
LuaScriptInterface::reportError(NULL, "Stack size changed!");
env->resetCallback();
m_scriptInterface->releaseScriptEnv();
}
else
{
std::cout << "[Error - TargetCallback::onTargetCombat] Call stack overflow." << std::endl;
return;
}
}
//**********************************************************
void AreaCombat::clear()
{
for(AreaCombatMap::iterator it = areas.begin(); it != areas.end(); ++it)
delete it->second;
areas.clear();
}
AreaCombat::AreaCombat(const AreaCombat& rhs)
{
hasExtArea = rhs.hasExtArea;
for(AreaCombatMap::const_iterator it = rhs.areas.begin(); it != rhs.areas.end(); ++it)
areas[it->first] = new MatrixArea(*it->second);
}
bool AreaCombat::getList(const Position& centerPos, const Position& targetPos, std::list& list) const
{
Tile* tile = g_game.getTile(targetPos);
const MatrixArea* area = getArea(centerPos, targetPos);
if(!area)
return false;
uint16_t tmpX = targetPos.x, tmpY = targetPos.y, centerY = 0, centerX = 0;
size_t cols = area->getCols(), rows = area->getRows();
area->getCenter(centerY, centerX);
tmpX -= centerX;
tmpY -= centerY;
for(size_t y = 0; y < rows; ++y)
{
for(size_t x = 0; x < cols; ++x)
{
if(area->getValue(y, x) != 0)
{
if(targetPos.z < MAP_MAX_LAYERS && g_game.isSightClear(targetPos, Position(tmpX, tmpY, targetPos.z), true))
{
tile = g_game.getTile(tmpX, tmpY, targetPos.z);
if(!tile)
{
tile = new Tile(tmpX, tmpY, targetPos.z);
g_game.setTile(tile);
}
list.push_back(tile);
}
}
tmpX++;
}
tmpX -= cols;
tmpY++;
}
return true;
}
void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const
{
uint16_t centerY, centerX;
input->getCenter(centerY, centerX);
if(op == MATRIXOPERATION_COPY)
{
for(uint32_t y = 0; y < input->getRows(); ++y)
{
for(uint32_t x = 0; x < input->getCols(); ++x)
(*output)[y][x] = (*input)[y][x];
}
output->setCenter(centerY, centerX);
}
else if(op == MATRIXOPERATION_MIRROR)
{
for(uint32_t y = 0; y < input->getRows(); ++y)
{
int32_t rx = 0;
for(int32_t x = input->getCols() - 1; x >= 0; --x)
(*output)[y][rx++] = (*input)[y][x];
}
output->setCenter(centerY, (input->getRows() - 1) - centerX);
}
else if(op == MATRIXOPERATION_FLIP)
{
for(uint32_t x = 0; x < input->getCols(); ++x)
{
int32_t ry = 0;
for(int32_t y = input->getRows() - 1; y >= 0; --y)
(*output)[ry++][x] = (*input)[y][x];
}
output->setCenter((input->getCols() - 1) - centerY, centerX);
}
//rotation
else
{
uint16_t centerX, centerY;
input->getCenter(centerY, centerX);
int32_t rotateCenterX = (output->getCols() / 2) - 1, rotateCenterY = (output->getRows() / 2) - 1, angle = 0;
switch(op)
{
case MATRIXOPERATION_ROTATE90:
angle = 90;
break;
case MATRIXOPERATION_ROTATE180:
angle = 180;
break;
case MATRIXOPERATION_ROTATE270:
angle = 270;
break;
default:
angle = 0;
break;
}
double angleRad = 3.1416 * angle / 180.0;
float a = std::cos(angleRad), b = -std::sin(angleRad);
float c = std::sin(angleRad), d = std::cos(angleRad);
for(int32_t x = 0; x < (long)input->getCols(); ++x)
{
for(int32_t y = 0; y < (long)input->getRows(); ++y)
{
//calculate new coordinates using rotation center
int32_t newX = x - centerX, newY = y - centerY;
//perform rotation
int32_t rotatedX = round(newX * a + newY * b);
int32_t rotatedY = round(newX * c + newY * d);
//write in the output matrix using rotated coordinates
(*output)[rotatedY + rotateCenterY][rotatedX + rotateCenterX] = (*input)[y][x];
}
}
output->setCenter(rotateCenterY, rotateCenterX);
}
}
MatrixArea* AreaCombat::createArea(const std::list& list, uint32_t rows)
{
uint32_t cols = list.size() / rows;
MatrixArea* area = new MatrixArea(rows, cols);
uint16_t x = 0, y = 0;
for(std::list::const_iterator it = list.begin(); it != list.end(); ++it)
{
if(*it == 1 || *it == 3)
area->setValue(y, x, true);
if(*it == 2 || *it == 3)
area->setCenter(y, x);
++x;
if(cols == x)
{
x = 0;
++y;
}
}
return area;
}
void AreaCombat::setupArea(const std::list& list, uint32_t rows)
{
//NORTH
MatrixArea* area = createArea(list, rows);
areas[NORTH] = area;
uint32_t maxOutput = std::max(area->getCols(), area->getRows()) * 2;
//SOUTH
MatrixArea* southArea = new MatrixArea(maxOutput, maxOutput);
copyArea(area, southArea, MATRIXOPERATION_ROTATE180);
areas[SOUTH] = southArea;
//EAST
MatrixArea* eastArea = new MatrixArea(maxOutput, maxOutput);
copyArea(area, eastArea, MATRIXOPERATION_ROTATE90);
areas[EAST] = eastArea;
//WEST
MatrixArea* westArea = new MatrixArea(maxOutput, maxOutput);
copyArea(area, westArea, MATRIXOPERATION_ROTATE270);
areas[WEST] = westArea;
}
void AreaCombat::setupArea(int32_t length, int32_t spread)
{
std::list list;
uint32_t rows = length;
int32_t cols = 1;
if(spread != 0)
cols = ((length - length % spread) / spread) * 2 + 1;
int32_t colSpread = cols;
for(uint32_t y = 1; y <= rows; ++y)
{
int32_t mincol = cols - colSpread + 1, maxcol = cols - (cols - colSpread);
for(int32_t x = 1; x <= cols; ++x)
{
if(y == rows && x == ((cols - cols % 2) / 2) + 1)
list.push_back(3);
else if(x >= mincol && x <= maxcol)
list.push_back(1);
else
list.push_back(0);
}
if(spread > 0 && y % spread == 0)
--colSpread;
}
setupArea(list, rows);
}
void AreaCombat::setupArea(int32_t radius)
{
int32_t area[13][13] =
{
{0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0},
{0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0},
{0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0},
{0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0},
{0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0},
{8, 7, 6, 5, 4, 2, 1, 2, 4, 5, 6, 7, 8},
{0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0},
{0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0},
{0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0},
{0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0},
{0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0}
};
std::list list;
for(int32_t y = 0; y < 13; ++y)
{
for(int32_t x = 0; x < 13; ++x)
{
if(area[y][x] == 1)
list.push_back(3);
else if(area[y][x] > 0 && area[y][x] <= radius)
list.push_back(1);
else
list.push_back(0);
}
}
setupArea(list, 13);
}
void AreaCombat::setupExtArea(const std::list& list, uint32_t rows)
{
if(list.empty())
return;
//NORTH-WEST
MatrixArea* area = createArea(list, rows);
areas[NORTHWEST] = area;
uint32_t maxOutput = std::max(area->getCols(), area->getRows()) * 2;
//NORTH-EAST
MatrixArea* neArea = new MatrixArea(maxOutput, maxOutput);
copyArea(area, neArea, MATRIXOPERATION_MIRROR);
areas[NORTHEAST] = neArea;
//SOUTH-WEST
MatrixArea* swArea = new MatrixArea(maxOutput, maxOutput);
copyArea(area, swArea, MATRIXOPERATION_FLIP);
areas[SOUTHWEST] = swArea;
//SOUTH-EAST
MatrixArea* seArea = new MatrixArea(maxOutput, maxOutput);
copyArea(swArea, seArea, MATRIXOPERATION_MIRROR);
areas[SOUTHEAST] = seArea;
hasExtArea = true;
}
//**********************************************************
void MagicField::onStepInField(Creature* creature, bool purposeful/* = true*/)
{
if(isBlocking())
{
g_game.internalRemoveItem(creature, this, 1);
return;
}
if(!purposeful)
return;
const ItemType& it = items[getID()];
if(!it.condition)
return;
Condition* conditionCopy = it.condition->clone();
uint32_t ownerId = getOwner();
if(ownerId && !getTile()->hasFlag(TILESTATE_PVPZONE))
{
if(Creature* owner = g_game.getCreatureByID(ownerId))
{
bool harmful = true;
if((g_game.getWorldType() == WORLD_TYPE_NO_PVP || getTile()->hasFlag(TILESTATE_NOPVPZONE)) &&
(owner->getPlayer() || (owner->isSummon() && owner->getMaster()->getPlayer())))
harmful = false;
else if(Player* targetPlayer = creature->getPlayer())
{
if(owner->getPlayer() && Combat::isProtected(owner->getPlayer(), targetPlayer))
harmful = false;
}
if(!harmful || (OTSYS_TIME() - createTime) <= (uint32_t)g_config.getNumber(
ConfigManager::FIELD_OWNERSHIP) || creature->hasBeenAttacked(ownerId))
conditionCopy->setParam(CONDITIONPARAM_OWNER, ownerId);
}
}
creature->addCondition(conditionCopy);
}