1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Attempt to compile refactored spells in MSVS.

This commit is contained in:
DjWarmonger 2015-03-09 19:24:45 +01:00
commit f32849a73e
89 changed files with 4181 additions and 3424 deletions

View File

@ -4,7 +4,7 @@
#include "../../lib/BattleState.h"
#include "../../CCallback.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/VCMI_Lib.h"
using boost::optional;

View File

@ -4,7 +4,7 @@
#include "../../lib/CBuildingHandler.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/Connection.h"
#include "../../lib/CGameState.h"
#include "../../lib/mapping/CMap.h"

View File

@ -530,6 +530,13 @@ void VCAI::showMarketWindow(const IMarket *market, const CGHeroInstance *visitor
NET_EVENT_HANDLER;
}
void VCAI::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions)
{
//TODO: AI support for ViewXXX spell
LOG_TRACE(logAi);
NET_EVENT_HANDLER;
}
void VCAI::init(shared_ptr<CCallback> CB)
{
LOG_TRACE(logAi);

View File

@ -12,7 +12,7 @@
#include "../../lib/CBuildingHandler.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/Connection.h"
#include "../../lib/CGameState.h"
#include "../../lib/mapping/CMap.h"
@ -241,6 +241,7 @@ public:
virtual void buildChanged(const CGTownInstance *town, BuildingID buildingID, int what) override;
virtual void heroBonusChanged(const CGHeroInstance *hero, const Bonus &bonus, bool gain) override;
virtual void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor) override;
void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override;
virtual void battleEnd(const BattleResult *br) override;

View File

@ -15,7 +15,7 @@
#include "lib/Connection.h"
#include "lib/NetPacks.h"
#include "client/mapHandler.h"
#include "lib/CSpellHandler.h"
#include "lib/spells/CSpellHandler.h"
#include "lib/CArtHandler.h"
#include "lib/GameConstants.h"
#ifdef min

View File

@ -19,7 +19,7 @@
#include "CVideoHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CCreatureHandler.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "CMusicHandler.h"
#include "CVideoHandler.h"
#include "CDefHandler.h"

View File

@ -4,7 +4,7 @@
#include "CMusicHandler.h"
#include "CGameInfo.h"
#include "../lib/CCreatureHandler.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/JsonNode.h"
#include "../lib/GameConstants.h"
#include "../lib/filesystem/Filesystem.h"

View File

@ -23,7 +23,7 @@
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/Connection.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/mapObjects/CObjectClassesHandler.h" // For displaying correct UI when interacting with objects
#include "../lib/BattleState.h"
@ -2189,6 +2189,13 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
}
const CSpell * spell = CGI->spellh->objects[spellID];
if(spellID == SpellID::VIEW_EARTH)
{
//TODO: implement on server side
int level = caster->getSpellSchoolLevel(spell);
adventureInt->worldViewOptions.showAllTerrain = (level>2);
}
auto castSoundPath = spell->getCastSound();
if (!castSoundPath.empty())
CCS->soundh->playSound(castSoundPath);
@ -2703,3 +2710,13 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance* h, CGPath path)
duringMovement = false;
}
void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectPositions)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
//TODO: showWorldViewEx
std::copy(objectPositions.begin(), objectPositions.end(), std::back_inserter(adventureInt->worldViewOptions.iconPositions));
viewWorldMap();
}

View File

@ -196,6 +196,7 @@ public:
void showComp(const Component &comp, std::string message) override; //display component in the advmapint infobox
void saveGame(COSer & h, const int version) override; //saving
void loadGame(CISer & h, const int version) override; //loading
void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
//for battles
void actionFinished(const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero

View File

@ -26,7 +26,7 @@
#include "CPlayerInterface.h"
#include "../CCallback.h"
#include "CMessage.h"
#include "../lib/CSpellHandler.h" /*for campaign bonuses*/
#include "../lib/spells/CSpellHandler.h" /*for campaign bonuses*/
#include "../lib/CArtHandler.h" /*for campaign bonuses*/
#include "../lib/CBuildingHandler.h" /*for campaign bonuses*/
#include "CBitmapHandler.h"

View File

@ -18,7 +18,7 @@
#include "../lib/CHeroHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CBuildingHandler.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/Connection.h"
#ifndef VCMI_ANDROID
#include "../lib/Interprocess.h"

View File

@ -15,7 +15,7 @@
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CCreatureHandler.h"
#include "CBitmapHandler.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CGameState.h"
#include "../lib/JsonNode.h"
#include "../lib/vcmi_endian.h"

View File

@ -13,7 +13,7 @@
#include "../lib/VCMI_Lib.h"
#include "../lib/mapping/CMap.h"
#include "../lib/VCMIDirs.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CSoundBase.h"
#include "../lib/StartInfo.h"
#include "mapHandler.h"
@ -834,6 +834,11 @@ void AdvmapSpellCast::applyCl(CClient *cl)
INTERFACE_CALL_IF_PRESENT(caster->getOwner(),advmapSpellCast, caster, spellID);
}
void ShowWorldViewEx::applyCl(CClient * cl)
{
CALL_ONLY_THAT_INTERFACE(player, showWorldViewEx, objectPositions);
}
void OpenWindow::applyCl(CClient *cl)
{
switch(window)

View File

@ -20,7 +20,7 @@
#include "../../lib/BattleState.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
/*
* CBattleAnimations.cpp, part of VCMI engine

View File

@ -28,7 +28,7 @@
#include "../../lib/CHeroHandler.h"
#include "../../lib/CondSh.h"
#include "../../lib/CRandomGenerator.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CGameState.h"
#include "../../lib/mapping/CMap.h"
@ -1228,13 +1228,18 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
{
const SpellID spellID(sc->id);
const CSpell &spell = * spellID.toSpell();
const std::string & spellName = spell.name;
const std::string& castSoundPath = spell.getCastSound();
std::string casterName("Something");
if(sc->castedByHero)
casterName = curInt->cb->battleGetHeroInfo(sc->side).name;
if(!castSoundPath.empty())
CCS->soundh->playSound(castSoundPath);
std::string casterCreatureName = "";
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position by default
{
const auto casterStackID = sc->casterStack;
@ -1244,8 +1249,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
const CStack * casterStack = curInt->cb->battleGetStackByID(casterStackID);
if(casterStack != nullptr)
{
casterCreatureName = casterStack->type->namePl;
casterName = casterStack->type->namePl;
srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this);
srccoord.x += 250;
srccoord.y += 240;
@ -1255,7 +1259,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
//TODO: play custom cast animation
{
}
//playing projectile animation
@ -1269,9 +1273,9 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
bool Vflip = (angle < 0);
if(Vflip)
angle = -angle;
std::string animToDisplay = spell.animationInfo.selectProjectile(angle);
if(!animToDisplay.empty())
{
//displaying animation
@ -1285,7 +1289,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
int dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
delete animDef;
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
}
}
waitForAnims();
@ -1296,11 +1300,11 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
for (auto & elem : sc->affectedCres)
{
BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
if(vstd::contains(sc->resisted,elem))
displayEffect(78, position);
else
displaySpellEffect(spellID, position);
displaySpellEffect(spellID, position);
}
switch(sc->id)
@ -1319,15 +1323,26 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
bool customSpell = false;
if(sc->affectedCres.size() == 1)
{
const CStack * attackedStack = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false);
const std::string attackedName = attackedStack->getName();
const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
std::string text = CGI->generaltexth->allTexts[195];
if(sc->castedByHero)
{
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetHeroInfo(sc->side).name);
boost::algorithm::replace_first(text, "%s", CGI->spellh->objects[sc->id]->name); //spell name
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl ); //target
boost::algorithm::replace_first(text, "%s", casterName);
boost::algorithm::replace_first(text, "%s", spellName);
boost::algorithm::replace_first(text, "%s", attackedNamePl); //target
}
else
{
auto getPluralText = [attackedStack](const int baseTextID) -> std::string
{
return CGI->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID+1 : baseTextID)];
};
bool plural = false; //add singular / plural form of creature text if this is true
int textID = 0;
switch(sc->id)
@ -1345,8 +1360,8 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
case SpellID::BIND:
customSpell = true;
text = CGI->generaltexth->allTexts[560];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl );
break; //Roots and vines bind the %s to the ground!
boost::algorithm::replace_first(text, "%s", attackedNamePl);
break;//Roots and vines bind the %s to the ground!
case SpellID::DISEASE:
customSpell = true;
plural = true;
@ -1360,25 +1375,17 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
case SpellID::AGE:
{
customSpell = true;
if (curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->count > 1)
{
text = CGI->generaltexth->allTexts[552];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
}
else
{
text = CGI->generaltexth->allTexts[551];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing);
}
text = getPluralText(551);
boost::algorithm::replace_first(text, "%s", attackedName);
//The %s shrivel with age, and lose %d hit points."
TBonusListPtr bl = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getBonuses(Selector::type(Bonus::STACK_HEALTH));
TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(bl->totalValue()/2));
}
break;
case SpellID::THUNDERBOLT:
text = CGI->generaltexth->allTexts[367];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
boost::algorithm::replace_first(text, "%s", attackedNamePl);
console->addText(text);
text = CGI->generaltexth->allTexts[343].substr(1, CGI->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay)); //no more text afterwards
@ -1388,7 +1395,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
break;
case SpellID::DISPEL_HELPFUL_SPELLS:
text = CGI->generaltexth->allTexts[555];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
boost::algorithm::replace_first(text, "%s", attackedNamePl);
customSpell = true;
break;
case SpellID::DEATH_STARE:
@ -1399,67 +1406,45 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
{
text = CGI->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl );
boost::algorithm::replace_first(text, "%s", attackedNamePl);
}
else
{
text = CGI->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing);
boost::algorithm::replace_first(text, "%s", attackedNameSing);
}
boost::algorithm::replace_first(text, "%s", casterCreatureName); //casting stack
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
}
else
text = "";
break;
default:
text = CGI->generaltexth->allTexts[565]; //The %s casts %s
if(casterCreatureName != "")
boost::algorithm::replace_first(text, "%s", casterCreatureName); //casting stack
else
boost::algorithm::replace_first(text, "%s", "@Unknown caster@"); //should not happen
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
}
if (plural)
{
if (curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->count > 1)
{
text = CGI->generaltexth->allTexts[textID + 1];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->getName());
}
else
{
text = CGI->generaltexth->allTexts[textID];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->getName());
}
text = getPluralText(textID);
boost::algorithm::replace_first(text, "%s", attackedName);
}
}
if (!customSpell && !sc->dmgToDisplay)
boost::algorithm::replace_first(text, "%s", CGI->spellh->objects[sc->id]->name); //simple spell name
boost::algorithm::replace_first(text, "%s", spellName); //simple spell name
if (text.size())
console->addText(text);
}
else
{
std::string text = CGI->generaltexth->allTexts[196];
if(sc->castedByHero)
{
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetHeroInfo(sc->side).name);
}
if(casterCreatureName != "")
{
boost::algorithm::replace_first(text, "%s", casterCreatureName); //creature caster
}
else
{
//TODO artifacts that cast spell; scripts some day
boost::algorithm::replace_first(text, "%s", "Something");
}
boost::algorithm::replace_first(text, "%s", CGI->spellh->objects[sc->id]->name);
boost::algorithm::replace_first(text, "%s", casterName);
boost::algorithm::replace_first(text, "%s", spellName);
console->addText(text);
}
if(sc->dmgToDisplay && !customSpell)
{
std::string dmgInfo = CGI->generaltexth->allTexts[376];
boost::algorithm::replace_first(dmgInfo, "%s", CGI->spellh->objects[sc->id]->name); //simple spell name
boost::algorithm::replace_first(dmgInfo, "%s", spellName); //simple spell name
boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
console->addText(dmgInfo); //todo: casualties (?)
}
@ -1502,7 +1487,7 @@ void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
}
}
void CBattleInterface::castThisSpell(int spellID)
void CBattleInterface::castThisSpell(SpellID spellID)
{
auto ba = new BattleAction;
ba->actionType = Battle::HERO_SPELL;

View File

@ -317,7 +317,7 @@ public:
void displayBattleFinished(); //displays battle result
void spellCast(const BattleSpellCast * sc); //called when a hero casts a spell
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
void castThisSpell(int spellID); //called when player has chosen a spell from spellbook
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield
void displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
void displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation

View File

@ -528,6 +528,40 @@ CMapHandler::CMapNormalBlitter::CMapNormalBlitter(CMapHandler * parent)
defaultTileRect = Rect(0, 0, tileSize, tileSize);
}
SDL_Surface * CMapHandler::CMapWorldViewBlitter::objectToIcon(Obj id, si32 subId, PlayerColor owner) const
{
int ownerIndex = 0;
if(owner < PlayerColor::PLAYER_LIMIT)
{
ownerIndex = owner.getNum() * 19;
}
else if (owner == PlayerColor::NEUTRAL)
{
ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * 19;
}
switch(id)
{
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
case Obj::MONOLITH_ONE_WAY_EXIT:
case Obj::MONOLITH_TWO_WAY:
return info->iconsDef->ourImages[(int)EWorldViewIcon::TELEPORT].bitmap;
case Obj::SUBTERRANEAN_GATE:
return info->iconsDef->ourImages[(int)EWorldViewIcon::GATE].bitmap;
case Obj::ARTIFACT:
return info->iconsDef->ourImages[(int)EWorldViewIcon::ARTIFACT].bitmap;
case Obj::TOWN:
return info->iconsDef->ourImages[(int)EWorldViewIcon::TOWN + ownerIndex].bitmap;
case Obj::HERO:
return info->iconsDef->ourImages[(int)EWorldViewIcon::HERO + ownerIndex].bitmap;
case Obj::MINE:
return info->iconsDef->ourImages[(int)EWorldViewIcon::MINE_WOOD + subId + ownerIndex].bitmap;
case Obj::RESOURCE:
return info->iconsDef->ourImages[(int)EWorldViewIcon::RES_WOOD + subId + ownerIndex].bitmap;
}
return nullptr;
}
void CMapHandler::CMapWorldViewBlitter::calculateWorldViewCameraPos()
{
bool outsideLeft = topTile.x < 0;
@ -609,65 +643,61 @@ void CMapHandler::CMapWorldViewBlitter::drawElement(EMapCacheType cacheType, SDL
void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf, const TerrainTile2 & tile) const
{
auto & objects = tile.objects;
for(auto & object : objects)
auto drawIcon = [this,targetSurf](Obj id, si32 subId, PlayerColor owner)
{
const CGObjectInstance * obj = object.obj;
SDL_Surface * wvIcon = this->objectToIcon(id, subId, owner);
if (obj->pos.z != pos.z)
continue;
if (!(*info->visibilityMap)[pos.x][pos.y][pos.z])
continue; // TODO needs to skip this check if we have view-air-like spell cast
if (!obj->visitableAt(pos.x, pos.y))
continue;
auto &ownerRaw = obj->tempOwner;
int ownerIndex = 0;
if (ownerRaw < PlayerColor::PLAYER_LIMIT)
{
ownerIndex = ownerRaw.getNum() * 19;
}
else if (ownerRaw == PlayerColor::NEUTRAL)
{
ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * 19;
}
SDL_Surface * wvIcon = nullptr;
switch (obj->ID)
{
default:
continue;
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
case Obj::MONOLITH_ONE_WAY_EXIT:
case Obj::MONOLITH_TWO_WAY:
wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::TELEPORT].bitmap;
break;
case Obj::SUBTERRANEAN_GATE:
wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::GATE].bitmap;
break;
case Obj::ARTIFACT:
wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::ARTIFACT].bitmap;
break;
case Obj::TOWN:
wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::TOWN + ownerIndex].bitmap;
break;
case Obj::HERO:
wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::HERO + ownerIndex].bitmap;
break;
case Obj::MINE:
wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::MINE_WOOD + obj->subID + ownerIndex].bitmap;
break;
case Obj::RESOURCE:
wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::RES_WOOD + obj->subID + ownerIndex].bitmap;
break;
}
if (wvIcon)
if (nullptr != wvIcon)
{
// centering icon on the object
Rect destRect(realPos.x + tileSize / 2 - wvIcon->w / 2, realPos.y + tileSize / 2 - wvIcon->h / 2, wvIcon->w, wvIcon->h);
CSDL_Ext::blitSurface(wvIcon, nullptr, targetSurf, &destRect);
}
};
auto & objects = tile.objects;
for(auto & object : objects)
{
const CGObjectInstance * obj = object.obj;
const bool sameLevel = obj->pos.z == pos.z;
const bool isVisible = (*info->visibilityMap)[pos.x][pos.y][pos.z];
const bool isVisitable = obj->visitableAt(pos.x, pos.y);
if(sameLevel && isVisible && isVisitable)
drawIcon(obj->ID, obj->subID, obj->tempOwner);
}
}
void CMapHandler::CMapWorldViewBlitter::drawOverlayEx(SDL_Surface * targetSurf)
{
if(nullptr == info->additionalIcons)
return;
const int3 bottomRight = pos + tileCount;
for(const ObjectPosInfo & iconInfo : *(info->additionalIcons))
{
if(!iconInfo.pos.z == pos.z)
continue;
if((iconInfo.pos.x < topTile.x) || (iconInfo.pos.y < topTile.y))
continue;
if((iconInfo.pos.x > bottomRight.x) || (iconInfo.pos.y > bottomRight.y))
continue;
realPos.x = initPos.x + (iconInfo.pos.x - topTile.x) * tileSize;
realPos.y = initPos.x + (iconInfo.pos.y - topTile.y) * tileSize;
SDL_Surface * wvIcon = this->objectToIcon(iconInfo.id, iconInfo.subId, iconInfo.owner);
if (nullptr != wvIcon)
{
// centering icon on the object
Rect destRect(realPos.x + tileSize / 2 - wvIcon->w / 2, realPos.y + tileSize / 2 - wvIcon->h / 2, wvIcon->w, wvIcon->h);
CSDL_Ext::blitSurface(wvIcon, nullptr, targetSurf, &destRect);
}
}
}
@ -785,6 +815,11 @@ void CMapHandler::CMapBlitter::drawFrame(SDL_Surface * targetSurf) const
drawElement(EMapCacheType::FRAME, parent->ttiles[pos.x][pos.y][topTile.z].terbitmap, nullptr, targetSurf, &destRect);
}
void CMapHandler::CMapBlitter::drawOverlayEx(SDL_Surface * targetSurf)
{
//nothing to do here
}
void CMapHandler::CMapBlitter::drawHeroFlag(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const
{
drawElement(EMapCacheType::HERO_FLAGS, sourceSurf, sourceRect, targetSurf, destRect, false);
@ -904,9 +939,8 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
{
if (pos.y < 0 || pos.y >= parent->sizes.y)
continue;
if (!canDrawCurrentTile())
continue;
const bool isVisible = canDrawCurrentTile();
realTileRect.x = realPos.x;
realTileRect.y = realPos.y;
@ -914,13 +948,17 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
const TerrainTile & tinfo = parent->map->getTile(pos);
const TerrainTile * tinfoUpper = pos.y > 0 ? &parent->map->getTile(int3(pos.x, pos.y - 1, pos.z)) : nullptr;
if(isVisible || info->showAllTerrain)
{
drawTileTerrain(targetSurf, tinfo, tile);
if (tinfo.riverType)
drawRiver(targetSurf, tinfo);
drawRoad(targetSurf, tinfo, tinfoUpper);
}
drawTileTerrain(targetSurf, tinfo, tile);
if (tinfo.riverType)
drawRiver(targetSurf, tinfo);
drawRoad(targetSurf, tinfo, tinfoUpper);
drawObjects(targetSurf, tile);
if(isVisible)
drawObjects(targetSurf, tile);
}
}
@ -940,7 +978,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
{
const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
if (!(*info->visibilityMap)[pos.x][pos.y][topTile.z])
if (!(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain)
drawFow(targetSurf);
// overlay needs to be drawn over fow, because of artifacts-aura-like spells
@ -972,6 +1010,8 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
}
}
}
drawOverlayEx(targetSurf);
// drawDebugGrid()
if (settings["session"]["showGrid"].Bool())

View File

@ -2,6 +2,7 @@
#include "../lib/int3.h"
#include "../lib/spells/ViewSpellInt.h"
#include "gui/Geometries.h"
#include "SDL.h"
@ -100,7 +101,11 @@ struct MapDrawingInfo
bool puzzleMode;
int3 grailPos; // location of grail for puzzle mode [in tiles]
const std::vector<ObjectPosInfo> * additionalIcons;
bool showAllTerrain; //for expert viewEarth
MapDrawingInfo(int3 &topTile_, const std::vector< std::vector< std::vector<ui8> > > * visibilityMap_, SDL_Rect * drawBounds_, CDefHandler * iconsDef_ = nullptr)
: scaled(false),
topTile(topTile_),
@ -113,7 +118,9 @@ struct MapDrawingInfo
heroAnim(0u),
movement(int3()),
puzzleMode(false),
grailPos(int3())
grailPos(int3()),
additionalIcons(nullptr),
showAllTerrain(false)
{}
ui8 getHeroAnim() const { return otherheroAnim ? anim : heroAnim; }
@ -238,6 +245,8 @@ class CMapHandler
virtual void drawFow(SDL_Surface * targetSurf) const;
/// draws map border frame on current position
virtual void drawFrame(SDL_Surface * targetSurf) const;
/// draws additional icons (for VIEW_AIR, VIEW_EARTH spells atm)
virtual void drawOverlayEx(SDL_Surface * targetSurf);
// third drawing pass
@ -293,6 +302,8 @@ class CMapHandler
class CMapWorldViewBlitter : public CMapBlitter
{
private:
SDL_Surface * objectToIcon(Obj id, si32 subId, PlayerColor owner) const;
protected:
void drawElement(EMapCacheType cacheType, SDL_Surface * sourceSurf, SDL_Rect * sourceRect,
SDL_Surface * targetSurf, SDL_Rect * destRect, bool alphaBlit = false, ui8 rotationInfo = 0u) const override;
@ -301,6 +312,7 @@ class CMapHandler
void drawHeroFlag(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const override;
void drawObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const override;
void drawFrame(SDL_Surface * targetSurf) const override {}
void drawOverlayEx(SDL_Surface * targetSurf);
void init(const MapDrawingInfo * info) override;
SDL_Rect clip(SDL_Surface * targetSurf) const override;

View File

@ -16,7 +16,7 @@
#include "../../CCallback.h"
#include "../../lib/CArtHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"

View File

@ -12,7 +12,7 @@
#include "../../lib/CArtHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/NetPacksBase.h"

View File

@ -32,7 +32,7 @@
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/CSoundBase.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/JsonNode.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
@ -310,7 +310,7 @@ void CTerrainRect::showAll(SDL_Surface * to)
MapDrawingInfo info(adventureInt->position, &LOCPLINT->cb->getVisibilityMap(), &pos, adventureInt->worldViewIconsDef);
info.scaled = true;
info.scale = adventureInt->worldViewScale;
adventureInt->worldViewOptions.adjustDrawingInfo(info);
CGI->mh->drawTerrainRectNew(to, &info);
}
}
@ -1783,6 +1783,9 @@ void CAdvMapInt::changeMode(EAdvMapMode newMode, float newScale /* = 0.4f */)
townList.activate();
heroList.activate();
infoBar.activate();
worldViewOptions.clear();
break;
case EAdvMapMode::WORLD_VIEW:
panelMain->deactivate();
@ -1843,3 +1846,23 @@ void CAdventureOptions::showScenarioInfo()
GH.pushInt(new CScenarioInfo(LOCPLINT->cb->getMapHeader(), LOCPLINT->cb->getStartInfo()));
}
}
CAdvMapInt::WorldViewOptions::WorldViewOptions()
{
clear();
}
void CAdvMapInt::WorldViewOptions::clear()
{
showAllTerrain = false;
iconPositions.clear();
}
void CAdvMapInt::WorldViewOptions::adjustDrawingInfo(MapDrawingInfo& info)
{
info.showAllTerrain = showAllTerrain;
info.additionalIcons = &iconPositions;
}

