/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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.
 *
 */

#ifdef ENABLE_EOB

#include "kyra/eobcommon.h"
#include "kyra/resource.h"
#include "common/system.h"

namespace Kyra {

void EoBCoreEngine::useMagicBookOrSymbol(int charIndex, int type) {
	EoBCharacter *c = &_characters[charIndex];
	_openBookSpellLevel = c->slotStatus[3];
	_openBookSpellSelectedItem = c->slotStatus[2];
	_openBookSpellListOffset = c->slotStatus[4];
	_openBookChar = charIndex;
	_openBookType = type;
	_openBookSpellList = (type == 1) ? _clericSpellList : _mageSpellList;
	_openBookAvailableSpells = (type == 1) ? c->clericSpells : c->mageSpells;
	int8 *tmp = _openBookAvailableSpells + _openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem;

	if (*tmp <= 0) {
		for (bool loop = true; loop && _openBookSpellSelectedItem < 10;) {
			tmp = _openBookAvailableSpells + _openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem;
			if (*tmp > 0) {
				if (_openBookSpellSelectedItem > 5) {
					_openBookSpellListOffset = 6;
					_openBookSpellSelectedItem -= 6;
				}
				loop = false;
			} else {
				_openBookSpellSelectedItem++;
			}
		}

		if (_openBookSpellSelectedItem == 10) {
			_openBookSpellListOffset = 0;
			_openBookSpellSelectedItem = 6;
		}
	}

	if (!_updateFlags)
		_screen->copyRegion(64, 121, 0, 0, 112, 56, 0, 10, Screen::CR_NO_P_CHECK);
	_updateFlags = 1;
	gui_setPlayFieldButtons();
	gui_drawSpellbook();
}

void EoBCoreEngine::useMagicScroll(int charIndex, int type, int weaponSlot) {
	_openBookCharBackup = _openBookChar;
	_openBookTypeBackup = _openBookType;
	_castScrollSlot = weaponSlot + 1;
	_openBookChar = charIndex;
	_openBookType = type <= _clericSpellOffset ? 0 : 1;
	castSpell(type, weaponSlot);
}

void EoBCoreEngine::usePotion(int charIndex, int weaponSlot) {
	EoBCharacter *c = &_characters[charIndex];

	int val = deleteInventoryItem(charIndex, weaponSlot);
	snd_playSoundEffect(10);

	if (_flags.gameID == GI_EOB1)
		val--;

	switch (val) {
	case 0:
		sparkEffectDefensive(charIndex);
		c->strengthCur = 22;
		c->strengthExtCur = 0;
		setCharEventTimer(charIndex, 546 * rollDice(1, 4, 4), 7, 1);
		break;

	case 1:
		sparkEffectDefensive(charIndex);
		modifyCharacterHitpoints(charIndex, rollDice(2, 4, 2));
		break;

	case 2:
		sparkEffectDefensive(charIndex);
		modifyCharacterHitpoints(charIndex, rollDice(3, 8, 3));
		break;

	case 3:
		statusAttack(charIndex, 2, _potionStrings[0], 0, 1, 8, 1);
		c->effectFlags &= ~0x2000;
		if (c->flags & 2)
			return;
		break;

	case 4:
		sparkEffectDefensive(charIndex);
		c->food = 100;
		if (_currentControlMode)
			gui_drawCharPortraitWithStats(charIndex);
		break;

	case 5:
		sparkEffectDefensive(charIndex);
		c->effectFlags |= 0x10000;
		setCharEventTimer(charIndex, 546 * rollDice(1, 4, 4), 12, 1);
		snd_playSoundEffect(100);
		gui_drawCharPortraitWithStats(charIndex);
		break;

	case 6:
		sparkEffectDefensive(charIndex);
		c->effectFlags |= 0x40;
		gui_drawCharPortraitWithStats(charIndex);
		break;

	case 7:
		sparkEffectDefensive(charIndex);
		neutralizePoison(charIndex);
		break;

	default:
		break;
	}

	_txt->printMessage(_potionStrings[1], -1, c->name, _potionEffectStrings[val]);
}

void EoBCoreEngine::useWand(int charIndex, int weaponSlot) {
	int v = _items[_characters[charIndex].inventory[weaponSlot]].value;
	if (!v) {
		_txt->printMessage(_wandStrings[0]);
		return;
	}

	if (v != 5)
		useMagicScroll(charIndex, _wandTypes[v], weaponSlot);
	else if (_flags.gameID == GI_EOB2)
		useMagicScroll(charIndex, 64, weaponSlot);
	else {
		uint16 bl1 = calcNewBlockPosition(_currentBlock, _currentDirection);
		uint16 bl2 = calcNewBlockPosition(bl1, _currentDirection);
		snd_playSoundEffect(98);
		sparkEffectOffensive();

		if ((_wllWallFlags[_levelBlockProperties[bl2].walls[_currentDirection ^ 2]] & 4) && !(_levelBlockProperties[bl2].flags & 7) && (_levelBlockProperties[bl1].flags & 7)) {
			for (int i = 0; i < 30; i++) {
				if (_monsters[i].block != bl1)
					continue;
				placeMonster(&_monsters[i], bl2, -1);
				_sceneUpdateRequired = true;
			}
		} else {
			_txt->printMessage(_wandStrings[1]);
		}
	}
}

void EoBCoreEngine::castSpell(int spell, int weaponSlot) {
	EoBSpell *s = &_spells[spell];
	EoBCharacter *c = &_characters[_openBookChar];
	_activeSpell = spell;

	if ((s->flags & 0x100) && (c->effectFlags & 0x40))
		// remove invisibility effect
		removeCharacterEffect(_flags.gameID == GI_EOB1 ? 8 : 10, _openBookChar, 1);

	int ci = _openBookChar;
	if (ci > 3)
		ci -= 2;

	_activeSpellCharacterPos = _dropItemDirIndex[(_currentDirection << 2) + ci];

	if (s->flags & 0x400) {
		if (c->inventory[0] && c->inventory[1]) {
			printWarning(_magicStrings1[2]);
			return;
		}

		if (isMagicEffectItem(c->inventory[0]) || isMagicEffectItem(c->inventory[1])) {
			printWarning(_magicStrings1[3]);
			return;
		}
	}

	if (!(_flags.gameID == GI_EOB2 && _activeSpell == 62)) {
		if (!_castScrollSlot) {
			int8 tmp = _openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem];
			if (_openBookSpellListOffset + _openBookSpellSelectedItem < 8)
				memmove(&_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem], &_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem + 1], 8 - (_openBookSpellListOffset + _openBookSpellSelectedItem));
			_openBookAvailableSpells[_openBookSpellLevel * 10 + 8] = -tmp;
			if (_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem] < 0) {
				if (--_openBookSpellSelectedItem == -1) {
					if (_openBookSpellListOffset) {
						_openBookSpellListOffset = 0;
						_openBookSpellSelectedItem = 5;
					} else {
						_openBookSpellSelectedItem = 6;
					}
				}
			}
		} else if (weaponSlot != -1) {
			updateUsedCharacterHandItem(_openBookChar, weaponSlot);
		}
	}

	_txt->printMessage(_magicStrings1[4], -1, c->name, s->name);

	if (s->flags & 0x20) {
		castOnWhomDialogue();
		return;
	}

	_activeSpellCharId = _openBookChar;
	startSpell(spell);
}

