mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-04-30 01:29:21 +02:00
1905 lines
48 KiB
C++
1905 lines
48 KiB
C++
/**
|
|
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
|
|
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
|
|
*
|
|
* 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 2 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, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "otpch.h"
|
|
|
|
#include "combat.h"
|
|
#include "configmanager.h"
|
|
#include "game.h"
|
|
#include "monster.h"
|
|
#include "pugicast.h"
|
|
#include "spells.h"
|
|
|
|
extern Game g_game;
|
|
extern Spells* g_spells;
|
|
extern Monsters g_monsters;
|
|
extern Vocations g_vocations;
|
|
extern ConfigManager g_config;
|
|
extern LuaEnvironment g_luaEnvironment;
|
|
|
|
Spells::Spells()
|
|
{
|
|
scriptInterface.initState();
|
|
}
|
|
|
|
Spells::~Spells()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words)
|
|
{
|
|
std::string str_words = words;
|
|
|
|
//strip trailing spaces
|
|
trimString(str_words);
|
|
|
|
std::ostringstream str_instantSpell;
|
|
for (size_t i = 0; i < str_words.length(); i++) {
|
|
if (!isspace(str_words[i]) || (i < str_words.length() - 1 && !isspace(str_words[i + 1]))) {
|
|
str_instantSpell << str_words[i];
|
|
}
|
|
}
|
|
|
|
str_words = str_instantSpell.str();
|
|
|
|
InstantSpell* instantSpell = getInstantSpell(str_words);
|
|
if (!instantSpell) {
|
|
return TALKACTION_CONTINUE;
|
|
}
|
|
|
|
std::string param;
|
|
|
|
if (instantSpell->getHasParam()) {
|
|
size_t spellLen = instantSpell->getWords().length();
|
|
size_t paramLen = str_words.length() - spellLen;
|
|
std::string paramText = str_words.substr(spellLen, paramLen);
|
|
if (!paramText.empty() && paramText.front() == ' ') {
|
|
size_t loc1 = paramText.find('"', 1);
|
|
if (loc1 != std::string::npos) {
|
|
size_t loc2 = paramText.find('"', loc1 + 1);
|
|
if (loc2 == std::string::npos) {
|
|
loc2 = paramText.length();
|
|
} else if (paramText.find_last_not_of(' ') != loc2) {
|
|
return TALKACTION_CONTINUE;
|
|
}
|
|
|
|
param = paramText.substr(loc1 + 1, loc2 - loc1 - 1);
|
|
} else {
|
|
trimString(paramText);
|
|
loc1 = paramText.find(' ', 0);
|
|
if (loc1 == std::string::npos) {
|
|
param = paramText;
|
|
} else {
|
|
return TALKACTION_CONTINUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (instantSpell->playerCastInstant(player, param)) {
|
|
return TALKACTION_BREAK;
|
|
}
|
|
|
|
return TALKACTION_FAILED;
|
|
}
|
|
|
|
void Spells::clear()
|
|
{
|
|
for (const auto& it : runes) {
|
|
delete it.second;
|
|
}
|
|
runes.clear();
|
|
|
|
for (const auto& it : instants) {
|
|
delete it.second;
|
|
}
|
|
instants.clear();
|
|
|
|
scriptInterface.reInitState();
|
|
}
|
|
|
|
LuaScriptInterface& Spells::getScriptInterface()
|
|
{
|
|
return scriptInterface;
|
|
}
|
|
|
|
std::string Spells::getScriptBaseName() const
|
|
{
|
|
return "spells";
|
|
}
|
|
|
|
Event* Spells::getEvent(const std::string& nodeName)
|
|
{
|
|
if (strcasecmp(nodeName.c_str(), "rune") == 0) {
|
|
return new RuneSpell(&scriptInterface);
|
|
} else if (strcasecmp(nodeName.c_str(), "instant") == 0) {
|
|
return new InstantSpell(&scriptInterface);
|
|
} else if (strcasecmp(nodeName.c_str(), "conjure") == 0) {
|
|
return new ConjureSpell(&scriptInterface);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool Spells::registerEvent(Event* event, const pugi::xml_node&)
|
|
{
|
|
InstantSpell* instant = dynamic_cast<InstantSpell*>(event);
|
|
if (instant) {
|
|
auto result = instants.emplace(instant->getWords(), instant);
|
|
if (!result.second) {
|
|
std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl;
|
|
}
|
|
return result.second;
|
|
}
|
|
|
|
RuneSpell* rune = dynamic_cast<RuneSpell*>(event);
|
|
if (rune) {
|
|
auto result = runes.emplace(rune->getRuneItemId(), rune);
|
|
if (!result.second) {
|
|
std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl;
|
|
}
|
|
return result.second;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Spell* Spells::getSpellByName(const std::string& name)
|
|
{
|
|
Spell* spell = getRuneSpellByName(name);
|
|
if (!spell) {
|
|
spell = getInstantSpellByName(name);
|
|
}
|
|
return spell;
|
|
}
|
|
|
|
RuneSpell* Spells::getRuneSpell(uint32_t id)
|
|
{
|
|
auto it = runes.find(id);
|
|
if (it == runes.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
RuneSpell* Spells::getRuneSpellByName(const std::string& name)
|
|
{
|
|
for (const auto& it : runes) {
|
|
if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) {
|
|
return it.second;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
InstantSpell* Spells::getInstantSpell(const std::string& words)
|
|
{
|
|
InstantSpell* result = nullptr;
|
|
|
|
for (const auto& it : instants) {
|
|
InstantSpell* instantSpell = it.second;
|
|
|
|
const std::string& instantSpellWords = instantSpell->getWords();
|
|
size_t spellLen = instantSpellWords.length();
|
|
if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) {
|
|
if (!result || spellLen > result->getWords().length()) {
|
|
result = instantSpell;
|
|
if (words.length() == spellLen) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result) {
|
|
const std::string& resultWords = result->getWords();
|
|
if (words.length() > resultWords.length()) {
|
|
size_t spellLen = resultWords.length();
|
|
size_t paramLen = words.length() - spellLen;
|
|
if (paramLen < 2 || words[spellLen] != ' ') {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
uint32_t Spells::getInstantSpellCount(const Player* player) const
|
|
{
|
|
uint32_t count = 0;
|
|
for (const auto& it : instants) {
|
|
InstantSpell* instantSpell = it.second;
|
|
if (instantSpell->canCast(player)) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
InstantSpell* Spells::getInstantSpellByIndex(const Player* player, uint32_t index)
|
|
{
|
|
uint32_t count = 0;
|
|
for (const auto& it : instants) {
|
|
InstantSpell* instantSpell = it.second;
|
|
if (instantSpell->canCast(player)) {
|
|
if (count == index) {
|
|
return instantSpell;
|
|
}
|
|
++count;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
InstantSpell* Spells::getInstantSpellByName(const std::string& name)
|
|
{
|
|
for (const auto& it : instants) {
|
|
if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) {
|
|
return it.second;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Position Spells::getCasterPosition(Creature* creature, Direction dir)
|
|
{
|
|
return getNextPosition(dir, creature->getPosition());
|
|
}
|
|
|
|
CombatSpell::CombatSpell(Combat* combat, bool needTarget, bool needDirection) :
|
|
Event(&g_spells->getScriptInterface()),
|
|
combat(combat),
|
|
needDirection(needDirection),
|
|
needTarget(needTarget)
|
|
{}
|
|
|
|
CombatSpell::~CombatSpell()
|
|
{
|
|
if (!scripted) {
|
|
delete combat;
|
|
}
|
|
}
|
|
|
|
bool CombatSpell::loadScriptCombat()
|
|
{
|
|
combat = g_luaEnvironment.getCombatObject(g_luaEnvironment.lastCombatId);
|
|
return combat != nullptr;
|
|
}
|
|
|
|
bool CombatSpell::castSpell(Creature* creature)
|
|
{
|
|
if (scripted) {
|
|
LuaVariant var;
|
|
var.type = VARIANT_POSITION;
|
|
|
|
if (needDirection) {
|
|
var.pos = Spells::getCasterPosition(creature, creature->getDirection());
|
|
} else {
|
|
var.pos = creature->getPosition();
|
|
}
|
|
|
|
return executeCastSpell(creature, var);
|
|
}
|
|
|
|
Position pos;
|
|
if (needDirection) {
|
|
pos = Spells::getCasterPosition(creature, creature->getDirection());
|
|
} else {
|
|
pos = creature->getPosition();
|
|
}
|
|
|
|
combat->doCombat(creature, pos);
|
|
return true;
|
|
}
|
|
|
|
bool CombatSpell::castSpell(Creature* creature, Creature* target)
|
|
{
|
|
if (scripted) {
|
|
LuaVariant var;
|
|
|
|
if (combat->hasArea()) {
|
|
var.type = VARIANT_POSITION;
|
|
|
|
if (needTarget) {
|
|
var.pos = target->getPosition();
|
|
} else if (needDirection) {
|
|
var.pos = Spells::getCasterPosition(creature, creature->getDirection());
|
|
} else {
|
|
var.pos = creature->getPosition();
|
|
}
|
|
} else {
|
|
var.type = VARIANT_NUMBER;
|
|
var.number = target->getID();
|
|
}
|
|
return executeCastSpell(creature, var);
|
|
}
|
|
|
|
if (combat->hasArea()) {
|
|
if (needTarget) {
|
|
combat->doCombat(creature, target->getPosition());
|
|
} else {
|
|
return castSpell(creature);
|
|
}
|
|
} else {
|
|
combat->doCombat(creature, target);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CombatSpell::executeCastSpell(Creature* creature, const LuaVariant& var)
|
|
{
|
|
//onCastSpell(creature, var)
|
|
if (!scriptInterface->reserveScriptEnv()) {
|
|
std::cout << "[Error - CombatSpell::executeCastSpell] Call stack overflow" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
ScriptEnvironment* env = scriptInterface->getScriptEnv();
|
|
env->setScriptId(scriptId, scriptInterface);
|
|
|
|
lua_State* L = scriptInterface->getLuaState();
|
|
|
|
scriptInterface->pushFunction(scriptId);
|
|
|
|
LuaScriptInterface::pushUserdata<Creature>(L, creature);
|
|
LuaScriptInterface::setCreatureMetatable(L, -1, creature);
|
|
|
|
LuaScriptInterface::pushVariant(L, var);
|
|
|
|
return scriptInterface->callFunction(2);
|
|
}
|
|
|
|
bool Spell::configureSpell(const pugi::xml_node& node)
|
|
{
|
|
pugi::xml_attribute nameAttribute = node.attribute("name");
|
|
if (!nameAttribute) {
|
|
std::cout << "[Error - Spell::configureSpell] Spell without name" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
name = nameAttribute.as_string();
|
|
|
|
/*static const char* reservedList[] = {
|
|
"melee",
|
|
"physical",
|
|
"poison",
|
|
"fire",
|
|
"energy",
|
|
"drown",
|
|
"lifedrain",
|
|
"manadrain",
|
|
"healing",
|
|
"speed",
|
|
"outfit",
|
|
"invisible",
|
|
"drunk",
|
|
"firefield",
|
|
"poisonfield",
|
|
"energyfield",
|
|
"firecondition",
|
|
"poisoncondition",
|
|
"energycondition",
|
|
"drowncondition",
|
|
};
|
|
|
|
//static size_t size = sizeof(reservedList) / sizeof(const char*);
|
|
//for (size_t i = 0; i < size; ++i) {
|
|
for (const char* reserved : reservedList) {
|
|
if (strcasecmp(reserved, name.c_str()) == 0) {
|
|
std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl;
|
|
return false;
|
|
}
|
|
}*/
|
|
|
|
pugi::xml_attribute attr;
|
|
if ((attr = node.attribute("spellid"))) {
|
|
spellId = pugi::cast<uint16_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("lvl"))) {
|
|
level = pugi::cast<uint32_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("maglv"))) {
|
|
magLevel = pugi::cast<uint32_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("mana"))) {
|
|
mana = pugi::cast<uint32_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("manapercent"))) {
|
|
manaPercent = pugi::cast<uint32_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("soul"))) {
|
|
soul = pugi::cast<uint32_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("range"))) {
|
|
range = pugi::cast<int32_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("exhaustion")) || (attr = node.attribute("cooldown"))) {
|
|
cooldown = pugi::cast<uint32_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("prem"))) {
|
|
premium = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("enabled"))) {
|
|
enabled = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("needtarget"))) {
|
|
needTarget = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("needweapon"))) {
|
|
needWeapon = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("selftarget"))) {
|
|
selfTarget = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("needlearn"))) {
|
|
learnable = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("blocking"))) {
|
|
blockingSolid = attr.as_bool();
|
|
blockingCreature = blockingSolid;
|
|
}
|
|
|
|
if ((attr = node.attribute("blocktype"))) {
|
|
std::string tmpStrValue = asLowerCaseString(attr.as_string());
|
|
if (tmpStrValue == "all") {
|
|
blockingSolid = true;
|
|
blockingCreature = true;
|
|
} else if (tmpStrValue == "solid") {
|
|
blockingSolid = true;
|
|
} else if (tmpStrValue == "creature") {
|
|
blockingCreature = true;
|
|
} else {
|
|
std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << attr.as_string() << "\" does not exist." << std::endl;
|
|
}
|
|
}
|
|
|
|
if ((attr = node.attribute("aggressive"))) {
|
|
aggressive = booleanString(attr.as_string());
|
|
}
|
|
|
|
for (auto vocationNode : node.children()) {
|
|
if (!(attr = vocationNode.attribute("name"))) {
|
|
continue;
|
|
}
|
|
|
|
int32_t vocationId = g_vocations.getVocationId(attr.as_string());
|
|
if (vocationId != -1) {
|
|
attr = vocationNode.attribute("showInDescription");
|
|
vocSpellMap[vocationId] = !attr || attr.as_bool();
|
|
} else {
|
|
std::cout << "[Warning - Spell::configureSpell] Wrong vocation name: " << attr.as_string() << std::endl;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Spell::playerSpellCheck(Player* player) const
|
|
{
|
|
if (player->hasFlag(PlayerFlag_CannotUseSpells)) {
|
|
return false;
|
|
}
|
|
|
|
if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) {
|
|
return true;
|
|
}
|
|
|
|
if (!enabled) {
|
|
return false;
|
|
}
|
|
|
|
if (aggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) {
|
|
player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE);
|
|
return false;
|
|
}
|
|
|
|
if (player->hasCondition(CONDITION_EXHAUST)) {
|
|
player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
|
|
|
|
if (isInstant()) {
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (player->getLevel() < level) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHLEVEL);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (player->getMagicLevel() < magLevel) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHMAGICLEVEL);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (player->getMana() < getManaCost(player) && !player->hasFlag(PlayerFlag_HasInfiniteMana)) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (player->getSoul() < soul && !player->hasFlag(PlayerFlag_HasInfiniteSoul)) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHSOUL);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (isInstant() && isLearnable()) {
|
|
if (!player->hasLearnedInstantSpell(getName())) {
|
|
player->sendCancelMessage(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
} else if (!vocSpellMap.empty() && vocSpellMap.find(player->getVocationId()) == vocSpellMap.end()) {
|
|
player->sendCancelMessage(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (needWeapon) {
|
|
Item* weapon = player->getWeapon();
|
|
if (!weapon) {
|
|
player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
switch (weapon->getWeaponType()) {
|
|
case WEAPON_SWORD:
|
|
case WEAPON_CLUB:
|
|
case WEAPON_AXE:
|
|
break;
|
|
|
|
default: {
|
|
player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isPremium() && !player->isPremium()) {
|
|
player->sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Spell::playerInstantSpellCheck(Player* player, const Position& toPos)
|
|
{
|
|
if (toPos.x == 0xFFFF) {
|
|
return true;
|
|
}
|
|
|
|
const Position& playerPos = player->getPosition();
|
|
if (playerPos.z > toPos.z) {
|
|
player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
} else if (playerPos.z < toPos.z) {
|
|
player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
Tile* tile = g_game.map.getTile(toPos);
|
|
if (!tile) {
|
|
tile = new StaticTile(toPos.x, toPos.y, toPos.z);
|
|
g_game.map.setTile(toPos, tile);
|
|
}
|
|
|
|
ReturnValue ret = Combat::canDoCombat(player, tile, aggressive);
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
player->sendCancelMessage(ret);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (blockingCreature && tile->getBottomVisibleCreature(player) != nullptr) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos)
|
|
{
|
|
if (!playerSpellCheck(player)) {
|
|
return false;
|
|
}
|
|
|
|
if (toPos.x == 0xFFFF) {
|
|
return true;
|
|
}
|
|
|
|
const Position& playerPos = player->getPosition();
|
|
if (playerPos.z > toPos.z) {
|
|
player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
} else if (playerPos.z < toPos.z) {
|
|
player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
Tile* tile = g_game.map.getTile(toPos);
|
|
if (!tile) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (range != -1 && !g_game.canThrowObjectTo(playerPos, toPos, true, range, range)) {
|
|
player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
ReturnValue ret = Combat::canDoCombat(player, tile, aggressive);
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
player->sendCancelMessage(ret);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
const Creature* visibleCreature = tile->getTopCreature();
|
|
if (blockingCreature && visibleCreature) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (needTarget && !visibleCreature) {
|
|
player->sendCancelMessage(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (aggressive && needTarget && visibleCreature && player->hasSecureMode()) {
|
|
const Player* targetPlayer = visibleCreature->getPlayer();
|
|
if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) {
|
|
player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool payCost /*= true*/) const
|
|
{
|
|
if (finishedCast) {
|
|
if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) {
|
|
if (aggressive) {
|
|
if (cooldown > 0) {
|
|
Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown);
|
|
player->addCondition(condition);
|
|
} else {
|
|
Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 2000);
|
|
player->addCondition(condition);
|
|
}
|
|
} else {
|
|
if (cooldown > 0) {
|
|
Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown);
|
|
player->addCondition(condition);
|
|
} else {
|
|
Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 1000);
|
|
player->addCondition(condition);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aggressive) {
|
|
player->addInFightTicks();
|
|
}
|
|
}
|
|
|
|
if (payCost) {
|
|
Spell::postCastSpell(player, getManaCost(player), getSoulCost());
|
|
}
|
|
}
|
|
|
|
void Spell::postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost)
|
|
{
|
|
if (manaCost > 0) {
|
|
player->addManaSpent(manaCost);
|
|
player->changeMana(-static_cast<int32_t>(manaCost));
|
|
}
|
|
|
|
if (!player->hasFlag(PlayerFlag_HasInfiniteSoul)) {
|
|
if (soulCost > 0) {
|
|
player->changeSoul(-static_cast<int32_t>(soulCost));
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t Spell::getManaCost(const Player* player) const
|
|
{
|
|
if (mana != 0) {
|
|
return mana;
|
|
}
|
|
|
|
if (manaPercent != 0) {
|
|
return player->getLevel() * manaPercent;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ReturnValue Spell::CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time)
|
|
{
|
|
ConditionOutfit* outfitCondition = new ConditionOutfit(CONDITIONID_COMBAT, CONDITION_OUTFIT, time);
|
|
outfitCondition->setOutfit(outfit);
|
|
creature->addCondition(outfitCondition);
|
|
return RETURNVALUE_NOERROR;
|
|
}
|
|
|
|
ReturnValue Spell::CreateIllusion(Creature* creature, const std::string& name, int32_t time)
|
|
{
|
|
const auto mType = g_monsters.getMonsterType(name);
|
|
if (mType == nullptr) {
|
|
return RETURNVALUE_CREATUREDOESNOTEXIST;
|
|
}
|
|
|
|
Player* player = creature->getPlayer();
|
|
if (player && !player->hasFlag(PlayerFlag_CanIllusionAll)) {
|
|
if (!mType->info.isIllusionable) {
|
|
return RETURNVALUE_NOTPOSSIBLE;
|
|
}
|
|
}
|
|
|
|
return CreateIllusion(creature, mType->info.outfit, time);
|
|
}
|
|
|
|
ReturnValue Spell::CreateIllusion(Creature* creature, uint32_t itemId, int32_t time)
|
|
{
|
|
const ItemType& it = Item::items[itemId];
|
|
if (it.id == 0) {
|
|
return RETURNVALUE_NOTPOSSIBLE;
|
|
}
|
|
|
|
Outfit_t outfit;
|
|
outfit.lookTypeEx = itemId;
|
|
|
|
return CreateIllusion(creature, outfit, time);
|
|
}
|
|
|
|
std::string InstantSpell::getScriptEventName() const
|
|
{
|
|
return "onCastSpell";
|
|
}
|
|
|
|
bool InstantSpell::configureEvent(const pugi::xml_node& node)
|
|
{
|
|
if (!Spell::configureSpell(node)) {
|
|
return false;
|
|
}
|
|
|
|
if (!TalkAction::configureEvent(node)) {
|
|
return false;
|
|
}
|
|
|
|
pugi::xml_attribute attr;
|
|
if ((attr = node.attribute("params"))) {
|
|
hasParam = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("playernameparam"))) {
|
|
hasPlayerNameParam = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("direction"))) {
|
|
needDirection = attr.as_bool();
|
|
} else if ((attr = node.attribute("casterTargetOrDirection"))) {
|
|
casterTargetOrDirection = attr.as_bool();
|
|
}
|
|
|
|
if ((attr = node.attribute("blockwalls"))) {
|
|
checkLineOfSight = attr.as_bool();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::loadFunction(const pugi::xml_attribute& attr)
|
|
{
|
|
const char* functionName = attr.as_string();
|
|
if (strcasecmp(functionName, "edithouseguest") == 0) {
|
|
function = HouseGuestList;
|
|
} else if (strcasecmp(functionName, "edithousesubowner") == 0) {
|
|
function = HouseSubOwnerList;
|
|
} else if (strcasecmp(functionName, "edithousedoor") == 0) {
|
|
function = HouseDoorList;
|
|
} else if (strcasecmp(functionName, "housekick") == 0) {
|
|
function = HouseKick;
|
|
} else if (strcasecmp(functionName, "searchplayer") == 0) {
|
|
function = SearchPlayer;
|
|
} else if (strcasecmp(functionName, "levitate") == 0) {
|
|
function = Levitate;
|
|
} else if (strcasecmp(functionName, "illusion") == 0) {
|
|
function = Illusion;
|
|
} else if (strcasecmp(functionName, "summonmonster") == 0) {
|
|
function = SummonMonster;
|
|
} else {
|
|
std::cout << "[Warning - InstantSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
scripted = false;
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::playerCastInstant(Player* player, std::string& param)
|
|
{
|
|
if (!playerSpellCheck(player)) {
|
|
return false;
|
|
}
|
|
|
|
LuaVariant var;
|
|
|
|
if (selfTarget) {
|
|
var.type = VARIANT_NUMBER;
|
|
var.number = player->getID();
|
|
} else if (needTarget || casterTargetOrDirection) {
|
|
Creature* target = nullptr;
|
|
bool useDirection = false;
|
|
|
|
if (hasParam) {
|
|
Player* playerTarget = nullptr;
|
|
ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget);
|
|
|
|
if (playerTarget && playerTarget->isAccessPlayer() && !player->isAccessPlayer()) {
|
|
playerTarget = nullptr;
|
|
}
|
|
|
|
target = playerTarget;
|
|
if (!target || target->getHealth() <= 0) {
|
|
if (!casterTargetOrDirection) {
|
|
player->sendCancelMessage(ret);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
useDirection = true;
|
|
}
|
|
|
|
if (playerTarget) {
|
|
param = playerTarget->getName();
|
|
}
|
|
} else {
|
|
target = player->getAttackedCreature();
|
|
if (!target || target->getHealth() <= 0) {
|
|
if (!casterTargetOrDirection) {
|
|
player->sendCancelMessage(RETURNVALUE_YOUCANONLYUSEITONCREATURES);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
useDirection = true;
|
|
}
|
|
}
|
|
|
|
if (!useDirection) {
|
|
if (!canThrowSpell(player, target)) {
|
|
player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
var.type = VARIANT_NUMBER;
|
|
var.number = target->getID();
|
|
} else {
|
|
var.type = VARIANT_POSITION;
|
|
var.pos = Spells::getCasterPosition(player, player->getDirection());
|
|
|
|
if (!playerInstantSpellCheck(player, var.pos)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else if (hasParam) {
|
|
var.type = VARIANT_STRING;
|
|
|
|
if (getHasPlayerNameParam()) {
|
|
Player* playerTarget = nullptr;
|
|
ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget);
|
|
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
player->sendCancelMessage(ret);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (playerTarget && (!playerTarget->isAccessPlayer() || player->isAccessPlayer())) {
|
|
param = playerTarget->getName();
|
|
}
|
|
}
|
|
|
|
var.text = param;
|
|
} else {
|
|
var.type = VARIANT_POSITION;
|
|
|
|
if (needDirection) {
|
|
var.pos = Spells::getCasterPosition(player, player->getDirection());
|
|
} else {
|
|
var.pos = player->getPosition();
|
|
}
|
|
|
|
if (!playerInstantSpellCheck(player, var.pos)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool result = internalCastSpell(player, var);
|
|
if (result) {
|
|
postCastSpell(player);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool InstantSpell::canThrowSpell(const Creature* creature, const Creature* target) const
|
|
{
|
|
const Position& fromPos = creature->getPosition();
|
|
const Position& toPos = target->getPosition();
|
|
if (fromPos.z != toPos.z ||
|
|
(range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight)) ||
|
|
(range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, range, range))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::castSpell(Creature* creature)
|
|
{
|
|
LuaVariant var;
|
|
|
|
if (casterTargetOrDirection) {
|
|
Creature* target = creature->getAttackedCreature();
|
|
if (target && target->getHealth() > 0) {
|
|
if (!canThrowSpell(creature, target)) {
|
|
return false;
|
|
}
|
|
|
|
var.type = VARIANT_NUMBER;
|
|
var.number = target->getID();
|
|
return internalCastSpell(creature, var);
|
|
}
|
|
|
|
return false;
|
|
} else if (needDirection) {
|
|
var.type = VARIANT_POSITION;
|
|
var.pos = Spells::getCasterPosition(creature, creature->getDirection());
|
|
} else {
|
|
var.type = VARIANT_POSITION;
|
|
var.pos = creature->getPosition();
|
|
}
|
|
|
|
return internalCastSpell(creature, var);
|
|
}
|
|
|
|
bool InstantSpell::castSpell(Creature* creature, Creature* target)
|
|
{
|
|
if (needTarget) {
|
|
LuaVariant var;
|
|
var.type = VARIANT_NUMBER;
|
|
var.number = target->getID();
|
|
return internalCastSpell(creature, var);
|
|
} else {
|
|
return castSpell(creature);
|
|
}
|
|
}
|
|
|
|
bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var)
|
|
{
|
|
if (scripted) {
|
|
return executeCastSpell(creature, var);
|
|
} else if (function) {
|
|
return function(this, creature, var.text);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var)
|
|
{
|
|
//onCastSpell(creature, var)
|
|
if (!scriptInterface->reserveScriptEnv()) {
|
|
std::cout << "[Error - InstantSpell::executeCastSpell] Call stack overflow" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
ScriptEnvironment* env = scriptInterface->getScriptEnv();
|
|
env->setScriptId(scriptId, scriptInterface);
|
|
|
|
lua_State* L = scriptInterface->getLuaState();
|
|
|
|
scriptInterface->pushFunction(scriptId);
|
|
|
|
LuaScriptInterface::pushUserdata<Creature>(L, creature);
|
|
LuaScriptInterface::setCreatureMetatable(L, -1, creature);
|
|
|
|
LuaScriptInterface::pushVariant(L, var);
|
|
|
|
return scriptInterface->callFunction(2);
|
|
}
|
|
|
|
House* InstantSpell::getHouseFromPos(Creature* creature)
|
|
{
|
|
if (!creature) {
|
|
return nullptr;
|
|
}
|
|
|
|
Player* player = creature->getPlayer();
|
|
if (!player) {
|
|
return nullptr;
|
|
}
|
|
|
|
HouseTile* houseTile = dynamic_cast<HouseTile*>(player->getTile());
|
|
if (!houseTile) {
|
|
return nullptr;
|
|
}
|
|
|
|
House* house = houseTile->getHouse();
|
|
if (!house) {
|
|
return nullptr;
|
|
}
|
|
|
|
return house;
|
|
}
|
|
|
|
bool InstantSpell::HouseGuestList(const InstantSpell*, Creature* creature, const std::string&)
|
|
{
|
|
House* house = getHouseFromPos(creature);
|
|
if (!house) {
|
|
return false;
|
|
}
|
|
|
|
Player* player = creature->getPlayer();
|
|
if (house->canEditAccessList(GUEST_LIST, player)) {
|
|
player->setEditHouse(house, GUEST_LIST);
|
|
player->sendHouseWindow(house, GUEST_LIST);
|
|
} else {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::HouseSubOwnerList(const InstantSpell*, Creature* creature, const std::string&)
|
|
{
|
|
House* house = getHouseFromPos(creature);
|
|
if (!house) {
|
|
return false;
|
|
}
|
|
|
|
Player* player = creature->getPlayer();
|
|
if (house->canEditAccessList(SUBOWNER_LIST, player)) {
|
|
player->setEditHouse(house, SUBOWNER_LIST);
|
|
player->sendHouseWindow(house, SUBOWNER_LIST);
|
|
} else {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::HouseDoorList(const InstantSpell*, Creature* creature, const std::string&)
|
|
{
|
|
House* house = getHouseFromPos(creature);
|
|
if (!house) {
|
|
return false;
|
|
}
|
|
|
|
Player* player = creature->getPlayer();
|
|
Position pos = Spells::getCasterPosition(player, player->getDirection());
|
|
Door* door = house->getDoorByPosition(pos);
|
|
if (door && house->canEditAccessList(door->getDoorId(), player)) {
|
|
player->setEditHouse(house, door->getDoorId());
|
|
player->sendHouseWindow(house, door->getDoorId());
|
|
} else {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::HouseKick(const InstantSpell*, Creature* creature, const std::string& param)
|
|
{
|
|
Player* player = creature->getPlayer();
|
|
|
|
Player* targetPlayer = g_game.getPlayerByName(param);
|
|
if (!targetPlayer) {
|
|
targetPlayer = player;
|
|
}
|
|
|
|
House* house = getHouseFromPos(targetPlayer);
|
|
if (!house) {
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
return false;
|
|
}
|
|
|
|
if (!house->kickPlayer(player, targetPlayer)) {
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::SearchPlayer(const InstantSpell*, Creature* creature, const std::string& param)
|
|
{
|
|
//a. From 1 to 4 sq's [Person] is standing next to you.
|
|
//b. From 5 to 100 sq's [Person] is to the south, north, east, west.
|
|
//c. From 101 to 274 sq's [Person] is far to the south, north, east, west.
|
|
//d. From 275 to infinite sq's [Person] is very far to the south, north, east, west.
|
|
//e. South-west, s-e, n-w, n-e (corner coordinates): this phrase appears if the player you're looking for has moved five squares in any direction from the south, north, east or west.
|
|
//f. Lower level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in.
|
|
//g. Higher level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in.
|
|
|
|
Player* player = creature->getPlayer();
|
|
if (!player) {
|
|
return false;
|
|
}
|
|
|
|
enum distance_t {
|
|
DISTANCE_BESIDE,
|
|
DISTANCE_CLOSE,
|
|
DISTANCE_FAR,
|
|
DISTANCE_VERYFAR,
|
|
};
|
|
|
|
enum direction_t {
|
|
DIR_N, DIR_S, DIR_E, DIR_W,
|
|
DIR_NE, DIR_NW, DIR_SE, DIR_SW,
|
|
};
|
|
|
|
enum level_t {
|
|
LEVEL_HIGHER,
|
|
LEVEL_LOWER,
|
|
LEVEL_SAME,
|
|
};
|
|
|
|
Player* playerExiva = g_game.getPlayerByName(param);
|
|
if (!playerExiva) {
|
|
return false;
|
|
}
|
|
|
|
if (playerExiva->isAccessPlayer() && !player->isAccessPlayer()) {
|
|
player->sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
const Position& lookPos = player->getPosition();
|
|
const Position& searchPos = playerExiva->getPosition();
|
|
|
|
int32_t dx = Position::getOffsetX(lookPos, searchPos);
|
|
int32_t dy = Position::getOffsetY(lookPos, searchPos);
|
|
int32_t dz = Position::getOffsetZ(lookPos, searchPos);
|
|
|
|
distance_t distance;
|
|
|
|
direction_t direction;
|
|
|
|
level_t level;
|
|
|
|
//getting floor
|
|
if (dz > 0) {
|
|
level = LEVEL_HIGHER;
|
|
} else if (dz < 0) {
|
|
level = LEVEL_LOWER;
|
|
} else {
|
|
level = LEVEL_SAME;
|
|
}
|
|
|
|
//getting distance
|
|
if (std::abs(dx) < 4 && std::abs(dy) < 4) {
|
|
distance = DISTANCE_BESIDE;
|
|
} else {
|
|
int32_t distance2 = dx * dx + dy * dy;
|
|
if (distance2 < 10000) {
|
|
distance = DISTANCE_CLOSE;
|
|
} else if (distance2 < 75076) {
|
|
distance = DISTANCE_FAR;
|
|
} else {
|
|
distance = DISTANCE_VERYFAR;
|
|
}
|
|
}
|
|
|
|
//getting direction
|
|
float tan;
|
|
if (dx != 0) {
|
|
tan = static_cast<float>(dy) / dx;
|
|
} else {
|
|
tan = 10.;
|
|
}
|
|
|
|
if (std::abs(tan) < 0.4142) {
|
|
if (dx > 0) {
|
|
direction = DIR_W;
|
|
} else {
|
|
direction = DIR_E;
|
|
}
|
|
} else if (std::abs(tan) < 2.4142) {
|
|
if (tan > 0) {
|
|
if (dy > 0) {
|
|
direction = DIR_NW;
|
|
} else {
|
|
direction = DIR_SE;
|
|
}
|
|
} else {
|
|
if (dx > 0) {
|
|
direction = DIR_SW;
|
|
} else {
|
|
direction = DIR_NE;
|
|
}
|
|
}
|
|
} else {
|
|
if (dy > 0) {
|
|
direction = DIR_N;
|
|
} else {
|
|
direction = DIR_S;
|
|
}
|
|
}
|
|
|
|
std::ostringstream ss;
|
|
ss << playerExiva->getName();
|
|
|
|
if (distance == DISTANCE_BESIDE) {
|
|
if (level == LEVEL_SAME) {
|
|
ss << " is standing next to you.";
|
|
} else if (level == LEVEL_HIGHER) {
|
|
ss << " is above you.";
|
|
} else if (level == LEVEL_LOWER) {
|
|
ss << " is below you.";
|
|
}
|
|
} else {
|
|
switch (distance) {
|
|
case DISTANCE_CLOSE:
|
|
if (level == LEVEL_SAME) {
|
|
ss << " is to the ";
|
|
} else if (level == LEVEL_HIGHER) {
|
|
ss << " is on a higher level to the ";
|
|
} else if (level == LEVEL_LOWER) {
|
|
ss << " is on a lower level to the ";
|
|
}
|
|
break;
|
|
case DISTANCE_FAR:
|
|
ss << " is far to the ";
|
|
break;
|
|
case DISTANCE_VERYFAR:
|
|
ss << " is very far to the ";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (direction) {
|
|
case DIR_N:
|
|
ss << "north.";
|
|
break;
|
|
case DIR_S:
|
|
ss << "south.";
|
|
break;
|
|
case DIR_E:
|
|
ss << "east.";
|
|
break;
|
|
case DIR_W:
|
|
ss << "west.";
|
|
break;
|
|
case DIR_NE:
|
|
ss << "north-east.";
|
|
break;
|
|
case DIR_NW:
|
|
ss << "north-west.";
|
|
break;
|
|
case DIR_SE:
|
|
ss << "south-east.";
|
|
break;
|
|
case DIR_SW:
|
|
ss << "south-west.";
|
|
break;
|
|
}
|
|
}
|
|
player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE);
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::SummonMonster(const InstantSpell* spell, Creature* creature, const std::string& param)
|
|
{
|
|
Player* player = creature->getPlayer();
|
|
if (!player) {
|
|
return false;
|
|
}
|
|
|
|
MonsterType* mType = g_monsters.getMonsterType(param);
|
|
if (!mType) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (!player->hasFlag(PlayerFlag_CanSummonAll)) {
|
|
if (!mType->info.isSummonable) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (player->getMana() < mType->info.manaCost) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (player->getSummonCount() >= 2) {
|
|
player->sendCancelMessage("You cannot summon more creatures.");
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Monster* monster = Monster::createMonster(param);
|
|
if (!monster) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
// Place the monster
|
|
creature->addSummon(monster);
|
|
|
|
if (!g_game.placeCreature(monster, creature->getPosition(), true)) {
|
|
creature->removeSummon(monster);
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
Spell::postCastSpell(player, mType->info.manaCost, spell->getSoulCost());
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE);
|
|
g_game.addMagicEffect(monster->getPosition(), CONST_ME_TELEPORT);
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::Levitate(const InstantSpell*, Creature* creature, const std::string& param)
|
|
{
|
|
Player* player = creature->getPlayer();
|
|
if (!player) {
|
|
return false;
|
|
}
|
|
|
|
const Position& currentPos = creature->getPosition();
|
|
const Position& destPos = Spells::getCasterPosition(creature, creature->getDirection());
|
|
|
|
ReturnValue ret = RETURNVALUE_NOTPOSSIBLE;
|
|
|
|
if (strcasecmp(param.c_str(), "up") == 0) {
|
|
if (currentPos.z != 8) {
|
|
Tile* tmpTile = g_game.map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1);
|
|
if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID))) {
|
|
tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.getZ() - 1);
|
|
if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) {
|
|
ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE);
|
|
}
|
|
}
|
|
}
|
|
} else if (strcasecmp(param.c_str(), "down") == 0) {
|
|
if (currentPos.z != 7) {
|
|
Tile* tmpTile = g_game.map.getTile(destPos);
|
|
if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) {
|
|
tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.z + 1);
|
|
if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) {
|
|
ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
player->sendCancelMessage(ret);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_TELEPORT);
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::Illusion(const InstantSpell*, Creature* creature, const std::string& param)
|
|
{
|
|
Player* player = creature->getPlayer();
|
|
if (!player) {
|
|
return false;
|
|
}
|
|
|
|
ReturnValue ret = CreateIllusion(creature, param, 180000);
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
player->sendCancelMessage(ret);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED);
|
|
return true;
|
|
}
|
|
|
|
bool InstantSpell::canCast(const Player* player) const
|
|
{
|
|
if (player->hasFlag(PlayerFlag_CannotUseSpells)) {
|
|
return false;
|
|
}
|
|
|
|
if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) {
|
|
return true;
|
|
}
|
|
|
|
if (isLearnable()) {
|
|
if (player->hasLearnedInstantSpell(getName())) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (vocSpellMap.empty() || vocSpellMap.find(player->getVocationId()) != vocSpellMap.end()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string ConjureSpell::getScriptEventName() const
|
|
{
|
|
return "onCastSpell";
|
|
}
|
|
|
|
bool ConjureSpell::configureEvent(const pugi::xml_node& node)
|
|
{
|
|
if (!InstantSpell::configureEvent(node)) {
|
|
return false;
|
|
}
|
|
|
|
pugi::xml_attribute attr;
|
|
if ((attr = node.attribute("conjureId"))) {
|
|
conjureId = pugi::cast<uint32_t>(attr.value());
|
|
}
|
|
|
|
if ((attr = node.attribute("conjureCount"))) {
|
|
conjureCount = pugi::cast<uint32_t>(attr.value());
|
|
} else if (conjureId != 0) {
|
|
// load default charges from items.xml
|
|
const ItemType& it = Item::items[conjureId];
|
|
if (it.charges != 0) {
|
|
conjureCount = it.charges;
|
|
}
|
|
}
|
|
|
|
if ((attr = node.attribute("reagentId"))) {
|
|
reagentId = pugi::cast<uint32_t>(attr.value());
|
|
}
|
|
|
|
ItemType& iType = Item::items.getItemType(conjureId);
|
|
if (iType.isRune()) {
|
|
iType.runeSpellName = words;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ConjureSpell::loadFunction(const pugi::xml_attribute&)
|
|
{
|
|
scripted = false;
|
|
return true;
|
|
}
|
|
|
|
bool ConjureSpell::conjureItem(Creature* creature) const
|
|
{
|
|
Player* player = creature->getPlayer();
|
|
if (!player) {
|
|
return false;
|
|
}
|
|
|
|
const uint32_t conjureCost = getManaCost(player);
|
|
const uint32_t soulCost = getSoulCost();
|
|
|
|
if (reagentId != 0) {
|
|
bool foundReagent = false;
|
|
|
|
Item* item = player->getInventoryItem(CONST_SLOT_LEFT);
|
|
if (item && item->getID() == reagentId) {
|
|
foundReagent = true;
|
|
|
|
// left arm conjure
|
|
int32_t index = player->getThingIndex(item);
|
|
g_game.internalRemoveItem(item);
|
|
|
|
Item* newItem = Item::CreateItem(conjureId, conjureCount);
|
|
if (!newItem) {
|
|
return false;
|
|
}
|
|
|
|
ReturnValue ret = g_game.internalAddItem(player, newItem, index);
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
delete newItem;
|
|
return false;
|
|
}
|
|
|
|
g_game.startDecay(newItem);
|
|
|
|
Spell::postCastSpell(player, conjureCost, soulCost);
|
|
}
|
|
|
|
item = player->getInventoryItem(CONST_SLOT_RIGHT);
|
|
if (item && item->getID() == reagentId && player->getMana() >= conjureCost) {
|
|
foundReagent = true;
|
|
|
|
// right arm conjure
|
|
int32_t index = player->getThingIndex(item);
|
|
g_game.internalRemoveItem(item);
|
|
|
|
Item* newItem = Item::CreateItem(conjureId, conjureCount);
|
|
if (!newItem) {
|
|
return false;
|
|
}
|
|
|
|
ReturnValue ret = g_game.internalAddItem(player, newItem, index);
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
delete newItem;
|
|
return false;
|
|
}
|
|
|
|
g_game.startDecay(newItem);
|
|
|
|
Spell::postCastSpell(player, conjureCost, soulCost);
|
|
}
|
|
|
|
if (!foundReagent) {
|
|
player->sendCancelMessage(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
} else {
|
|
Item* newItem = Item::CreateItem(conjureId, conjureCount);
|
|
if (!newItem) {
|
|
return false;
|
|
}
|
|
|
|
ReturnValue ret = g_game.internalPlayerAddItem(player, newItem);
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
delete newItem;
|
|
return false;
|
|
}
|
|
|
|
g_game.startDecay(newItem);
|
|
Spell::postCastSpell(player, conjureCost, soulCost);
|
|
}
|
|
|
|
postCastSpell(player, true, false);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED);
|
|
return true;
|
|
}
|
|
|
|
bool ConjureSpell::playerCastInstant(Player* player, std::string& param)
|
|
{
|
|
if (!playerSpellCheck(player)) {
|
|
return false;
|
|
}
|
|
|
|
if (scripted) {
|
|
LuaVariant var;
|
|
var.type = VARIANT_STRING;
|
|
var.text = param;
|
|
return executeCastSpell(player, var);
|
|
}
|
|
return conjureItem(player);
|
|
}
|
|
|
|
std::string RuneSpell::getScriptEventName() const
|
|
{
|
|
return "onCastSpell";
|
|
}
|
|
|
|
bool RuneSpell::configureEvent(const pugi::xml_node& node)
|
|
{
|
|
if (!Spell::configureSpell(node)) {
|
|
return false;
|
|
}
|
|
|
|
if (!Action::configureEvent(node)) {
|
|
return false;
|
|
}
|
|
|
|
pugi::xml_attribute attr;
|
|
if (!(attr = node.attribute("id"))) {
|
|
std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl;
|
|
return false;
|
|
}
|
|
runeId = pugi::cast<uint16_t>(attr.value());
|
|
|
|
uint32_t charges;
|
|
if ((attr = node.attribute("charges"))) {
|
|
charges = pugi::cast<uint32_t>(attr.value());
|
|
} else {
|
|
charges = 0;
|
|
}
|
|
|
|
hasCharges = (charges > 0);
|
|
|
|
//Change information in the ItemType to get accurate description
|
|
ItemType& iType = Item::items.getItemType(runeId);
|
|
iType.runeMagLevel = magLevel;
|
|
iType.runeLevel = level;
|
|
iType.charges = charges;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RuneSpell::loadFunction(const pugi::xml_attribute& attr)
|
|
{
|
|
const char* functionName = attr.as_string();
|
|
if (strcasecmp(functionName, "chameleon") == 0) {
|
|
runeFunction = Illusion;
|
|
} else if (strcasecmp(functionName, "convince") == 0) {
|
|
runeFunction = Convince;
|
|
} else {
|
|
std::cout << "[Warning - RuneSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
scripted = false;
|
|
return true;
|
|
}
|
|
|
|
bool RuneSpell::Illusion(const RuneSpell*, Player* player, const Position& posTo)
|
|
{
|
|
Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_MOVE);
|
|
if (!thing) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
Item* illusionItem = thing->getItem();
|
|
if (!illusionItem || !illusionItem->isMoveable()) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
uint32_t itemId = illusionItem->getID();
|
|
if (illusionItem->isDisguised()) {
|
|
itemId = illusionItem->getDisguiseId();
|
|
}
|
|
|
|
ReturnValue ret = CreateIllusion(player, itemId, 200000);
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
player->sendCancelMessage(ret);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED);
|
|
return true;
|
|
}
|
|
|
|
bool RuneSpell::Convince(const RuneSpell* spell, Player* player, const Position& posTo)
|
|
{
|
|
if (!player->hasFlag(PlayerFlag_CanConvinceAll)) {
|
|
if (player->getSummonCount() >= 2) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_LOOK);
|
|
if (!thing) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
Creature* convinceCreature = thing->getCreature();
|
|
if (!convinceCreature) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
uint32_t manaCost = 0;
|
|
if (convinceCreature->getMonster()) {
|
|
manaCost = convinceCreature->getMonster()->getManaCost();
|
|
}
|
|
|
|
if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && player->getMana() < manaCost) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
if (!convinceCreature->convinceCreature(player)) {
|
|
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
|
|
return false;
|
|
}
|
|
|
|
Spell::postCastSpell(player, manaCost, spell->getSoulCost());
|
|
g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED);
|
|
return true;
|
|
}
|
|
|
|
ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& toPos)
|
|
{
|
|
if (player->hasFlag(PlayerFlag_CannotUseSpells)) {
|
|
return RETURNVALUE_CANNOTUSETHISOBJECT;
|
|
}
|
|
|
|
ReturnValue ret = Action::canExecuteAction(player, toPos);
|
|
if (ret != RETURNVALUE_NOERROR) {
|
|
return ret;
|
|
}
|
|
|
|
if (toPos.x == 0xFFFF) {
|
|
if (needTarget) {
|
|
return RETURNVALUE_CANONLYUSETHISRUNEONCREATURES;
|
|
} else if (!selfTarget) {
|
|
return RETURNVALUE_NOTENOUGHROOM;
|
|
}
|
|
}
|
|
|
|
return RETURNVALUE_NOERROR;
|
|
}
|
|
|
|
bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition, bool isHotkey)
|
|
{
|
|
if (!playerRuneSpellCheck(player, toPosition)) {
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
if (scripted) {
|
|
LuaVariant var;
|
|
|
|
if (needTarget) {
|
|
var.type = VARIANT_NUMBER;
|
|
|
|
if (target == nullptr) {
|
|
Tile* toTile = g_game.map.getTile(toPosition);
|
|
if (toTile) {
|
|
const Creature* visibleCreature = toTile->getTopCreature();
|
|
if (visibleCreature) {
|
|
var.number = visibleCreature->getID();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
var.number = target->getCreature()->getID();
|
|
}
|
|
}
|
|
else {
|
|
var.type = VARIANT_POSITION;
|
|
var.pos = toPosition;
|
|
}
|
|
|
|
result = internalCastSpell(player, var, isHotkey);
|
|
} else if (runeFunction) {
|
|
result = runeFunction(this, player, toPosition);
|
|
}
|
|
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
|
|
postCastSpell(player);
|
|
if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) {
|
|
int32_t newCount = std::max<int32_t>(0, item->getCharges() - 1);
|
|
g_game.transformItem(item, item->getID(), newCount);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RuneSpell::castSpell(Creature* creature)
|
|
{
|
|
LuaVariant var;
|
|
var.type = VARIANT_NUMBER;
|
|
var.number = creature->getID();
|
|
return internalCastSpell(creature, var, false);
|
|
}
|
|
|
|
bool RuneSpell::castSpell(Creature* creature, Creature* target)
|
|
{
|
|
LuaVariant var;
|
|
var.type = VARIANT_NUMBER;
|
|
var.number = target->getID();
|
|
return internalCastSpell(creature, var, false);
|
|
}
|
|
|
|
bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey)
|
|
{
|
|
bool result;
|
|
if (scripted) {
|
|
result = executeCastSpell(creature, var, isHotkey);
|
|
}
|
|
else {
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey)
|
|
{
|
|
//onCastSpell(creature, var, isHotkey)
|
|
if (!scriptInterface->reserveScriptEnv()) {
|
|
std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
ScriptEnvironment* env = scriptInterface->getScriptEnv();
|
|
env->setScriptId(scriptId, scriptInterface);
|
|
|
|
lua_State* L = scriptInterface->getLuaState();
|
|
|
|
scriptInterface->pushFunction(scriptId);
|
|
|
|
LuaScriptInterface::pushUserdata<Creature>(L, creature);
|
|
LuaScriptInterface::setCreatureMetatable(L, -1, creature);
|
|
|
|
LuaScriptInterface::pushVariant(L, var);
|
|
|
|
LuaScriptInterface::pushBoolean(L, isHotkey);
|
|
|
|
return scriptInterface->callFunction(3);
|
|
}
|