View File

@ -6,6 +6,8 @@
#include "../widgets/TextControls.h"
#include "../widgets/Buttons.h"
#include "../../lib/spells/ViewSpellInt.h"
class CDefHandler;
class CCallback;
struct CGPath;
@ -18,6 +20,8 @@ class IShipyard;
enum class EMapAnimRedrawStatus;
class CFadeAnimation;
struct MapDrawingInfo;
/*****************************/
/*
@ -126,6 +130,21 @@ public:
EAdvMapMode mode;
float worldViewScale;
struct WorldViewOptions
{
bool showAllTerrain; //for expert viewEarth
std::vector<ObjectPosInfo> iconPositions;
WorldViewOptions();
void clear();
void adjustDrawingInfo(MapDrawingInfo & info);
};
WorldViewOptions worldViewOptions;
SDL_Surface * bg;
SDL_Surface * bgWorldView;

View File

@ -26,7 +26,7 @@
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CModHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/GameConstants.h"
#include "../../lib/mapObjects/CGHeroInstance.h"

View File

@ -16,7 +16,7 @@
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CModHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CGameState.h"
using namespace CSDL_Ext;

View File

@ -27,7 +27,7 @@
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/GameConstants.h"
#include "../../lib/CGameState.h"
#include "../../lib/mapObjects/CGTownInstance.h"
@ -107,15 +107,11 @@ CSpellWindow::CSpellWindow(const SDL_Rect &, const CGHeroInstance * _myHero, CPl
Uint8 *sitesPerOurTab = s.combatSpell ? sitesPerTabBattle : sitesPerTabAdv;
++sitesPerOurTab[4];
if(s.air)
++sitesPerOurTab[0];
if(s.fire)
++sitesPerOurTab[1];
if(s.water)
++sitesPerOurTab[2];
if(s.earth)
++sitesPerOurTab[3];
s.forEachSchool([&sitesPerOurTab](const SpellSchoolInfo & school, bool & stop)
{
++sitesPerOurTab[(ui8)school.id];
});
}
if(sitesPerTabAdv[4] % 12 == 0)
sitesPerTabAdv[4]/=12;
@ -382,23 +378,17 @@ public:
if(A.level<B.level)
return true;
if(A.level>B.level)
return false;
if(A.air && !B.air)
return true;
if(!A.air && B.air)
return false;
if(A.fire && !B.fire)
return true;
if(!A.fire && B.fire)
return false;
if(A.water && !B.water)
return true;
if(!A.water && B.water)
return false;
if(A.earth && !B.earth)
return true;
if(!A.earth && B.earth)
return false;
return false;
for(ui8 schoolId = 0; schoolId < 4; schoolId++)
{
if(A.school.at((ESpellSchool)schoolId) && !B.school.at((ESpellSchool)schoolId))
return true;
if(!A.school.at((ESpellSchool)schoolId) && B.school.at((ESpellSchool)schoolId))
return false;
}
return A.name < B.name;
}
} spellsorter;
@ -406,17 +396,15 @@ public:
void CSpellWindow::computeSpellsPerArea()
{
std::vector<SpellID> spellsCurSite;
for(auto it = mySpells.cbegin(); it != mySpells.cend(); ++it)
for(const SpellID & spellID : mySpells)
{
if(CGI->spellh->objects[*it]->combatSpell ^ !battleSpellsOnly
&& ((CGI->spellh->objects[*it]->air && selectedTab == 0) ||
(CGI->spellh->objects[*it]->fire && selectedTab == 1) ||
(CGI->spellh->objects[*it]->water && selectedTab == 2) ||
(CGI->spellh->objects[*it]->earth && selectedTab == 3) ||
selectedTab == 4 )
CSpell * s = spellID.toSpell();
if(s->combatSpell ^ !battleSpellsOnly
&& ((selectedTab == 4) || (s->school[(ESpellSchool)selectedTab]))
)
{
spellsCurSite.push_back(*it);
spellsCurSite.push_back(spellID);
}
}
std::sort(spellsCurSite.begin(), spellsCurSite.end(), spellsorter);
@ -605,12 +593,6 @@ Uint8 CSpellWindow::pagesWithinCurrentTab()
return battleSpellsOnly ? sitesPerTabBattle[selectedTab] : sitesPerTabAdv[selectedTab];
}
void CSpellWindow::teleportTo( int town, const CGHeroInstance * hero )
{
const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(town));
LOCPLINT->cb->castSpell(hero, SpellID::TOWN_PORTAL, dest->visitablePos());
}
CSpellWindow::SpellArea::SpellArea(SDL_Rect pos, CSpellWindow * owner)
{
this->pos = pos;
@ -623,9 +605,9 @@ CSpellWindow::SpellArea::SpellArea(SDL_Rect pos, CSpellWindow * owner)
void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
{
if(!down && mySpell!=-1)
if(!down && mySpell != SpellID::NONE)
{
const CSpell *sp = CGI->spellh->objects[mySpell];
const CSpell * sp = mySpell.toSpell();
int spellCost = owner->myInt->cb->getSpellCost(sp, owner->myHero);
if(spellCost > owner->myHero->mana) //insufficient mana
@ -637,8 +619,8 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
}
//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
if((sp->combatSpell && !owner->myInt->battleInt)
|| (!sp->combatSpell && owner->myInt->battleInt))
if((sp->isCombatSpell() && !owner->myInt->battleInt)
|| (sp->isAdventureSpell() && owner->myInt->battleInt))
{
std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell, 0));
LOCPLINT->showInfoDialog(sp->getLevelInfo(schoolLevel).description, hlp);
@ -653,9 +635,8 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
{
case ESpellCastProblem::OK:
{
int spell = mySpell;
owner->fexitb();
owner->myInt->battleInt->castThisSpell(spell);
owner->myInt->battleInt->castThisSpell(mySpell);
}
break;
case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED:
@ -714,97 +695,102 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
break;
}
}
else if(!sp->combatSpell && !owner->myInt->battleInt) //adventure spell
else if(sp->isAdventureSpell() && !owner->myInt->battleInt) //adventure spell and not in battle
{
SpellID spell = mySpell;
const CGHeroInstance *h = owner->myHero;
owner->fexitb();
switch(spell)
if(mySpell == SpellID::TOWN_PORTAL)
{
case SpellID::SUMMON_BOAT:
//special case
//todo: move to mechanics
std::vector <int> availableTowns;
std::vector <const CGTownInstance*> Towns = LOCPLINT->cb->getTownsInfo(true);
if (Towns.empty())
{
int3 pos = h->bestLocation();
if(pos.x < 0)
{
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[334]); //There is no place to put the boat.
return;
}
}
break;
case SpellID::SCUTTLE_BOAT:
case SpellID::DIMENSION_DOOR:
adventureInt->enterCastingMode(sp);
return;
case SpellID::VISIONS:
case SpellID::VIEW_EARTH:
case SpellID::DISGUISE:
case SpellID::VIEW_AIR:
case SpellID::FLY:
case SpellID::WATER_WALK:
break;
case SpellID::TOWN_PORTAL:
{
std::vector <int> availableTowns;
std::vector <const CGTownInstance*> Towns = LOCPLINT->cb->getTownsInfo(true);
if (Towns.empty())
{
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
return;
}
if (h->getSpellSchoolLevel(CGI->spellh->objects[spell]) < 2) //not advanced or expert - teleport to nearest available city
{
auto nearest = Towns.cbegin(); //nearest town's iterator
si32 dist = LOCPLINT->cb->getTown((*nearest)->id)->pos.dist2dSQ(h->pos);
for (auto i = nearest + 1; i != Towns.cend(); ++i)
{
const CGTownInstance * dest = LOCPLINT->cb->getTown((*i)->id);
si32 curDist = dest->pos.dist2dSQ(h->pos);
if (curDist < dist)
{
nearest = i;
dist = curDist;
}
}
if ((*nearest)->visitingHero)
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[123]);
else
{
const CGTownInstance * town = LOCPLINT->cb->getTown((*nearest)->id);
LOCPLINT->cb->castSpell(h, spell, town->visitablePos());// - town->getVisitableOffset());
}
}
else
{ //let the player choose
for(auto & Town : Towns)
{
const CGTownInstance *t = Town;
if (t->visitingHero == nullptr) //empty town and this is
{
availableTowns.push_back(t->id.getNum());//add to the list
}
}
if (availableTowns.empty())
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
else
GH.pushInt (new CObjectListWindow(availableTowns,
new CAnimImage("SPELLSCR",spell),
CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41],
std::bind (&CSpellWindow::teleportTo, owner, _1, h)));
}
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
return;
}
break;
default:
assert(0);
}
//can return earlier in some cases
LOCPLINT->cb->castSpell(h, spell);
if (h->getSpellSchoolLevel(sp) < 2) //not advanced or expert - teleport to nearest available city
{
auto nearest = Towns.cbegin(); //nearest town's iterator
si32 dist = LOCPLINT->cb->getTown((*nearest)->id)->pos.dist2dSQ(h->pos);
for (auto i = nearest + 1; i != Towns.cend(); ++i)
{
const CGTownInstance * dest = LOCPLINT->cb->getTown((*i)->id);
si32 curDist = dest->pos.dist2dSQ(h->pos);
if (curDist < dist)
{
nearest = i;
dist = curDist;
}
}
if ((*nearest)->visitingHero)
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[123]);
else
{
const CGTownInstance * town = LOCPLINT->cb->getTown((*nearest)->id);
LOCPLINT->cb->castSpell(h, mySpell, town->visitablePos());// - town->getVisitableOffset());
}
}
else
{ //let the player choose
for(auto & Town : Towns)
{
const CGTownInstance *t = Town;
if (t->visitingHero == nullptr) //empty town and this is
{
availableTowns.push_back(t->id.getNum());//add to the list
}
}
auto castTownPortal = [h](int townId)
{
const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(townId));
LOCPLINT->cb->castSpell(h, SpellID::TOWN_PORTAL, dest->visitablePos());
};
if (availableTowns.empty())
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
else
GH.pushInt (new CObjectListWindow(availableTowns,
new CAnimImage("SPELLSCR",mySpell),
CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41],
castTownPortal));
}
return;
}
if(mySpell == SpellID::SUMMON_BOAT)
{
//special case
//todo: move to mechanics
int3 pos = h->bestLocation();
if(pos.x < 0)
{
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[334]); //There is no place to put the boat.
return;
}
}
if(sp->getTargetType() == CSpell::LOCATION)
{
adventureInt->enterCastingMode(sp);
}
else if(sp->getTargetType() == CSpell::NO_TARGET)
{
LOCPLINT->cb->castSpell(h, mySpell);
}
else
{
logGlobal->error("Invalid spell target type");
}
}
}
}
@ -816,7 +802,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
std::string dmgInfo;
const CGHeroInstance * hero = owner->myHero;
int causedDmg = owner->myInt->cb->estimateSpellDamage( CGI->spellh->objects[mySpell], (hero ? hero : nullptr));
if(causedDmg == 0 || mySpell == 57) //Titan's Lightning Bolt already has damage info included
if(causedDmg == 0 || mySpell == SpellID::TITANS_LIGHTNING_BOLT) //Titan's Lightning Bolt already has damage info included
dmgInfo = "";
else
{

View File

@ -112,6 +112,4 @@ public:
void deactivate();
void showAll(SDL_Surface * to);
void show(SDL_Surface * to);
void teleportTo(int town, const CGHeroInstance * hero);
};

View File

@ -42,7 +42,7 @@
#include "../lib/CHeroHandler.h"
#include "../lib/CModHandler.h"
#include "../lib/CondSh.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CStopWatch.h"
#include "../lib/CTownHandler.h"
#include "../lib/GameConstants.h"

View File

@ -411,7 +411,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town):
CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "TOWNQVBK", toScreen(position))
{
InfoAboutTown iah;
LOCPLINT->cb->getTownInfo(town, iah);
LOCPLINT->cb->getTownInfo(town, iah, adventureInt->selection); //todo: should this be nearest hero?
OBJ_CONSTRUCTION_CAPTURING_ALL;
new CTownTooltip(Point(9, 10), iah);
@ -421,7 +421,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero):
CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "HEROQVBK", toScreen(position))
{
InfoAboutHero iah;
LOCPLINT->cb->getHeroInfo(hero, iah);
LOCPLINT->cb->getHeroInfo(hero, iah, adventureInt->selection);//todo: should this be nearest hero?
OBJ_CONSTRUCTION_CAPTURING_ALL;
new CHeroTooltip(Point(9, 10), iah);
@ -439,11 +439,15 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr):
CIntObject * CRClickPopup::createInfoWin(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero
{
if(!specific)
if(nullptr == specific)
specific = adventureInt->selection;
assert(specific);
if(nullptr == specific)
{
logGlobal->error("createInfoWin: no object to describe");
return nullptr;
}
switch(specific->ID)
{
case Obj::HERO:

View File

@ -128,6 +128,11 @@
}
},
"DISGUISED":
{
"hidden": true
},
"EARTH_IMMUNITY":
{
"graphics":
@ -328,6 +333,11 @@
"icon": "zvs/Lib1.res/E_MIND"
}
},
"NONE":
{
"hidden": true
},
"NO_DISTANCE_PENALTY":
{
@ -515,6 +525,11 @@
"icon": "zvs/Lib1.res/E_RETAIL1"
}
},
"VISIONS":
{
"hidden": true
},
"WATER_IMMUNITY":
{
"graphics":

View File

@ -40,7 +40,52 @@
},
"levels" : {
"base":{
"range" : "X"
"range" : "0",
"effects" : {
"visionsMonsters" : {
"type" : "VISIONS",
"subtype" : 0,
"duration" : "ONE_DAY",
"val" : 1,
"valueType" : "INDEPENDENT_MAX"
}
}
},
"advanced":{
"effects" : {
"visionsMonsters" : {
"val" : 2
},
"visionsHeroes" :{
"type" : "VISIONS",
"subtype" : 1,
"duration" : "ONE_DAY",
"val" : 2,
"valueType" : "INDEPENDENT_MAX"
}
}
},
"expert":{
"effects" : {
"visionsMonsters" : {
"val" : 3
},
"visionsHeroes" :{
"type" : "VISIONS",
"subtype" : 1,
"duration" : "ONE_DAY",
"val" : 3,
"valueType" : "INDEPENDENT_MAX"
},
"visionsTowns" :{
"type" : "VISIONS",
"subtype" : 2,
"duration" : "ONE_DAY",
"val" : 3,
"valueType" : "INDEPENDENT_MAX"
}
}
}
},
"flags" : {
@ -72,7 +117,30 @@
},
"levels" : {
"base":{
"range" : "X"
"range" : "0",
"effects" : {
"stealth" : {
"type" : "DISGUISED",
"subtype" : 0, //required
"duration" : "ONE_DAY",
"val" : 1,
"valueType" : "INDEPENDENT_MAX"
}
}
},
"advanced":{
"effects" : {
"stealth" : {
"val" : 2
}
}
},
"expert":{
"effects" : {
"stealth" : {
"val" : 3
}
}
}
},
"flags" : {
@ -104,7 +172,29 @@
},
"levels" : {
"base":{
"range" : "X"
"range" : "0",
"effects" : {
"fly" : {
"type" : "FLYING_MOVEMENT",
"subtype" : 2,
"duration" : "ONE_DAY",
"val" : 0 //in fact unused
}
}
},
"advanced":{
"effects" : {
"fly" : {
"subtype" : 1
}
}
},
"expert":{
"effects" : {
"fly" : {
"subtype" : 1
}
}
}
},
"flags" : {
@ -120,7 +210,29 @@
},
"levels" : {
"base":{
"range" : "X"
"range" : "0",
"effects" : {
"waterWalk" : {
"type" : "WATER_WALKING",
"subtype" : 2,
"duration" : "ONE_DAY",
"val" : 0 //in fact unused
}
}
},
"advanced":{
"effects" : {
"waterWalk" : {
"subtype" : 1
}
}
},
"expert":{
"effects" : {
"waterWalk" : {
"subtype" : 1
}
}
}
},
"flags" : {

View File

@ -16,7 +16,7 @@
#include "mapObjects/CObjectHandler.h"
#include "CHeroHandler.h"
#include "CCreatureHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
#include "CTownHandler.h"
#include "NetPacks.h"
#include "JsonNode.h"

View File

@ -15,7 +15,7 @@
#include "CGeneralTextHandler.h"
#include "VCMI_Lib.h"
#include "CModHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
#include "mapObjects/MapObjects.h"
#include "NetPacksBase.h"
#include "GameConstants.h"

View File

@ -3,7 +3,7 @@
#include "BattleState.h"
#include "CGameState.h"
#include "NetPacks.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
#include "VCMI_Lib.h"
#include "CTownHandler.h"

View File

@ -15,7 +15,7 @@
#include "GameConstants.h"
#include "CCreatureHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
///MacroString
@ -127,7 +127,7 @@ CBonusTypeHandler::~CBonusTypeHandler()
}
std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBearer *bearer, bool description) const
{
{
auto getValue = [=](const std::string &name) -> std::string
{
if (name == "val")
@ -156,6 +156,8 @@ std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBea
};
const CBonusType& bt = bonusTypes[bonus->type];
if(bt.hidden)
return "";
const MacroString& macro = description ? bt.description : bt.name;
return macro.build(getValue);

View File

@ -8,7 +8,7 @@
#include "IGameCallback.h"
#include "CGameState.h"
#include "CGeneralTextHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
#include "CHeroHandler.h"
#include "IBonusTypeHandler.h"

View File

@ -17,7 +17,7 @@
#include "BattleState.h" // for BattleInfo
#include "NetPacks.h" // for InfoWindow
#include "CModHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
//TODO make clean
#define ERROR_VERBOSE_OR_NOT_RET_VAL_IF(cond, verbose, txt, retVal) do {if(cond){if(verbose)logGlobal->errorStream() << BOOST_CURRENT_FUNCTION << ": " << txt; return retVal;}} while(0)
@ -201,14 +201,22 @@ int CGameInfoCallback::howManyTowns(PlayerColor Player) const
return gs->players[Player].towns.size();
}
bool CGameInfoCallback::getTownInfo( const CGObjectInstance *town, InfoAboutTown &dest ) const
bool CGameInfoCallback::getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject/* = nullptr*/) const
{
ERROR_RET_VAL_IF(!isVisible(town, player), "Town is not visible!", false); //it's not a town or it's not visible for layer
bool detailed = hasAccess(town->tempOwner);
//TODO vision support
if(town->ID == Obj::TOWN)
{
if(!detailed && nullptr != selectedObject)
{
const CGHeroInstance * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);
if(nullptr != selectedHero)
detailed = selectedHero->hasVisions(town, 1);
}
dest.initFromTown(static_cast<const CGTownInstance *>(town), detailed);
}
else if(town->ID == Obj::GARRISON || town->ID == Obj::GARRISON2)
dest.initFromArmy(static_cast<const CArmedInstance *>(town), detailed);
else
@ -233,15 +241,109 @@ std::vector<const CGObjectInstance*> CGameInfoCallback::getGuardingCreatures (in
return ret;
}
bool CGameInfoCallback::getHeroInfo( const CGObjectInstance *hero, InfoAboutHero &dest ) const
bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject/* = nullptr*/) const
{
const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(hero);
ERROR_RET_VAL_IF(!h, "That's not a hero!", false);
ERROR_RET_VAL_IF(!isVisible(h->getPosition(false)), "That hero is not visible!", false);
//TODO vision support
dest.initFromHero(h, hasAccess(h->tempOwner));
bool accessFlag = hasAccess(h->tempOwner);
if(!accessFlag && nullptr != selectedObject)
{
const CGHeroInstance * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);
if(nullptr != selectedHero)
accessFlag = selectedHero->hasVisions(hero, 1);
}
dest.initFromHero(h, accessFlag);
//DISGUISED bonus implementation
if(getPlayerRelations(getLocalPlayer(), hero->tempOwner) == PlayerRelations::ENEMIES)
{
//todo: bonus cashing
int disguiseLevel = h->valOfBonuses(Selector::typeSubtype(Bonus::DISGUISED, 0));
auto doBasicDisguise = [disguiseLevel](InfoAboutHero & info)
{
int maxAIValue = 0;
const CCreature * mostStrong = nullptr;
for(auto & elem : info.army)
{
if(elem.second.type->AIValue > maxAIValue)
{
maxAIValue = elem.second.type->AIValue;
mostStrong = elem.second.type;
}
}
if(nullptr == mostStrong)//just in case
logGlobal->errorStream() << "CGameInfoCallback::getHeroInfo: Unable to select most strong stack" << disguiseLevel;
else
for(auto & elem : info.army)
{
elem.second.type = mostStrong;
}
};
auto doAdvancedDisguise = [accessFlag, &doBasicDisguise](InfoAboutHero & info)
{
doBasicDisguise(info);
for(auto & elem : info.army)
elem.second.count = 0;
};
auto doExpertDisguise = [this,h](InfoAboutHero & info)
{
for(auto & elem : info.army)
elem.second.count = 0;
const auto factionIndex = getStartInfo(false)->playerInfos.at(h->tempOwner).castle;
int maxAIValue = 0;
const CCreature * mostStrong = nullptr;
for(auto creature : VLC->creh->creatures)
{
if(creature->faction == factionIndex && creature->AIValue > maxAIValue)
{
maxAIValue = creature->AIValue;
mostStrong = creature;
}
}
if(nullptr != mostStrong) //possible, faction may have no creatures at all
for(auto & elem : info.army)
elem.second.type = mostStrong;
};
switch (disguiseLevel)
{
case 0:
//no bonus at all - do nothing
break;
case 1:
doBasicDisguise(dest);
break;
case 2:
doAdvancedDisguise(dest);
break;
case 3:
doExpertDisguise(dest);
break;
default:
//invalid value
logGlobal->errorStream() << "CGameInfoCallback::getHeroInfo: Invalid DISGUISED bonus value " << disguiseLevel;
break;
}
}
return true;
}