void EoBCoreEngine::removeCharacterEffect(int spell, int charIndex, int showWarning) {
	assert(spell >= 0);
	EoBCharacter *c = &_characters[charIndex];
	EoBSpell *s = &_spells[spell];

	if (showWarning) {
		int od = _screen->curDimIndex();
		Screen::FontId of = _screen->setFont(Screen::FID_6_FNT);
		_screen->setScreenDim(7);
		printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 3 : 2], c->name, s->name).c_str());
		_screen->setScreenDim(od);
		_screen->setFont(of);
	}

	if (s->endCallback)
		(this->*s->endCallback)(c);

	if (s->flags & 1)
		c->effectFlags &= ~s->effectFlags;

	if (s->flags & 4)
		_partyEffectFlags &= ~s->effectFlags;

	if (s->flags & 0x200) {
		for (int i = 0; i < 6; i++) {
			if (!testCharacter(i, 1))
				continue;
			if (!testCharacter(i, 2) && !(s->flags & 0x800))
				continue;
			_characters[i].effectFlags &= ~s->effectFlags;
		}
	}

	if (s->flags & 0x2)
		recalcArmorClass(_activeSpellCharId);

	if (showWarning) {
		if (s->flags & 0x20A0)
			gui_drawCharPortraitWithStats(charIndex);
		else if (s->flags & 0x40)
			gui_drawAllCharPortraitsWithStats();
	}
}

void EoBCoreEngine::removeAllCharacterEffects(int charIndex) {
	EoBCharacter *c = &_characters[charIndex];
	c->effectFlags = 0;
	memset(c->effectsRemainder, 0, 4);

	for (int i = 0; i < 10; i++) {
		if (c->events[i] < 0) {
			removeCharacterEffect(-c->events[i], charIndex, 0);
			c->timers[i] = 0;
			c->events[i] = 0;
		}
	}

	setupCharacterTimers();
	recalcArmorClass(charIndex);
	c->disabledSlots = 0;
	c->slotStatus[0] = c->slotStatus[1] = 0;
	c->damageTaken = 0;
	c->strengthCur = c->strengthMax;
	c->strengthExtCur = c->strengthExtMax;
	gui_drawAllCharPortraitsWithStats();
}

void EoBCoreEngine::castOnWhomDialogue() {
	printWarning(_magicStrings3[0]);
	gui_setCastOnWhomButtons();
}

void EoBCoreEngine::startSpell(int spell) {
	EoBSpell *s = &_spells[spell];
	EoBCharacter *c = &_characters[_activeSpellCharId];
	snd_playSoundEffect(s->sound);

	if (s->flags & 0xA0)
		sparkEffectDefensive(_activeSpellCharId);
	else if (s->flags & 0x40)
		sparkEffectDefensive(-1);
	else if (s->flags & 0x1000)
		sparkEffectOffensive();

	if (s->flags & 0x20) {
		_txt->printMessage(c->name);
		_txt->printMessage(_flags.gameID == GI_EOB1 ? _magicStrings3[1] : _magicStrings1[5]);
	}

	if ((s->flags & 0x30) && (s->effectFlags & c->effectFlags)) {
		if (_flags.gameID == GI_EOB2)
			printWarning(Common::String::format(_magicStrings7[0], c->name, s->name).c_str());
	} else if ((s->flags & 0x50) && (s->effectFlags & _partyEffectFlags)) {
		if (_flags.gameID == GI_EOB1 && s->effectFlags == 0x400)
			// EOB 1 only warns in case of a bless spell
			printWarning(_magicStrings8[1]);
		else
			printWarning(Common::String::format(_magicStrings7[1], s->name).c_str());
	} else {
		if (s->flags & 8)
			setSpellEventTimer(spell, s->timingPara[0], s->timingPara[1], s->timingPara[2], s->timingPara[3]);

		_returnAfterSpellCallback = false;
		if (s->startCallback)
			(this->*s->startCallback)();
		if (_returnAfterSpellCallback)
			return;

		if (s->flags & 1)
			c->effectFlags |= s->effectFlags;
		if (s->flags & 4)
			_partyEffectFlags |= s->effectFlags;

		if (s->flags & 0x200) {
			for (int i = 0; i < 6; i++) {
				if (!testCharacter(i, 1))
					continue;
				if (!testCharacter(i, 2) && !(s->flags & 0x800))
					continue;
				_characters[i].effectFlags |= s->effectFlags;
			}
		}

		if (s->flags & 2)
			recalcArmorClass(_activeSpellCharId);

		if (s->flags & 0x20A0)
			gui_drawCharPortraitWithStats(_activeSpellCharId);
		if (s->flags & 0x40)
			gui_drawAllCharPortraitsWithStats();
	}

	if (_castScrollSlot) {
		gui_updateSlotAfterScrollUse();
	} else {
		_characters[_openBookChar].disabledSlots |= 4;
		setCharEventTimer(_openBookChar, 72, 11, 1);
		gui_toggleButtons();
		gui_drawSpellbook();
	}

	if (_flags.gameID == GI_EOB2) {
		//_castSpellWd1 = spell;
		runLevelScript(_currentBlock, 0x800);
		//_castSpellWd1 = 0;
	}
}

