combat.cpp

main 1511 lines · 40.7 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
24#include "game.h"
25#include "weapons.h"
26#include "configmanager.h"
27#include "events.h"
28
29extern Game g_game;
30extern Weapons* g_weapons;
31extern ConfigManager g_config;
32extern Events* g_events;
33
34CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const
35{
36 CombatDamage damage;
37 damage.origin = params.origin;
38 damage.primary.type = params.combatType;
39 if (formulaType == COMBAT_FORMULA_DAMAGE) {
40 damage.primary.value = normal_random(
41 static_cast<int32_t>(mina),
42 static_cast<int32_t>(maxa)
43 );
44 } else if (creature) {
45 int32_t min, max;
46 if (creature->getCombatValues(min, max)) {
47 damage.primary.value = normal_random(min, max);
48 } else if (Player* player = creature->getPlayer()) {
49 if (params.valueCallback) {
50 params.valueCallback->getMinMaxValues(player, damage);
51 } else if (formulaType == COMBAT_FORMULA_LEVELMAGIC) {
52 int32_t levelFormula = player->getLevel() * 2 + player->getMagicLevel() * 3;
53 damage.primary.value = normal_random(
54 static_cast<int32_t>(levelFormula * mina + minb),
55 static_cast<int32_t>(levelFormula * maxa + maxb)
56 );
57 } else if (formulaType == COMBAT_FORMULA_SKILL) {
58 Item* tool = player->getWeapon();
59 const Weapon* weapon = g_weapons->getWeapon(tool);
60 if (weapon) {
61 damage.primary.value = normal_random(
62 static_cast<int32_t>(minb),
63 static_cast<int32_t>(weapon->getWeaponDamage(player, target, tool, true) * maxa + maxb)
64 );
65
66 damage.secondary.type = weapon->getElementType();
67 damage.secondary.value = weapon->getElementDamage(player, target, tool);
68 } else {
69 damage.primary.value = normal_random(
70 static_cast<int32_t>(minb),
71 static_cast<int32_t>(maxb)
72 );
73 }
74 }
75 }
76 }
77 return damage;
78}
79
80void Combat::getCombatArea(const Position& centerPos, const Position& targetPos, const AreaCombat* area, std::forward_list<Tile*>& list)
81{
82 if (targetPos.z >= MAP_MAX_LAYERS) {
83 return;
84 }
85
86 if (area) {
87 area->getList(centerPos, targetPos, list);
88 } else {
89 Tile* tile = g_game.map.getTile(targetPos);
90 if (!tile) {
91 tile = new StaticTile(targetPos.x, targetPos.y, targetPos.z);
92 g_game.map.setTile(targetPos, tile);
93 }
94 list.push_front(tile);
95 }
96}
97
98CombatType_t Combat::ConditionToDamageType(ConditionType_t type)
99{
100 switch (type) {
101 case CONDITION_FIRE:
102 return COMBAT_FIREDAMAGE;
103
104 case CONDITION_ENERGY:
105 return COMBAT_ENERGYDAMAGE;
106
107 case CONDITION_BLEEDING:
108 return COMBAT_PHYSICALDAMAGE;
109
110 case CONDITION_DROWN:
111 return COMBAT_DROWNDAMAGE;
112
113 case CONDITION_POISON:
114 return COMBAT_EARTHDAMAGE;
115
116 case CONDITION_FREEZING:
117 return COMBAT_ICEDAMAGE;
118
119 case CONDITION_DAZZLED:
120 return COMBAT_HOLYDAMAGE;
121
122 case CONDITION_CURSED:
123 return COMBAT_DEATHDAMAGE;
124
125 default:
126 break;
127 }
128
129 return COMBAT_NONE;
130}
131
132ConditionType_t Combat::DamageToConditionType(CombatType_t type)
133{
134 switch (type) {
135 case COMBAT_FIREDAMAGE:
136 return CONDITION_FIRE;
137
138 case COMBAT_ENERGYDAMAGE:
139 return CONDITION_ENERGY;
140
141 case COMBAT_DROWNDAMAGE:
142 return CONDITION_DROWN;
143
144 case COMBAT_EARTHDAMAGE:
145 return CONDITION_POISON;
146
147 case COMBAT_ICEDAMAGE:
148 return CONDITION_FREEZING;
149
150 case COMBAT_HOLYDAMAGE:
151 return CONDITION_DAZZLED;
152
153 case COMBAT_DEATHDAMAGE:
154 return CONDITION_CURSED;
155
156 case COMBAT_PHYSICALDAMAGE:
157 return CONDITION_BLEEDING;
158
159 default:
160 return CONDITION_NONE;
161 }
162}
163
164bool Combat::isPlayerCombat(const Creature* target)
165{
166 if (target->getPlayer()) {
167 return true;
168 }
169
170 if (target->isSummon() && target->getMaster()->getPlayer()) {
171 return true;
172 }
173
174 return false;
175}
176
177ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target)
178{
179 if (attacker == target) {
180 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
181 }
182
183 if (target->getNpc()) {
184 return Combat::canDoCombat(attacker, target);
185 }
186
187 if (!attacker->hasFlag(PlayerFlag_IgnoreProtectionZone)) {
188 //pz-zone
189 if (attacker->getZone() == ZONE_PROTECTION) {
190 if (target->getNpc()) {
191 return Combat::canDoCombat(attacker, target);
192 } else {
193 return RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE;
194 }
195 }
196
197 if (target->getZone() == ZONE_PROTECTION) {
198 if (target->getNpc()) {
199 return Combat::canDoCombat(attacker, target);
200 } else {
201 return RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE;
202 }
203 }
204
205 //nopvp-zone
206 if (isPlayerCombat(target)) {
207 if (attacker->getZone() == ZONE_NOPVP) {
208 return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE;
209 }
210
211 if (target->getZone() == ZONE_NOPVP) {
212 return RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE;
213 }
214 }
215 }
216
217 if (attacker->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) {
218 if (target->getPlayer()) {
219 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
220 } else {
221 return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE;
222 }
223 }
224
225 if (target->getPlayer()) {
226 if (isProtected(attacker, target->getPlayer())) {
227 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
228 }
229
230 if (attacker->hasSecureMode() && !Combat::isInPvpZone(attacker, target) && attacker->getSkullClient(target->getPlayer()) == SKULL_NONE) {
231 return RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS;
232 }
233 }
234
235 return Combat::canDoCombat(attacker, target);
236}
237
238ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive)
239{
240 if (tile->hasProperty(CONST_PROP_BLOCKPROJECTILE)) {
241 return RETURNVALUE_NOTENOUGHROOM;
242 }
243
244 if (tile->hasFlag(TILESTATE_FLOORCHANGE)) {
245 return RETURNVALUE_NOTENOUGHROOM;
246 }
247
248 if (tile->getTeleportItem()) {
249 return RETURNVALUE_NOTENOUGHROOM;
250 }
251
252 if (caster) {
253 const Position& casterPosition = caster->getPosition();
254 const Position& tilePosition = tile->getPosition();
255 if (casterPosition.z < tilePosition.z) {
256 return RETURNVALUE_FIRSTGODOWNSTAIRS;
257 } else if (casterPosition.z > tilePosition.z) {
258 return RETURNVALUE_FIRSTGOUPSTAIRS;
259 }
260
261 if (const Player* player = caster->getPlayer()) {
262 if (player->hasFlag(PlayerFlag_IgnoreProtectionZone)) {
263 return RETURNVALUE_NOERROR;
264 }
265 }
266 }
267
268 //pz-zone
269 if (aggressive && tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
270 return RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE;
271 }
272
273 return g_events->eventCreatureOnAreaCombat(caster, tile, aggressive);
274}
275
276bool Combat::isInPvpZone(const Creature* attacker, const Creature* target)
277{
278 return attacker->getZone() == ZONE_PVP && target->getZone() == ZONE_PVP;
279}
280
281bool Combat::isProtected(const Player* attacker, const Player* target)
282{
283 uint32_t protectionLevel = g_config.getNumber(ConfigManager::PROTECTION_LEVEL);
284 if (target->getLevel() < protectionLevel || attacker->getLevel() < protectionLevel) {
285 return true;
286 }
287
288 if (attacker->getVocationId() == VOCATION_NONE || target->getVocationId() == VOCATION_NONE) {
289 return true;
290 }
291
292 if (attacker->getSkull() == SKULL_BLACK && attacker->getSkullClient(target) == SKULL_NONE) {
293 return true;
294 }
295
296 return false;
297}
298
299ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target)
300{
301 if (!attacker) {
302 return g_events->eventCreatureOnTargetCombat(attacker, target);
303 }
304
305 if (const Player* targetPlayer = target->getPlayer()) {
306 if (targetPlayer->hasFlag(PlayerFlag_CannotBeAttacked)) {
307 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
308 }
309
310 if (const Player* attackerPlayer = attacker->getPlayer()) {
311 if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackPlayer)) {
312 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
313 }
314
315 if (isProtected(attackerPlayer, targetPlayer)) {
316 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
317 }
318
319 //nopvp-zone
320 const Tile* targetPlayerTile = targetPlayer->getTile();
321 if (targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE)) {
322 return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE;
323 } else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) {
324 return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE;
325 }
326 }
327
328 if (attacker->isSummon()) {
329 if (const Player* masterAttackerPlayer = attacker->getMaster()->getPlayer()) {
330 if (masterAttackerPlayer->hasFlag(PlayerFlag_CannotAttackPlayer)) {
331 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
332 }
333
334 if (targetPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE)) {
335 return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE;
336 }
337
338 if (isProtected(masterAttackerPlayer, targetPlayer)) {
339 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
340 }
341 }
342 }
343 } else if (target->getMonster()) {
344 if (const Player* attackerPlayer = attacker->getPlayer()) {
345 if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackMonster)) {
346 return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE;
347 }
348
349 if (target->isSummon() && target->getMaster()->getPlayer() && target->getZone() == ZONE_NOPVP) {
350 return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE;
351 }
352 } else if (attacker->getMonster()) {
353 const Creature* targetMaster = target->getMaster();
354
355 if (!targetMaster || !targetMaster->getPlayer()) {
356 const Creature* attackerMaster = attacker->getMaster();
357
358 if (!attackerMaster || !attackerMaster->getPlayer()) {
359 return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE;
360 }
361 }
362 }
363 }
364
365 if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) {
366 if (attacker->getPlayer() || (attacker->isSummon() && attacker->getMaster()->getPlayer())) {
367 if (target->getPlayer()) {
368 if (!isInPvpZone(attacker, target)) {
369 return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER;
370 }
371 }
372
373 if (target->isSummon() && target->getMaster()->getPlayer()) {
374 if (!isInPvpZone(attacker, target)) {
375 return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE;
376 }
377 }
378 }
379 }
380 return g_events->eventCreatureOnTargetCombat(attacker, target);
381}
382
383void Combat::setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb)
384{
385 this->formulaType = formulaType;
386 this->mina = mina;
387 this->minb = minb;
388 this->maxa = maxa;
389 this->maxb = maxb;
390}
391
392bool Combat::setParam(CombatParam_t param, uint32_t value)
393{
394 switch (param) {
395 case COMBAT_PARAM_TYPE: {
396 params.combatType = static_cast<CombatType_t>(value);
397 return true;
398 }
399
400 case COMBAT_PARAM_EFFECT: {
401 params.impactEffect = static_cast<uint16_t>(value);
402 return true;
403 }
404
405 case COMBAT_PARAM_DISTANCEEFFECT: {
406 params.distanceEffect = static_cast<uint8_t>(value);
407 return true;
408 }
409
410 case COMBAT_PARAM_BLOCKARMOR: {
411 params.blockedByArmor = (value != 0);
412 return true;
413 }
414
415 case COMBAT_PARAM_BLOCKSHIELD: {
416 params.blockedByShield = (value != 0);
417 return true;
418 }
419
420 case COMBAT_PARAM_TARGETCASTERORTOPMOST: {
421 params.targetCasterOrTopMost = (value != 0);
422 return true;
423 }
424
425 case COMBAT_PARAM_CREATEITEM: {
426 params.itemId = value;
427 return true;
428 }
429
430 case COMBAT_PARAM_AGGRESSIVE: {
431 params.aggressive = (value != 0);
432 return true;
433 }
434
435 case COMBAT_PARAM_DISPEL: {
436 params.dispelType = static_cast<ConditionType_t>(value);
437 return true;
438 }
439
440 case COMBAT_PARAM_USECHARGES: {
441 params.useCharges = (value != 0);
442 return true;
443 }
444 }
445 return false;
446}
447
448bool Combat::setCallback(CallBackParam_t key)
449{
450 switch (key) {
451 case CALLBACK_PARAM_LEVELMAGICVALUE: {
452 params.valueCallback.reset(new ValueCallback(COMBAT_FORMULA_LEVELMAGIC));
453 return true;
454 }
455
456 case CALLBACK_PARAM_SKILLVALUE: {
457 params.valueCallback.reset(new ValueCallback(COMBAT_FORMULA_SKILL));
458 return true;
459 }
460
461 case CALLBACK_PARAM_TARGETTILE: {
462 params.tileCallback.reset(new TileCallback());
463 return true;
464 }
465
466 case CALLBACK_PARAM_TARGETCREATURE: {
467 params.targetCallback.reset(new TargetCallback());
468 return true;
469 }
470 }
471 return false;
472}
473
474CallBack* Combat::getCallback(CallBackParam_t key)
475{
476 switch (key) {
477 case CALLBACK_PARAM_LEVELMAGICVALUE:
478 case CALLBACK_PARAM_SKILLVALUE: {
479 return params.valueCallback.get();
480 }
481
482 case CALLBACK_PARAM_TARGETTILE: {
483 return params.tileCallback.get();
484 }
485
486 case CALLBACK_PARAM_TARGETCREATURE: {
487 return params.targetCallback.get();
488 }
489 }
490 return nullptr;
491}
492
493void Combat::combatTileEffects(const SpectatorVec& spectators, Creature* caster, Tile* tile, const CombatParams& params)
494{
495 if (params.itemId != 0) {
496 uint16_t itemId = params.itemId;
497 switch (itemId) {
498 case ITEM_FIREFIELD_PERSISTENT_FULL:
499 itemId = ITEM_FIREFIELD_PVP_FULL;
500 break;
501
502 case ITEM_FIREFIELD_PERSISTENT_MEDIUM:
503 itemId = ITEM_FIREFIELD_PVP_MEDIUM;
504 break;
505
506 case ITEM_FIREFIELD_PERSISTENT_SMALL:
507 itemId = ITEM_FIREFIELD_PVP_SMALL;
508 break;
509
510 case ITEM_ENERGYFIELD_PERSISTENT:
511 itemId = ITEM_ENERGYFIELD_PVP;
512 break;
513
514 case ITEM_POISONFIELD_PERSISTENT:
515 itemId = ITEM_POISONFIELD_PVP;
516 break;
517
518 case ITEM_MAGICWALL_PERSISTENT:
519 itemId = ITEM_MAGICWALL;
520 break;
521
522 case ITEM_WILDGROWTH_PERSISTENT:
523 itemId = ITEM_WILDGROWTH;
524 break;
525
526 default:
527 break;
528 }
529
530 if (caster) {
531 Player* casterPlayer;
532 if (caster->isSummon()) {
533 casterPlayer = caster->getMaster()->getPlayer();
534 } else {
535 casterPlayer = caster->getPlayer();
536 }
537
538 if (casterPlayer) {
539 if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || tile->hasFlag(TILESTATE_NOPVPZONE)) {
540 if (itemId == ITEM_FIREFIELD_PVP_FULL) {
541 itemId = ITEM_FIREFIELD_NOPVP;
542 } else if (itemId == ITEM_POISONFIELD_PVP) {
543 itemId = ITEM_POISONFIELD_NOPVP;
544 } else if (itemId == ITEM_ENERGYFIELD_PVP) {
545 itemId = ITEM_ENERGYFIELD_NOPVP;
546 }
547 } else if (itemId == ITEM_FIREFIELD_PVP_FULL || itemId == ITEM_POISONFIELD_PVP || itemId == ITEM_ENERGYFIELD_PVP) {
548 casterPlayer->addInFightTicks();
549 }
550 }
551 }
552
553 Item* item = Item::CreateItem(itemId);
554 if (caster) {
555 item->setOwner(caster->getID());
556 }
557
558 ReturnValue ret = g_game.internalAddItem(tile, item);
559 if (ret == RETURNVALUE_NOERROR) {
560 g_game.startDecay(item);
561 } else {
562 delete item;
563 }
564 }
565
566 if (params.tileCallback) {
567 params.tileCallback->onTileCombat(caster, tile);
568 }
569
570 if (params.impactEffect != CONST_ME_NONE) {
571 Game::addMagicEffect(spectators, tile->getPosition(), params.impactEffect);
572 }
573}
574
575void Combat::postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params)
576{
577 if (caster && params.distanceEffect != CONST_ANI_NONE) {
578 addDistanceEffect(caster, caster->getPosition(), pos, params.distanceEffect);
579 }
580}
581
582void Combat::addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect)
583{
584 if (effect == CONST_ANI_WEAPONTYPE) {
585 if (!caster) {
586 return;
587 }
588
589 Player* player = caster->getPlayer();
590 if (!player) {
591 return;
592 }
593
594 switch (player->getWeaponType()) {
595 case WEAPON_AXE:
596 effect = CONST_ANI_WHIRLWINDAXE;
597 break;
598 case WEAPON_SWORD:
599 effect = CONST_ANI_WHIRLWINDSWORD;
600 break;
601 case WEAPON_CLUB:
602 effect = CONST_ANI_WHIRLWINDCLUB;
603 break;
604 default:
605 effect = CONST_ANI_NONE;
606 break;
607 }
608 }
609
610 if (effect != CONST_ANI_NONE) {
611 g_game.addDistanceEffect(fromPos, toPos, effect);
612 }
613}
614
615void Combat::doCombat(Creature* caster, Creature* target) const
616{
617 //target combat callback function
618 if (params.combatType != COMBAT_NONE) {
619 CombatDamage damage = getCombatDamage(caster, target);
620
621 bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR);
622 if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) {
623 g_game.addMagicEffect(target->getPosition(), params.impactEffect);
624 }
625
626 if (canCombat) {
627 doTargetCombat(caster, target, damage, params);
628 }
629 } else {
630 if (!params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR)) {
631 SpectatorVec spectators;
632 g_game.map.getSpectators(spectators, target->getPosition(), true, true);
633
634 if (params.origin != ORIGIN_MELEE) {
635 for (const auto& condition : params.conditionList) {
636 if (caster == target || !target->isImmune(condition->getType())) {
637 Condition* conditionCopy = condition->clone();
638 conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID());
639 target->addCombatCondition(conditionCopy);
640 }
641 }
642 }
643
644 if (params.dispelType == CONDITION_PARALYZE) {
645 target->removeCondition(CONDITION_PARALYZE);
646 } else {
647 target->removeCombatCondition(params.dispelType);
648 }
649
650 combatTileEffects(spectators, caster, target->getTile(), params);
651
652 if (params.targetCallback) {
653 params.targetCallback->onTargetCombat(caster, target);
654 }
655
656 /*
657 if (params.impactEffect != CONST_ME_NONE) {
658 g_game.addMagicEffect(target->getPosition(), params.impactEffect);
659 }
660 */
661
662 if (caster && params.distanceEffect != CONST_ANI_NONE) {
663 addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect);
664 }
665 }
666 }
667}
668
669void Combat::doCombat(Creature* caster, const Position& position) const
670{
671 //area combat callback function
672 if (params.combatType != COMBAT_NONE) {
673 CombatDamage damage = getCombatDamage(caster, nullptr);
674 doAreaCombat(caster, position, area.get(), damage, params);
675 } else {
676 std::forward_list<Tile*> tileList;
677
678 if (caster) {
679 getCombatArea(caster->getPosition(), position, area.get(), tileList);
680 } else {
681 getCombatArea(position, position, area.get(), tileList);
682 }
683
684 SpectatorVec spectators;
685 uint32_t maxX = 0;
686 uint32_t maxY = 0;
687
688 //calculate the max viewable range
689 for (Tile* tile : tileList) {
690 const Position& tilePos = tile->getPosition();
691
692 uint32_t diff = Position::getDistanceX(tilePos, position);
693 if (diff > maxX) {
694 maxX = diff;
695 }
696
697 diff = Position::getDistanceY(tilePos, position);
698 if (diff > maxY) {
699 maxY = diff;
700 }
701 }
702
703 const int32_t rangeX = maxX + Map::maxViewportX;
704 const int32_t rangeY = maxY + Map::maxViewportY;
705 g_game.map.getSpectators(spectators, position, true, true, rangeX, rangeX, rangeY, rangeY);
706
707 postCombatEffects(caster, position, params);
708
709 for (Tile* tile : tileList) {
710 if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) {
711 continue;
712 }
713
714 combatTileEffects(spectators, caster, tile, params);
715
716 if (CreatureVector* creatures = tile->getCreatures()) {
717 const Creature* topCreature = tile->getTopCreature();
718 for (Creature* creature : *creatures) {
719 if (params.targetCasterOrTopMost) {
720 if (caster && caster->getTile() == tile) {
721 if (creature != caster) {
722 continue;
723 }
724 } else if (creature != topCreature) {
725 continue;
726 }
727 }
728
729 if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) {
730 for (const auto& condition : params.conditionList) {
731 if (caster == creature || !creature->isImmune(condition->getType())) {
732 Condition* conditionCopy = condition->clone();
733 if (caster) {
734 conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID());
735 }
736
737 //TODO: infight condition until all aggressive conditions has ended
738 creature->addCombatCondition(conditionCopy);
739 }
740 }
741 }
742
743 if (params.dispelType == CONDITION_PARALYZE) {
744 creature->removeCondition(CONDITION_PARALYZE);
745 } else {
746 creature->removeCombatCondition(params.dispelType);
747 }
748
749 if (params.targetCallback) {
750 params.targetCallback->onTargetCombat(caster, creature);
751 }
752
753 if (params.targetCasterOrTopMost) {
754 break;
755 }
756 }
757 }
758 }
759 }
760}
761
762void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params)
763{
764 Player* casterPlayer = caster ? caster->getPlayer() : nullptr;
765 if (casterPlayer) {
766 if (damage.primary.value < 0 || damage.secondary.value < 0) {
767 Player* targetPlayer = target ? target->getPlayer() : nullptr;
768 if (targetPlayer && targetPlayer->getSkull() != SKULL_BLACK) {
769 damage.primary.value /= 2;
770 damage.secondary.value /= 2;
771 }
772
773 Combat::checkCriticalHit(casterPlayer, damage);
774 Combat::checkLeech(casterPlayer, damage);
775 }
776 }
777
778 if (caster && target && params.distanceEffect != CONST_ANI_NONE) {
779 addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect);
780 }
781
782 if (damage.critical && target) {
783 g_game.addMagicEffect(target->getPosition(), CONST_ME_CRITICAL_DAMAGE);
784 }
785
786 bool success = false;
787 if (damage.primary.type != COMBAT_MANADRAIN) {
788 if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) {
789 return;
790 }
791
792 success = g_game.combatChangeHealth(caster, target, damage);
793 } else {
794 success = g_game.combatChangeMana(caster, target, damage);
795 }
796
797 if (success) {
798 if (damage.blockType == BLOCK_NONE || damage.blockType == BLOCK_ARMOR) {
799 for (const auto& condition : params.conditionList) {
800 if (caster == target || !target->isImmune(condition->getType())) {
801 Condition* conditionCopy = condition->clone();
802 if (caster) {
803 conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID());
804 }
805
806 //TODO: infight condition until all aggressive conditions has ended
807 target->addCombatCondition(conditionCopy);
808 }
809 }
810 }
811
812 if (params.dispelType == CONDITION_PARALYZE) {
813 target->removeCondition(CONDITION_PARALYZE);
814 } else {
815 target->removeCombatCondition(params.dispelType);
816 }
817 }
818
819 if (params.targetCallback) {
820 params.targetCallback->onTargetCombat(caster, target);
821 }
822}
823
824void Combat::doAreaCombat(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params)
825{
826 std::forward_list<Tile*> tileList;
827
828 if (caster) {
829 getCombatArea(caster->getPosition(), position, area, tileList);
830 } else {
831 getCombatArea(position, position, area, tileList);
832 }
833
834 Player* casterPlayer = caster ? caster->getPlayer() : nullptr;
835 int32_t criticalPrimary = 0;
836 int32_t criticalSecondary = 0;
837 if (casterPlayer) {
838 Combat::checkLeech(casterPlayer, damage);
839 if (!damage.critical && damage.origin != ORIGIN_CONDITION && (damage.primary.value < 0 || damage.secondary.value < 0)) {
840 uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE);
841 if (chance != 0 && uniform_random(1, 100) <= chance) {
842 uint16_t criticalHit = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT);
843 criticalPrimary = std::round(damage.primary.value * (criticalHit / 100.));
844 criticalSecondary = std::round(damage.secondary.value * (criticalHit / 100.));
845 damage.critical = true;
846 }
847 }
848 }
849
850 uint32_t maxX = 0;
851 uint32_t maxY = 0;
852
853 //calculate the max viewable range
854 for (Tile* tile : tileList) {
855 const Position& tilePos = tile->getPosition();
856
857 uint32_t diff = Position::getDistanceX(tilePos, position);
858 if (diff > maxX) {
859 maxX = diff;
860 }
861
862 diff = Position::getDistanceY(tilePos, position);
863 if (diff > maxY) {
864 maxY = diff;
865 }
866 }
867
868 const int32_t rangeX = maxX + Map::maxViewportX;
869 const int32_t rangeY = maxY + Map::maxViewportY;
870
871 SpectatorVec spectators;
872 g_game.map.getSpectators(spectators, position, true, true, rangeX, rangeX, rangeY, rangeY);
873
874 postCombatEffects(caster, position, params);
875
876 for (Tile* tile : tileList) {
877 if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) {
878 continue;
879 }
880
881 combatTileEffects(spectators, caster, tile, params);
882
883 if (CreatureVector* creatures = tile->getCreatures()) {
884 const Creature* topCreature = tile->getTopCreature();
885 for (Creature* creature : *creatures) {
886 if (params.targetCasterOrTopMost) {
887 if (caster && caster->getTile() == tile) {
888 if (creature != caster) {
889 continue;
890 }
891 } else if (creature != topCreature) {
892 continue;
893 }
894 }
895
896 if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) {
897 CombatDamage damageCopy = damage; // we cannot avoid copying here, because we don't know if it's player combat or not, so we can't modify the initial damage.
898 bool playerCombatReduced = false;
899 if ((damageCopy.primary.value < 0 || damageCopy.secondary.value < 0) && caster) {
900 Player* targetPlayer = creature->getPlayer();
901 if (targetPlayer && caster->getPlayer() && targetPlayer->getSkull() != SKULL_BLACK) {
902 damageCopy.primary.value /= 2;
903 damageCopy.secondary.value /= 2;
904 playerCombatReduced = true;
905 }
906 }
907
908 damageCopy.primary.value += playerCombatReduced ? criticalPrimary / 2 : criticalPrimary;
909 damageCopy.secondary.value += playerCombatReduced ? criticalSecondary / 2 : criticalSecondary;
910
911 if (damageCopy.critical) {
912 g_game.addMagicEffect(creature->getPosition(), CONST_ME_CRITICAL_DAMAGE);
913 }
914
915 bool success = false;
916 if (damageCopy.primary.type != COMBAT_MANADRAIN) {
917 if (g_game.combatBlockHit(damageCopy, caster, creature, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) {
918 continue;
919 }
920 success = g_game.combatChangeHealth(caster, creature, damageCopy);
921 } else {
922 success = g_game.combatChangeMana(caster, creature, damageCopy);
923 }
924
925 if (success) {
926 if (damage.blockType == BLOCK_NONE || damage.blockType == BLOCK_ARMOR) {
927 for (const auto& condition : params.conditionList) {
928 if (caster == creature || !creature->isImmune(condition->getType())) {
929 Condition* conditionCopy = condition->clone();
930 if (caster) {
931 conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID());
932 }
933
934 //TODO: infight condition until all aggressive conditions has ended
935 creature->addCombatCondition(conditionCopy);
936 }
937 }
938 }
939
940 if (params.dispelType == CONDITION_PARALYZE) {
941 creature->removeCondition(CONDITION_PARALYZE);
942 } else {
943 creature->removeCombatCondition(params.dispelType);
944 }
945 }
946
947 if (params.targetCallback) {
948 params.targetCallback->onTargetCombat(caster, creature);
949 }
950
951 if (params.targetCasterOrTopMost) {
952 break;
953 }
954 }
955 }
956 }
957 }
958}
959
960void Combat::checkCriticalHit(Player* caster, CombatDamage& damage)
961{
962 if (damage.critical || damage.origin == ORIGIN_CONDITION) {
963 return;
964 }
965
966 if (damage.primary.value > 0 || damage.secondary.value > 0) {
967 return;
968 }
969
970 uint16_t chance = caster->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE);
971 uint16_t criticalHit = caster->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT);
972 if (criticalHit != 0 && chance != 0 && normal_random(1, 100) <= chance) {
973 damage.primary.value += std::round(damage.primary.value * (criticalHit / 100.));
974 damage.secondary.value += std::round(damage.secondary.value * (criticalHit / 100.));
975 damage.critical = true;
976 }
977}
978
979void Combat::checkLeech(Player* caster, CombatDamage& damage)
980{
981 if (damage.origin == ORIGIN_CONDITION) {
982 return;
983 }
984
985 if (damage.primary.value > 0 || damage.secondary.value > 0) {
986 return;
987 }
988
989 if (caster->getHealth() != caster->getMaxHealth()) {
990 uint16_t chance = caster->getSpecialSkill(SPECIALSKILL_LIFELEECHCHANCE);
991 uint16_t skill = caster->getSpecialSkill(SPECIALSKILL_LIFELEECHAMOUNT);
992 if (skill != 0 && chance != 0 && normal_random(1, 100) <= chance) {
993 CombatDamage healAmount;
994 healAmount.primary.value += std::round(std::abs(damage.primary.value) * (skill / 100.));
995 healAmount.secondary.value += std::round(std::abs(damage.secondary.value) * (skill / 100.));
996 g_game.combatChangeHealth(nullptr, caster, healAmount);
997 caster->sendMagicEffect(caster->getPosition(), CONST_ME_MAGIC_RED);
998 }
999 }
1000
1001 if (caster->getMana() != caster->getMaxMana()) {
1002 uint16_t chance = caster->getSpecialSkill(SPECIALSKILL_MANALEECHCHANCE);
1003 uint16_t skill = caster->getSpecialSkill(SPECIALSKILL_MANALEECHAMOUNT);
1004 if (skill != 0 && chance != 0 && normal_random(1, 100) <= chance) {
1005 CombatDamage manaAmount;
1006 manaAmount.primary.value += std::round(std::abs(damage.primary.value) * (skill / 100.));
1007 manaAmount.secondary.value += std::round(std::abs(damage.secondary.value) * (skill / 100.));
1008 g_game.combatChangeMana(nullptr, caster, manaAmount);
1009 caster->sendMagicEffect(caster->getPosition(), CONST_ME_MAGIC_BLUE);
1010 }
1011 }
1012}
1013
1014//**********************************************************//
1015
1016void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage) const
1017{
1018 //onGetPlayerMinMaxValues(...)
1019 if (!scriptInterface->reserveScriptEnv()) {
1020 std::cout << "[Error - ValueCallback::getMinMaxValues] Call stack overflow" << std::endl;
1021 return;
1022 }
1023
1024 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1025 if (!env->setCallbackId(scriptId, scriptInterface)) {
1026 scriptInterface->resetScriptEnv();
1027 return;
1028 }
1029
1030 lua_State* L = scriptInterface->getLuaState();
1031
1032 scriptInterface->pushFunction(scriptId);
1033
1034 LuaScriptInterface::pushUserdata<Player>(L, player);
1035 LuaScriptInterface::setMetatable(L, -1, "Player");
1036
1037 int parameters = 1;
1038 switch (type) {
1039 case COMBAT_FORMULA_LEVELMAGIC: {
1040 //onGetPlayerMinMaxValues(player, level, maglevel)
1041 lua_pushnumber(L, player->getLevel());
1042 lua_pushnumber(L, player->getMagicLevel());
1043 parameters += 2;
1044 break;
1045 }
1046
1047 case COMBAT_FORMULA_SKILL: {
1048 //onGetPlayerMinMaxValues(player, attackSkill, attackValue, attackFactor)
1049 Item* tool = player->getWeapon();
1050 const Weapon* weapon = g_weapons->getWeapon(tool);
1051
1052 int32_t attackValue = 7;
1053 if (weapon) {
1054 attackValue = tool->getAttack();
1055 if (tool->getWeaponType() == WEAPON_AMMO) {
1056 Item* item = player->getWeapon(true);
1057 if (item) {
1058 attackValue += item->getAttack();
1059 }
1060 }
1061
1062 damage.secondary.type = weapon->getElementType();
1063 damage.secondary.value = weapon->getElementDamage(player, nullptr, tool);
1064 }
1065
1066 lua_pushnumber(L, player->getWeaponSkill(tool));
1067 lua_pushnumber(L, attackValue);
1068 lua_pushnumber(L, player->getAttackFactor());
1069 parameters += 3;
1070 break;
1071 }
1072
1073 default: {
1074 std::cout << "ValueCallback::getMinMaxValues - unknown callback type" << std::endl;
1075 scriptInterface->resetScriptEnv();
1076 return;
1077 }
1078 }
1079
1080 int size0 = lua_gettop(L);
1081 if (lua_pcall(L, parameters, 2, 0) != 0) {
1082 LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L));
1083 } else {
1084 damage.primary.value = normal_random(
1085 LuaScriptInterface::getNumber<int32_t>(L, -2),
1086 LuaScriptInterface::getNumber<int32_t>(L, -1)
1087 );
1088 lua_pop(L, 2);
1089 }
1090
1091 if ((lua_gettop(L) + parameters + 1) != size0) {
1092 LuaScriptInterface::reportError(nullptr, "Stack size changed!");
1093 }
1094
1095 scriptInterface->resetScriptEnv();
1096}
1097
1098//**********************************************************//
1099
1100void TileCallback::onTileCombat(Creature* creature, Tile* tile) const
1101{
1102 //onTileCombat(creature, pos)
1103 if (!scriptInterface->reserveScriptEnv()) {
1104 std::cout << "[Error - TileCallback::onTileCombat] Call stack overflow" << std::endl;
1105 return;
1106 }
1107
1108 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1109 if (!env->setCallbackId(scriptId, scriptInterface)) {
1110 scriptInterface->resetScriptEnv();
1111 return;
1112 }
1113
1114 lua_State* L = scriptInterface->getLuaState();
1115
1116 scriptInterface->pushFunction(scriptId);
1117 if (creature) {
1118 LuaScriptInterface::pushUserdata<Creature>(L, creature);
1119 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
1120 } else {
1121 lua_pushnil(L);
1122 }
1123 LuaScriptInterface::pushPosition(L, tile->getPosition());
1124
1125 scriptInterface->callFunction(2);
1126}
1127
1128//**********************************************************//
1129
1130void TargetCallback::onTargetCombat(Creature* creature, Creature* target) const
1131{
1132 //onTargetCombat(creature, target)
1133 if (!scriptInterface->reserveScriptEnv()) {
1134 std::cout << "[Error - TargetCallback::onTargetCombat] Call stack overflow" << std::endl;
1135 return;
1136 }
1137
1138 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1139 if (!env->setCallbackId(scriptId, scriptInterface)) {
1140 scriptInterface->resetScriptEnv();
1141 return;
1142 }
1143
1144 lua_State* L = scriptInterface->getLuaState();
1145
1146 scriptInterface->pushFunction(scriptId);
1147
1148 if (creature) {
1149 LuaScriptInterface::pushUserdata<Creature>(L, creature);
1150 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
1151 } else {
1152 lua_pushnil(L);
1153 }
1154
1155 if (target) {
1156 LuaScriptInterface::pushUserdata<Creature>(L, target);
1157 LuaScriptInterface::setCreatureMetatable(L, -1, target);
1158 } else {
1159 lua_pushnil(L);
1160 }
1161
1162 int size0 = lua_gettop(L);
1163
1164 if (lua_pcall(L, 2, 0 /*nReturnValues*/, 0) != 0) {
1165 LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L));
1166 }
1167
1168 if ((lua_gettop(L) + 2 /*nParams*/ + 1) != size0) {
1169 LuaScriptInterface::reportError(nullptr, "Stack size changed!");
1170 }
1171
1172 scriptInterface->resetScriptEnv();
1173}
1174
1175//**********************************************************//
1176
1177void AreaCombat::clear()
1178{
1179 for (const auto& it : areas) {
1180 delete it.second;
1181 }
1182 areas.clear();
1183}
1184
1185AreaCombat::AreaCombat(const AreaCombat& rhs)
1186{
1187 hasExtArea = rhs.hasExtArea;
1188 for (const auto& it : rhs.areas) {
1189 areas[it.first] = new MatrixArea(*it.second);
1190 }
1191}
1192
1193void AreaCombat::getList(const Position& centerPos, const Position& targetPos, std::forward_list<Tile*>& list) const
1194{
1195 const MatrixArea* area = getArea(centerPos, targetPos);
1196 if (!area) {
1197 return;
1198 }
1199
1200 uint32_t centerY, centerX;
1201 area->getCenter(centerY, centerX);
1202
1203 Position tmpPos(targetPos.x - centerX, targetPos.y - centerY, targetPos.z);
1204 uint32_t cols = area->getCols();
1205 for (uint32_t y = 0, rows = area->getRows(); y < rows; ++y) {
1206 for (uint32_t x = 0; x < cols; ++x) {
1207 if (area->getValue(y, x) != 0) {
1208 if (g_game.isSightClear(targetPos, tmpPos, true)) {
1209 Tile* tile = g_game.map.getTile(tmpPos);
1210 if (!tile) {
1211 tile = new StaticTile(tmpPos.x, tmpPos.y, tmpPos.z);
1212 g_game.map.setTile(tmpPos, tile);
1213 }
1214 list.push_front(tile);
1215 }
1216 }
1217 tmpPos.x++;
1218 }
1219 tmpPos.x -= cols;
1220 tmpPos.y++;
1221 }
1222}
1223
1224void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op)
1225{
1226 uint32_t centerY, centerX;
1227 input->getCenter(centerY, centerX);
1228
1229 if (op == MATRIXOPERATION_COPY) {
1230 for (uint32_t y = 0; y < input->getRows(); ++y) {
1231 for (uint32_t x = 0; x < input->getCols(); ++x) {
1232 (*output)[y][x] = (*input)[y][x];
1233 }
1234 }
1235
1236 output->setCenter(centerY, centerX);
1237 } else if (op == MATRIXOPERATION_MIRROR) {
1238 for (uint32_t y = 0; y < input->getRows(); ++y) {
1239 uint32_t rx = 0;
1240 for (int32_t x = input->getCols(); --x >= 0;) {
1241 (*output)[y][rx++] = (*input)[y][x];
1242 }
1243 }
1244
1245 output->setCenter(centerY, (input->getRows() - 1) - centerX);
1246 } else if (op == MATRIXOPERATION_FLIP) {
1247 for (uint32_t x = 0; x < input->getCols(); ++x) {
1248 uint32_t ry = 0;
1249 for (int32_t y = input->getRows(); --y >= 0;) {
1250 (*output)[ry++][x] = (*input)[y][x];
1251 }
1252 }
1253
1254 output->setCenter((input->getCols() - 1) - centerY, centerX);
1255 } else {
1256 // rotation
1257 int32_t rotateCenterX = (output->getCols() / 2) - 1;
1258 int32_t rotateCenterY = (output->getRows() / 2) - 1;
1259 int32_t angle;
1260
1261 switch (op) {
1262 case MATRIXOPERATION_ROTATE90:
1263 angle = 90;
1264 break;
1265
1266 case MATRIXOPERATION_ROTATE180:
1267 angle = 180;
1268 break;
1269
1270 case MATRIXOPERATION_ROTATE270:
1271 angle = 270;
1272 break;
1273
1274 default:
1275 angle = 0;
1276 break;
1277 }
1278
1279 double angleRad = M_PI * angle / 180.0;
1280
1281 double a = std::cos(angleRad);
1282 double b = -std::sin(angleRad);
1283 double c = std::sin(angleRad);
1284 double d = std::cos(angleRad);
1285
1286 const uint32_t rows = input->getRows();
1287 for (uint32_t x = 0, cols = input->getCols(); x < cols; ++x) {
1288 for (uint32_t y = 0; y < rows; ++y) {
1289 //calculate new coordinates using rotation center
1290 int32_t newX = x - centerX;
1291 int32_t newY = y - centerY;
1292
1293 //perform rotation
1294 int32_t rotatedX = static_cast<int32_t>(round(newX * a + newY * b));
1295 int32_t rotatedY = static_cast<int32_t>(round(newX * c + newY * d));
1296
1297 //write in the output matrix using rotated coordinates
1298 (*output)[rotatedY + rotateCenterY][rotatedX + rotateCenterX] = (*input)[y][x];
1299 }
1300 }
1301
1302 output->setCenter(rotateCenterY, rotateCenterX);
1303 }
1304}
1305
1306MatrixArea* AreaCombat::createArea(const std::list<uint32_t>& list, uint32_t rows)
1307{
1308 uint32_t cols;
1309 if (rows == 0) {
1310 cols = 0;
1311 } else {
1312 cols = list.size() / rows;
1313 }
1314
1315 MatrixArea* area = new MatrixArea(rows, cols);
1316
1317 uint32_t x = 0;
1318 uint32_t y = 0;
1319
1320 for (uint32_t value : list) {
1321 if (value == 1 || value == 3) {
1322 area->setValue(y, x, true);
1323 }
1324
1325 if (value == 2 || value == 3) {
1326 area->setCenter(y, x);
1327 }
1328
1329 ++x;
1330
1331 if (cols == x) {
1332 x = 0;
1333 ++y;
1334 }
1335 }
1336 return area;
1337}
1338
1339void AreaCombat::setupArea(const std::list<uint32_t>& list, uint32_t rows)
1340{
1341 MatrixArea* area = createArea(list, rows);
1342
1343 //NORTH
1344 areas[DIRECTION_NORTH] = area;
1345
1346 uint32_t maxOutput = std::max<uint32_t>(area->getCols(), area->getRows()) * 2;
1347
1348 //SOUTH
1349 MatrixArea* southArea = new MatrixArea(maxOutput, maxOutput);
1350 AreaCombat::copyArea(area, southArea, MATRIXOPERATION_ROTATE180);
1351 areas[DIRECTION_SOUTH] = southArea;
1352
1353 //EAST
1354 MatrixArea* eastArea = new MatrixArea(maxOutput, maxOutput);
1355 AreaCombat::copyArea(area, eastArea, MATRIXOPERATION_ROTATE90);
1356 areas[DIRECTION_EAST] = eastArea;
1357
1358 //WEST
1359 MatrixArea* westArea = new MatrixArea(maxOutput, maxOutput);
1360 AreaCombat::copyArea(area, westArea, MATRIXOPERATION_ROTATE270);
1361 areas[DIRECTION_WEST] = westArea;
1362}
1363
1364void AreaCombat::setupArea(int32_t length, int32_t spread)
1365{
1366 std::list<uint32_t> list;
1367
1368 uint32_t rows = length;
1369 int32_t cols = 1;
1370
1371 if (spread != 0) {
1372 cols = ((length - (length % spread)) / spread) * 2 + 1;
1373 }
1374
1375 int32_t colSpread = cols;
1376
1377 for (uint32_t y = 1; y <= rows; ++y) {
1378 int32_t mincol = cols - colSpread + 1;
1379 int32_t maxcol = cols - (cols - colSpread);
1380
1381 for (int32_t x = 1; x <= cols; ++x) {
1382 if (y == rows && x == ((cols - (cols % 2)) / 2) + 1) {
1383 list.push_back(3);
1384 } else if (x >= mincol && x <= maxcol) {
1385 list.push_back(1);
1386 } else {
1387 list.push_back(0);
1388 }
1389 }
1390
1391 if (spread > 0 && y % spread == 0) {
1392 --colSpread;
1393 }
1394 }
1395
1396 setupArea(list, rows);
1397}
1398
1399void AreaCombat::setupArea(int32_t radius)
1400{
1401 int32_t area[13][13] = {
1402 {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0},
1403 {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0},
1404 {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0},
1405 {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0},
1406 {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0},
1407 {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0},
1408 {8, 7, 6, 5, 4, 2, 1, 2, 4, 5, 6, 7, 8},
1409 {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0},
1410 {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0},
1411 {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0},
1412 {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0},
1413 {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0},
1414 {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0}
1415 };
1416
1417 std::list<uint32_t> list;
1418
1419 for (auto& row : area) {
1420 for (int cell : row) {
1421 if (cell == 1) {
1422 list.push_back(3);
1423 } else if (cell > 0 && cell <= radius) {
1424 list.push_back(1);
1425 } else {
1426 list.push_back(0);
1427 }
1428 }
1429 }
1430
1431 setupArea(list, 13);
1432}
1433
1434void AreaCombat::setupExtArea(const std::list<uint32_t>& list, uint32_t rows)
1435{
1436 if (list.empty()) {
1437 return;
1438 }
1439
1440 hasExtArea = true;
1441 MatrixArea* area = createArea(list, rows);
1442
1443 //NORTH-WEST
1444 areas[DIRECTION_NORTHWEST] = area;
1445
1446 uint32_t maxOutput = std::max<uint32_t>(area->getCols(), area->getRows()) * 2;
1447
1448 //NORTH-EAST
1449 MatrixArea* neArea = new MatrixArea(maxOutput, maxOutput);
1450 AreaCombat::copyArea(area, neArea, MATRIXOPERATION_MIRROR);
1451 areas[DIRECTION_NORTHEAST] = neArea;
1452
1453 //SOUTH-WEST
1454 MatrixArea* swArea = new MatrixArea(maxOutput, maxOutput);
1455 AreaCombat::copyArea(area, swArea, MATRIXOPERATION_FLIP);
1456 areas[DIRECTION_SOUTHWEST] = swArea;
1457
1458 //SOUTH-EAST
1459 MatrixArea* seArea = new MatrixArea(maxOutput, maxOutput);
1460 AreaCombat::copyArea(swArea, seArea, MATRIXOPERATION_MIRROR);
1461 areas[DIRECTION_SOUTHEAST] = seArea;
1462}
1463
1464//**********************************************************//
1465
1466void MagicField::onStepInField(Creature* creature)
1467{
1468 //remove magic walls/wild growth
1469 if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || id == ITEM_MAGICWALL_SAFE || id == ITEM_WILDGROWTH_SAFE || isBlocking()) {
1470 if (!creature->isInGhostMode()) {
1471 g_game.internalRemoveItem(this, 1);
1472 }
1473
1474 return;
1475 }
1476
1477 const ItemType& it = items[getID()];
1478 if (it.conditionDamage) {
1479 Condition* conditionCopy = it.conditionDamage->clone();
1480 uint32_t ownerId = getOwner();
1481 if (ownerId) {
1482 bool harmfulField = true;
1483
1484 if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || getTile()->hasFlag(TILESTATE_NOPVPZONE)) {
1485 Creature* owner = g_game.getCreatureByID(ownerId);
1486 if (owner) {
1487 if (owner->getPlayer() || (owner->isSummon() && owner->getMaster()->getPlayer())) {
1488 harmfulField = false;
1489 }
1490 }
1491 }
1492
1493 Player* targetPlayer = creature->getPlayer();
1494 if (targetPlayer) {
1495 Player* attackerPlayer = g_game.getPlayerByID(ownerId);
1496 if (attackerPlayer) {
1497 if (Combat::isProtected(attackerPlayer, targetPlayer)) {
1498 harmfulField = false;
1499 }
1500 }
1501 }
1502
1503 if (!harmfulField || (OTSYS_TIME() - createTime <= 5000) || creature->hasBeenAttacked(ownerId)) {
1504 conditionCopy->setParam(CONDITION_PARAM_OWNER, ownerId);
1505 }
1506 }
1507
1508 creature->addCondition(conditionCopy);
1509 }
1510}
1511
Top