View File

@ -69,7 +69,7 @@ public:
const CGHeroInstance* getHero(ObjectInstanceID objid) const;
const CGHeroInstance* getHeroWithSubid(int subid) const;
int getHeroCount(PlayerColor player, bool includeGarrisoned) const;
bool getHeroInfo(const CGObjectInstance *hero, InfoAboutHero &dest) const;
bool getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject = nullptr) const;
int getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //when called during battle, takes into account creatures' spell cost reduction
int estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
@ -99,7 +99,7 @@ public:
std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited
std::string getTavernGossip(const CGObjectInstance * townOrTavern) const;
EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
virtual bool getTownInfo(const CGObjectInstance *town, InfoAboutTown &dest) const;
virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
const CTown *getNativeTown(PlayerColor color) const;
//from gs

View File

@ -5,6 +5,8 @@
#include "IGameEventsReceiver.h"
#include "CGameStateFwd.h"
#include "spells/ViewSpellInt.h"
/*
* CGameInterface.h, part of VCMI engine
*
@ -93,6 +95,8 @@ public:
// all stacks operations between these objects become allowed, interface has to call onEnd when done
virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) = 0;
virtual void finish(){}; //if for some reason we want to end
virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions){};
};
class DLL_LINKAGE CDynLibHandler

View File

@ -7,7 +7,7 @@
#include "CBuildingHandler.h"
#include "CGeneralTextHandler.h"
#include "CTownHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
#include "CHeroHandler.h"
#include "mapObjects/CObjectHandler.h"
#include "CCreatureHandler.h"

View File

@ -67,7 +67,7 @@ namespace boost
class shared_mutex;
}
//numbers of creatures are exact numbers if detailed else they are quantity ids (0 - a few, 1 - several and so on; additionally -1 - unknown)
//numbers of creatures are exact numbers if detailed else they are quantity ids (1 - a few, 2 - several and so on; additionally 0 - unknown)
struct ArmyDescriptor : public std::map<SlotID, CStackBasicDescriptor>
{
bool isDetailed;

View File

@ -54,6 +54,14 @@ set(lib_SRCS
rmg/CZoneGraphGenerator.cpp
rmg/CZonePlacer.cpp
spells/CSpellHandler.cpp
spells/ISpellMechanics.cpp
spells/AdventureSpellMechanics.cpp
spells/BattleSpellMechanics.cpp
spells/CreatureSpellMechanics.cpp
spells/CDefaultSpellMechanics.cpp
spells/ViewSpellInt.cpp
BattleAction.cpp
BattleHex.cpp
BattleState.cpp
@ -71,7 +79,7 @@ set(lib_SRCS
CModHandler.cpp
CObstacleInstance.cpp
CRandomGenerator.cpp
CSpellHandler.cpp
CThreadHelper.cpp
CTownHandler.cpp
GameConstants.cpp
@ -83,7 +91,6 @@ set(lib_SRCS
VCMI_Lib.cpp
VCMIDirs.cpp
IHandlerBase.cpp
SpellMechanics.cpp
IGameCallback.cpp
CGameInfoCallback.cpp
@ -96,7 +103,7 @@ set(lib_SRCS
registerTypes/TypesClientPacks2.cpp
registerTypes/TypesMapObjects1.cpp
registerTypes/TypesMapObjects2.cpp
registerTypes/TypesMapObjects3.cpp
registerTypes/TypesMapObjects3.cpp
registerTypes/TypesPregamePacks.cpp
registerTypes/TypesServerPacks.cpp
)

View File

@ -14,7 +14,7 @@
#include "StringConstants.h"
#include "CStopWatch.h"
#include "IHandlerBase.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
/*
* CModHandler.cpp, part of VCMI engine

View File

@ -2,7 +2,7 @@
#include "CObstacleInstance.h"
#include "CHeroHandler.h"
#include "VCMI_Lib.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
/*
* CObstacleInstance.cpp, part of VCMI engine

View File

@ -9,7 +9,7 @@
#include "CModHandler.h"
#include "CHeroHandler.h"
#include "CArtHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
#include "filesystem/Filesystem.h"
#include "mapObjects/CObjectClassesHandler.h"
#include "mapObjects/CObjectHandler.h"

View File

@ -16,7 +16,7 @@
#include "mapObjects/CObjectClassesHandler.h"
#include "CArtHandler.h"
#include "CCreatureHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253);

View File

@ -895,6 +895,8 @@ public:
ESpellID num;
};
ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
enum class ESpellSchool: ui8
{
AIR = 0,
@ -903,8 +905,6 @@ enum class ESpellSchool: ui8
EARTH = 3
};
ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
// Typedef declarations
typedef ui8 TFaction;
typedef si64 TExpType;

View File

@ -12,7 +12,7 @@
#include "HeroBonus.h"
#include "VCMI_Lib.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
#include "CCreatureHandler.h"
#include "CCreatureSet.h"
#include "CHeroHandler.h"

View File

@ -215,7 +215,10 @@ public:
BONUS_NAME(REBIRTH) /* val - percent of life restored, subtype = 0 - regular, 1 - at least one unit (sacred Phoenix) */\
BONUS_NAME(ADDITIONAL_UNITS) /*val of units with id = subtype will be added to hero's army at the beginning of battle */\
BONUS_NAME(SPOILS_OF_WAR) /*val * 10^-6 * gained exp resources of subtype will be given to hero after battle*/\
BONUS_NAME(BLOCK)
BONUS_NAME(BLOCK)\
BONUS_NAME(DISGUISED) /* subtype - spell level */\
BONUS_NAME(VISIONS) /* subtype - spell level */
#define BONUS_SOURCE_LIST \
BONUS_SOURCE(ARTIFACT)\