void EoBCoreEngine::sparkEffectDefensive(int charIndex) {
	int first = charIndex;
	int last = charIndex;
	if (charIndex == -1) {
		first = 0;
		last = 5;
	}

	for (int i = 0; i < 8; i++) {
		for (int ii = first; ii <= last; ii++) {
			if (!testCharacter(ii, 1) || (_currentControlMode && ii != _updateCharNum))
				continue;

			gui_drawCharPortraitWithStats(ii);

			for (int iii = 0; iii < 4; iii++) {
				int shpIndex = ((_sparkEffectDefSteps[i] & _sparkEffectDefSubSteps[iii]) >> _sparkEffectDefShift[iii]);
				if (!shpIndex)
					continue;
				int x = _sparkEffectDefAdd[iii * 2] - 8;
				int y = _sparkEffectDefAdd[iii * 2 + 1];
				if (_currentControlMode) {
					x += 181;
					y += 3;
				} else {
					x += (_sparkEffectDefX[ii] << 3);
					y += _sparkEffectDefY[ii];
				}
				_screen->drawShape(0, _sparkShapes[shpIndex - 1], x, y, 0);
				_screen->updateScreen();
			}
		}
		delay(2 * _tickLength);
	}

	for (int i = first; i < last; i++)
		gui_drawCharPortraitWithStats(i);
}

void EoBCoreEngine::sparkEffectOffensive() {
	disableSysTimer(2);
	_screen->copyRegion(0, 0, 0, 0, 176, 120, 0, 2, Screen::CR_NO_P_CHECK);

	for (int i = 0; i < 16; i++)
		_screen->copyRegionToBuffer(0, _sparkEffectOfX[i], _sparkEffectOfY[i], 16, 16, &_spellAnimBuffer[i << 8]);
	_screen->updateScreen();

	for (int i = 0; i < 11; i++) {
		for (int ii = 0; ii < 16; ii++)
			_screen->copyBlockToPage(2, _sparkEffectOfX[ii], _sparkEffectOfY[ii], 16, 16, &_spellAnimBuffer[ii << 8]);

		for (int ii = 0; ii < 16; ii++) {
			int shpIndex = (_sparkEffectOfFlags1[i] & _sparkEffectOfFlags2[ii]) >> _sparkEffectOfShift[ii];
			if (shpIndex)
				_screen->drawShape(2, _sparkShapes[shpIndex - 1], _sparkEffectOfX[ii], _sparkEffectOfY[ii], 0);
		}
		delay(2 * _tickLength);
		_screen->copyRegion(0, 0, 0, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK);
		_screen->updateScreen();
	}

	for (int i = 0; i < 16; i++)
		_screen->copyBlockToPage(0, _sparkEffectOfX[i], _sparkEffectOfY[i], 16, 16, &_spellAnimBuffer[i << 8]);

	_screen->updateScreen();
	enableSysTimer(2);
}

void EoBCoreEngine::setSpellEventTimer(int spell, int timerBaseFactor, int timerLength, int timerLevelFactor, int updateExistingTimer) {
	assert(spell >= 0);
	int l = _openBookType == 1 ? getClericPaladinLevel(_openBookChar) : getMageLevel(_openBookChar);
	uint32 countdown = timerLength * timerBaseFactor + timerLength * l * timerLevelFactor;
	setCharEventTimer(_activeSpellCharId, countdown, -spell, updateExistingTimer);
}

void EoBCoreEngine::sortCharacterSpellList(int charIndex) {
	int8 *list = _characters[charIndex].mageSpells;

	for (int i = 0; i < 16;) {
		bool p = false;
		for (int ii = 0; ii < 9; ii++) {
			int8 *pos = &list[ii];

			int s1 = pos[0];
			int s2 = pos[1];

			if (s1 == 0)
				s1 = 80;
			else if (s1 < 0)
				s1 = s1 * -1 + 40;

			if (s2 == 0)
				s2 = 80;
			else if (s2 < 0)
				s2 = s2 * -1 + 40;

			if (s1 > s2) {
				SWAP(pos[0], pos[1]);
				p = true;
			}
		}

		if (p)
			continue;

		list += 10;
		if (++i == 8)
			list = _characters[charIndex].clericSpells;
	}
}

