mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Lots of refactoring & work for creature spells.
No new features yet, but summon elementals now work properly again.
This commit is contained in:
parent
623325ca61
commit
6fdb984799
@ -1184,7 +1184,7 @@ void CBattleInterface::addNewAnim(CBattleAnimation * anim)
|
||||
|
||||
CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSet * army2, CGHeroInstance *hero1, CGHeroInstance *hero2, const SDL_Rect & myRect, CPlayerInterface * att, CPlayerInterface * defen)
|
||||
: queue(NULL), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
|
||||
activeStack(NULL), stackToActivate(NULL), mouseHoveredStack(-1), lastMouseHoveredStackAnimationTime(-1), previouslyHoveredHex(-1),
|
||||
activeStack(NULL), stackToActivate(NULL), mouseHoveredStack(-1), lastMouseHoveredStackAnimationTime(-1), previouslyHoveredHex(-1), spellSelMode(NO_LOCATION),
|
||||
currentlyHoveredHex(-1), attackingHex(-1), tacticianInterface(NULL), stackCanCastSpell(false), spellDestSelectMode(false), spellToCast(NULL),
|
||||
siegeH(NULL), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0), givenCommand(NULL),
|
||||
myTurn(false), resWindow(NULL), moveStarted(false), moveSh(-1), bresult(NULL)
|
||||
@ -2003,7 +2003,7 @@ void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
|
||||
}
|
||||
void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
|
||||
{
|
||||
if(activeStack!= NULL && !spellDestSelectMode)
|
||||
if(activeStack && !spellDestSelectMode)
|
||||
{
|
||||
int lastMouseHoveredStack = mouseHoveredStack;
|
||||
mouseHoveredStack = -1;
|
||||
@ -2039,6 +2039,18 @@ void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
|
||||
//display the possibility to heal this creature
|
||||
CCS->curh->changeGraphic(1,17);
|
||||
}
|
||||
else if (sactive->hasBonusOfType(Bonus::DAEMON_SUMMONING))
|
||||
{
|
||||
CCS->curh->changeGraphic(3, 0);
|
||||
}
|
||||
else if (stackCanCastSpell && spellSelMode > STACK_SPELL_CANCELLED) //player did not decide to cancel this spell
|
||||
{
|
||||
//spellDestSelectMode
|
||||
if (curInt->cb->battleCanCastThisSpell(creatureSpellToCast, THex(myNumber)) == SpellCasting::OK)
|
||||
CCS->curh->changeGraphic(3, 0);
|
||||
//if (battleIsImmune(NULL, spellToCast, SpellCasting::CREATURE_ACTIVE_CASTING, myNumber) == SpellCasting::OK)
|
||||
//if (battleCanCastThisSpell(curInt->playerID, spellToCast, SpellCasting::CREATURE_ACTIVE_CASTING))
|
||||
}
|
||||
else
|
||||
{
|
||||
//info about creature
|
||||
@ -2709,11 +2721,13 @@ void CBattleInterface::giveCommand(ui8 action, THex tile, ui32 stack, si32 addit
|
||||
//some basic validations
|
||||
switch(action)
|
||||
{
|
||||
case 6:
|
||||
assert(curInt->cb->battleGetStackByPos(additional)); //stack to attack must exist
|
||||
case 2: case 7: case 9:
|
||||
assert(tile < BFIELD_SIZE);
|
||||
break;
|
||||
case BattleAction::WALK_AND_ATTACK:
|
||||
assert(curInt->cb->battleGetStackByPos(additional)); //stack to attack must exist
|
||||
case BattleAction::WALK:
|
||||
case BattleAction::SHOOT:
|
||||
case BattleAction::CATAPULT:
|
||||
assert(tile < BFIELD_SIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!tacticsMode)
|
||||
@ -2784,7 +2798,7 @@ const CGHeroInstance * CBattleInterface::getActiveHero()
|
||||
void CBattleInterface::hexLclicked(int whichOne)
|
||||
{
|
||||
const CStack * actSt = activeStack;
|
||||
const CStack* dest = curInt->cb->battleGetStackByPos(whichOne); //creature at destination tile; -1 if there is no one
|
||||
const CStack* dest = curInt->cb->battleGetStackByPos(whichOne, false); //creature at destination tile; -1 if there is no one
|
||||
if(!actSt)
|
||||
{
|
||||
tlog3 << "Hex l-clicked when no active stack!\n";
|
||||
@ -2797,29 +2811,30 @@ void CBattleInterface::hexLclicked(int whichOne)
|
||||
{
|
||||
if(!myTurn)
|
||||
return; //we are not permit to do anything
|
||||
if(spellDestSelectMode)
|
||||
if(spellDestSelectMode) //TODO: choose target for area creature spell
|
||||
{
|
||||
//checking destination
|
||||
bool allowCasting = true;
|
||||
bool onlyAlive = spellToCast->additionalInfo != 38 && spellToCast->additionalInfo != 39; //when casting resurrection or animate dead we should be allow to select dead stack
|
||||
//TODO: more general handling of dead targets
|
||||
switch(spellSelMode)
|
||||
{
|
||||
case 1:
|
||||
if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID != curInt->cb->battleGetStackByPos(whichOne, onlyAlive)->owner )
|
||||
case FRIENDLY_CREATURE:
|
||||
if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID != dest->owner )
|
||||
allowCasting = false;
|
||||
break;
|
||||
case 2:
|
||||
if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID == curInt->cb->battleGetStackByPos(whichOne, onlyAlive)->owner )
|
||||
case HOSTILE_CREATURE:
|
||||
if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID == dest->owner )
|
||||
allowCasting = false;
|
||||
break;
|
||||
case 3:
|
||||
case ANY_CREATURE:
|
||||
if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive))
|
||||
allowCasting = false;
|
||||
break;
|
||||
case 4:
|
||||
case OBSTACLE:
|
||||
if(!blockedByObstacle(whichOne))
|
||||
allowCasting = false;
|
||||
case 5: //teleport
|
||||
case TELEPORT: //teleport
|
||||
const CSpell *s = CGI->spellh->spells[spellToCast->additionalInfo];
|
||||
ui8 skill = getActiveHero()->getSpellSchoolLevel(s); //skill level
|
||||
if (!curInt->cb->battleCanTeleportTo(activeStack, whichOne, skill))
|
||||
@ -2836,11 +2851,198 @@ void CBattleInterface::hexLclicked(int whichOne)
|
||||
endCastingSpell();
|
||||
}
|
||||
}
|
||||
else //we don't cast any spell
|
||||
else //we don't aim for spell target
|
||||
{
|
||||
if(!dest || !dest->alive()) //no creature at that tile
|
||||
bool walkableTile = false;
|
||||
if (dest)
|
||||
{
|
||||
if(std::find(occupyableHexes.begin(),occupyableHexes.end(),whichOne)!=occupyableHexes.end())// and it's in our range
|
||||
if (dest->alive())
|
||||
{
|
||||
if(dest->owner != actSt->owner && curInt->cb->battleCanShoot(activeStack, whichOne)) //shooting
|
||||
{
|
||||
CCS->curh->changeGraphic(1, 6); //cursor should be changed
|
||||
giveCommand (BattleAction::SHOOT, whichOne, activeStack->ID);
|
||||
}
|
||||
else if(dest->owner != actSt->owner) //attacking
|
||||
{
|
||||
const CStack * actStack = activeStack;
|
||||
int attackFromHex = -1; //hex from which we will attack chosen stack
|
||||
switch(CCS->curh->number)
|
||||
{
|
||||
case 12: //from bottom right
|
||||
{
|
||||
bool doubleWide = actStack->doubleWide();
|
||||
int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 ) +
|
||||
(actStack->attackerOwned && doubleWide ? 1 : 0);
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(actStack->attackerOwned) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 7: //from bottom left
|
||||
{
|
||||
int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH-1 : BFIELD_WIDTH );
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(actStack->attackerOwned) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 8: //from left
|
||||
{
|
||||
if(actStack->doubleWide() && !actStack->attackerOwned)
|
||||
{
|
||||
std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
|
||||
if(vstd::contains(acc, whichOne))
|
||||
attackFromHex = whichOne - 1;
|
||||
else
|
||||
attackFromHex = whichOne - 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackFromHex = whichOne - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 9: //from top left
|
||||
{
|
||||
int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH+1 : BFIELD_WIDTH );
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(actStack->attackerOwned) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 10: //from top right
|
||||
{
|
||||
bool doubleWide = actStack->doubleWide();
|
||||
int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 ) +
|
||||
(actStack->attackerOwned && doubleWide ? 1 : 0);
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(actStack->attackerOwned) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 11: //from right
|
||||
{
|
||||
if(actStack->doubleWide() && actStack->attackerOwned)
|
||||
{
|
||||
std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
|
||||
if(vstd::contains(acc, whichOne))
|
||||
attackFromHex = whichOne + 1;
|
||||
else
|
||||
attackFromHex = whichOne + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackFromHex = whichOne + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 13: //from bottom
|
||||
{
|
||||
int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 );
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 14: //from top
|
||||
{
|
||||
int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 );
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
|
||||
{
|
||||
giveCommand(BattleAction::WALK_AND_ATTACK, attackFromHex, activeStack->ID, whichOne);
|
||||
|
||||
CCS->curh->changeGraphic(1, 6); //cursor should be changed
|
||||
}
|
||||
|
||||
}
|
||||
else if (actSt->hasBonusOfType(Bonus::HEALER) && actSt->owner == dest->owner) //friendly creature we can heal
|
||||
{ //TODO: spellDestSelectMode > -2 if we don't want to heal but perform some other (?) action
|
||||
giveCommand(BattleAction::STACK_HEAL, whichOne, activeStack->ID); //command healing
|
||||
|
||||
CCS->curh->changeGraphic(1, 6); //cursor should be changed
|
||||
}
|
||||
|
||||
} //stack is not alive
|
||||
else if (actSt->hasBonusOfType(Bonus::DAEMON_SUMMONING) && actSt->casts &&
|
||||
actSt->owner == dest->owner && spellSelMode > -2)//friendly body we can (and want) rise
|
||||
{
|
||||
giveCommand(BattleAction::DAEMON_SUMMONING, whichOne, activeStack->ID);
|
||||
|
||||
CCS->curh->changeGraphic(1, 6); //cursor should be changed
|
||||
}
|
||||
else //not a subject of resurrection
|
||||
walkableTile = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
walkableTile = true;
|
||||
}
|
||||
|
||||
if (walkableTile) // we can try to move to this tile
|
||||
{
|
||||
if(std::find(occupyableHexes.begin(), occupyableHexes.end(), whichOne) != occupyableHexes.end())// and it's in our range
|
||||
{
|
||||
CCS->curh->changeGraphic(1, 6); //cursor should be changed
|
||||
if(activeStack->doubleWide())
|
||||
@ -2848,186 +3050,20 @@ void CBattleInterface::hexLclicked(int whichOne)
|
||||
std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
|
||||
int shiftedDest = whichOne + (activeStack->attackerOwned ? 1 : -1);
|
||||
if(vstd::contains(acc, whichOne))
|
||||
giveCommand(2,whichOne,activeStack->ID);
|
||||
giveCommand (BattleAction::WALK ,whichOne, activeStack->ID);
|
||||
else if(vstd::contains(acc, shiftedDest))
|
||||
giveCommand(2,shiftedDest,activeStack->ID);
|
||||
giveCommand (BattleAction::WALK, shiftedDest, activeStack->ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
giveCommand(2,whichOne,activeStack->ID);
|
||||
giveCommand(BattleAction::WALK, whichOne, activeStack->ID);
|
||||
}
|
||||
}
|
||||
else if(actSt->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(whichOne)) //attacking (catapult)
|
||||
{
|
||||
giveCommand(9,whichOne,activeStack->ID);
|
||||
giveCommand(BattleAction::CATAPULT, whichOne, activeStack->ID);
|
||||
}
|
||||
}
|
||||
else if(dest->owner != actSt->owner
|
||||
&& curInt->cb->battleCanShoot(activeStack, whichOne) ) //shooting
|
||||
{
|
||||
CCS->curh->changeGraphic(1, 6); //cursor should be changed
|
||||
giveCommand(7,whichOne,activeStack->ID);
|
||||
}
|
||||
else if(dest->owner != actSt->owner) //attacking
|
||||
{
|
||||
const CStack * actStack = activeStack;
|
||||
int attackFromHex = -1; //hex from which we will attack chosen stack
|
||||
switch(CCS->curh->number)
|
||||
{
|
||||
case 12: //from bottom right
|
||||
{
|
||||
bool doubleWide = actStack->doubleWide();
|
||||
int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 ) +
|
||||
(actStack->attackerOwned && doubleWide ? 1 : 0);
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(actStack->attackerOwned) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 7: //from bottom left
|
||||
{
|
||||
int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH-1 : BFIELD_WIDTH );
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(actStack->attackerOwned) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 8: //from left
|
||||
{
|
||||
if(actStack->doubleWide() && !actStack->attackerOwned)
|
||||
{
|
||||
std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
|
||||
if(vstd::contains(acc, whichOne))
|
||||
attackFromHex = whichOne - 1;
|
||||
else
|
||||
attackFromHex = whichOne - 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackFromHex = whichOne - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 9: //from top left
|
||||
{
|
||||
int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH+1 : BFIELD_WIDTH );
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(actStack->attackerOwned) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 10: //from top right
|
||||
{
|
||||
bool doubleWide = actStack->doubleWide();
|
||||
int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 ) +
|
||||
(actStack->attackerOwned && doubleWide ? 1 : 0);
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(actStack->attackerOwned) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 11: //from right
|
||||
{
|
||||
if(actStack->doubleWide() && actStack->attackerOwned)
|
||||
{
|
||||
std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
|
||||
if(vstd::contains(acc, whichOne))
|
||||
attackFromHex = whichOne + 1;
|
||||
else
|
||||
attackFromHex = whichOne + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackFromHex = whichOne + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 13: //from bottom
|
||||
{
|
||||
int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 );
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 14: //from top
|
||||
{
|
||||
int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 );
|
||||
if(vstd::contains(occupyableHexes, destHex))
|
||||
attackFromHex = destHex;
|
||||
else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex+1))
|
||||
attackFromHex = destHex+1;
|
||||
}
|
||||
else //if we are defender
|
||||
{
|
||||
if(vstd::contains(occupyableHexes, destHex-1))
|
||||
attackFromHex = destHex-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
|
||||
{
|
||||
giveCommand(6, attackFromHex, activeStack->ID, whichOne);
|
||||
|
||||
CCS->curh->changeGraphic(1, 6); //cursor should be changed
|
||||
}
|
||||
|
||||
}
|
||||
else if (actSt->hasBonusOfType(Bonus::HEALER) && actSt->owner == dest->owner) //friendly creature we can heal
|
||||
{
|
||||
giveCommand(12, whichOne, activeStack->ID); //command healing
|
||||
|
||||
CCS->curh->changeGraphic(1, 6); //cursor should be changed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3451,6 +3487,7 @@ void CBattleInterface::activateStack()
|
||||
activeStack = stackToActivate;
|
||||
stackToActivate = NULL;
|
||||
const CStack *s = activeStack;
|
||||
stackSpells.clear();
|
||||
|
||||
myTurn = true;
|
||||
if(attackerInt && defenderInt) //hotseat -> need to pick which interface "takes over" as active
|
||||
@ -3468,8 +3505,18 @@ void CBattleInterface::activateStack()
|
||||
|
||||
//set casting flag to true if creature can use it to not check it every time
|
||||
if (s->casts && s->hasBonus(Selector::type(Bonus::SPELLCASTER) || Selector::type(Bonus::DAEMON_SUMMONING)))
|
||||
{
|
||||
stackCanCastSpell = true;
|
||||
|
||||
TBonusListPtr bl = s->getBonuses(Selector::type(Bonus::SPELLCASTER));
|
||||
BOOST_FOREACH(Bonus * b, *bl)
|
||||
{
|
||||
stackSpells.push_back(CGI->spellh->spells[b->subtype]);
|
||||
}
|
||||
if (stackSpells.size())
|
||||
creatureSpellToCast = stackSpells[111 % stackSpells.size()]; //TODO: randomize? weighted chance?
|
||||
}
|
||||
|
||||
GH.fakeMouseMove();
|
||||
|
||||
if(!pendingAnims.size() && !active)
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <list>
|
||||
#include "GUIBase.h"
|
||||
#include "../lib/CCreatureSet.h"
|
||||
#include "../lib/ConstTransitivePtr.h" //may be reundant
|
||||
#include "CAnimation.h"
|
||||
|
||||
/*
|
||||
@ -419,7 +420,7 @@ class CBattleInterface : public CIntObject
|
||||
{
|
||||
enum SpellSelectionType
|
||||
{
|
||||
ANY_LOCATION = 0, FRIENDLY_CREATURE, HOSTILE_CREATURE, ANY_CREATURE, OBSTACLE, TELEPORT, NO_LOCATION = -1
|
||||
ANY_LOCATION = 0, FRIENDLY_CREATURE, HOSTILE_CREATURE, ANY_CREATURE, OBSTACLE, TELEPORT, NO_LOCATION = -1, STACK_SPELL_CANCELLED = -2
|
||||
};
|
||||
private:
|
||||
SDL_Surface * background, * menu, * amountNormal, * amountNegative, * amountPositive, * amountEffNeutral, * cellBorders, * backgroundWithHexes;
|
||||
@ -456,6 +457,8 @@ private:
|
||||
bool spellDestSelectMode; //if true, player is choosing destination for his spell
|
||||
SpellSelectionType spellSelMode;
|
||||
BattleAction * spellToCast; //spell for which player is choosing destination
|
||||
CSpell * creatureSpellToCast;
|
||||
std::vector< ConstTransitivePtr<CSpell> > stackSpells; //all spells possible to cast by the stack
|
||||
void endCastingSpell(); //ends casting spell (eg. when spell has been cast or canceled)
|
||||
|
||||
void showAliveStack(const CStack *stack, SDL_Surface * to); //helper function for function show
|
||||
|
@ -632,11 +632,6 @@ void StartAction::applyFirstCl( CClient *cl )
|
||||
void BattleSpellCast::applyCl( CClient *cl )
|
||||
{
|
||||
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleSpellCast,this);
|
||||
|
||||
if(id >= 66 && id <= 69) //elemental summoning
|
||||
{
|
||||
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleNewStackAppeared,GS(cl)->curB->stacks.back());
|
||||
}
|
||||
}
|
||||
|
||||
void SetStackEffect::applyCl( CClient *cl )
|
||||
@ -691,6 +686,11 @@ void BattleStacksRemoved::applyCl( CClient *cl )
|
||||
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStacksRemoved, *this);
|
||||
}
|
||||
|
||||
void BattleStackAdded::applyCl( CClient *cl )
|
||||
{
|
||||
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleNewStackAppeared, GS(cl)->curB->stacks.back());
|
||||
}
|
||||
|
||||
CGameState* CPackForClient::GS( CClient *cl )
|
||||
{
|
||||
return cl->gs;
|
||||
|
2
global.h
2
global.h
@ -327,7 +327,7 @@ namespace SpellCasting
|
||||
};
|
||||
|
||||
enum ECastingMode {HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
|
||||
MAGIC_MIRROR};
|
||||
MAGIC_MIRROR, CREATURE_ACTIVE_CASTING};
|
||||
}
|
||||
|
||||
namespace Buildings
|
||||
|
@ -22,10 +22,10 @@ struct DLL_EXPORT BattleAction
|
||||
ui32 stackNumber;//stack ID, -1 left hero, -2 right hero,
|
||||
enum ActionType
|
||||
{
|
||||
END_TACTIC_PHASE = -2, INVALID = -1, NO_ACTION = 0, HERO_SPELL, WALK, DEFEND, RETREAT, SURRENDER, WALK_AND_ATTACK, SHOOT, WAIT, CATAPULT, MONSTER_SPELL, BAD_MORALE, STACK_HEAL
|
||||
END_TACTIC_PHASE = -2, INVALID = -1, NO_ACTION = 0, HERO_SPELL, WALK, DEFEND, RETREAT, SURRENDER, WALK_AND_ATTACK, SHOOT, WAIT, CATAPULT, MONSTER_SPELL, BAD_MORALE,
|
||||
STACK_HEAL, DAEMON_SUMMONING
|
||||
};
|
||||
si8 actionType; //use ActionType enum for values
|
||||
//10 = Monster casts a spell (i.e. Faerie Dragons) 11 - Bad morale freeze 12 - stacks heals another stack
|
||||
THex destinationTile;
|
||||
si32 additionalInfo; // e.g. spell number if type is 1 || 10; tile to attack if type is 6
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
|
@ -376,6 +376,41 @@ std::vector<THex> BattleInfo::getAccessibility( const CStack * stack, bool addOc
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int BattleInfo::getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos) const
|
||||
{
|
||||
int pos;
|
||||
if (initialPos > -1)
|
||||
pos = initialPos;
|
||||
else
|
||||
{
|
||||
if (attackerOwned)
|
||||
pos = 0; //top left
|
||||
else
|
||||
pos = BFIELD_WIDTH; //top right
|
||||
}
|
||||
|
||||
bool ac[BFIELD_SIZE];
|
||||
std::set<THex> occupyable;
|
||||
bool twoHex = VLC->creh->creatures[creID]->isDoubleWide();
|
||||
bool flying = VLC->creh->creatures[creID]->isFlying();// vstd::contains(VLC->creh->creatures[creID]->bonuses, Bonus::FLYING);
|
||||
getAccessibilityMap(ac, twoHex, attackerOwned, true, occupyable, flying);
|
||||
for (int g = pos; -1 < g < BFIELD_SIZE; )
|
||||
{
|
||||
if ((g % BFIELD_WIDTH != 0) && (g % BFIELD_WIDTH != BFIELD_WIDTH-1) && BattleInfo::isAccessible (g, ac, twoHex, attackerOwned, flying, true))
|
||||
{
|
||||
pos = g;
|
||||
break;
|
||||
}
|
||||
if (attackerOwned)
|
||||
++g; //probably some more sophisticated range-based iteration is needed
|
||||
else
|
||||
--g;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool BattleInfo::isStackBlocked(const CStack * stack) const
|
||||
{
|
||||
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked
|
||||
@ -1776,7 +1811,7 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastSpell(int player, Spell
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case SpellCasting::HERO_CASTING:
|
||||
case SpellCasting::HERO_CASTING:
|
||||
{
|
||||
if(castSpells[side] > 0)
|
||||
return SpellCasting::ALREADY_CASTED_THIS_TURN;
|
||||
@ -1800,7 +1835,7 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpell( int player,
|
||||
int cside = sides[0] == player ? 0 : 1; //caster's side
|
||||
switch(mode)
|
||||
{
|
||||
case SpellCasting::HERO_CASTING:
|
||||
case SpellCasting::HERO_CASTING:
|
||||
{
|
||||
const CGHeroInstance * caster = heroes[cside];
|
||||
if(!caster->canCastThisSpell(spell))
|
||||
@ -1900,7 +1935,10 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpellHere( int play
|
||||
if(moreGeneralProblem != SpellCasting::OK)
|
||||
return moreGeneralProblem;
|
||||
|
||||
return battleIsImmune(getHero(player), spell, mode, dest);
|
||||
if (mode != SpellCasting::CREATURE_ACTIVE_CASTING)
|
||||
return battleIsImmune(getHero(player), spell, mode, dest);
|
||||
else
|
||||
return battleIsImmune(NULL, spell, mode, dest);
|
||||
}
|
||||
|
||||
const CGHeroInstance * BattleInfo::getHero( int player ) const
|
||||
|
@ -87,6 +87,7 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode
|
||||
const CStack * getStackT(THex tileID, bool onlyAlive = true) const;
|
||||
void getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set<THex> & occupyable, bool flying, const CStack* stackToOmmit = NULL) const; //send pointer to at least 187 allocated bytes
|
||||
static bool isAccessible(THex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos); //helper for makeBFS
|
||||
int getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos = -1) const; //find place for summon / clone effects
|
||||
void makeBFS(THex start, bool*accessibility, THex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const; //*accessibility must be prepared bool[187] array; last two pointers must point to the at least 187-elements int arrays - there is written result
|
||||
std::pair< std::vector<THex>, int > getPath(THex start, THex dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned); //returned value: pair<path, length>; length may be different than number of elements in path since flying vreatures jump between distant hexes
|
||||
std::vector<THex> getAccessibility(const CStack * stack, bool addOccupiable, std::vector<THex> * attackable = NULL) const; //returns vector of accessible tiles (taking into account the creature range)
|
||||
|
@ -107,6 +107,18 @@ SpellCasting::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( con
|
||||
return gs->curB->battleCanCastThisSpell(player, spell, SpellCasting::HERO_CASTING);
|
||||
}
|
||||
|
||||
SpellCasting::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const CSpell * spell, THex destination)
|
||||
{
|
||||
if(!gs->curB)
|
||||
{
|
||||
|
||||
tlog1 << "battleCanCastThisSpell called when there is no battle!\n";
|
||||
return SpellCasting::NO_HERO_TO_CAST_SPELL;
|
||||
}
|
||||
|
||||
return gs->curB->battleCanCastThisSpellHere(player, spell, SpellCasting::CREATURE_ACTIVE_CASTING, destination);
|
||||
}
|
||||
|
||||
si8 CBattleInfoCallback::battleGetTacticDist()
|
||||
{
|
||||
if (!gs->curB)
|
||||
|
@ -104,6 +104,7 @@ public:
|
||||
bool battleCanShoot(const CStack * stack, THex dest); //returns true if unit with id ID can shoot to dest
|
||||
bool battleCanCastSpell(); //returns true, if caller can cast a spell
|
||||
SpellCasting::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell); //determines if given spell can be casted (and returns problem description)
|
||||
SpellCasting::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, THex destination); //determines if creature can cast a spell here
|
||||
bool battleCanFlee(); //returns true if caller can flee from the battle
|
||||
int battleGetSurrenderCost(); //returns cost of surrendering battle, -1 if surrendering is not possible
|
||||
const CGTownInstance * battleGetDefendedTown(); //returns defended town if current battle is a siege, NULL instead
|
||||
|
@ -1483,6 +1483,25 @@ struct BattleStacksRemoved : public CPackForClient //3016
|
||||
}
|
||||
};
|
||||
|
||||
struct BattleStackAdded : public CPackForClient //3017
|
||||
{
|
||||
BattleStackAdded(){type = 3017;};
|
||||
|
||||
DLL_EXPORT void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
|
||||
int attacker; // if true, stack belongs to attacker
|
||||
int creID;
|
||||
int amount;
|
||||
int pos;
|
||||
int summoned; //if true, remove it afterwards
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & attacker & creID & amount & pos & summoned;
|
||||
}
|
||||
};
|
||||
|
||||
struct ShowInInfobox : public CPackForClient //107
|
||||
{
|
||||
ShowInInfobox(){type = 107;};
|
||||
|
@ -1019,9 +1019,9 @@ DLL_EXPORT void StartAction::applyGs( CGameState *gs )
|
||||
case BattleAction::WAIT:
|
||||
st->state.insert(WAITING);
|
||||
return;
|
||||
case BattleAction::NO_ACTION: case BattleAction::WALK: case BattleAction::WALK_AND_ATTACK:
|
||||
case BattleAction::SHOOT: case BattleAction::CATAPULT: case BattleAction::MONSTER_SPELL:
|
||||
case BattleAction::BAD_MORALE: case BattleAction::STACK_HEAL:
|
||||
case BattleAction::HERO_SPELL: //no change in current stack state
|
||||
return;
|
||||
default: //any active stack action - attack, catapult, heal, spell...
|
||||
st->state.insert(MOVED);
|
||||
break;
|
||||
}
|
||||
@ -1046,7 +1046,8 @@ DLL_EXPORT void BattleSpellCast::applyGs( CGameState *gs )
|
||||
spellCost = VLC->spellh->spells[id]->costs[skill];
|
||||
}
|
||||
h->mana -= spellCost;
|
||||
if(h->mana < 0) h->mana = 0;
|
||||
if (h->mana < 0)
|
||||
h->mana = 0;
|
||||
}
|
||||
if(side >= 0 && side < 2 && castedByHero)
|
||||
{
|
||||
@ -1069,55 +1070,6 @@ DLL_EXPORT void BattleSpellCast::applyGs( CGameState *gs )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//elemental summoning
|
||||
if(id >= 66 && id <= 69)
|
||||
{
|
||||
int creID;
|
||||
switch(id)
|
||||
{
|
||||
case 66:
|
||||
creID = 114; //fire elemental
|
||||
break;
|
||||
case 67:
|
||||
creID = 113; //earth elemental
|
||||
break;
|
||||
case 68:
|
||||
creID = 115; //water elemental
|
||||
break;
|
||||
case 69:
|
||||
creID = 112; //air elemental
|
||||
break;
|
||||
}
|
||||
// const int3 & tile = gs->curB->tile;
|
||||
// TerrainTile::EterrainType ter = gs->map->terrain[tile.x][tile.y][tile.z].tertype;
|
||||
|
||||
int pos; //position of stack on the battlefield - to be calculated
|
||||
|
||||
bool ac[BFIELD_SIZE];
|
||||
std::set<THex> occupyable;
|
||||
bool twoHex = VLC->creh->creatures[creID]->isDoubleWide();
|
||||
bool flying = VLC->creh->creatures[creID]->isFlying();// vstd::contains(VLC->creh->creatures[creID]->bonuses, Bonus::FLYING);
|
||||
gs->curB->getAccessibilityMap(ac, twoHex, !side, true, occupyable, flying);
|
||||
for(int g=0; g<BFIELD_SIZE; ++g)
|
||||
{
|
||||
if(g % BFIELD_WIDTH != 0 && g % BFIELD_WIDTH != BFIELD_WIDTH-1 && BattleInfo::isAccessible(g, ac, twoHex, !side, flying, true) )
|
||||
{
|
||||
pos = g;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int summonedElementals = h->getPrimSkillLevel(2) * VLC->spellh->spells[id]->powers[skill] * (100 + h->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id) / 100.0f); //new feature - percentage bonus
|
||||
CStackInstance *csi = new CStackInstance(creID, summonedElementals); //deleted by d-tor of summoned stack
|
||||
csi->setArmyObj(h);
|
||||
CStack * summonedStack = gs->curB->generateNewStack(*csi, gs->curB->stacks.size(), !side, 255, pos);
|
||||
summonedStack->state.insert(SUMMONED);
|
||||
summonedStack->attachTo(csi);
|
||||
summonedStack->postInit();
|
||||
//summonedStack->addNewBonus( makeFeature(HeroBonus::SUMMONED, HeroBonus::ONE_BATTLE, 0, 0, HeroBonus::BONUS_FROM_HERO) );
|
||||
gs->curB->stacks.push_back(summonedStack);
|
||||
}
|
||||
}
|
||||
|
||||
void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)
|
||||
@ -1308,6 +1260,23 @@ DLL_EXPORT void BattleStacksRemoved::applyGs( CGameState *gs )
|
||||
}
|
||||
}
|
||||
|
||||
DLL_EXPORT void BattleStackAdded::applyGs(CGameState *gs)
|
||||
{
|
||||
if (!THex(pos).isValid())
|
||||
{
|
||||
tlog2 << "No place found for new stack!\n";
|
||||
return;
|
||||
}
|
||||
CStackInstance *csi = new CStackInstance(creID, amount);
|
||||
csi->setArmyObj(gs->curB->belligerents[attacker ? 0 : 1]);
|
||||
CStack * summonedStack = gs->curB->generateNewStack(*csi, gs->curB->stacks.size(), attacker, 255, pos); //TODO: netpacks?
|
||||
if (summoned)
|
||||
summonedStack->state.insert(SUMMONED);
|
||||
summonedStack->attachTo(csi);
|
||||
summonedStack->postInit();
|
||||
gs->curB->stacks.push_back(summonedStack); //the stack is not "SUMMONED", it is permanent
|
||||
}
|
||||
|
||||
DLL_EXPORT void YourTurn::applyGs( CGameState *gs )
|
||||
{
|
||||
gs->currentPlayer = player;
|
||||
|
@ -156,6 +156,7 @@ void registerTypes2(Serializer &s)
|
||||
s.template registerType<ObstaclesRemoved>();
|
||||
s.template registerType<CatapultAttack>();
|
||||
s.template registerType<BattleStacksRemoved>();
|
||||
s.template registerType<BattleStackAdded>();
|
||||
s.template registerType<ShowInInfobox>();
|
||||
s.template registerType<AdvmapSpellCast>();
|
||||
s.template registerType<OpenWindow>();
|
||||
|
@ -3280,6 +3280,35 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
}
|
||||
|
||||
|
||||
sendAndApply(&end_action);
|
||||
break;
|
||||
}
|
||||
case BattleAction::DAEMON_SUMMONING:
|
||||
//TODO: From Strategija:
|
||||
//Summon Demon is a level 2 spell.
|
||||
//Cloned Pit Lord stack can use the specialty as well.
|
||||
{
|
||||
StartAction start_action(ba);
|
||||
sendAndApply(&start_action);
|
||||
|
||||
CStack *summoner = gs->curB->getStack(ba.stackNumber),
|
||||
*destStack = gs->curB->getStackT(ba.destinationTile, false);
|
||||
|
||||
BattleStackAdded bsa;
|
||||
bsa.attacker = summoner->attackerOwned;
|
||||
|
||||
bsa.creID = summoner->getBonus(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream!
|
||||
ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID);
|
||||
bsa.amount = std::min ((ui32)(risedHp/destStack->MaxHealth()), destStack->baseAmount);
|
||||
|
||||
bsa.pos = gs->curB->getAvaliableHex(bsa.creID, bsa.attacker, destStack->position);
|
||||
bsa.summoned = false;
|
||||
|
||||
BattleStacksRemoved bsr; //remove body
|
||||
bsr.stackIDs.insert(destStack->ID);
|
||||
sendAndApply(&bsr);
|
||||
sendAndApply(&bsa);
|
||||
|
||||
sendAndApply(&end_action);
|
||||
break;
|
||||
}
|
||||
@ -3673,6 +3702,41 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, THex destinati
|
||||
sendAndApply(&shr);
|
||||
break;
|
||||
}
|
||||
case 66:
|
||||
case 67:
|
||||
case 68:
|
||||
case 69:
|
||||
{ //elemental summoning
|
||||
int creID;
|
||||
switch(spellID)
|
||||
{
|
||||
case 66:
|
||||
creID = 114; //fire elemental
|
||||
break;
|
||||
case 67:
|
||||
creID = 113; //earth elemental
|
||||
break;
|
||||
case 68:
|
||||
creID = 115; //water elemental
|
||||
break;
|
||||
case 69:
|
||||
creID = 112; //air elemental
|
||||
break;
|
||||
}
|
||||
|
||||
BattleStackAdded bsa;
|
||||
|
||||
bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it
|
||||
|
||||
bsa.amount = caster->getPrimSkillLevel(2) * VLC->spellh->spells[spellID]->powers[spellLvl] *
|
||||
(100 + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID)) / 100.0f; //new feature - percentage bonus
|
||||
|
||||
bsa.creID = creID;
|
||||
bsa.attacker = !(bool)casterSide;
|
||||
bsa.summoned = true;
|
||||
sendAndApply(&bsa);
|
||||
}
|
||||
break;
|
||||
case 64: //remove obstacle
|
||||
{
|
||||
ObstaclesRemoved obr;
|
||||
@ -3731,6 +3795,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, THex destinati
|
||||
sendAndApply(&sc);
|
||||
if(!si.stacks.empty()) //after spellcast info shows
|
||||
sendAndApply(&si);
|
||||
|
||||
//Magic Mirror effect
|
||||
if (spell->positiveness < 0 && mode != SpellCasting::MAGIC_MIRROR && spell->level && spell->range[0] == "0") //it is actual spell and can be reflected to single target, no recurrence
|
||||
{
|
||||
@ -3779,7 +3844,8 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
}
|
||||
|
||||
const CSpell *s = VLC->spellh->spells[ba.additionalInfo];
|
||||
if (s->mainEffectAnim > -1) //TODO: special effects, like Clone
|
||||
if (s->mainEffectAnim > -1 || (s->id >= 66 || s->id <= 69)) //allow summon elementals
|
||||
//TODO: special effects, like Clone
|
||||
{
|
||||
ui8 skill = h->getSpellSchoolLevel(s); //skill level
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user