View File

@ -12,7 +12,7 @@
#include "IGameCallback.h"
#include "CHeroHandler.h" // for CHeroHandler
#include "CSpellHandler.h" // for CSpell
#include "spells/CSpellHandler.h"// for CSpell
#include "NetPacks.h"
#include "CBonusTypeHandler.h"
#include "CModHandler.h"

View File

@ -16,6 +16,8 @@
#include "mapping/CMap.h"
#include "CObstacleInstance.h"
#include "spells/ViewSpellInt.h"
/*
* NetPacks.h, part of VCMI engine
*
@ -1685,6 +1687,22 @@ struct AdvmapSpellCast : public CPackForClient //108
}
};
struct ShowWorldViewEx : public CPackForClient //4000
{
PlayerColor player;
std::vector<ObjectPosInfo> objectPositions;
ShowWorldViewEx(){type = 4000;}
void applyCl(CClient *cl);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & player & objectPositions;
}
};
/***********************************************************************************************************/
struct CommitPackage : public CPackForServer

View File

@ -9,7 +9,7 @@
#include "CModHandler.h"
#include "VCMI_Lib.h"
#include "mapping/CMap.h"
#include "CSpellHandler.h"
#include "spells/SpellMechanics.h"
#include "CCreatureHandler.h"
#include "CGameState.h"
#include "BattleState.h"
@ -1339,7 +1339,7 @@ DLL_LINKAGE void BattleSpellCast::applyGs( CGameState *gs )
const CSpell * spell = SpellID(id).toSpell();
spell->afterCast(gs->curB, this);
spell->applyBattle(gs->curB, this);
}
void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@
#include "mapObjects/CObjectHandler.h"
#include "CTownHandler.h"
#include "CBuildingHandler.h"
#include "CSpellHandler.h"
#include "spells/CSpellHandler.h"
#include "CGeneralTextHandler.h"
#include "CModHandler.h"
#include "IGameEventsReceiver.h"

View File

@ -150,8 +150,6 @@
<Unit filename="CRandomGenerator.h" />
<Unit filename="CScriptingModule.h" />
<Unit filename="CSoundBase.h" />
<Unit filename="CSpellHandler.cpp" />
<Unit filename="CSpellHandler.h" />
<Unit filename="CStopWatch.h" />
<Unit filename="CThreadHelper.cpp" />
<Unit filename="CThreadHelper.h" />
@ -185,8 +183,6 @@
<Unit filename="ResourceSet.cpp" />
<Unit filename="ResourceSet.h" />
<Unit filename="ScopeGuard.h" />
<Unit filename="SpellMechanics.cpp" />
<Unit filename="SpellMechanics.h" />
<Unit filename="StartInfo.h" />
<Unit filename="StdInc.h">
<Option weight="0" />
@ -294,6 +290,20 @@
<Unit filename="rmg/CZoneGraphGenerator.h" />
<Unit filename="rmg/CZonePlacer.cpp" />
<Unit filename="rmg/CZonePlacer.h" />
<Unit filename="spells/AdventureSpellMechanics.cpp" />
<Unit filename="spells/AdventureSpellMechanics.h" />
<Unit filename="spells/BattleSpellMechanics.cpp" />
<Unit filename="spells/BattleSpellMechanics.h" />
<Unit filename="spells/CDefaultSpellMechanics.cpp" />
<Unit filename="spells/CDefaultSpellMechanics.h" />
<Unit filename="spells/CSpellHandler.cpp" />
<Unit filename="spells/CSpellHandler.h" />
<Unit filename="spells/CreatureSpellMechanics.cpp" />
<Unit filename="spells/CreatureSpellMechanics.h" />
<Unit filename="spells/ISpellMechanics.cpp" />
<Unit filename="spells/ISpellMechanics.h" />
<Unit filename="spells/ViewSpellInt.cpp" />
<Unit filename="spells/ViewSpellInt.h" />
<Unit filename="vcmi_endian.h" />
<Extensions>
<code_completion />

View File

@ -184,11 +184,16 @@
<ClCompile Include="CModHandler.cpp" />
<ClCompile Include="CObstacleInstance.cpp" />
<ClCompile Include="Connection.cpp" />
<ClCompile Include="CSpellHandler.cpp" />
<ClCompile Include="SpellMechanics.cpp" />
<ClCompile Include="CThreadHelper.cpp" />
<ClCompile Include="CTownHandler.cpp" />
<ClCompile Include="CRandomGenerator.cpp" />
<ClCompile Include="spells\CSpellHandler.cpp" />
<ClCompile Include="spells\ISpellMechanics.cpp" />
<ClCompile Include="spells\AdventureSpellMechanics.cpp" />
<ClCompile Include="spells\BattleSpellMechanics.cpp" />
<ClCompile Include="spells\CreatureSpellMechanics.cpp" />
<ClCompile Include="spells\CDefaultSpellMechanics.cpp" />
<ClCompile Include="spells\ViewSpellInt.cpp" />
<ClCompile Include="filesystem\AdapterLoaders.cpp" />
<ClCompile Include="filesystem\CArchiveLoader.cpp" />
<ClCompile Include="filesystem\CBinaryReader.cpp" />
@ -287,7 +292,6 @@
<ClInclude Include="ConstTransitivePtr.h" />
<ClInclude Include="CRandomGenerator.h" />
<ClInclude Include="CScriptingModule.h" />
<ClInclude Include="CSpellHandler.h" />
<ClInclude Include="CStopWatch.h" />
<ClInclude Include="CThreadHelper.h" />
<ClInclude Include="CTownHandler.h" />
@ -353,7 +357,14 @@
<ClInclude Include="rmg\CZoneGraphGenerator.h" />
<ClInclude Include="rmg\CZonePlacer.h" />
<ClInclude Include="rmg\float3.h" />
<ClInclude Include="SpellMechanics.h" />
<ClInclude Include="spells\AdventureSpellMechanics.h" />
<ClInclude Include="spells\BattleSpellMechanics.h" />
<ClInclude Include="spells\CDefaultSpellMechanics.h" />
<ClInclude Include="spells\CreatureSpellMechanics.h" />
<ClInclude Include="spells\CSpellHandler.h" />
<ClInclude Include="spells\ISpellMechanics.h" />
<ClInclude Include="spells\SpellMechanics.h" />
<ClInclude Include="spells\ViewSpellInt.h" />
<ClInclude Include="StartInfo.h" />
<ClInclude Include="StdInc.h" />
<ClInclude Include="UnlockGuard.h" />

View File

@ -26,6 +26,9 @@
<Filter Include="mapObjects">
<UniqueIdentifier>{ee24c7f7-f4e2-4d35-b994-94a6e29ea92f}</UniqueIdentifier>
</Filter>
<Filter Include="spells">
<UniqueIdentifier>{bda963b1-00e1-412a-9b44-f5cd3f8e9e33}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="BattleAction.cpp" />
@ -35,7 +38,6 @@
<ClCompile Include="CCreatureHandler.cpp" />
<ClCompile Include="CGeneralTextHandler.cpp" />
<ClCompile Include="CHeroHandler.cpp" />
<ClCompile Include="CSpellHandler.cpp" />
<ClCompile Include="CTownHandler.cpp" />
<ClCompile Include="CCreatureSet.cpp" />
<ClCompile Include="CGameState.cpp" />
@ -205,7 +207,27 @@
</ClCompile>
<ClCompile Include="registerTypes\TypesMapObjects3.cpp" />
<ClCompile Include="IHandlerBase.cpp" />
<ClCompile Include="SpellMechanics.cpp" />
<ClCompile Include="spells\AdventureSpellMechanics.cpp">
<Filter>spells</Filter>
</ClCompile>
<ClCompile Include="spells\CDefaultSpellMechanics.cpp">
<Filter>spells</Filter>
</ClCompile>
<ClCompile Include="spells\CSpellHandler.cpp">
<Filter>spells</Filter>
</ClCompile>
<ClCompile Include="spells\ViewSpellInt.cpp">
<Filter>spells</Filter>
</ClCompile>
<ClCompile Include="spells\ISpellMechanics.cpp">
<Filter>spells</Filter>
</ClCompile>
<ClCompile Include="spells\BattleSpellMechanics.cpp">
<Filter>spells</Filter>
</ClCompile>
<ClCompile Include="spells\CreatureSpellMechanics.cpp">
<Filter>spells</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CCreatureSet.h">
@ -259,9 +281,6 @@
<ClInclude Include="ConstTransitivePtr.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CSpellHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CTownHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -493,8 +512,29 @@
<ClInclude Include="CGameStateFwd.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SpellMechanics.h">
<Filter>Header Files</Filter>
<ClInclude Include="spells\AdventureSpellMechanics.h">
<Filter>spells</Filter>
</ClInclude>
<ClInclude Include="spells\BattleSpellMechanics.h">
<Filter>spells</Filter>
</ClInclude>
<ClInclude Include="spells\CreatureSpellMechanics.h">
<Filter>spells</Filter>
</ClInclude>
<ClInclude Include="spells\CSpellHandler.h">
<Filter>spells</Filter>
</ClInclude>
<ClInclude Include="spells\ISpellMechanics.h">
<Filter>spells</Filter>
</ClInclude>
<ClInclude Include="spells\ViewSpellInt.h">
<Filter>spells</Filter>
</ClInclude>
<ClInclude Include="spells\CDefaultSpellMechanics.h">
<Filter>spells</Filter>
</ClInclude>
<ClInclude Include="spells\SpellMechanics.h">
<Filter>spells</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -15,7 +15,7 @@
#include "../CGeneralTextHandler.h"
#include "../CSoundBase.h"
#include "CommonConstructors.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../IGameCallback.h"
#include "../CGameState.h"

View File

@ -16,7 +16,7 @@
#include "../CHeroHandler.h"
#include "../CModHandler.h"
#include "../CSoundBase.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "CObjectClassesHandler.h"
#include "../IGameCallback.h"
#include "../CGameState.h"
@ -1343,3 +1343,24 @@ void CGHeroInstance::levelUpAutomatically()
levelUp(proposedSecondarySkills);
}
}
bool CGHeroInstance::hasVisions(const CGObjectInstance * target, const int subtype) const
{
//VISIONS spell support
const std::string cached = boost::to_string((boost::format("type_%d__subtype_%d") % Bonus::VISIONS % subtype));
const int visionsMultiplier = valOfBonuses(Selector::typeSubtype(Bonus::VISIONS,subtype), cached);
int visionsRange = visionsMultiplier * getPrimSkillLevel(PrimarySkill::SPELL_POWER);
if (visionsMultiplier > 0)
vstd::amax(visionsRange, 3); //minimum range is 3 tiles, but only if VISIONS bonus present
const int distance = target->pos.dist2d(getPosition(false));
logGlobal->debug(boost::to_string(boost::format("Visions: dist %d, mult %d, range %d") % distance % visionsMultiplier % visionsRange));
return (distance < visionsRange) && (target->pos.z == pos.z);
}

View File

@ -193,6 +193,8 @@ public:
void Updatespecialty();
void recreateSecondarySkillsBonuses();
void updateSkill(SecondarySkill which, int val);
bool hasVisions(const CGObjectInstance * target, const int subtype) const;
CGHeroInstance();
virtual ~CGHeroInstance();

View File

@ -14,7 +14,7 @@
#include "../NetPacks.h"
#include "../CSoundBase.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../StartInfo.h"
#include "../IGameCallback.h"

View File