bool EoBCoreEngine::magicObjectDamageHit(EoBFlyingObject *fo, int dcTimes, int dcPips, int dcOffs, int level) {
	int ignoreAttackerId = fo->flags & 0x10;
	int singleTargetCheckAdjacent = fo->flags & 1;
	int blockDamage = fo->flags & 2;
	int hitTest = fo->flags & 4;

	int savingThrowType = 5;
	int savingThrowEffect = 3;
	if (fo->flags & 8) {
		savingThrowType = 4;
		savingThrowEffect = 0;
	}

	int dmgFlag = _spells[fo->callBackIndex].damageFlags;
	if (fo->attackerId >= 0)
		dmgFlag |= 0x800;

	bool res = false;
	if (!level)
		level = 1;

	if ((_levelBlockProperties[fo->curBlock].flags & 7) && (fo->attackerId >= 0 || ignoreAttackerId)) {
		_preventMonsterFlash = true;

		for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, blockDamage, singleTargetCheckAdjacent); *m != -1; m++) {
			int dmg = rollDice(dcTimes, dcPips, dcOffs) * level;

			if (hitTest) {
				if (!characterAttackHitTest(fo->attackerId, *m, 0, 0))
					continue;
			}

			calcAndInflictMonsterDamage(&_monsters[*m], 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect);
			res = true;
		}
		updateAllMonsterShapes();

	} else if (fo->curBlock == _currentBlock && (fo->attackerId < 0 || ignoreAttackerId)) {
		if (blockDamage) {
			for (int i = 0; i < 6; i++) {
				if (!testCharacter(i, 1))
					continue;
				if (hitTest && !monsterAttackHitTest(&_monsters[0], i))
					continue;

				int dmg = rollDice(dcTimes, dcPips, dcOffs) * level;
				res = true;

				calcAndInflictCharacterDamage(i, 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect);
			}
		} else {
			int c = _dscItemPosIndex[(_currentDirection << 2) + (fo->curPos & 3)];
			if ((c > 2) && (testCharacter(4, 1) || testCharacter(5, 1)) && rollDice(1, 2, -1))
				c += 2;

			if (!fo->item && (_characters[c].effectFlags & 8)) {
				res = true;
			} else {
				if ((_characters[c].flags & 1) && (!hitTest || monsterAttackHitTest(&_monsters[0], c))) {
					int dmg = rollDice(dcTimes, dcPips, dcOffs) * level;
					res = true;
					calcAndInflictCharacterDamage(c, 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect);
				}
			}
		}
	}

	if (res && (fo->flags & 0x40))
		explodeObject(fo, fo->curBlock, fo->item);
	else if ((_flags.gameID == GI_EOB1 && fo->item == 5) || (_flags.gameID == GI_EOB2 && fo->item == 4))
		res = false;

	return res;
}

bool EoBCoreEngine::magicObjectStatusHit(EoBMonsterInPlay *m, int type, bool tryEvade, int mod) {
	EoBMonsterProperty *p = &_monsterProps[m->type];
	if (tryEvade) {
		if (tryMonsterAttackEvasion(m) || (p->immunityFlags & 0x10))
			return true;
	}

	if (trySavingThrow(m, 0, p->level, mod, 6))
		return false;

	int para = 0;

	switch (type) {
	case 0:
	case 1:
	case 2:
		para = (type == 0) ? ((p->typeFlags & 1) ? 1 : 0) : ((type == 1) ? ((p->typeFlags & 2) ? 1 : 0) : 1);
		if (para && !(p->immunityFlags & 2)) {
			m->mode = 10;
			m->spellStatusLeft = 15;
		}

		break;

	case 3:
		if (!(p->immunityFlags & 8))
			inflictMonsterDamage(m, 1000, true);
		break;

	case 4:
		inflictMonsterDamage(m, 1000, true);
		break;

	case 5:
		m->flags |= 0x20;
		_sceneUpdateRequired = true;
		break;

	case 6:
		if (!(_flags.gameID == GI_EOB1 && !(p->typeFlags & 3)) && !(p->immunityFlags & 4) && m->mode != 7 && m->mode != 8 && m->mode != 10) {
			m->mode = 0;
			m->spellStatusLeft = 20;
			m->flags |= 8;
			walkMonsterNextStep(m, -1, (getNextMonsterDirection(m->block, _currentBlock) ^ 4) >> 1);
		}
		break;

	default:
		break;
	}

	return true;
}

bool EoBCoreEngine::turnUndeadHit(EoBMonsterInPlay *m, int hitChance, int casterLevel) {
	assert(_monsterProps[m->type].tuResist > 0);
	uint8 e = _turnUndeadEffect[_monsterProps[m->type].tuResist * 14 + MIN(casterLevel, 14)];

	if (e == 0xFF) {
		calcAndInflictMonsterDamage(m, 0, 0, 500, 0x200, 5, 3);
	} else if (hitChance < e) {
		return false;
	} else {
		m->mode = 0;
		m->flags |= 8;
		m->spellStatusLeft = 40;
		m->dir = (getNextMonsterDirection(m->block, _currentBlock) ^ 4) >> 1;
	}

	return true;
}

int EoBCoreEngine::getMagicWeaponSlot(int charIndex) {
	return _characters[charIndex].inventory[1] ? 0 : 1;
}

void EoBCoreEngine::causeWounds(int dcTimes, int dcPips, int dcOffs) {
	if (_openBookChar == 0 || _openBookChar == 1) {
		int d = getClosestMonster(_openBookChar, calcNewBlockPosition(_currentBlock, _currentDirection));
		if (d != -1) {
			if (!characterAttackHitTest(_openBookChar, d, 0, 1))
				return;

			if (dcTimes == -1) {
				dcOffs = _monsters[d].hitPointsMax - rollDice(1, 4);
				dcTimes = dcPips = 0;
			}
			calcAndInflictMonsterDamage(&_monsters[d], dcTimes, dcPips, dcOffs, 0x801, 4, 2);
		} else {
			printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 4 : 3], _characters[_openBookChar].name).c_str());
		}
	} else {
		printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 5 : 4], _characters[_openBookChar].name).c_str());
	}
}

int EoBCoreEngine::createMagicWeaponType(int invFlags, int handFlags, int armorClass, int allowedClasses, int dmgNumDice, int dmgPips, int dmgInc, int extraProps) {
	int i = 51;
	for (; i < 57; i++) {
		if (_itemTypes[i].armorClass == -30)
			break;
	}

	if (i == 57)
		return -1;

	EoBItemType *tp = &_itemTypes[i];
	tp->invFlags = invFlags;
	tp->requiredHands = 0;
	tp->handFlags = handFlags;
	tp->armorClass = armorClass;
	tp->allowedClasses = allowedClasses;
	tp->dmgNumDiceL = tp->dmgNumDiceS = dmgNumDice;
	tp->dmgNumPipsL = tp->dmgNumPipsS = dmgPips;
	tp->dmgIncL = tp->dmgIncS = dmgInc;
	tp->extraProperties = extraProps;

	return i;
}

