spells.cpp

main 1302 lines · 33.6 KB Raw
Alex Alex Commit Server upload 18/10/2026 23:38
1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "combat.h"
23#include "configmanager.h"
24#include "game.h"
25#include "monster.h"
26#include "pugicast.h"
27#include "spells.h"
28
29extern Game g_game;
30extern Spells* g_spells;
31extern Monsters g_monsters;
32extern Vocations g_vocations;
33extern ConfigManager g_config;
34extern LuaEnvironment g_luaEnvironment;
35
36Spells::Spells()
37{
38 scriptInterface.initState();
39}
40
41Spells::~Spells()
42{
43 clear(false);
44}
45
46TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words)
47{
48 std::string str_words = words;
49
50 //strip trailing spaces
51 trimString(str_words);
52
53 InstantSpell* instantSpell = getInstantSpell(str_words);
54 if (!instantSpell) {
55 return TALKACTION_CONTINUE;
56 }
57
58 std::string param;
59
60 if (instantSpell->getHasParam()) {
61 size_t spellLen = instantSpell->getWords().length();
62 size_t paramLen = str_words.length() - spellLen;
63 std::string paramText = str_words.substr(spellLen, paramLen);
64 if (!paramText.empty() && paramText.front() == ' ') {
65 size_t loc1 = paramText.find('"', 1);
66 if (loc1 != std::string::npos) {
67 size_t loc2 = paramText.find('"', loc1 + 1);
68 if (loc2 == std::string::npos) {
69 loc2 = paramText.length();
70 } else if (paramText.find_last_not_of(' ') != loc2) {
71 return TALKACTION_CONTINUE;
72 }
73
74 param = paramText.substr(loc1 + 1, loc2 - loc1 - 1);
75 } else {
76 trimString(paramText);
77 loc1 = paramText.find(' ', 0);
78 if (loc1 == std::string::npos) {
79 param = paramText;
80 } else {
81 return TALKACTION_CONTINUE;
82 }
83 }
84 }
85 }
86
87 if (instantSpell->playerCastInstant(player, param)) {
88 words = instantSpell->getWords();
89
90 if (instantSpell->getHasParam() && !param.empty()) {
91 words += " \"" + param + "\"";
92 }
93
94 return TALKACTION_BREAK;
95 }
96
97 return TALKACTION_FAILED;
98}
99
100void Spells::clearMaps(bool fromLua)
101{
102 for (auto instant = instants.begin(); instant != instants.end(); ) {
103 if (fromLua == instant->second.fromLua) {
104 instant = instants.erase(instant);
105 } else {
106 ++instant;
107 }
108 }
109
110 for (auto rune = runes.begin(); rune != runes.end(); ) {
111 if (fromLua == rune->second.fromLua) {
112 rune = runes.erase(rune);
113 } else {
114 ++rune;
115 }
116 }
117}
118
119void Spells::clear(bool fromLua)
120{
121 clearMaps(fromLua);
122
123 reInitState(fromLua);
124}
125
126LuaScriptInterface& Spells::getScriptInterface()
127{
128 return scriptInterface;
129}
130
131std::string Spells::getScriptBaseName() const
132{
133 return "spells";
134}
135
136Event_ptr Spells::getEvent(const std::string& nodeName)
137{
138 if (strcasecmp(nodeName.c_str(), "rune") == 0) {
139 return Event_ptr(new RuneSpell(&scriptInterface));
140 } else if (strcasecmp(nodeName.c_str(), "instant") == 0) {
141 return Event_ptr(new InstantSpell(&scriptInterface));
142 }
143 return nullptr;
144}
145
146bool Spells::registerEvent(Event_ptr event, const pugi::xml_node&)
147{
148 InstantSpell* instant = dynamic_cast<InstantSpell*>(event.get());
149 if (instant) {
150 auto result = instants.emplace(instant->getWords(), std::move(*instant));
151 if (!result.second) {
152 std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl;
153 }
154 return result.second;
155 }
156
157 RuneSpell* rune = dynamic_cast<RuneSpell*>(event.get());
158 if (rune) {
159 auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune));
160 if (!result.second) {
161 std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl;
162 }
163 return result.second;
164 }
165
166 return false;
167}
168
169bool Spells::registerInstantLuaEvent(InstantSpell* event)
170{
171 InstantSpell_ptr instant { event };
172 if (instant) {
173 std::string words = instant->getWords();
174 auto result = instants.emplace(instant->getWords(), std::move(*instant));
175 if (!result.second) {
176 std::cout << "[Warning - Spells::registerInstantLuaEvent] Duplicate registered instant spell with words: " << words << std::endl;
177 }
178 return result.second;
179 }
180
181 return false;
182}
183
184bool Spells::registerRuneLuaEvent(RuneSpell* event)
185{
186 RuneSpell_ptr rune { event };
187 if (rune) {
188 uint16_t id = rune->getRuneItemId();
189 auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune));
190 if (!result.second) {
191 std::cout << "[Warning - Spells::registerRuneLuaEvent] Duplicate registered rune with id: " << id << std::endl;
192 }
193 return result.second;
194 }
195
196 return false;
197}
198
199Spell* Spells::getSpellByName(const std::string& name)
200{
201 Spell* spell = getRuneSpellByName(name);
202 if (!spell) {
203 spell = getInstantSpellByName(name);
204 }
205 return spell;
206}
207
208RuneSpell* Spells::getRuneSpell(uint32_t id)
209{
210 auto it = runes.find(id);
211 if (it == runes.end()) {
212 for (auto& rune : runes) {
213 if (rune.second.getId() == id) {
214 return &rune.second;
215 }
216 }
217 return nullptr;
218 }
219 return &it->second;
220}
221
222RuneSpell* Spells::getRuneSpellByName(const std::string& name)
223{
224 for (auto& it : runes) {
225 if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) {
226 return &it.second;
227 }
228 }
229 return nullptr;
230}
231
232InstantSpell* Spells::getInstantSpell(const std::string& words)
233{
234 InstantSpell* result = nullptr;
235
236 for (auto& it : instants) {
237 const std::string& instantSpellWords = it.second.getWords();
238 size_t spellLen = instantSpellWords.length();
239 if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) {
240 if (!result || spellLen > result->getWords().length()) {
241 result = &it.second;
242 if (words.length() == spellLen) {
243 break;
244 }
245 }
246 }
247 }
248
249 if (result) {
250 const std::string& resultWords = result->getWords();
251 if (words.length() > resultWords.length()) {
252 if (!result->getHasParam()) {
253 return nullptr;
254 }
255
256 size_t spellLen = resultWords.length();
257 size_t paramLen = words.length() - spellLen;
258 if (paramLen < 2 || words[spellLen] != ' ') {
259 return nullptr;
260 }
261 }
262 return result;
263 }
264 return nullptr;
265}
266
267InstantSpell* Spells::getInstantSpellById(uint32_t spellId)
268{
269 for (auto& it : instants) {
270 if (it.second.getId() == spellId) {
271 return &it.second;
272 }
273 }
274 return nullptr;
275}
276
277InstantSpell* Spells::getInstantSpellByName(const std::string& name)
278{
279 for (auto& it : instants) {
280 if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) {
281 return &it.second;
282 }
283 }
284 return nullptr;
285}
286
287Position Spells::getCasterPosition(Creature* creature, Direction dir)
288{
289 return getNextPosition(dir, creature->getPosition());
290}
291
292CombatSpell::CombatSpell(Combat* combat, bool needTarget, bool needDirection) :
293 Event(&g_spells->getScriptInterface()),
294 combat(combat),
295 needDirection(needDirection),
296 needTarget(needTarget)
297{}
298
299CombatSpell::~CombatSpell()
300{
301 if (!scripted) {
302 delete combat;
303 }
304}
305
306bool CombatSpell::loadScriptCombat()
307{
308 combat = g_luaEnvironment.getCombatObject(g_luaEnvironment.lastCombatId);
309 return combat != nullptr;
310}
311
312bool CombatSpell::castSpell(Creature* creature)
313{
314 if (scripted) {
315 LuaVariant var;
316 var.type = VARIANT_POSITION;
317
318 if (needDirection) {
319 var.pos = Spells::getCasterPosition(creature, creature->getDirection());
320 } else {
321 var.pos = creature->getPosition();
322 }
323
324 return executeCastSpell(creature, var);
325 }
326
327 Position pos;
328 if (needDirection) {
329 pos = Spells::getCasterPosition(creature, creature->getDirection());
330 } else {
331 pos = creature->getPosition();
332 }
333
334 combat->doCombat(creature, pos);
335 return true;
336}
337
338bool CombatSpell::castSpell(Creature* creature, Creature* target)
339{
340 if (scripted) {
341 LuaVariant var;
342
343 if (combat->hasArea()) {
344 var.type = VARIANT_POSITION;
345
346 if (needTarget) {
347 var.pos = target->getPosition();
348 } else if (needDirection) {
349 var.pos = Spells::getCasterPosition(creature, creature->getDirection());
350 } else {
351 var.pos = creature->getPosition();
352 }
353 } else {
354 var.type = VARIANT_NUMBER;
355 var.number = target->getID();
356 }
357 return executeCastSpell(creature, var);
358 }
359
360 if (combat->hasArea()) {
361 if (needTarget) {
362 combat->doCombat(creature, target->getPosition());
363 } else {
364 return castSpell(creature);
365 }
366 } else {
367 combat->doCombat(creature, target);
368 }
369 return true;
370}
371
372bool CombatSpell::executeCastSpell(Creature* creature, const LuaVariant& var)
373{
374 //onCastSpell(creature, var)
375 if (!scriptInterface->reserveScriptEnv()) {
376 std::cout << "[Error - CombatSpell::executeCastSpell] Call stack overflow" << std::endl;
377 return false;
378 }
379
380 ScriptEnvironment* env = scriptInterface->getScriptEnv();
381 env->setScriptId(scriptId, scriptInterface);
382
383 lua_State* L = scriptInterface->getLuaState();
384
385 scriptInterface->pushFunction(scriptId);
386
387 LuaScriptInterface::pushUserdata<Creature>(L, creature);
388 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
389
390 LuaScriptInterface::pushVariant(L, var);
391
392 return scriptInterface->callFunction(2);
393}
394
395bool Spell::configureSpell(const pugi::xml_node& node)
396{
397 pugi::xml_attribute nameAttribute = node.attribute("name");
398 if (!nameAttribute) {
399 std::cout << "[Error - Spell::configureSpell] Spell without name" << std::endl;
400 return false;
401 }
402
403 name = nameAttribute.as_string();
404
405 static const char* reservedList[] = {
406 "melee",
407 "physical",
408 "poison",
409 "fire",
410 "energy",
411 "drown",
412 "lifedrain",
413 "manadrain",
414 "healing",
415 "speed",
416 "outfit",
417 "invisible",
418 "drunk",
419 "firefield",
420 "poisonfield",
421 "energyfield",
422 "firecondition",
423 "poisoncondition",
424 "energycondition",
425 "drowncondition",
426 "freezecondition",
427 "cursecondition",
428 "dazzlecondition"
429 };
430
431 //static size_t size = sizeof(reservedList) / sizeof(const char*);
432 //for (size_t i = 0; i < size; ++i) {
433 for (const char* reserved : reservedList) {
434 if (strcasecmp(reserved, name.c_str()) == 0) {
435 std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl;
436 return false;
437 }
438 }
439
440 pugi::xml_attribute attr;
441 if ((attr = node.attribute("spellid"))) {
442 spellId = pugi::cast<uint16_t>(attr.value());
443 }
444
445 if ((attr = node.attribute("group"))) {
446 std::string tmpStr = asLowerCaseString(attr.as_string());
447 if (tmpStr == "none" || tmpStr == "0") {
448 group = SPELLGROUP_NONE;
449 } else if (tmpStr == "attack" || tmpStr == "1") {
450 group = SPELLGROUP_ATTACK;
451 } else if (tmpStr == "healing" || tmpStr == "2") {
452 group = SPELLGROUP_HEALING;
453 } else if (tmpStr == "support" || tmpStr == "3") {
454 group = SPELLGROUP_SUPPORT;
455 } else if (tmpStr == "special" || tmpStr == "4") {
456 group = SPELLGROUP_SPECIAL;
457 } else {
458 std::cout << "[Warning - Spell::configureSpell] Unknown group: " << attr.as_string() << std::endl;
459 }
460 }
461
462 if ((attr = node.attribute("groupcooldown"))) {
463 groupCooldown = pugi::cast<uint32_t>(attr.value());
464 }
465
466 if ((attr = node.attribute("secondarygroup"))) {
467 std::string tmpStr = asLowerCaseString(attr.as_string());
468 if (tmpStr == "none" || tmpStr == "0") {
469 secondaryGroup = SPELLGROUP_NONE;
470 } else if (tmpStr == "attack" || tmpStr == "1") {
471 secondaryGroup = SPELLGROUP_ATTACK;
472 } else if (tmpStr == "healing" || tmpStr == "2") {
473 secondaryGroup = SPELLGROUP_HEALING;
474 } else if (tmpStr == "support" || tmpStr == "3") {
475 secondaryGroup = SPELLGROUP_SUPPORT;
476 } else if (tmpStr == "special" || tmpStr == "4") {
477 secondaryGroup = SPELLGROUP_SPECIAL;
478 } else {
479 std::cout << "[Warning - Spell::configureSpell] Unknown secondarygroup: " << attr.as_string() << std::endl;
480 }
481 }
482
483 if ((attr = node.attribute("secondarygroupcooldown"))) {
484 secondaryGroupCooldown = pugi::cast<uint32_t>(attr.value());
485 }
486
487 if ((attr = node.attribute("level")) || (attr = node.attribute("lvl"))) {
488 level = pugi::cast<uint32_t>(attr.value());
489 }
490
491 if ((attr = node.attribute("magiclevel")) || (attr = node.attribute("maglv"))) {
492 magLevel = pugi::cast<uint32_t>(attr.value());
493 }
494
495 if ((attr = node.attribute("mana"))) {
496 mana = pugi::cast<uint32_t>(attr.value());
497 }
498
499 if ((attr = node.attribute("manapercent"))) {
500 manaPercent = pugi::cast<uint32_t>(attr.value());
501 }
502
503 if ((attr = node.attribute("soul"))) {
504 soul = pugi::cast<uint32_t>(attr.value());
505 }
506
507 if ((attr = node.attribute("range"))) {
508 range = pugi::cast<int32_t>(attr.value());
509 }
510
511 if ((attr = node.attribute("cooldown")) || (attr = node.attribute("exhaustion"))) {
512 cooldown = pugi::cast<uint32_t>(attr.value());
513 }
514
515 if ((attr = node.attribute("premium")) || (attr = node.attribute("prem"))) {
516 premium = attr.as_bool();
517 }
518
519 if ((attr = node.attribute("enabled"))) {
520 enabled = attr.as_bool();
521 }
522
523 if ((attr = node.attribute("needtarget"))) {
524 needTarget = attr.as_bool();
525 }
526
527 if ((attr = node.attribute("needweapon"))) {
528 needWeapon = attr.as_bool();
529 }
530
531 if ((attr = node.attribute("selftarget"))) {
532 selfTarget = attr.as_bool();
533 }
534
535 if ((attr = node.attribute("needlearn"))) {
536 learnable = attr.as_bool();
537 }
538
539 if ((attr = node.attribute("blocking"))) {
540 blockingSolid = attr.as_bool();
541 blockingCreature = blockingSolid;
542 }
543
544 if ((attr = node.attribute("blocktype"))) {
545 std::string tmpStrValue = asLowerCaseString(attr.as_string());
546 if (tmpStrValue == "all") {
547 blockingSolid = true;
548 blockingCreature = true;
549 } else if (tmpStrValue == "solid") {
550 blockingSolid = true;
551 } else if (tmpStrValue == "creature") {
552 blockingCreature = true;
553 } else {
554 std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << attr.as_string() << "\" does not exist." << std::endl;
555 }
556 }
557
558 if ((attr = node.attribute("pzlock"))) {
559 pzLock = booleanString(attr.as_string());
560 }
561
562 if ((attr = node.attribute("aggressive"))) {
563 aggressive = booleanString(attr.as_string());
564 }
565
566 if (group == SPELLGROUP_NONE) {
567 group = (aggressive ? SPELLGROUP_ATTACK : SPELLGROUP_HEALING);
568 }
569
570 for (auto vocationNode : node.children()) {
571 if (!(attr = vocationNode.attribute("name"))) {
572 continue;
573 }
574
575 int32_t vocationId = g_vocations.getVocationId(attr.as_string());
576 if (vocationId != -1) {
577 attr = vocationNode.attribute("showInDescription");
578 vocSpellMap[vocationId] = !attr || attr.as_bool();
579 } else {
580 std::cout << "[Warning - Spell::configureSpell] Wrong vocation name: " << attr.as_string() << std::endl;
581 }
582 }
583 return true;
584}
585
586bool Spell::playerSpellCheck(Player* player) const
587{
588 if (player->hasFlag(PlayerFlag_CannotUseSpells)) {
589 return false;
590 }
591
592 if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) {
593 return true;
594 }
595
596 if (!enabled) {
597 return false;
598 }
599
600 if ((aggressive || pzLock) && (range < 1 || (range > 0 && !player->getAttackedCreature())) && player->getSkull() == SKULL_BLACK) {
601 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
602 return false;
603 }
604
605 if ((aggressive || pzLock) && player->hasCondition(CONDITION_PACIFIED)) {
606 player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
607 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
608 return false;
609 }
610
611 if ((aggressive || pzLock) && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) {
612 player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE);
613 return false;
614 }
615
616 if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || player->hasCondition(CONDITION_SPELLCOOLDOWN, spellId) || (secondaryGroup != SPELLGROUP_NONE && player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, secondaryGroup))) {
617 player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
618
619 if (isInstant()) {
620 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
621 }
622
623 return false;
624 }
625
626 if (player->getLevel() < level) {
627 player->sendCancelMessage(RETURNVALUE_NOTENOUGHLEVEL);
628 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
629 return false;
630 }
631
632 if (player->getMagicLevel() < magLevel) {
633 player->sendCancelMessage(RETURNVALUE_NOTENOUGHMAGICLEVEL);
634 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
635 return false;
636 }
637
638 if (player->getMana() < getManaCost(player) && !player->hasFlag(PlayerFlag_HasInfiniteMana)) {
639 player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA);
640 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
641 return false;
642 }
643
644 if (player->getSoul() < soul && !player->hasFlag(PlayerFlag_HasInfiniteSoul)) {
645 player->sendCancelMessage(RETURNVALUE_NOTENOUGHSOUL);
646 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
647 return false;
648 }
649
650 if (isInstant() && isLearnable()) {
651 if (!player->hasLearnedInstantSpell(getName())) {
652 player->sendCancelMessage(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL);
653 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
654 return false;
655 }
656 } else if (!vocSpellMap.empty() && vocSpellMap.find(player->getVocationId()) == vocSpellMap.end()) {
657 player->sendCancelMessage(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL);
658 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
659 return false;
660 }
661
662 if (needWeapon) {
663 switch (player->getWeaponType()) {
664 case WEAPON_SWORD:
665 case WEAPON_CLUB:
666 case WEAPON_AXE:
667 break;
668
669 default: {
670 player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL);
671 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
672 return false;
673 }
674 }
675 }
676
677 if (isPremium() && !player->isPremium()) {
678 player->sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
679 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
680 return false;
681 }
682
683 return true;
684}
685
686bool Spell::playerInstantSpellCheck(Player* player, const Position& toPos)
687{
688 if (toPos.x == 0xFFFF) {
689 return true;
690 }
691
692 const Position& playerPos = player->getPosition();
693 if (playerPos.z > toPos.z) {
694 player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS);
695 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
696 return false;
697 } else if (playerPos.z < toPos.z) {
698 player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS);
699 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
700 return false;
701 }
702
703 Tile* tile = g_game.map.getTile(toPos);
704 if (!tile) {
705 tile = new StaticTile(toPos.x, toPos.y, toPos.z);
706 g_game.map.setTile(toPos, tile);
707 }
708
709 ReturnValue ret = Combat::canDoCombat(player, tile, aggressive);
710 if (ret != RETURNVALUE_NOERROR) {
711 player->sendCancelMessage(ret);
712 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
713 return false;
714 }
715
716 if (blockingCreature && tile->getBottomVisibleCreature(player) != nullptr) {
717 player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
718 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
719 return false;
720 }
721
722 if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) {
723 player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
724 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
725 return false;
726 }
727
728 return true;
729}
730
731bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos)
732{
733 if (!playerSpellCheck(player)) {
734 return false;
735 }
736
737 if (toPos.x == 0xFFFF) {
738 return true;
739 }
740
741 const Position& playerPos = player->getPosition();
742 if (playerPos.z > toPos.z) {
743 player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS);
744 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
745 return false;
746 } else if (playerPos.z < toPos.z) {
747 player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS);
748 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
749 return false;
750 }
751
752 Tile* tile = g_game.map.getTile(toPos);
753 if (!tile) {
754 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
755 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
756 return false;
757 }
758
759 if (range != -1 && !g_game.canThrowObjectTo(playerPos, toPos, true, range, range)) {
760 player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH);
761 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
762 return false;
763 }
764
765 ReturnValue ret = Combat::canDoCombat(player, tile, aggressive);
766 if (ret != RETURNVALUE_NOERROR) {
767 player->sendCancelMessage(ret);
768 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
769 return false;
770 }
771
772 const Creature* topVisibleCreature = tile->getBottomVisibleCreature(player);
773 if (blockingCreature && topVisibleCreature) {
774 player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
775 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
776 return false;
777 } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) {
778 player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
779 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
780 return false;
781 }
782
783 if (needTarget && !topVisibleCreature) {
784 player->sendCancelMessage(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES);
785 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
786 return false;
787 }
788
789 if (aggressive && needTarget && topVisibleCreature && player->hasSecureMode()) {
790 const Player* targetPlayer = topVisibleCreature->getPlayer();
791 if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) {
792 player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS);
793 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
794 return false;
795 }
796 }
797 return true;
798}
799
800void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool payCost /*= true*/) const
801{
802 if (finishedCast) {
803 if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) {
804 if (cooldown > 0) {
805 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId);
806 player->addCondition(condition);
807 }
808
809 if (groupCooldown > 0) {
810 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group);
811 player->addCondition(condition);
812 }
813
814 if (secondaryGroupCooldown > 0) {
815 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup);
816 player->addCondition(condition);
817 }
818 }
819
820 if (aggressive) {
821 player->addInFightTicks();
822 }
823 }
824
825 if (payCost) {
826 Spell::postCastSpell(player, getManaCost(player), getSoulCost());
827 }
828}
829
830void Spell::postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost)
831{
832 if (manaCost > 0) {
833 player->addManaSpent(manaCost);
834 player->changeMana(-static_cast<int32_t>(manaCost));
835 }
836
837 if (!player->hasFlag(PlayerFlag_HasInfiniteSoul)) {
838 if (soulCost > 0) {
839 player->changeSoul(-static_cast<int32_t>(soulCost));
840 }
841 }
842}
843
844uint32_t Spell::getManaCost(const Player* player) const
845{
846 if (mana != 0) {
847 return mana;
848 }
849
850 if (manaPercent != 0) {
851 uint32_t maxMana = player->getMaxMana();
852 uint32_t manaCost = (maxMana * manaPercent) / 100;
853 return manaCost;
854 }
855
856 return 0;
857}
858
859std::string InstantSpell::getScriptEventName() const
860{
861 return "onCastSpell";
862}
863
864bool InstantSpell::configureEvent(const pugi::xml_node& node)
865{
866 if (!Spell::configureSpell(node)) {
867 return false;
868 }
869
870 if (!TalkAction::configureEvent(node)) {
871 return false;
872 }
873
874 spellType = SPELL_INSTANT;
875
876 pugi::xml_attribute attr;
877 if ((attr = node.attribute("params"))) {
878 hasParam = attr.as_bool();
879 }
880
881 if ((attr = node.attribute("playernameparam"))) {
882 hasPlayerNameParam = attr.as_bool();
883 }
884
885 if ((attr = node.attribute("direction"))) {
886 needDirection = attr.as_bool();
887 } else if ((attr = node.attribute("casterTargetOrDirection"))) {
888 casterTargetOrDirection = attr.as_bool();
889 }
890
891 if ((attr = node.attribute("blockwalls"))) {
892 checkLineOfSight = attr.as_bool();
893 }
894 return true;
895}
896
897bool InstantSpell::playerCastInstant(Player* player, std::string& param)
898{
899 if (!playerSpellCheck(player)) {
900 return false;
901 }
902
903 LuaVariant var;
904
905 if (selfTarget) {
906 var.type = VARIANT_NUMBER;
907 var.number = player->getID();
908 } else if (needTarget || casterTargetOrDirection) {
909 Creature* target = nullptr;
910 bool useDirection = false;
911
912 if (hasParam) {
913 Player* playerTarget = nullptr;
914 ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget);
915
916 if (playerTarget && playerTarget->isAccessPlayer() && !player->isAccessPlayer()) {
917 playerTarget = nullptr;
918 }
919
920 target = playerTarget;
921 if (!target || target->getHealth() <= 0) {
922 if (!casterTargetOrDirection) {
923 if (cooldown > 0) {
924 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId);
925 player->addCondition(condition);
926 }
927
928 if (groupCooldown > 0) {
929 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group);
930 player->addCondition(condition);
931 }
932
933 if (secondaryGroupCooldown > 0) {
934 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup);
935 player->addCondition(condition);
936 }
937
938 player->sendCancelMessage(ret);
939 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
940 return false;
941 }
942
943 useDirection = true;
944 }
945
946 if (playerTarget) {
947 param = playerTarget->getName();
948 }
949 } else {
950 target = player->getAttackedCreature();
951 if (!target || target->getHealth() <= 0) {
952 if (!casterTargetOrDirection) {
953 player->sendCancelMessage(RETURNVALUE_YOUCANONLYUSEITONCREATURES);
954 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
955 return false;
956 }
957
958 useDirection = true;
959 }
960 }
961
962 if (!useDirection) {
963 if (!canThrowSpell(player, target)) {
964 player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE);
965 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
966 return false;
967 }
968
969 var.type = VARIANT_NUMBER;
970 var.number = target->getID();
971 } else {
972 var.type = VARIANT_POSITION;
973 var.pos = Spells::getCasterPosition(player, player->getDirection());
974
975 if (!playerInstantSpellCheck(player, var.pos)) {
976 return false;
977 }
978 }
979 } else if (hasParam) {
980 var.type = VARIANT_STRING;
981
982 if (getHasPlayerNameParam()) {
983 Player* playerTarget = nullptr;
984 ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget);
985
986 if (ret != RETURNVALUE_NOERROR) {
987 if (cooldown > 0) {
988 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId);
989 player->addCondition(condition);
990 }
991
992 if (groupCooldown > 0) {
993 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group);
994 player->addCondition(condition);
995 }
996
997 if (secondaryGroupCooldown > 0) {
998 Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup);
999 player->addCondition(condition);
1000 }
1001
1002 player->sendCancelMessage(ret);
1003 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
1004 return false;
1005 }
1006
1007 if (playerTarget && (!playerTarget->isAccessPlayer() || player->isAccessPlayer())) {
1008 param = playerTarget->getName();
1009 }
1010 }
1011
1012 var.text = param;
1013 } else {
1014 var.type = VARIANT_POSITION;
1015
1016 if (needDirection) {
1017 var.pos = Spells::getCasterPosition(player, player->getDirection());
1018 } else {
1019 var.pos = player->getPosition();
1020 }
1021
1022 if (!playerInstantSpellCheck(player, var.pos)) {
1023 return false;
1024 }
1025 }
1026
1027 bool result = internalCastSpell(player, var);
1028 if (result) {
1029 postCastSpell(player);
1030 }
1031
1032 return result;
1033}
1034
1035bool InstantSpell::canThrowSpell(const Creature* creature, const Creature* target) const
1036{
1037 const Position& fromPos = creature->getPosition();
1038 const Position& toPos = target->getPosition();
1039 if (fromPos.z != toPos.z ||
1040 (range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight)) ||
1041 (range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, range, range))) {
1042 return false;
1043 }
1044 return true;
1045}
1046
1047bool InstantSpell::castSpell(Creature* creature)
1048{
1049 LuaVariant var;
1050
1051 if (casterTargetOrDirection) {
1052 Creature* target = creature->getAttackedCreature();
1053 if (target && target->getHealth() > 0) {
1054 if (!canThrowSpell(creature, target)) {
1055 return false;
1056 }
1057
1058 var.type = VARIANT_NUMBER;
1059 var.number = target->getID();
1060 return internalCastSpell(creature, var);
1061 }
1062
1063 return false;
1064 } else if (needDirection) {
1065 var.type = VARIANT_POSITION;
1066 var.pos = Spells::getCasterPosition(creature, creature->getDirection());
1067 } else {
1068 var.type = VARIANT_POSITION;
1069 var.pos = creature->getPosition();
1070 }
1071
1072 return internalCastSpell(creature, var);
1073}
1074
1075bool InstantSpell::castSpell(Creature* creature, Creature* target)
1076{
1077 if (needTarget) {
1078 LuaVariant var;
1079 var.type = VARIANT_NUMBER;
1080 var.number = target->getID();
1081 return internalCastSpell(creature, var);
1082 } else {
1083 return castSpell(creature);
1084 }
1085}
1086
1087bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var)
1088{
1089 return executeCastSpell(creature, var);
1090}
1091
1092bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var)
1093{
1094 //onCastSpell(creature, var)
1095 if (!scriptInterface->reserveScriptEnv()) {
1096 std::cout << "[Error - InstantSpell::executeCastSpell] Call stack overflow" << std::endl;
1097 return false;
1098 }
1099
1100 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1101 env->setScriptId(scriptId, scriptInterface);
1102
1103 lua_State* L = scriptInterface->getLuaState();
1104
1105 scriptInterface->pushFunction(scriptId);
1106
1107 LuaScriptInterface::pushUserdata<Creature>(L, creature);
1108 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
1109
1110 LuaScriptInterface::pushVariant(L, var);
1111
1112 return scriptInterface->callFunction(2);
1113}
1114
1115bool InstantSpell::canCast(const Player* player) const
1116{
1117 if (player->hasFlag(PlayerFlag_CannotUseSpells)) {
1118 return false;
1119 }
1120
1121 if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) {
1122 return true;
1123 }
1124
1125 if (isLearnable()) {
1126 if (player->hasLearnedInstantSpell(getName())) {
1127 return true;
1128 }
1129 } else {
1130 if (vocSpellMap.empty() || vocSpellMap.find(player->getVocationId()) != vocSpellMap.end()) {
1131 return true;
1132 }
1133 }
1134
1135 return false;
1136}
1137
1138std::string RuneSpell::getScriptEventName() const
1139{
1140 return "onCastSpell";
1141}
1142
1143bool RuneSpell::configureEvent(const pugi::xml_node& node)
1144{
1145 if (!Spell::configureSpell(node)) {
1146 return false;
1147 }
1148
1149 if (!Action::configureEvent(node)) {
1150 return false;
1151 }
1152
1153 spellType = SPELL_RUNE;
1154
1155 pugi::xml_attribute attr;
1156 if (!(attr = node.attribute("id"))) {
1157 std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl;
1158 return false;
1159 }
1160 runeId = pugi::cast<uint16_t>(attr.value());
1161
1162 if ((attr = node.attribute("charges"))) {
1163 charges = pugi::cast<uint32_t>(attr.value());
1164 } else {
1165 charges = 0;
1166 }
1167
1168 hasCharges = (charges > 0);
1169 if (magLevel != 0 || level != 0) {
1170 //Change information in the ItemType to get accurate description
1171 ItemType& iType = Item::items.getItemType(runeId);
1172 iType.runeMagLevel = magLevel;
1173 iType.runeLevel = level;
1174 iType.charges = charges;
1175 }
1176
1177 return true;
1178}
1179
1180ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& toPos)
1181{
1182 if (player->hasFlag(PlayerFlag_CannotUseSpells)) {
1183 return RETURNVALUE_CANNOTUSETHISOBJECT;
1184 }
1185
1186 ReturnValue ret = Action::canExecuteAction(player, toPos);
1187 if (ret != RETURNVALUE_NOERROR) {
1188 return ret;
1189 }
1190
1191 if (toPos.x == 0xFFFF) {
1192 if (needTarget) {
1193 return RETURNVALUE_CANONLYUSETHISRUNEONCREATURES;
1194 } else if (!selfTarget) {
1195 return RETURNVALUE_NOTENOUGHROOM;
1196 }
1197 }
1198
1199 return RETURNVALUE_NOERROR;
1200}
1201
1202bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition, bool isHotkey)
1203{
1204 if (!playerRuneSpellCheck(player, toPosition)) {
1205 return false;
1206 }
1207
1208 if (!scripted) {
1209 return false;
1210 }
1211
1212 LuaVariant var;
1213
1214 if (needTarget) {
1215 var.type = VARIANT_NUMBER;
1216
1217 if (target == nullptr) {
1218 Tile* toTile = g_game.map.getTile(toPosition);
1219 if (toTile) {
1220 const Creature* visibleCreature = toTile->getBottomVisibleCreature(player);
1221 if (visibleCreature) {
1222 var.number = visibleCreature->getID();
1223 }
1224 }
1225 } else {
1226 var.number = target->getCreature()->getID();
1227 }
1228 } else {
1229 var.type = VARIANT_POSITION;
1230 var.pos = toPosition;
1231 }
1232
1233 if (!internalCastSpell(player, var, isHotkey)) {
1234 return false;
1235 }
1236
1237 postCastSpell(player);
1238
1239 target = g_game.getCreatureByID(var.number);
1240 if (getPzLock() && target) {
1241 player->onAttackedCreature(target->getCreature());
1242 }
1243
1244 if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) {
1245 int32_t newCount = std::max<int32_t>(0, item->getItemCount() - 1);
1246 g_game.transformItem(item, item->getID(), newCount);
1247 }
1248 return true;
1249}
1250
1251bool RuneSpell::castSpell(Creature* creature)
1252{
1253 LuaVariant var;
1254 var.type = VARIANT_NUMBER;
1255 var.number = creature->getID();
1256 return internalCastSpell(creature, var, false);
1257}
1258
1259bool RuneSpell::castSpell(Creature* creature, Creature* target)
1260{
1261 LuaVariant var;
1262 var.type = VARIANT_NUMBER;
1263 var.number = target->getID();
1264 return internalCastSpell(creature, var, false);
1265}
1266
1267bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey)
1268{
1269 bool result;
1270 if (scripted) {
1271 result = executeCastSpell(creature, var, isHotkey);
1272 } else {
1273 result = false;
1274 }
1275 return result;
1276}
1277
1278bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey)
1279{
1280 //onCastSpell(creature, var, isHotkey)
1281 if (!scriptInterface->reserveScriptEnv()) {
1282 std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow" << std::endl;
1283 return false;
1284 }
1285
1286 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1287 env->setScriptId(scriptId, scriptInterface);
1288
1289 lua_State* L = scriptInterface->getLuaState();
1290
1291 scriptInterface->pushFunction(scriptId);
1292
1293 LuaScriptInterface::pushUserdata<Creature>(L, creature);
1294 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
1295
1296 LuaScriptInterface::pushVariant(L, var);
1297
1298 LuaScriptInterface::pushBoolean(L, isHotkey);
1299
1300 return scriptInterface->callFunction(3);
1301}
1302
Top