@ -20,7 +20,7 @@
#include "../CArtHandler.h"
#include "../CCreatureHandler.h"
#include "../CCreatureSet.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
namespace JsonRandom
{

View File

@ -17,7 +17,7 @@
#include "../CModHandler.h"
#include "CObjectClassesHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../IGameCallback.h"
#include "../CGameState.h"
@ -101,7 +101,41 @@ std::string CGCreature::getHoverText(PlayerColor player) const
std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
{
std::string hoverName = getHoverText(hero->tempOwner);
std::string hoverName;
if(hero->hasVisions(this, 0))
{
MetaString ms;
ms << stacks.begin()->second->count;
ms << " " ;
ms.addTxt(MetaString::CRE_PL_NAMES,subID);
ms << "\n";
int decision = takenAction(hero, true);
switch (decision)
{
case FIGHT:
ms.addTxt(MetaString::GENERAL_TXT,246);
break;
case FLEE:
ms.addTxt(MetaString::GENERAL_TXT,245);
break;
case JOIN_FOR_FREE:
ms.addTxt(MetaString::GENERAL_TXT,243);
break;
default: //decision = cost in gold
VLC->generaltexth->allTexts[244];
ms << boost::to_string(boost::format(VLC->generaltexth->allTexts[244]) % decision);
break;
}
ms.toString(hoverName);
}
else
{
hoverName = getHoverText(hero->tempOwner);
}
const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"];

View File

@ -9,7 +9,7 @@
#include "../mapObjects/CObjectClassesHandler.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../CGeneralTextHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "CMapEditManager.h"
SHeroName::SHeroName() : heroId(-1)

View File

@ -16,7 +16,7 @@
#include "../CStopWatch.h"
#include "../filesystem/Filesystem.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CCreatureHandler.h"
#include "../CGeneralTextHandler.h"
#include "../CHeroHandler.h"

View File

@ -13,7 +13,7 @@
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"

View File

@ -248,6 +248,7 @@ void registerTypesClientPacks1(Serializer &s)
s.template registerType<CPackForClient, HeroVisit>();
s.template registerType<CPackForClient, SetCommanderProperty>();
s.template registerType<CPackForClient, ChangeObjectVisitors>();
s.template registerType<CPackForClient, ShowWorldViewEx>();
}
template<typename Serializer>

View File

@ -12,7 +12,7 @@
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"

View File

@ -12,7 +12,7 @@
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"

View File

@ -12,7 +12,7 @@
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"

View File

@ -12,7 +12,7 @@
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"

View File

@ -12,7 +12,7 @@
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"

View File

@ -12,7 +12,7 @@
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"

View File

@ -12,7 +12,7 @@
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../CSpellHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"

View File

@ -17,7 +17,7 @@
#include "../VCMI_Lib.h"
#include "../CTownHandler.h"
#include "../CCreatureHandler.h"
#include "../CSpellHandler.h" //for choosing random spells
#include "../spells/CSpellHandler.h" //for choosing random spells
#include "../mapObjects/CommonConstructors.h"
#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
@ -2187,9 +2187,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
}
//Pandora with 15 spells of certain school
for (int i = 1; i <= 4; i++)
for (int i = 0; i < 4; i++)
{
oi.generateObject = [i, gen]() -> CGObjectInstance *
oi.generateObject = [i,gen]() -> CGObjectInstance *
{
auto obj = new CGPandoraBox();
obj->ID = Obj::PANDORAS_BOX;
@ -2198,27 +2198,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
std::vector <CSpell *> spells;
for (auto spell : VLC->spellh->objects)
{
if (!spell->isSpecialSpell())
{
bool school = false; //TODO: we could have better interface for iterating schools
switch (i)
{
case 1:
school = spell->air;
break;
case 2:
school = spell->earth;
break;
case 3:
school = spell->fire;
break;
case 4:
school = spell->water;
break;
}
if (school)
spells.push_back(spell);
}
if (!spell->isSpecialSpell() && spell->school[(ESpellSchool)i])
spells.push_back(spell);
}
RandomGeneratorUtil::randomShuffle(spells, gen->rand);

View File

@ -0,0 +1,272 @@
/*
* AdventureSpellMechanics.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "AdventureSpellMechanics.h"
#include "../CRandomGenerator.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../NetPacks.h"
#include "../BattleState.h"
#include "../CGameState.h"
#include "../CGameInfoCallback.h"
///SummonBoatMechanics
bool SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
{
const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
//check if spell works at all
if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
{
InfoWindow iw;
iw.player = parameters.caster->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
iw.text.addReplacement(parameters.caster->name);
env->sendAndApply(&iw);
return true;
}
//try to find unoccupied boat to summon
const CGBoat * nearest = nullptr;
double dist = 0;
int3 summonPos = parameters.caster->bestLocation();
if(summonPos.x < 0)
{
env->complain("There is no water tile available!");
return false;
}
for(const CGObjectInstance * obj : env->getMap()->objects)
{
if(obj && obj->ID == Obj::BOAT)
{
const CGBoat *b = static_cast<const CGBoat*>(obj);
if(b->hero)
continue; //we're looking for unoccupied boat
double nDist = b->pos.dist2d(parameters.caster->getPosition());
if(!nearest || nDist < dist) //it's first boat or closer than previous
{
nearest = b;
dist = nDist;
}
}
}
if(nullptr != nearest) //we found boat to summon
{
ChangeObjPos cop;
cop.objid = nearest->id;
cop.nPos = summonPos + int3(1,0,0);;
cop.flags = 1;
env->sendAndApply(&cop);
}
else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
{
InfoWindow iw;
iw.player = parameters.caster->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
env->sendAndApply(&iw);
}
else //create boat
{
NewObject no;
no.ID = Obj::BOAT;
no.subID = parameters.caster->getBoatType();
no.pos = summonPos + int3(1,0,0);;
env->sendAndApply(&no);
}
return true;
}
///ScuttleBoatMechanics
bool ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
{
const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
//check if spell works at all
if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
{
InfoWindow iw;
iw.player = parameters.caster->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
iw.text.addReplacement(parameters.caster->name);
env->sendAndApply(&iw);
return true;
}
if(!env->getMap()->isInTheMap(parameters.pos))
{
env->complain("Invalid dst tile for scuttle!");
return false;
}
//TODO: test range, visibility
const TerrainTile *t = &env->getMap()->getTile(parameters.pos);
if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
{
env->complain("There is no boat to scuttle!");
return false;
}
RemoveObject ro;
ro.id = t->visitableObjects.back()->id;
env->sendAndApply(&ro);
return true;
}
///DimensionDoorMechanics
bool DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
{
if(!env->getMap()->isInTheMap(parameters.pos))
{
env->complain("Destination is out of map!");
return false;
}
const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getSightCenter());
if(nullptr == dest)
{
env->complain("Destination tile doesn't exist!");
return false;
}
if(nullptr == curr)
{
env->complain("Source tile doesn't exist!");
return false;
}
if(parameters.caster->movement <= 0)
{
env->complain("Hero needs movement points to cast Dimension Door!");
return false;
}
const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
if(parameters.caster->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= owner->getPower(schoolLevel)) //limit casts per turn
{
InfoWindow iw;
iw.player = parameters.caster->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
iw.text.addReplacement(parameters.caster->name);
env->sendAndApply(&iw);
return true;
}
GiveBonus gb;
gb.id = parameters.caster->id.getNum();
gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id);
env->sendAndApply(&gb);
if(!dest->isClear(curr)) //wrong dest tile
{
InfoWindow iw;
iw.player = parameters.caster->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
env->sendAndApply(&iw);
}
else if(env->moveHero(parameters.caster->id, parameters.pos + parameters.caster->getVisitableOffset(), true))
{
SetMovePoints smp;
smp.hid = parameters.caster->id;
smp.val = std::max<ui32>(0, parameters.caster->movement - 300);
env->sendAndApply(&smp);
}
return true;
}
///TownPortalMechanics
bool TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const
{
if (!env->getMap()->isInTheMap(parameters.pos))
{
env->complain("Destination tile not present!");
return false;
}
TerrainTile tile = env->getMap()->getTile(parameters.pos);
if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN)
{
env->complain("Town not found for Town Portal!");
return false;
}
CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
if (town->tempOwner != parameters.caster->tempOwner)
{
env->complain("Can't teleport to another player!");
return false;
}
if (town->visitingHero)
{
env->complain("Can't teleport to occupied town!");
return false;
}
if (parameters.caster->getSpellSchoolLevel(owner) < 2)
{
si32 dist = town->pos.dist2dSQ(parameters.caster->pos);
ObjectInstanceID nearest = town->id; //nearest town's ID
for(const CGTownInstance * currTown : env->getCb()->getPlayer(parameters.caster->tempOwner)->towns)
{
si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos);
if (currDist < dist)
{
nearest = currTown->id;
dist = currDist;
}
}
if (town->id != nearest)
{
env->complain("This hero can only teleport to nearest town!");
return false;
}
}
env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1);
return true;
}
bool ViewMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
{
ShowWorldViewEx pack;
pack.player = parameters.caster->tempOwner;
const int spellLevel = parameters.caster->getSpellSchoolLevel(owner);
for(const CGObjectInstance * obj : env->getMap()->objects)
{
//todo:we need to send only not visible objects
if(filterObject(obj, spellLevel))
pack.objectPositions.push_back(ObjectPosInfo(obj));
}
env->sendAndApply(&pack);
return true;
}
bool ViewAirMechanics::filterObject(const CGObjectInstance * obj, const int spellLevel) const
{
return (obj->ID == Obj::ARTIFACT) || (spellLevel>1 && obj->ID == Obj::HERO) || (spellLevel>2 && obj->ID == Obj::TOWN);
}
bool ViewEarthMechanics::filterObject(const CGObjectInstance * obj, const int spellLevel) const
{
return (obj->ID == Obj::RESOURCE) || (spellLevel>1 && obj->ID == Obj::MINE);
}

View File

@ -0,0 +1,75 @@
/*
* AdventureSpellMechanics.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CDefaultSpellMechanics.h"
class ISpellMechanics;
class DefaultSpellMechanics;
class DLL_LINKAGE SummonBoatMechanics : public DefaultSpellMechanics
{
public:
SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
};
class DLL_LINKAGE ScuttleBoatMechanics : public DefaultSpellMechanics
{
public:
ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
};
class DLL_LINKAGE DimensionDoorMechanics : public DefaultSpellMechanics
{
public:
DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
};
class DLL_LINKAGE TownPortalMechanics : public DefaultSpellMechanics
{
public:
TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
};
class DLL_LINKAGE ViewMechanics : public DefaultSpellMechanics
{
public:
ViewMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
virtual bool filterObject(const CGObjectInstance * obj, const int spellLevel) const = 0;
};
class DLL_LINKAGE ViewAirMechanics : public ViewMechanics
{
public:
ViewAirMechanics(CSpell * s): ViewMechanics(s){};
protected:
bool filterObject(const CGObjectInstance * obj, const int spellLevel) const override;
};
class DLL_LINKAGE ViewEarthMechanics : public ViewMechanics
{
public:
ViewEarthMechanics(CSpell * s): ViewMechanics(s){};
protected:
bool filterObject(const CGObjectInstance * obj, const int spellLevel) const override;
};

View File

@ -0,0 +1,417 @@
/*
* BattleSpellMechanics.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleSpellMechanics.h"
#include "../NetPacks.h"
#include "../BattleState.h"
///ChainLightningMechanics
std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
{
std::set<const CStack* > attackedCres;
std::set<BattleHex> possibleHexes;
for(auto stack : ctx.cb->battleGetAllStacks())
{
if(stack->isValidTarget())
{
for(auto hex : stack->getHexes())
{
possibleHexes.insert (hex);
}
}
}
int targetsOnLevel[4] = {4, 4, 5, 5};
BattleHex lightningHex = ctx.destination;
for(int i = 0; i < targetsOnLevel[ctx.schoolLvl]; ++i)
{
auto stack = ctx.cb->battleGetStackByPos(lightningHex, true);
if(!stack)
break;
attackedCres.insert (stack);
for(auto hex : stack->getHexes())
{
possibleHexes.erase(hex); //can't hit same place twice
}
if(possibleHexes.empty()) //not enough targets
break;
lightningHex = BattleHex::getClosestTile(stack->attackerOwned, ctx.destination, possibleHexes);
}
return attackedCres;
}
///CloneMechanics
void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
const CStack * clonedStack = nullptr;
if(ctx.attackedCres.size())
clonedStack = *ctx.attackedCres.begin();
if(!clonedStack)
{
env->complain ("No target stack to clone!");
return;
}
const int attacker = !(bool)parameters.casterSide;
BattleStackAdded bsa;
bsa.creID = clonedStack->type->idNumber;
bsa.attacker = attacker;
bsa.summoned = true;
bsa.pos = parameters.cb->getAvaliableHex(bsa.creID, attacker); //TODO: unify it
bsa.amount = clonedStack->count;
env->sendAndApply(&bsa);
BattleSetStackProperty ssp;
ssp.stackID = bsa.newStackID;//we know stack ID after apply
ssp.which = BattleSetStackProperty::CLONED;
ssp.val = 0;
ssp.absolute = 1;
env->sendAndApply(&ssp);
}
ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
{
//can't clone already cloned creature
if(vstd::contains(obj->state, EBattleStackState::CLONED))
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
//TODO: how about stacks casting Clone?
//currently Clone casted by stack is assumed Expert level
ui8 schoolLevel;
if(caster)
{
schoolLevel = caster->getSpellSchoolLevel(owner);
}
else
{
schoolLevel = 3;
}
if(schoolLevel < 3)
{
int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
int creLevel = obj->getCreature()->level;
if(maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
}
//use default algorithm only if there is no mechanics-related problem
return DefaultSpellMechanics::isImmuneByStack(caster, obj);
}
///CureMechanics
void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{
DefaultSpellMechanics::applyBattle(battle, packet);
for(auto stackID : packet->affectedCres)
{
if(vstd::contains(packet->resisted, stackID))
{
logGlobal->errorStream() << "Resistance to positive spell CURE";
continue;
}
CStack *s = battle->getStack(stackID);
s->popBonuses([&](const Bonus *b) -> bool
{
if(b->source == Bonus::SPELL_EFFECT)
{
CSpell * sp = SpellID(b->sid).toSpell();
return sp->isNegative();
}
return false; //not a spell effect
});
}
}
///DispellMechanics
void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{
DefaultSpellMechanics::applyBattle(battle, packet);
for(auto stackID : packet->affectedCres)
{
if(vstd::contains(packet->resisted, stackID))
continue;
CStack *s = battle->getStack(stackID);
s->popBonuses([&](const Bonus *b) -> bool
{
return Selector::sourceType(Bonus::SPELL_EFFECT)(b);
});
}
}
///HypnotizeMechanics
ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
{
if(nullptr != caster) //do not resist hypnotize casted after attack, for example
{
//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
//apply 'damage' bonus for hypnotize, including hero specialty
ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj);
if (subjectHealth > maxHealth)
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
}
return DefaultSpellMechanics::isImmuneByStack(caster, obj);
}
///ObstacleMechanics
void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
auto placeObstacle = [&, this](BattleHex pos)
{
static int obstacleIdToGive = parameters.cb->obstacles.size()
? (parameters.cb->obstacles.back()->uniqueID+1)
: 0;
auto obstacle = make_shared<SpellCreatedObstacle>();
switch(owner->id) // :/
{
case SpellID::QUICKSAND:
obstacle->obstacleType = CObstacleInstance::QUICKSAND;
obstacle->turnsRemaining = -1;
obstacle->visibleForAnotherSide = false;
break;
case SpellID::LAND_MINE:
obstacle->obstacleType = CObstacleInstance::LAND_MINE;
obstacle->turnsRemaining = -1;
obstacle->visibleForAnotherSide = false;
break;
case SpellID::FIRE_WALL:
obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
obstacle->turnsRemaining = 2;
obstacle->visibleForAnotherSide = true;
break;
case SpellID::FORCE_FIELD:
obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
obstacle->turnsRemaining = 2;
obstacle->visibleForAnotherSide = true;
break;
default:
//this function cannot be used with spells that do not create obstacles
assert(0);
}
obstacle->pos = pos;
obstacle->casterSide = parameters.casterSide;
obstacle->ID = owner->id;
obstacle->spellLevel = parameters.spellLvl;
obstacle->casterSpellPower = parameters.usedSpellPower;
obstacle->uniqueID = obstacleIdToGive++;
BattleObstaclePlaced bop;
bop.obstacle = obstacle;
env->sendAndApply(&bop);
};
switch(owner->id)
{
case SpellID::QUICKSAND:
case SpellID::LAND_MINE:
{
std::vector<BattleHex> availableTiles;
for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
{
BattleHex hex = i;
if(hex.getX() > 2 && hex.getX() < 14 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false)))
availableTiles.push_back(hex);
}
boost::range::random_shuffle(availableTiles);
const int patchesForSkill[] = {4, 4, 6, 8};
const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
//land mines or quicksand patches are handled as spell created obstacles
for (int i = 0; i < patchesToPut; i++)
placeObstacle(availableTiles.at(i));
}
break;
case SpellID::FORCE_FIELD:
placeObstacle(parameters.destination);
break;
case SpellID::FIRE_WALL:
{
//fire wall is build from multiple obstacles - one fire piece for each affected hex
auto affectedHexes = owner->rangeInHexes(parameters.destination, parameters.spellLvl, parameters.casterSide);
for(BattleHex hex : affectedHexes)
placeObstacle(hex);
}
break;
default:
assert(0);
}
}
///WallMechanics
std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const
{
std::vector<BattleHex> ret;
//Special case - shape of obstacle depends on caster's side
//TODO make it possible through spell config
BattleHex::EDir firstStep, secondStep;
if(side)
{
firstStep = BattleHex::TOP_LEFT;
secondStep = BattleHex::TOP_RIGHT;
}
else
{
firstStep = BattleHex::TOP_RIGHT;
secondStep = BattleHex::TOP_LEFT;
}
//Adds hex to the ret if it's valid. Otherwise sets output arg flag if given.
auto addIfValid = [&](BattleHex hex)
{
if(hex.isValid())
ret.push_back(hex);
else if(outDroppedHexes)
*outDroppedHexes = true;
};
ret.push_back(centralHex);
addIfValid(centralHex.moveInDir(firstStep, false));
if(schoolLvl >= 2) //advanced versions of fire wall / force field cotnains of 3 hexes
addIfValid(centralHex.moveInDir(secondStep, false)); //moveInDir function modifies subject hex
return ret;
}
///RemoveObstacleMechanics
void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false))
{
ObstaclesRemoved obr;
obr.obstacles.insert(obstacleToRemove->uniqueID);
env->sendAndApply(&obr);
}
else
env->complain("There's no obstacle to remove!");
}
///SpecialRisingSpellMechanics
void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
RisingSpellMechanics::applyBattleEffects(env, parameters, ctx);
if(parameters.selectedStack == parameters.cb->battleActiveStack())
//set another active stack than the one removed, or bad things will happen
//TODO: make that part of BattleStacksRemoved? what about client update?
{
//makeStackDoNothing(gs->curB->getStack (selectedStack));
BattleSetActiveStack sas;
//std::vector<const CStack *> hlp;
//battleGetStackQueue(hlp, 1, selectedStack); //next after this one
//if(hlp.size())
//{
// sas.stack = hlp[0]->ID;
//}
//else
// complain ("No new stack to activate!");
sas.stack = parameters.cb->getNextStack()->ID; //why the hell next stack has same ID as current?
env->sendAndApply(&sas);
}
BattleStacksRemoved bsr;
bsr.stackIDs.insert(parameters.selectedStack->ID); //somehow it works for teleport?
env->sendAndApply(&bsr);
}
///SpecialRisingSpellMechanics
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
{
// following does apply to resurrect and animate dead(?) only
// for sacrifice health calculation and health limit check don't matter
if(obj->count >= obj->baseAmount)
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
if(caster) //FIXME: Archangels can cast immune stack
{
auto maxHealth = calculateHealedHP(caster, obj, nullptr);
if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
}
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
}
///SummonMechanics
void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
//todo: make configurable
CreatureID creID = CreatureID::NONE;
switch(owner->id)
{
case SpellID::SUMMON_FIRE_ELEMENTAL:
creID = CreatureID::FIRE_ELEMENTAL;
break;
case SpellID::SUMMON_EARTH_ELEMENTAL:
creID = CreatureID::EARTH_ELEMENTAL;
break;
case SpellID::SUMMON_WATER_ELEMENTAL:
creID = CreatureID::WATER_ELEMENTAL;
break;
case SpellID::SUMMON_AIR_ELEMENTAL:
creID = CreatureID::AIR_ELEMENTAL;
break;
default:
env->complain("Unable to determine summoned creature");
return;
}
BattleStackAdded bsa;
bsa.creID = creID;
bsa.attacker = !(bool)parameters.casterSide;
bsa.summoned = true;
bsa.pos = parameters.cb->getAvaliableHex(creID, !(bool)parameters.casterSide); //TODO: unify it
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
int percentBonus = parameters.caster ? parameters.caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
bsa.amount = parameters.usedSpellPower
* owner->getPower(parameters.spellLvl)
* (100 + percentBonus) / 100.0; //new feature - percentage bonus
if(bsa.amount)
env->sendAndApply(&bsa);
else
env->complain("Summoning didn't summon any!");
}
///TeleportMechanics
void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
BattleStackMoved bsm;
bsm.distance = -1;
bsm.stack = parameters.selectedStack->ID;
std::vector<BattleHex> tiles;
tiles.push_back(parameters.destination);
bsm.tilesToMove = tiles;
bsm.teleporting = true;
env->sendAndApply(&bsm);
}

View File

@ -0,0 +1,116 @@
/*
* BattleSpellMechanics.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CDefaultSpellMechanics.h"
class DLL_LINKAGE ChainLightningMechanics : public DefaultSpellMechanics
{
public:
ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};
std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
};
class DLL_LINKAGE CloneMechanics : public DefaultSpellMechanics
{
public:
CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
protected:
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
class DLL_LINKAGE CureMechanics : public DefaultSpellMechanics
{
public:
CureMechanics(CSpell * s): DefaultSpellMechanics(s){};
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
};
class DLL_LINKAGE DispellMechanics : public DefaultSpellMechanics
{
public:
DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
};
class DLL_LINKAGE HypnotizeMechanics : public DefaultSpellMechanics
{
public:
HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
};
class DLL_LINKAGE ObstacleMechanics : public DefaultSpellMechanics
{
public:
ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
class DLL_LINKAGE WallMechanics : public ObstacleMechanics
{
public:
WallMechanics(CSpell * s): ObstacleMechanics(s){};
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;
};
class DLL_LINKAGE RemoveObstacleMechanics : public DefaultSpellMechanics
{
public:
RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
///all rising spells
class DLL_LINKAGE RisingSpellMechanics : public DefaultSpellMechanics
{
public:
RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
};
class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
{
public:
SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};
protected:
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
///all rising spells but SACRIFICE
class DLL_LINKAGE SpecialRisingSpellMechanics : public RisingSpellMechanics
{
public:
SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
};
class DLL_LINKAGE SummonMechanics : public DefaultSpellMechanics
{
public:
SummonMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
class DLL_LINKAGE TeleportMechanics: public DefaultSpellMechanics
{
public:
TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};

View File

@ -0,0 +1,719 @@
/*
* CDefaultSpellMechanics.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CDefaultSpellMechanics.h"
#include "../NetPacks.h"
#include "../BattleState.h"
namespace SRSLPraserHelpers
{
static int XYToHex(int x, int y)
{
return x + GameConstants::BFIELD_WIDTH * y;
}
static int XYToHex(std::pair<int, int> xy)
{
return XYToHex(xy.first, xy.second);
}
static int hexToY(int battleFieldPosition)
{
return battleFieldPosition/GameConstants::BFIELD_WIDTH;
}
static int hexToX(int battleFieldPosition)
{
int pos = battleFieldPosition - hexToY(battleFieldPosition) * GameConstants::BFIELD_WIDTH;
return pos;
}
static std::pair<int, int> hexToPair(int battleFieldPosition)
{
return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
}
//moves hex by one hex in given direction
//0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
static std::pair<int, int> gotoDir(int x, int y, int direction)
{
switch(direction)
{
case 0: //top left
return std::make_pair((y%2) ? x-1 : x, y-1);
case 1: //top right
return std::make_pair((y%2) ? x : x+1, y-1);
case 2: //right
return std::make_pair(x+1, y);
case 3: //right bottom
return std::make_pair((y%2) ? x : x+1, y+1);
case 4: //left bottom
return std::make_pair((y%2) ? x-1 : x, y+1);
case 5: //left
return std::make_pair(x-1, y);
default:
throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
}
}
static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
{
return gotoDir(xy.first, xy.second, direction);
}
static bool isGoodHex(std::pair<int, int> xy)
{
return xy.first >=0 && xy.first < GameConstants::BFIELD_WIDTH && xy.second >= 0 && xy.second < GameConstants::BFIELD_HEIGHT;
}
//helper function for rangeInHexes
static std::set<ui16> getInRange(unsigned int center, int low, int high)
{
std::set<ui16> ret;
if(low == 0)
{
ret.insert(center);
}
std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
for(auto & elem : mainPointForLayer)
elem = hexToPair(center);
for(int it=1; it<=high; ++it) //it - distance to the center
{
for(int b=0; b<6; ++b)
mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
if(it>=low)
{
std::pair<int, int> curHex;
//adding lines (A-b, B-c, C-d, etc)
for(int v=0; v<6; ++v)
{
curHex = mainPointForLayer[v];
for(int h=0; h<it; ++h)
{
if(isGoodHex(curHex))
ret.insert(XYToHex(curHex));
curHex = gotoDir(curHex, (v+2)%6);
}
}
} //if(it>=low)
}
return ret;
}
}
///DefaultSpellMechanics
void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{
if (packet->castedByHero)
{
if (packet->side < 2)
{
battle->sides[packet->side].castSpellsCount++;
}
}
//handle countering spells
for(auto stackID : packet->affectedCres)
{
if(vstd::contains(packet->resisted, stackID))
continue;
CStack * s = battle->getStack(stackID);
s->popBonuses([&](const Bonus * b) -> bool
{
//check for each bonus if it should be removed
const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
const int spellID = isSpellEffect ? b->sid : -1;
return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
});
}
}
bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
{
if(!owner->isAdventureSpell())
{
env->complain("Attempt to cast non adventure spell in adventure mode");
return false;
}
const CGHeroInstance * caster = parameters.caster;
const int cost = caster->getSpellCost(owner);
if(!caster->canCastThisSpell(owner))
{
env->complain("Hero cannot cast this spell!");
return false;
}
if(caster->mana < cost)
{
env->complain("Hero doesn't have enough spell points to cast this spell!");
return false;
}
{
AdvmapSpellCast asc;
asc.caster = caster;
asc.spellID = owner->id;
env->sendAndApply(&asc);
}
if(applyAdventureEffects(env, parameters))
{
SetMana sm;
sm.hid = caster->id;
sm.absolute = false;
sm.val = -cost;
env->sendAndApply(&sm);
return true;
}
return false;
}
bool DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
{
if(owner->hasEffects())
{
const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
std::vector<Bonus> bonuses;
owner->getEffects(bonuses, schoolLevel);
for(Bonus b : bonuses)
{
GiveBonus gb;
gb.id = parameters.caster->id.getNum();
gb.bonus = b;
env->sendAndApply(&gb);
}
return true;
}
else
{
//There is no generic algorithm of adventure cast
env->complain("Unimplemented adventure spell");
return false;
}
}
void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
{
BattleSpellCast sc;
sc.side = parameters.casterSide;
sc.id = owner->id;
sc.skill = parameters.spellLvl;
sc.tile = parameters.destination;
sc.dmgToDisplay = 0;
sc.castedByHero = nullptr != parameters.caster;
sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
sc.manaGained = 0;
int spellCost = 0;
//calculate spell cost
if(parameters.caster)
{
spellCost = parameters.cb->battleGetSpellCost(owner, parameters.caster);
if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
{
int manaChannel = 0;
for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
{
if(stack->owner == parameters.secHero->tempOwner)
{
vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
}
}
sc.manaGained = (manaChannel * spellCost) / 100;
}
}
//calculating affected creatures for all spells
//must be vector, as in Chain Lightning order matters
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.caster);
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
for (auto cre : attackedCres)
{
sc.affectedCres.insert(cre->ID);
}
//checking if creatures resist
//resistance is applied only to negative spells
if(owner->isNegative())
{
for(auto s : attackedCres)
{
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
if(env->getRandomGenerator().nextInt(99) < prob)
{
sc.resisted.push_back(s->ID);
}
}
}
StacksInjured si;
SpellCastContext ctx(attackedCres, sc, si);
applyBattleEffects(env, parameters, ctx);
env->sendAndApply(&sc);
//spend mana
if(parameters.caster)
{
SetMana sm;
sm.absolute = false;
sm.hid = parameters.caster->id;
sm.val = -spellCost;
env->sendAndApply(&sm);
if(sc.manaGained > 0)
{
assert(parameters.secHero);
sm.hid = parameters.secHero->id;
sm.val = sc.manaGained;
env->sendAndApply(&sm);
}
}
if(!si.stacks.empty()) //after spellcast info shows
env->sendAndApply(&si);
//reduce number of casts remaining
//TODO: this should be part of BattleSpellCast apply
if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)
{
assert(parameters.casterStack);
BattleSetStackProperty ssp;
ssp.stackID = parameters.casterStack->ID;
ssp.which = BattleSetStackProperty::CASTS;
ssp.val = -1;
ssp.absolute = false;
env->sendAndApply(&ssp);
}
//Magic Mirror effect
if(owner->isNegative() && parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
{
for(auto & attackedCre : attackedCres)
{
int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
if(mirrorChance > env->getRandomGenerator().nextInt(99))
{
std::vector<const CStack *> mirrorTargets;
auto battleStacks = parameters.cb->battleGetAllStacks(true);
for(auto & battleStack : battleStacks)
{
if(battleStack->owner == parameters.casterColor) //get enemy stacks which can be affected by this spell
{
if (ESpellCastProblem::OK == owner->isImmuneByStack(nullptr, battleStack))
mirrorTargets.push_back(battleStack);
}
}
if(!mirrorTargets.empty())
{
int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
BattleSpellCastParameters mirrorParameters = parameters;
mirrorParameters.spellLvl = 0;
mirrorParameters.casterSide = 1-parameters.casterSide;
mirrorParameters.casterColor = (attackedCre)->owner;
mirrorParameters.caster = nullptr;
mirrorParameters.destination = targetHex;
mirrorParameters.secHero = parameters.caster;
mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
mirrorParameters.casterStack = (attackedCre);
mirrorParameters.selectedStack = nullptr;
battleCast(env, mirrorParameters);
}
}
}
}
}
int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
{
if(!caster)
{
if (!usedSpellPower)
return 3; //default duration of all creature spells
else
return usedSpellPower; //use creature spell power
}
switch(owner->id)
{
case SpellID::FRENZY:
return 1;
default: //other spells
return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
}
}
ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
{
int healedHealth;
if(!owner->isHealingSpell())
{
logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< owner->name;
return 0;
}
const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
const int levelPower = owner->getPower(caster->getSpellSchoolLevel(owner));
if (owner->id == SpellID::SACRIFICE && sacrificedStack)
healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
else
healedHealth = spellPowerSkill * owner->power + levelPower; //???
healedHealth = owner->calculateBonus(healedHealth, caster, stack);
return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));
}
void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
//applying effects
if(owner->isOffensiveSpell())
{
int spellDamage = 0;
if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
{
int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
if(unitSpellPower)
ctx.sc.dmgToDisplay = spellDamage = parameters.casterStack->count * unitSpellPower; //TODO: handle immunities
else //Faerie Dragon
{
parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
ctx.sc.dmgToDisplay = 0;
}
}
int chainLightningModifier = 0;
for(auto & attackedCre : ctx.attackedCres)
{
if(vstd::contains(ctx.sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
continue;
BattleStackAttacked bsa;
if(spellDamage)
bsa.damageAmount = spellDamage >> chainLightningModifier;
else
bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier;
ctx.sc.dmgToDisplay += bsa.damageAmount;
bsa.stackAttacked = (attackedCre)->ID;
if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
bsa.attackerID = parameters.casterStack->ID;
else
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
ctx.si.stacks.push_back(bsa);
if(owner->id == SpellID::CHAIN_LIGHTNING)
++chainLightningModifier;
}
}
if(owner->hasEffects())
{
int stackSpellPower = 0;
if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
{
stackSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
}
SetStackEffect sse;
Bonus pseudoBonus;
pseudoBonus.sid = owner->id;
pseudoBonus.val = parameters.spellLvl;
pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
CStack::stackEffectToFeature(sse.effect, pseudoBonus);
if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
{
sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
}
if(owner->id == SpellID::BIND && parameters.casterStack)//bind
{
sse.effect.back().additionalInfo = parameters.casterStack->ID; //we need to know who casted Bind
}
const Bonus * bonus = nullptr;
if(parameters.caster)
bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
//TODO does hero specialty should affects his stack casting spells?
si32 power = 0;
for(const CStack * affected : ctx.attackedCres)
{
if(vstd::contains(ctx.sc.resisted, affected->ID)) //this creature resisted the spell
continue;
sse.stacks.push_back(affected->ID);
//Apply hero specials - peculiar enchants
const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
if(bonus)
{
switch(bonus->additionalInfo)
{
case 0: //normal
{
switch(tier)
{
case 1: case 2:
power = 3;
break;
case 3: case 4:
power = 2;
break;
case 5: case 6:
power = 1;
break;
}
Bonus specialBonus(sse.effect.back());
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
}
break;
case 1: //only Coronius as yet
{
power = std::max(5 - tier, 0);
Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
specialBonus.sid = owner->id;
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
}
break;
}
}
if (parameters.caster && parameters.caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
{
int damagePercent = parameters.caster->level * parameters.caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
specialBonus.valType = Bonus::PERCENT_TO_ALL;
specialBonus.sid = owner->id;
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
}
}
if(!sse.stacks.empty())
env->sendAndApply(&sse);
}
if(owner->isHealingSpell())
{
int hpGained = 0;
if(parameters.casterStack)
{
int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
if(unitSpellPower)
hpGained = parameters.casterStack->count * unitSpellPower; //Archangel
else //Faerie Dragon-like effect - unused so far
parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
}
StacksHealedOrResurrected shr;
shr.lifeDrain = false;
shr.tentHealing = false;
for(auto & attackedCre : ctx.attackedCres)
{
StacksHealedOrResurrected::HealInfo hi;
hi.stackID = (attackedCre)->ID;
if (parameters.casterStack) //casted by creature
{
const bool resurrect = owner->isRisingSpell();
if (hpGained)
{
//archangel
hi.healedHP = std::min<ui32>(hpGained, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
}
else
{
//any typical spell (commander's cure or animate dead)
int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(parameters.spellLvl);
hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
}
}
else
hi.healedHP = calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero
hi.lowLevelResurrection = parameters.spellLvl <= 1;
shr.healedStacks.push_back(hi);
}
if(!shr.healedStacks.empty())
env->sendAndApply(&shr);
}
}
std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
{
using namespace SRSLPraserHelpers;
std::vector<BattleHex> ret;
std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
if(rng.size() >= 2 && rng[0] != 'X') //there is at lest one hex in range (+artificial comma)
{
std::string number1, number2;
int beg, end;
bool readingFirst = true;
for(auto & elem : rng)
{
if(std::isdigit(elem) ) //reading number
{
if(readingFirst)
number1 += elem;
else
number2 += elem;
}
else if(elem == ',') //comma
{
//calculating variables
if(readingFirst)
{
beg = atoi(number1.c_str());
number1 = "";
}
else
{
end = atoi(number2.c_str());
number2 = "";
}
//obtaining new hexes
std::set<ui16> curLayer;
if(readingFirst)
{
curLayer = getInRange(centralHex, beg, beg);
}
else
{
curLayer = getInRange(centralHex, beg, end);
readingFirst = true;
}
//adding abtained hexes
for(auto & curLayer_it : curLayer)
{
ret.push_back(curLayer_it);
}
}
else if(elem == '-') //dash
{
beg = atoi(number1.c_str());
number1 = "";
readingFirst = false;
}
}
}
//remove duplicates (TODO check if actually needed)
range::unique(ret);
return ret;
}
std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
{
std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
const ui8 attackerSide = ctx.cb->playerToSide(ctx.casterColor) == 1;
const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
const CSpell::TargetInfo ti(owner, ctx.schoolLvl, ctx.mode);
//TODO: more generic solution for mass spells
if(owner->getLevelInfo(ctx.schoolLvl).range.size() > 1) //custom many-hex range
{
for(BattleHex hex : attackedHexes)
{
if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
{
attackedCres.insert(st);
}
}
}
else if(ti.type == CSpell::CREATURE)
{
auto predicate = [=](const CStack * s){
const bool positiveToAlly = owner->isPositive() && s->owner == ctx.casterColor;
const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.casterColor;
const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
//for single target spells select stacks covering destination tile
const bool rangeCovers = ti.massive || s->coversPos(ctx.destination);
//handle smart targeting
const bool positivenessFlag = !ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
return rangeCovers && positivenessFlag && validTarget;
};
TStacks stacks = ctx.cb->battleGetStacksIf(predicate);
if(ti.massive)
{
//for massive spells add all targets
for (auto stack : stacks)
attackedCres.insert(stack);
}
else
{
//for single target spells we must select one target. Alive stack is preferred (issue #1763)
for(auto stack : stacks)
{
if(stack->alive())
{
attackedCres.insert(stack);
break;
}
}
if(attackedCres.empty() && !stacks.empty())
{
attackedCres.insert(stacks.front());
}
}
}
else //custom range from attackedHexes
{
for(BattleHex hex : attackedHexes)
{
if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
attackedCres.insert(st);
}
}
return attackedCres;
}
ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
{
//by default use general algorithm
return owner->isImmuneBy(obj);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,397 +1,386 @@
#pragma once
#include "IHandlerBase.h"
#include "../lib/ConstTransitivePtr.h"
#include "int3.h"
#include "GameConstants.h"
#include "BattleHex.h"
#include "HeroBonus.h"
/*
* CSpellHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
class CSpell;
class ISpellMechanics;
class CLegacyConfigParser;
class CGHeroInstance;
class CStack;
class CBattleInfoCallback;
class BattleInfo;
struct CPackForClient;
struct BattleSpellCast;
class CRandomGenerator;
struct SpellSchoolInfo
{
ESpellSchool id; //backlink
Bonus::BonusType damagePremyBonus;
Bonus::BonusType immunityBonus;
std::string jsonName;
SecondarySkill::ESecondarySkill skill;
Bonus::BonusType knoledgeBonus;
};
///callback to be provided by server
class DLL_LINKAGE SpellCastEnvironment
{
public:
virtual ~SpellCastEnvironment(){};
virtual void sendAndApply(CPackForClient * info) const = 0;
virtual CRandomGenerator & getRandomGenerator() const = 0;
virtual void complain(const std::string & problem) const = 0;
};
///helper struct
struct DLL_LINKAGE BattleSpellCastParameters
{
public:
BattleSpellCastParameters(const BattleInfo * cb);
int spellLvl;
BattleHex destination;
ui8 casterSide;
PlayerColor casterColor;
const CGHeroInstance * caster;
const CGHeroInstance * secHero;
int usedSpellPower;
ECastingMode::ECastingMode mode;
const CStack * casterStack;
const CStack * selectedStack;
const BattleInfo * cb;
};
enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
class DLL_LINKAGE CSpell
{
public:
struct ProjectileInfo
{
///in radians. Only positive value. Negative angle is handled by vertical flip
double minimumAngle;
///resource name
std::string resourceName;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & minimumAngle & resourceName;
}
};
struct AnimationItem
{
std::string resourceName;
VerticalPosition verticalPosition;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & resourceName & verticalPosition;
}
};
typedef AnimationItem TAnimation;
typedef std::vector<TAnimation> TAnimationQueue;
struct DLL_LINKAGE AnimationInfo
{
AnimationInfo();
~AnimationInfo();
///displayed on all affected targets.
TAnimationQueue affect;
///displayed on caster.
TAnimationQueue cast;
///displayed on target hex. If spell was casted with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
TAnimationQueue hit;
///displayed "between" caster and (first) target. Ignored if spell was casted with no target selection.
///use selectProjectile to access
std::vector<ProjectileInfo> projectile;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & projectile & hit & cast;
}
std::string selectProjectile(const double angle) const;
} animationInfo;
public:
struct LevelInfo
{
std::string description; //descriptions of spell for skill level
si32 cost;
si32 power;
si32 AIValue;
bool smartTarget;
bool clearTarget;
bool clearAffected;
std::string range;
std::vector<Bonus> effects;
LevelInfo();
~LevelInfo();
template <typename Handler> void serialize(Handler &h, const int version)
{
h & description & cost & power & AIValue & smartTarget & range & effects;
h & clearTarget & clearAffected;
}
};
/** \brief Low level accessor. Don`t use it if absolutely necessary
*
* \param level. spell school level
* \return Spell level info structure
*
*/
const CSpell::LevelInfo& getLevelInfo(const int level) const;
public:
enum ETargetType {NO_TARGET, CREATURE, OBSTACLE, LOCATION};
enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
struct TargetInfo
{
ETargetType type;
bool smart;
bool massive;
bool onlyAlive;
///no immunity on primary target (mostly spell-like attack)
bool alwaysHitDirectly;
bool clearTarget;
bool clearAffected;
TargetInfo(const CSpell * spell, const int level);
TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode);
private:
void init(const CSpell * spell, const int level);
};
SpellID id;
std::string identifier; //???
std::string name;
si32 level;
bool earth; //deprecated
bool water; //deprecated
bool fire; //deprecated
bool air; //deprecated
std::map<ESpellSchool, bool> school; //todo: use this instead of separate boolean fields
si32 power; //spell's power
std::map<TFaction, si32> probabilities; //% chance to gain for castles
bool combatSpell; //is this spell combat (true) or adventure (false)
bool creatureAbility; //if true, only creatures can use this spell
si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
std::vector<SpellID> counteredSpells; //spells that are removed when effect of this spell is placed on creature (for bless-curse, haste-slow, and similar pairs)
CSpell();
~CSpell();
bool isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const;
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
ETargetType getTargetType() const; //deprecated
CSpell::TargetInfo getTargetInfo(const int level) const;
bool isCombatSpell() const;
bool isAdventureSpell() const;
bool isCreatureAbility() const;
bool isPositive() const;
bool isNegative() const;
bool isNeutral() const;
bool isDamageSpell() const;
bool isHealingSpell() const;
bool isRisingSpell() const;
bool isOffensiveSpell() const;
bool isSpecialSpell() const;
bool hasEffects() const;
void getEffects(std::vector<Bonus> &lst, const int level) const;
///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
//internal, for use only by Mechanics classes
ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
//checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
//internal, for use only by Mechanics classes. applying secondary skills
ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
ui32 calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
///calculate healed HP for all spells casted by hero
ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) const;
///selects from allStacks actually affected stacks
std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
si32 getCost(const int skillLevel) const;
/**
* Returns spell level power, base power ignored
*/
si32 getPower(const int skillLevel) const;
// /**
// * Returns spell power, taking base power into account
// */
// si32 calculatePower(const int skillLevel) const;
si32 getProbability(const TFaction factionId) const;
/**
* Calls cb for each school this spell belongs to
*
* Set stop to true to abort looping
*/
void forEachSchool(const std::function<void (const SpellSchoolInfo &, bool &)> & cb) const;
/**
* Returns resource name of icon for SPELL_IMMUNITY bonus
*/
const std::string& getIconImmune() const;
const std::string& getCastSound() const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & identifier & id & name & level & power
& probabilities & attributes & combatSpell & creatureAbility & positiveness & counteredSpells;
h & isRising & isDamage & isOffensive;
h & targetType;
h & immunities & limiters & absoluteImmunities & absoluteLimiters;
h & iconImmune;
h & defaultProbability;
h & isSpecial;
h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll;
h & levels;
h & school;
h & animationInfo;
if(!h.saving)
setup();
}
friend class CSpellHandler;
friend class Graphics;
public:
///Server logic. Has write access to GameState via packets.
///May be executed on client side by (future) non-cheat-proof scripts.
//void adventureCast() const;
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
public:
///Client-server logic. Has direct write access to GameState.
///Shall be called (only) when applying packets on BOTH SIDES
///implementation of BattleSpellCast applying
void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const;
private:
void setIsOffensive(const bool val);
void setIsRising(const bool val);
//call this after load or deserialization. cant be done in constructor.
void setup();
void setupMechanics();
private:
si32 defaultProbability;
bool isRising;
bool isDamage;
bool isOffensive;
bool isSpecial;
std::string attributes; //reference only attributes //todo: remove or include in configuration format, currently unused
ETargetType targetType;
std::vector<Bonus::BonusType> immunities; //any of these grants immunity
std::vector<Bonus::BonusType> absoluteImmunities; //any of these grants immunity, can't be negated
std::vector<Bonus::BonusType> limiters; //all of them are required to be affected
std::vector<Bonus::BonusType> absoluteLimiters; //all of them are required to be affected, can't be negated
///graphics related stuff
std::string iconImmune;
std::string iconBook;
std::string iconEffect;
std::string iconScenarioBonus;
std::string iconScroll;
///sound related stuff
std::string castSound;
std::vector<LevelInfo> levels;
ISpellMechanics * mechanics;//(!) do not serialize
};
bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door
class DLL_LINKAGE CSpellHandler: public CHandlerBase<SpellID, CSpell>
{
public:
CSpellHandler();
virtual ~CSpellHandler();
///IHandler base
std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
void afterLoadFinalization() override;
void beforeValidate(JsonNode & object) override;
/**
* Gets a list of default allowed spells. OH3 spells are all allowed by default.
*
* @return a list of allowed spells, the index is the spell id and the value either 0 for not allowed or 1 for allowed
*/
std::vector<bool> getDefaultAllowed() const override;
const std::string getTypeName() const override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & objects ;
}
protected:
CSpell * loadFromJson(const JsonNode & json) override;
};
/*
* CSpellHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../IHandlerBase.h"
#include "../ConstTransitivePtr.h"
#include "../int3.h"
#include "../GameConstants.h"
#include "../BattleHex.h"
#include "../HeroBonus.h"
class CGObjectInstance;
class CSpell;
class ISpellMechanics;
class CLegacyConfigParser;
class CGHeroInstance;
class CStack;
class CBattleInfoCallback;
class BattleInfo;
struct CPackForClient;
struct BattleSpellCast;
class CGameInfoCallback;
class CRandomGenerator;
class CMap;
struct SpellSchoolInfo
{
ESpellSchool id; //backlink
Bonus::BonusType damagePremyBonus;
Bonus::BonusType immunityBonus;
std::string jsonName;
SecondarySkill::ESecondarySkill skill;
Bonus::BonusType knoledgeBonus;
};
///callback to be provided by server
class DLL_LINKAGE SpellCastEnvironment
{
public:
virtual ~SpellCastEnvironment(){};
virtual void sendAndApply(CPackForClient * info) const = 0;
virtual CRandomGenerator & getRandomGenerator() const = 0;
virtual void complain(const std::string & problem) const = 0;
virtual const CMap * getMap() const = 0;
virtual const CGameInfoCallback * getCb() const = 0;
virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const =0; //TODO: remove
};
///helper struct
struct DLL_LINKAGE BattleSpellCastParameters
{
public:
BattleSpellCastParameters(const BattleInfo * cb);
int spellLvl;
BattleHex destination;
ui8 casterSide;
PlayerColor casterColor;
const CGHeroInstance * caster;
const CGHeroInstance * secHero;
int usedSpellPower;
ECastingMode::ECastingMode mode;
const CStack * casterStack;
const CStack * selectedStack;
const BattleInfo * cb;
};
struct DLL_LINKAGE AdventureSpellCastParameters
{
const CGHeroInstance * caster;
int3 pos;
};
enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
class DLL_LINKAGE CSpell
{
public:
struct ProjectileInfo
{
///in radians. Only positive value. Negative angle is handled by vertical flip
double minimumAngle;
///resource name
std::string resourceName;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & minimumAngle & resourceName;
}
};
struct AnimationItem
{
std::string resourceName;
VerticalPosition verticalPosition;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & resourceName & verticalPosition;
}
};
typedef AnimationItem TAnimation;
typedef std::vector<TAnimation> TAnimationQueue;
struct DLL_LINKAGE AnimationInfo
{
AnimationInfo();
~AnimationInfo();
///displayed on all affected targets.
TAnimationQueue affect;
///displayed on caster.
TAnimationQueue cast;
///displayed on target hex. If spell was casted with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
TAnimationQueue hit;
///displayed "between" caster and (first) target. Ignored if spell was casted with no target selection.
///use selectProjectile to access
std::vector<ProjectileInfo> projectile;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & projectile & hit & cast;
}
std::string selectProjectile(const double angle) const;
} animationInfo;
public:
struct LevelInfo
{
std::string description; //descriptions of spell for skill level
si32 cost;
si32 power;
si32 AIValue;
bool smartTarget;
bool clearTarget;
bool clearAffected;
std::string range;
std::vector<Bonus> effects;
LevelInfo();
~LevelInfo();
template <typename Handler> void serialize(Handler &h, const int version)
{
h & description & cost & power & AIValue & smartTarget & range & effects;
h & clearTarget & clearAffected;
}
};
/** \brief Low level accessor. Don`t use it if absolutely necessary
*
* \param level. spell school level
* \return Spell level info structure
*
*/
const CSpell::LevelInfo& getLevelInfo(const int level) const;
public:
enum ETargetType {NO_TARGET, CREATURE, OBSTACLE, LOCATION};
enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
struct TargetInfo
{
ETargetType type;
bool smart;
bool massive;
bool onlyAlive;
///no immunity on primary target (mostly spell-like attack)
bool alwaysHitDirectly;
bool clearTarget;
bool clearAffected;
TargetInfo(const CSpell * spell, const int level);
TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode);
private:
void init(const CSpell * spell, const int level);
};
SpellID id;
std::string identifier; //???
std::string name;
si32 level;
std::map<ESpellSchool, bool> school; //todo: use this instead of separate boolean fields
si32 power; //spell's power
std::map<TFaction, si32> probabilities; //% chance to gain for castles
bool combatSpell; //is this spell combat (true) or adventure (false)
bool creatureAbility; //if true, only creatures can use this spell
si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
std::vector<SpellID> counteredSpells; //spells that are removed when effect of this spell is placed on creature (for bless-curse, haste-slow, and similar pairs)
CSpell();
~CSpell();
bool isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const;
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
ETargetType getTargetType() const; //deprecated
CSpell::TargetInfo getTargetInfo(const int level) const;
bool isCombatSpell() const;
bool isAdventureSpell() const;
bool isCreatureAbility() const;
bool isPositive() const;
bool isNegative() const;
bool isNeutral() const;
bool isDamageSpell() const;
bool isHealingSpell() const;
bool isRisingSpell() const;
bool isOffensiveSpell() const;
bool isSpecialSpell() const;
bool hasEffects() const;
void getEffects(std::vector<Bonus> &lst, const int level) const;
///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
//internal, for use only by Mechanics classes
ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
//checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
//internal, for use only by Mechanics classes. applying secondary skills
ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
ui32 calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
///selects from allStacks actually affected stacks
std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
si32 getCost(const int skillLevel) const;
/**
* Returns spell level power, base power ignored
*/
si32 getPower(const int skillLevel) const;
si32 getProbability(const TFaction factionId) const;
/**
* Calls cb for each school this spell belongs to
*
* Set stop to true to abort looping
*/
void forEachSchool(const std::function<void (const SpellSchoolInfo &, bool &)> & cb) const;
/**
* Returns resource name of icon for SPELL_IMMUNITY bonus
*/
const std::string& getIconImmune() const;
const std::string& getCastSound() const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & identifier & id & name & level & power
& probabilities & attributes & combatSpell & creatureAbility & positiveness & counteredSpells;
h & isRising & isDamage & isOffensive;
h & targetType;
h & immunities & limiters & absoluteImmunities & absoluteLimiters;
h & iconImmune;
h & defaultProbability;
h & isSpecial;
h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll;
h & levels;
h & school;
h & animationInfo;
if(!h.saving)
setup();
}
friend class CSpellHandler;
friend class Graphics;
public:
///Server logic. Has write access to GameState via packets.
///May be executed on client side by (future) non-cheat-proof scripts.
bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
public:
///Client-server logic. Has direct write access to GameState.
///Shall be called (only) when applying packets on BOTH SIDES
///implementation of BattleSpellCast applying
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const;
private:
void setIsOffensive(const bool val);
void setIsRising(const bool val);
//call this after load or deserialization. cant be done in constructor.
void setup();
void setupMechanics();
private:
si32 defaultProbability;
bool isRising;
bool isDamage;
bool isOffensive;
bool isSpecial;
std::string attributes; //reference only attributes //todo: remove or include in configuration format, currently unused
ETargetType targetType;
std::vector<Bonus::BonusType> immunities; //any of these grants immunity
std::vector<Bonus::BonusType> absoluteImmunities; //any of these grants immunity, can't be negated
std::vector<Bonus::BonusType> limiters; //all of them are required to be affected
std::vector<Bonus::BonusType> absoluteLimiters; //all of them are required to be affected, can't be negated
///graphics related stuff
std::string iconImmune;
std::string iconBook;
std::string iconEffect;
std::string iconScenarioBonus;
std::string iconScroll;
///sound related stuff
std::string castSound;
std::vector<LevelInfo> levels;
ISpellMechanics * mechanics;//(!) do not serialize
};
bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door
class DLL_LINKAGE CSpellHandler: public CHandlerBase<SpellID, CSpell>
{
public:
CSpellHandler();
virtual ~CSpellHandler();
///IHandler base
std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
void afterLoadFinalization() override;
void beforeValidate(JsonNode & object) override;
/**
* Gets a list of default allowed spells. OH3 spells are all allowed by default.
*
* @return a list of allowed spells, the index is the spell id and the value either 0 for not allowed or 1 for allowed
*/
std::vector<bool> getDefaultAllowed() const override;
const std::string getTypeName() const override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & objects ;
}
protected:
CSpell * loadFromJson(const JsonNode & json) override;
};