Item EoBCoreEngine::createMagicWeaponItem(int flags, int icon, int value, int type) {
	Item i = 11;
	for (; i < 17; i++) {
		if (_items[i].block == -2)
			break;
	}

	if (i == 17)
		return -1;

	EoBItem *itm = &_items[i];
	itm->flags = 0x20 | flags;
	itm->icon = icon;
	itm->value = value;
	itm->type = type;
	itm->pos = 0;
	itm->block = 0;
	itm->nameId = itm->nameUnid = 0;
	itm->prev = itm->next = 0;

	return i;
}

void EoBCoreEngine::removeMagicWeaponItem(Item item) {
	_itemTypes[_items[item].type].armorClass = -30;
	_items[item].block = -2;
	_items[item].level = 0xFF;
}

void EoBCoreEngine::updateWallOfForceTimers() {
	uint32 ct = _system->getMillis();
	for (int i = 0; i < 5; i++) {
		if (!_wallsOfForce[i].block)
			continue;
		if (_wallsOfForce[i].duration < ct)
			destroyWallOfForce(i);
	}
}

void EoBCoreEngine::destroyWallOfForce(int index) {
	memset(_levelBlockProperties[_wallsOfForce[index].block].walls, 0, 4);
	_wallsOfForce[index].block = 0;
	_sceneUpdateRequired = true;
}

int EoBCoreEngine::findSingleSpellTarget(int dist) {
	uint16 bl = _currentBlock;
	int res = -1;

	for (int i = 0; i < dist && res == -1; i++) {
		bl = calcNewBlockPosition(bl, _currentDirection);
		res = getClosestMonster(_openBookChar, bl);
		if (!(_wllWallFlags[_levelBlockProperties[bl].walls[_sceneDrawVarDown]] & 1)) {
			i = dist;
			res = -1;
		}
	}

	return res;
}

int EoBCoreEngine::findFirstCharacterSpellTarget() {
	int curCharIndex = rollDice(1, 6, -1);
	for (_characterSpellTarget = 0; _characterSpellTarget < 6; _characterSpellTarget++) {
		if (testCharacter(curCharIndex, 3))
			return curCharIndex;
		if (++curCharIndex == 6)
			curCharIndex = 0;
	}
	return -1;
}

int EoBCoreEngine::findNextCharacterSpellTarget(int curCharIndex) {
	for (_characterSpellTarget++; _characterSpellTarget < 6;) {
		if (++curCharIndex == 6)
			curCharIndex = 0;
		if (testCharacter(curCharIndex, 3))
			return curCharIndex;
	}
	return -1;
}

int EoBCoreEngine::charDeathSavingThrow(int charIndex, int div) {
	bool _beholderOrgBhv = true;
	// Due to a bug in the original code the saving throw result is completely ignored
	// here. The Beholders' disintegrate spell will alway succeed while their flesh to
	// stone spell will always fail.
	if (_beholderOrgBhv)
		div >>= 1;
	else
		div = specialAttackSavingThrow(charIndex, 4) ? 1 : 0;
	return div;
}

void EoBCoreEngine::printWarning(const char *str) {
	_txt->printMessage(str);
	snd_playSoundEffect(79);
}

void EoBCoreEngine::printNoEffectWarning() {
	printWarning(_magicStrings4[0]);
}

void EoBCoreEngine::spellCallback_start_armor() {
	_characters[_activeSpellCharId].effectsRemainder[0] = getMageLevel(_openBookChar) + 8;
	if ((getDexterityArmorClassModifier(_characters[_activeSpellCharId].dexterityCur) + 6) >= _characters[_activeSpellCharId].armorClass)
		printWarning(Common::String::format(_magicStrings6[0], _characters[_activeSpellCharId].name).c_str());
}

void EoBCoreEngine::spellCallback_start_burningHands() {
	static const int16 bX[] = { 0, 152, 24, 120, 56, 88 };
	static const int8 bY[] = { 64, 64, 56, 56, 56, 56 };

	for (int i = 0; i < 6; i++)
		drawBlockObject(i & 1, 0, _firebeamShapes[(5 - i) >> 1], bX[i], bY[i], 0);
	_screen->updateScreen();
	delay(2 * _tickLength);

	int cl = getMageLevel(_openBookChar);
	int bl = calcNewBlockPosition(_currentBlock, _currentDirection);

	const int8 *pos = getMonstersOnBlockPositions(bl);
	_preventMonsterFlash = true;

	int numDest = (_flags.gameID == GI_EOB1) ? 2 : 6;
	const uint8 *d = &_burningHandsDest[_currentDirection * (_flags.gameID == GI_EOB1 ? 2 : 8)];

	for (int i = 0; i < numDest; i++, d++) {
		if (pos[*d] == -1)
			continue;
		calcAndInflictMonsterDamage(&_monsters[pos[*d]], 1, 3, cl << 1, 0x21, 4, 0);
	}

	updateAllMonsterShapes();
	_sceneUpdateRequired = true;
}

void EoBCoreEngine::spellCallback_start_detectMagic() {
	setHandItem(_itemInHand);
}

bool EoBCoreEngine::spellCallback_end_detectMagic(void *) {
	setHandItem(_itemInHand);
	return true;
}

void EoBCoreEngine::spellCallback_start_magicMissile() {
	launchMagicObject(_openBookChar, 0, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_magicMissile(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	return magicObjectDamageHit(fo, 1, 4, 1, (getMageLevel(fo->attackerId) - 1) >> 1);
}

void EoBCoreEngine::spellCallback_start_shockingGrasp() {
	int t = createMagicWeaponType(0, 0, 0, 0x0F, 1, 8, getMageLevel(_openBookChar), 1);
	Item i = (t != -1) ? createMagicWeaponItem(0x10, 82, 0, t) : -1;
	if (t == -1 || i == -1) {
		if (_flags.gameID == GI_EOB2)
			printWarning(_magicStrings8[0]);
		removeCharacterEffect(_activeSpell, _activeSpellCharId, 0);
		deleteCharEventTimer(_activeSpellCharId, -_activeSpell);
		_returnAfterSpellCallback = true;
	} else {
		_characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i;
	}
}

bool EoBCoreEngine::spellCallback_end_shockingGraspFlameBlade(void *obj) {
	EoBCharacter *c = (EoBCharacter *)obj;
	for (int i = 0; i < 2; i++) {
		if (isMagicEffectItem(c->inventory[i])) {
			removeMagicWeaponItem(c->inventory[i]);
			c->inventory[i] = 0;
		}
	}
	return true;
}

void EoBCoreEngine::spellCallback_start_improvedIdentify() {
	for (int i = 0; i < 2; i++) {
		Item itm = _characters[_activeSpellCharId].inventory[i];
		if (itm)
			_items[itm].flags |= 0x40;
	}
}

void EoBCoreEngine::spellCallback_start_melfsAcidArrow() {
	launchMagicObject(_openBookChar, 1, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_melfsAcidArrow(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	assert(fo);
	return magicObjectDamageHit(fo, 2, 4, 0, getMageLevel(fo->attackerId) / 3);
}

void EoBCoreEngine::spellCallback_start_dispelMagic() {
	for (int i = 0; i < 6; i++) {
		if (testCharacter(i, 1))
			removeAllCharacterEffects(i);
	}
}

void EoBCoreEngine::spellCallback_start_fireball() {
	launchMagicObject(_openBookChar, 2, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_fireball(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	return magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId));
}

void EoBCoreEngine::spellCallback_start_flameArrow() {
	launchMagicObject(_openBookChar, 3, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_flameArrow(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	return magicObjectDamageHit(fo, 5, 6, 0, getMageLevel(fo->attackerId));
}

void EoBCoreEngine::spellCallback_start_holdPerson() {
	launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 4 : 3, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_holdPerson(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	bool res = false;

	if (_flags.gameID == GI_EOB2 && fo->curBlock == _currentBlock) {
		// party hit
		int numChar = rollDice(1, 4, 0);
		int charIndex = rollDice(1, 6, -1);
		for (int i = 0; i < 6 && numChar; i++) {
			if (testCharacter(charIndex, 3)) {
				statusAttack(charIndex, 4, _magicStrings8[1], 4, 5, 9, 1);
				numChar--;
			}
			charIndex = (charIndex + 1) % 6;
		}
		res = true;

	} else {
		// monster hit
		for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, 1, 1); *m != -1; m++)
			res |= magicObjectStatusHit(&_monsters[*m], 0, true, 4);
	}

	return res;
}

void EoBCoreEngine::spellCallback_start_lightningBolt() {
	launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 5 : 4, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_lightningBolt(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	return magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId));
}

void EoBCoreEngine::spellCallback_start_vampiricTouch() {
	int t = createMagicWeaponType(0, 0, 0, 0x0F, getMageLevel(_openBookChar) >> 1, 6, 0, 1);
	Item i = (t != -1) ? createMagicWeaponItem(0x18, 83, 0, t) : -1;
	if (t == -1 || i == -1) {
		if (_flags.gameID == GI_EOB2)
			printWarning(_magicStrings8[2]);
		removeCharacterEffect(_activeSpell, _activeSpellCharId, 0);
		deleteCharEventTimer(_activeSpellCharId, -_activeSpell);
		_returnAfterSpellCallback = true;
	} else {
		_characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i;
	}
}

bool EoBCoreEngine::spellCallback_end_vampiricTouch(void *obj) {
	EoBCharacter *c = (EoBCharacter *)obj;
	if (c->hitPointsCur > c->hitPointsMax)
		c->hitPointsCur = c->hitPointsMax;
	spellCallback_end_shockingGraspFlameBlade(obj);
	return true;
}

void EoBCoreEngine::spellCallback_start_fear() {
	sparkEffectOffensive();
	uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection);
	for (int i = 0; i < 30; i++) {
		if (_monsters[i].block == bl)
			magicObjectStatusHit(&_monsters[i], 6, true, 4);
	}
}

void EoBCoreEngine::spellCallback_start_iceStorm() {
	launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 6 : 5, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_iceStorm(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	static int8 blockAdv[] = { -32, 32, 1, -1 };
	bool res = magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId));
	if (res) {
		for (int i = 0; i < 4; i++) {
			uint16 bl = fo->curBlock;
			fo->curBlock = (fo->curBlock + blockAdv[i]) & 0x3FF;
			magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId));
			fo->curBlock = bl;
		}
	}
	return res;
}

void EoBCoreEngine::spellCallback_start_stoneSkin() {
	_characters[_activeSpellCharId].effectsRemainder[1] = (getMageLevel(_openBookChar) >> 1) + rollDice(1, 4);
}

void EoBCoreEngine::spellCallback_start_removeCurse() {
	for (int i = 0; i < 27; i++) {
		Item itm = _characters[_activeSpellCharId].inventory[i];
		if (itm && (_items[itm].flags & 0x20) && !isMagicEffectItem(itm))
			_items[itm].flags = (_items[itm].flags & ~0x20) | 0x40;
	}
}

void EoBCoreEngine::spellCallback_start_coneOfCold() {
	const int8 *dirTables[] = { _coneOfColdDest1, _coneOfColdDest2, _coneOfColdDest3, _coneOfColdDest4 };

	int cl = getMageLevel(_openBookChar);

	_screen->setCurPage(2);
	_screen->fillRect(0, 0, 176, 120, 0);
	_screen->setGfxParameters(0, 0, _screen->getPagePixel(2, 0, 0));
	drawSceneShapes(7);
	_screen->setCurPage(0);
	disableSysTimer(2);
	_screen->drawVortex(150, 50, 10, 1, 100, _coneOfColdGfxTbl, _coneOfColdGfxTblSize);
	enableSysTimer(2);

	const int8 *tbl = dirTables[_currentDirection];
	_preventMonsterFlash = true;

	for (int i = 0; i < 7; i++) {
		for (const int16 *m = findBlockMonsters((_currentBlock + tbl[i]) & 0x3FF, 4, _currentDirection, 1, 1); *m != -1; m++)
			calcAndInflictMonsterDamage(&_monsters[*m], cl, 4, cl, 0x41, 5, 0);
	}

	updateAllMonsterShapes();
}