View File

@ -0,0 +1,95 @@
/*
* CreatureSpellMechanics.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CreatureSpellMechanics.h"
#include "../NetPacks.h"
#include "../BattleState.h"
///AcidBreathDamageMechanics
void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
//calculating dmg to display
ctx.sc.dmgToDisplay = parameters.usedSpellPower;
for(auto & attackedCre : ctx.attackedCres) //no immunities
{
BattleStackAttacked bsa;
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
bsa.spellID = owner->id;
bsa.damageAmount = parameters.usedSpellPower; //damage times the number of attackers
bsa.stackAttacked = (attackedCre)->ID;
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
ctx.si.stacks.push_back(bsa);
}
}
///DeathStareMechanics
void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
//calculating dmg to display
ctx.sc.dmgToDisplay = parameters.usedSpellPower;
if(!ctx.attackedCres.empty())
vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
for(auto & attackedCre : ctx.attackedCres)
{
BattleStackAttacked bsa;
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
bsa.spellID = owner->id;
bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
bsa.stackAttacked = (attackedCre)->ID;
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
ctx.si.stacks.push_back(bsa);
}
}
///DispellHelpfulMechanics
void DispellHelpfulMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{
DefaultSpellMechanics::applyBattle(battle, packet);
for(auto stackID : packet->affectedCres)
{
if(vstd::contains(packet->resisted, stackID))
continue;
CStack *s = battle->getStack(stackID);
s->popBonuses([&](const Bonus *b) -> bool
{
return Selector::positiveSpellEffects(b);
});
}
}
ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
{
TBonusListPtr spellBon = obj->getSpellBonuses();
bool hasPositiveSpell = false;
for(const Bonus * b : *spellBon)
{
if(SpellID(b->sid).toSpell()->isPositive())
{
hasPositiveSpell = true;
break;
}
}
if(!hasPositiveSpell)
{
return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
}
//use default algorithm only if there is no mechanics-related problem
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
}

View File

@ -0,0 +1,40 @@
/*
* CreatureSpellMechanics.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "ISpellMechanics.h"
#include "CDefaultSpellMechanics.h"
class DLL_LINKAGE AcidBreathDamageMechanics : public DefaultSpellMechanics
{
public:
AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
class DLL_LINKAGE DeathStareMechanics : public DefaultSpellMechanics
{
public:
DeathStareMechanics(CSpell * s): DefaultSpellMechanics(s){};
protected:
void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
};
class DLL_LINKAGE DispellHelpfulMechanics : public DefaultSpellMechanics
{
public:
DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
};

View File

@ -0,0 +1,87 @@
/*
* ISpellMechanics.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "ISpellMechanics.h"
#include "CDefaultSpellMechanics.h"
#include "AdventureSpellMechanics.h"
#include "BattleSpellMechanics.h"
#include "CreatureSpellMechanics.h"
///ISpellMechanics
ISpellMechanics::ISpellMechanics(CSpell * s):
owner(s)
{
}
ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
{
switch (s->id)
{
case SpellID::ACID_BREATH_DAMAGE:
return new AcidBreathDamageMechanics(s);
case SpellID::CHAIN_LIGHTNING:
return new ChainLightningMechanics(s);
case SpellID::CLONE:
return new CloneMechanics(s);
case SpellID::CURE:
return new CureMechanics(s);
case SpellID::DEATH_STARE:
return new DeathStareMechanics(s);
case SpellID::DISPEL:
return new DispellMechanics(s);
case SpellID::DISPEL_HELPFUL_SPELLS:
return new DispellHelpfulMechanics(s);
case SpellID::FIRE_WALL:
case SpellID::FORCE_FIELD:
return new WallMechanics(s);
case SpellID::HYPNOTIZE:
return new HypnotizeMechanics(s);
case SpellID::LAND_MINE:
case SpellID::QUICKSAND:
return new ObstacleMechanics(s);
case SpellID::REMOVE_OBSTACLE:
return new RemoveObstacleMechanics(s);
case SpellID::SACRIFICE:
return new SacrificeMechanics(s);
case SpellID::SUMMON_FIRE_ELEMENTAL:
case SpellID::SUMMON_EARTH_ELEMENTAL:
case SpellID::SUMMON_WATER_ELEMENTAL:
case SpellID::SUMMON_AIR_ELEMENTAL:
return new SummonMechanics(s);
case SpellID::TELEPORT:
return new TeleportMechanics(s);
case SpellID::SUMMON_BOAT:
return new SummonBoatMechanics(s);
case SpellID::SCUTTLE_BOAT:
return new ScuttleBoatMechanics(s);
case SpellID::DIMENSION_DOOR:
return new DimensionDoorMechanics(s);
case SpellID::FLY:
case SpellID::WATER_WALK:
case SpellID::VISIONS:
case SpellID::DISGUISE:
return new DefaultSpellMechanics(s); //implemented using bonus system
case SpellID::TOWN_PORTAL:
return new TownPortalMechanics(s);
case SpellID::VIEW_EARTH:
return new ViewEarthMechanics(s);
case SpellID::VIEW_AIR:
return new ViewAirMechanics(s);
default:
if(s->isRisingSpell())
return new SpecialRisingSpellMechanics(s);
else
return new DefaultSpellMechanics(s);
}
}

View File

@ -1,5 +1,5 @@
/*
* SpellMechanics.h, part of VCMI engine
* ISpellMechanics.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@ -7,48 +7,43 @@
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CSpellHandler.h"
#include "BattleHex.h"
#include "../BattleHex.h"
class DLL_LINKAGE ISpellMechanics
{
public:
struct DLL_LINKAGE SpellTargetingContext
{
const CBattleInfoCallback * cb;
const CBattleInfoCallback * cb;
CSpell::TargetInfo ti;
ECastingMode::ECastingMode mode;
BattleHex destination;
PlayerColor casterColor;
int schoolLvl;
SpellTargetingContext(const CSpell * s, const CBattleInfoCallback * c, ECastingMode::ECastingMode m, PlayerColor cc, int lvl, BattleHex dest)
: cb(c), ti(s,lvl, m), mode(m), destination(dest), casterColor(cc), schoolLvl(lvl)
{};
};
public:
ISpellMechanics(CSpell * s);
virtual ~ISpellMechanics(){};
virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const = 0;
virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
//virtual bool adventureCast(const SpellCastContext & context) const = 0;
virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
static ISpellMechanics * createMechanics(CSpell * s);
virtual void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
protected:
CSpell * owner;
};
virtual ~ISpellMechanics(){};
virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
static ISpellMechanics * createMechanics(CSpell * s);
protected:
CSpell * owner;
};

View File

@ -0,0 +1,27 @@
/*
* ViewSpellInt.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "ViewSpellInt.h"
#include "../mapObjects/CObjectHandler.h"
ObjectPosInfo::ObjectPosInfo():
pos(),id(Obj::NO_OBJ), subId(-1), owner(PlayerColor::CANNOT_DETERMINE)
{
}
ObjectPosInfo::ObjectPosInfo(const CGObjectInstance * obj):
pos(obj->pos),id(obj->ID), subId(obj->subID), owner(obj->tempOwner)
{
}

32
lib/spells/ViewSpellInt.h Normal file
View File

@ -0,0 +1,32 @@
/*
* ViewSpellInt.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../int3.h"
#include "../GameConstants.h"
class CGObjectInstance;
struct DLL_LINKAGE ObjectPosInfo
{
int3 pos;
Obj id;
si32 subId;
PlayerColor owner;
ObjectPosInfo();
ObjectPosInfo(const CGObjectInstance * obj);
template <typename Handler> void serialize(Handler & h, const int version)
{
h & pos & id & subId & owner;
}
};

View File

@ -9,7 +9,7 @@
#include "../lib/CArtHandler.h"
#include "../lib/CBuildingHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CCreatureHandler.h"
@ -67,8 +67,11 @@ public:
void sendAndApply(CPackForClient * info) const override;
CRandomGenerator & getRandomGenerator() const override;
void complain(const std::string & problem) const override;
const CMap * getMap() const override;
const CGameInfoCallback * getCb() const override;
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const override;
private:
CGameHandler * gh;
mutable CGameHandler * gh;
};
CondSh<bool> battleMadeAction;
@ -4970,222 +4973,12 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos)
{
const CSpell *s = spellID.toSpell();
int cost = h->getSpellCost(s);
int schoolLevel = h->getSpellSchoolLevel(s);
if(!h->canCastThisSpell(s))
COMPLAIN_RET("Hero cannot cast this spell!");
if(h->mana < cost)
COMPLAIN_RET("Hero doesn't have enough spell points to cast this spell!");
if(s->combatSpell)
COMPLAIN_RET("This function can be used only for adventure map spells!");
AdvmapSpellCast asc;
asc.caster = h;
asc.spellID = spellID;
sendAndApply(&asc);
switch(spellID)
{
case SpellID::SUMMON_BOAT:
{
//check if spell works at all
if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
iw.text.addReplacement(h->name);
sendAndApply(&iw);
break;
}
//try to find unoccupied boat to summon
const CGBoat *nearest = nullptr;
double dist = 0;
int3 summonPos = h->bestLocation();
if(summonPos.x < 0)
COMPLAIN_RET("There is no water tile available!");
for(const CGObjectInstance *obj : gs->map->objects)
{
if(obj && obj->ID == Obj::BOAT)
{
const CGBoat *b = static_cast<const CGBoat*>(obj);
if(b->hero) continue; //we're looking for unoccupied boat
double nDist = distance(b->pos, h->getPosition());
if(!nearest || nDist < dist) //it's first boat or closer than previous
{
nearest = b;
dist = nDist;
}
}
}
if(nearest) //we found boat to summon
{
ChangeObjPos cop;
cop.objid = nearest->id;
cop.nPos = summonPos + int3(1,0,0);;
cop.flags = 1;
sendAndApply(&cop);
}
else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
sendAndApply(&iw);
}
else //create boat
{
NewObject no;
no.ID = Obj::BOAT;
no.subID = h->getBoatType();
no.pos = summonPos + int3(1,0,0);;
sendAndApply(&no);
}
break;
}
case SpellID::SCUTTLE_BOAT:
{
//check if spell works at all
if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
iw.text.addReplacement(h->name);
sendAndApply(&iw);
break;
}
if(!gs->map->isInTheMap(pos))
COMPLAIN_RET("Invalid dst tile for scuttle!");
//TODO: test range, visibility
const TerrainTile *t = &gs->map->getTile(pos);
if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
COMPLAIN_RET("There is no boat to scuttle!");
RemoveObject ro;
ro.id = t->visitableObjects.back()->id;
sendAndApply(&ro);
break;
}
case SpellID::DIMENSION_DOOR:
{
const TerrainTile *dest = getTile(pos);
const TerrainTile *curr = getTile(h->getSightCenter());
if(!dest)
COMPLAIN_RET("Destination tile doesn't exist!");
if(!h->movement)
COMPLAIN_RET("Hero needs movement points to cast Dimension Door!");
if(h->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= s->getPower(schoolLevel)) //limit casts per turn
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
iw.text.addReplacement(h->name);
sendAndApply(&iw);
break;
}
GiveBonus gb;
gb.id = h->id.getNum();
gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, SpellID::DIMENSION_DOOR);
sendAndApply(&gb);
if(!dest->isClear(curr)) //wrong dest tile
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
sendAndApply(&iw);
break;
}
if (moveHero(h->id, pos + h->getVisitableOffset(), true))
{
SetMovePoints smp;
smp.hid = h->id;
smp.val = std::max<ui32>(0, h->movement - 300);
sendAndApply(&smp);
}
}
break;
case SpellID::FLY:
{
int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
GiveBonus gb;
gb.id = h->id.getNum();
gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::FLYING_MOVEMENT, Bonus::SPELL_EFFECT, 0, SpellID::FLY, subtype);
sendAndApply(&gb);
}
break;
case SpellID::WATER_WALK:
{
int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
GiveBonus gb;
gb.id = h->id.getNum();
gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::WATER_WALKING, Bonus::SPELL_EFFECT, 0, SpellID::WATER_WALK, subtype);
sendAndApply(&gb);
}
break;
case SpellID::TOWN_PORTAL:
{
if (!gs->map->isInTheMap(pos))
COMPLAIN_RET("Destination tile not present!")
TerrainTile tile = gs->map->getTile(pos);
if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN )
COMPLAIN_RET("Town not found for Town Portal!");
CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
if (town->tempOwner != h->tempOwner)
COMPLAIN_RET("Can't teleport to another player!");
if (town->visitingHero)
COMPLAIN_RET("Can't teleport to occupied town!");
if (h->getSpellSchoolLevel(s) < 2)
{
si32 dist = town->pos.dist2dSQ(h->pos);
ObjectInstanceID nearest = town->id; //nearest town's ID
for(const CGTownInstance * currTown : gs->getPlayer(h->tempOwner)->towns)
{
si32 currDist = currTown->pos.dist2dSQ(h->pos);
if (currDist < dist)
{
nearest = currTown->id;
dist = currDist;
}
}
if (town->id != nearest)
COMPLAIN_RET("This hero can only teleport to nearest town!")
}
moveHero(h->id, town->visitablePos() + h->getVisitableOffset() ,1);
}
break;
case SpellID::VISIONS:
case SpellID::VIEW_EARTH:
case SpellID::DISGUISE:
case SpellID::VIEW_AIR:
default:
COMPLAIN_RET("This spell is not implemented yet!");
}
SetMana sm;
sm.hid = h->id;
sm.absolute = false;
sm.val = -cost;
sendAndApply(&sm);
return true;
AdventureSpellCastParameters p;
p.caster = h;
p.pos = pos;
return s->adventureCast(spellEnv, p);
}
void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h)
@ -6010,7 +5803,7 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
winnerHero = loserHero = nullptr;
}
///ServerSpellCastEnvironment
ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
{
@ -6030,3 +5823,20 @@ void ServerSpellCastEnvironment::complain(const std::string& problem) const
{
gh->complain(problem);
}
const CGameInfoCallback * ServerSpellCastEnvironment::getCb() const
{
return gh;
}
const CMap * ServerSpellCastEnvironment::getMap() const
{
return gh->gameState()->map;
}
bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker) const
{
return gh->moveHero(hid, dst, teleporting, asker);
}

View File

@ -12,7 +12,7 @@
#include "../lib/CHeroHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CBuildingHandler.h"
#include "../lib/CSpellHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CCreatureHandler.h"
#include "zlib.h"
#include "CVCMIServer.h"