void EoBCoreEngine::spellCallback_start_holdMonster() {
	launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 7 : 6, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_holdMonster(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	bool res = false;
	for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, 1, 1); *m != -1; m++)
		res |= magicObjectStatusHit(&_monsters[*m], 1, true, 4);
	return res;
}

void EoBCoreEngine::spellCallback_start_wallOfForce() {
	uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection);
	LevelBlockProperty *l = &_levelBlockProperties[bl];
	if (l->walls[0] || l->walls[1] || l->walls[2] || l->walls[3] || (l->flags & 7)) {
		printWarning(_magicStrings8[3]);
		return;
	}

	uint32 dur = 0xFFFFFFFF;
	int s = 0;
	int i = 0;

	for (; i < 5; i++) {
		if (!_wallsOfForce[i].block)
			break;
		if (_wallsOfForce[i].duration < dur) {
			dur = _wallsOfForce[i].duration;
			s = i;
		}
	}

	if (i == 5)
		destroyWallOfForce(s);

	memset(_levelBlockProperties[bl].walls, 74, 4);
	_wallsOfForce[s].block = bl;
	_wallsOfForce[s].duration = _system->getMillis() + (((getMageLevel(_openBookChar) * 546) >> 1) + 546) * _tickLength;
	_sceneUpdateRequired = true;
}

void EoBCoreEngine::spellCallback_start_disintegrate() {
	int d = findSingleSpellTarget(1);
	if (d != -1)
		magicObjectStatusHit(&_monsters[d], 4, true, 4);
	memset(_visibleBlocks[13]->walls, 0, 4);
	_sceneUpdateRequired = true;
}

void EoBCoreEngine::spellCallback_start_fleshToStone() {
	sparkEffectOffensive();
	int t = getClosestMonster(_openBookChar, calcNewBlockPosition(_currentBlock, _currentDirection));
	if (t != -1)
		magicObjectStatusHit(&_monsters[t], 5, true, 4);
	else
		printWarning(_magicStrings8[4]);
}

void EoBCoreEngine::spellCallback_start_stoneToFlesh() {
	if (_characters[_activeSpellCharId].flags & 8)
		_characters[_activeSpellCharId].flags &= ~8;
	else
		printNoEffectWarning();
}

void EoBCoreEngine::spellCallback_start_trueSeeing() {
	_wllVmpMap[46] = 0;
}

bool EoBCoreEngine::spellCallback_end_trueSeeing(void *) {
	_wllVmpMap[46] = 1;
	return true;
}

void EoBCoreEngine::spellCallback_start_slayLiving() {
	int d = findSingleSpellTarget(2);
	if (d != -1) {
		if (!magicObjectStatusHit(&_monsters[d], 3, true, 4))
			inflictMonsterDamage(&_monsters[d], rollDice(2, 8, 1), true);
	}
}

void EoBCoreEngine::spellCallback_start_powerWordStun() {
	int d = findSingleSpellTarget(2);
	if (d != -1) {
		if (_monsters[d].hitPointsCur < 90)
			magicObjectStatusHit(&_monsters[d], 5, true, 4);
	}
}

void EoBCoreEngine::spellCallback_start_causeLightWounds() {
	causeWounds(1, 8, 0);
}

void EoBCoreEngine::spellCallback_start_cureLightWounds() {
	modifyCharacterHitpoints(_activeSpellCharId, rollDice(1, 8));
}

void EoBCoreEngine::spellCallback_start_aid() {
	if (!testCharacter(_activeSpellCharId, 3)) {
		printNoEffectWarning();
	} else if (_characters[_activeSpellCharId].effectsRemainder[3]) {
		printWarning(Common::String::format(_magicStrings8[(_flags.gameID == GI_EOB1) ? 2 : 5], _characters[_activeSpellCharId].name).c_str());
	} else {
		_characters[_activeSpellCharId].effectsRemainder[3] = rollDice(1, 8);
		_characters[_activeSpellCharId].hitPointsCur += _characters[_activeSpellCharId].effectsRemainder[3];
		_characters[_activeSpellCharId].effectFlags |= 0x1000;
		return;
	}

	removeCharacterEffect(_activeSpell, _activeSpellCharId, 0);
	deleteCharEventTimer(_activeSpellCharId, -_activeSpell);
}

bool EoBCoreEngine::spellCallback_end_aid(void *obj) {
	EoBCharacter *c = (EoBCharacter *)obj;
	c->hitPointsCur -= c->effectsRemainder[3];
	c->effectsRemainder[3] = 0;
	c->effectFlags &= ~0x1000;
	return true;
}

void EoBCoreEngine::spellCallback_start_flameBlade() {
	int t = createMagicWeaponType(0, 0, 0, 0x0F, 1, 4, 4, 1);
	Item i = (t != -1) ? createMagicWeaponItem(0, 84, 0, t) : -1;
	if (t == -1 || i == -1) {
		if (_flags.gameID == GI_EOB2)
			printWarning(_magicStrings8[0]);
		removeCharacterEffect(_activeSpell, _activeSpellCharId, 0);
		deleteCharEventTimer(_activeSpellCharId, -_activeSpell);
		_returnAfterSpellCallback = true;
	} else {
		_characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i;
	}
}

void EoBCoreEngine::spellCallback_start_slowPoison() {
	if (_characters[_activeSpellCharId].flags & 2) {
		_characters[_activeSpellCharId].effectFlags |= 0x2000;
		setSpellEventTimer(_activeSpell, 1, 32760, 1, 1);
	} else {
		printNoEffectWarning();
	}
}

bool EoBCoreEngine::spellCallback_end_slowPoison(void *obj) {
	EoBCharacter *c = (EoBCharacter *)obj;
	c->effectFlags &= ~0x2000;
	return true;
}

void EoBCoreEngine::spellCallback_start_createFood() {
	for (int i = 0; i < 6; i++) {
		if (!testCharacter(i, 3))
			continue;
		_characters[i].food = 100;
	}
}

void EoBCoreEngine::spellCallback_start_removeParalysis() {
	int numChar = 4;
	for (int i = 0; i < 6; i++) {
		if (!(_characters[i].flags & 4) || !numChar)
			continue;
		_characters[i].flags &= ~4;
		numChar--;
	}
}

void EoBCoreEngine::spellCallback_start_causeSeriousWounds() {
	causeWounds(2, 8, 1);
}

void EoBCoreEngine::spellCallback_start_cureSeriousWounds() {
	modifyCharacterHitpoints(_activeSpellCharId, rollDice(2, 8, 1));
}

void EoBCoreEngine::spellCallback_start_neutralizePoison() {
	if (_characters[_activeSpellCharId].flags & 2)
		neutralizePoison(_activeSpellCharId);
	else
		printNoEffectWarning();
}

void EoBCoreEngine::spellCallback_start_causeCriticalWounds() {
	causeWounds(3, 8, 3);
}

void EoBCoreEngine::spellCallback_start_cureCriticalWounds() {
	modifyCharacterHitpoints(_activeSpellCharId, rollDice(3, 8, 3));
}

void EoBCoreEngine::spellCallback_start_flameStrike() {
	launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 8 : 7, _currentBlock, _activeSpellCharacterPos, _currentDirection);
}

bool EoBCoreEngine::spellCallback_end_flameStrike(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	return magicObjectDamageHit(fo, 6, 8, 0, 0);
}

void EoBCoreEngine::spellCallback_start_raiseDead() {
	if (_characters[_activeSpellCharId].hitPointsCur == -10 && ((_characters[_activeSpellCharId].raceSex >> 1) != 1)) {
		_characters[_activeSpellCharId].hitPointsCur = 1;
		gui_drawCharPortraitWithStats(_activeSpellCharId);
	} else {
		printNoEffectWarning();
	}
}

void EoBCoreEngine::spellCallback_start_harm() {
	causeWounds(-1, -1, -1);
}

void EoBCoreEngine::spellCallback_start_heal() {
	EoBCharacter *c = &_characters[_activeSpellCharId];
	if (c->hitPointsMax <= c->hitPointsCur)
		printWarning(_magicStrings4[0]);
	else
		modifyCharacterHitpoints(_activeSpellCharId, c->hitPointsMax - c->hitPointsCur);
}

void EoBCoreEngine::spellCallback_start_layOnHands() {
	modifyCharacterHitpoints(_activeSpellCharId, _characters[_openBookChar].level[0] << 1);
}

void EoBCoreEngine::spellCallback_start_turnUndead() {
	uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection);
	if (!(_levelBlockProperties[bl].flags & 7))
		return;

	int cl = _openBookCasterLevel ? _openBookCasterLevel : getClericPaladinLevel(_openBookChar);
	int r = rollDice(1, 20);
	bool hit = false;

	for (const int16 *m = findBlockMonsters(bl, 4, 4, 1, 1); *m != -1; m++) {
		if ((_monsterProps[_monsters[*m].type].typeFlags & 4) && !(_monsters[*m].flags & 0x10)) {
			_preventMonsterFlash = true;
			_monsters[*m].flags |= 0x10;
			hit |= turnUndeadHit(&_monsters[*m], r, cl);
		}
	}

	if (hit) {
		turnUndeadAutoHit();
		snd_playSoundEffect(95);
		updateAllMonsterShapes();
	}

	_preventMonsterFlash = false;
}

bool EoBCoreEngine::spellCallback_end_monster_lightningBolt(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	return magicObjectDamageHit(fo, 0, 0, 12, 1);
}

bool EoBCoreEngine::spellCallback_end_monster_fireball1(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	bool res = false;
	if (_partyEffectFlags & 0x20000) {
		res = magicObjectDamageHit(fo, 4, 10, 6, 0);
		if (res) {
			gui_drawAllCharPortraitsWithStats();
			_partyEffectFlags &= ~0x20000;
		}
	} else {
		res = magicObjectDamageHit(fo, 12, 10, 6, 0);
	}
	return res;
}

bool EoBCoreEngine::spellCallback_end_monster_fireball2(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	return magicObjectDamageHit(fo, 0, 0, 18, 0);
}

bool EoBCoreEngine::spellCallback_end_monster_deathSpell(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	if (fo->curBlock != _currentBlock)
		return false;

	int numDest = rollDice(1, 4);
	_txt->printMessage(_magicStrings2[2]);
	for (int d = findFirstCharacterSpellTarget(); d != -1 && numDest; d = findNextCharacterSpellTarget(d)) {
		if (_characters[d].level[0] < 8) {
			inflictCharacterDamage(d, 300);
			numDest--;
		}
	}

	return true;
}

bool EoBCoreEngine::spellCallback_end_monster_disintegrate(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	if (fo->curBlock != _currentBlock)
		return false;

	int d = findFirstCharacterSpellTarget();
	if (d != -1) {
		if (!charDeathSavingThrow(d, 1)) {
			inflictCharacterDamage(d, 300);
			_txt->printMessage(_magicStrings2[1], -1, _characters[d].name);
		}
	}

	return true;
}

bool EoBCoreEngine::spellCallback_end_monster_causeCriticalWounds(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	if (fo->curBlock != _currentBlock)
		return false;

	int d = findFirstCharacterSpellTarget();
	if (d != -1) {
		_txt->printMessage(_magicStrings2[3], -1, _characters[d].name);
		inflictCharacterDamage(d, rollDice(3, 8, 3));
	}

	return true;
}

bool EoBCoreEngine::spellCallback_end_monster_fleshToStone(void *obj) {
	EoBFlyingObject *fo = (EoBFlyingObject *)obj;
	if (fo->curBlock != _currentBlock)
		return false;

	int d = findFirstCharacterSpellTarget();
	while (d != -1) {
		if (!charDeathSavingThrow(d, 2)) {
			statusAttack(d, 8, _magicStrings2[4], 5, 0, 0, 1);
			d = -1;
		} else {
			d = findNextCharacterSpellTarget(d);
		}
	}

	return true;
}

} // End of namespace Kyra

#endif // ENABLE_EOB