From 4d62bf22f2793d911375d9f7dad6cd73f1cc960e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 21 Jan 2023 16:14:22 +0200 Subject: [PATCH 01/13] Removed no longer used code --- client/battle/BattleInterfaceClasses.cpp | 36 +++++------------------- client/battle/BattleInterfaceClasses.h | 2 -- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 7ee7707ff..30b7ff447 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -291,7 +291,7 @@ void BattleHero::clickLeft(tribool down, bool previousState) if(boost::logic::indeterminate(down)) return; - if(!hero || down || !owner.myTurn) + if(!hero || down || !owner.makingTurn()) return; if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions @@ -317,7 +317,7 @@ void BattleHero::clickRight(tribool down, bool previousState) windowPosition.y = owner.fieldController->pos.y + 135; InfoAboutHero targetHero; - if(down && (owner.myTurn || settings["session"]["spectate"].Bool())) + if(down && (owner.makingTurn() || settings["session"]["spectate"].Bool())) { auto h = defender ? owner.defendingHeroInstance : owner.attackingHeroInstance; targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE); @@ -685,14 +685,12 @@ void ClickableHex::hover(bool on) { hovered = on; //Hoverable::hover(on); - if(!on && setAlterText) - { - GH.statusbar->clear(); - setAlterText = false; - } } -ClickableHex::ClickableHex() : setAlterText(false), myNumber(-1), strictHovered(false), myInterface(nullptr) +ClickableHex::ClickableHex() + : myNumber(-1) + , strictHovered(false) + , myInterface(nullptr) { addUsedEvents(LCLICK | RCLICK | HOVER | MOVE); } @@ -700,33 +698,13 @@ ClickableHex::ClickableHex() : setAlterText(false), myNumber(-1), strictHovered( void ClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent) { strictHovered = myInterface->fieldController->isPixelInHex(Point(sEvent.x-pos.x, sEvent.y-pos.y)); - - if(hovered && strictHovered) //print attacked creature to console - { - const CStack * attackedStack = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber); - if( attackedStack != nullptr && - attackedStack->owner != myInterface->getCurrentPlayerInterface()->playerID && - attackedStack->alive()) - { - MetaString text; - text.addTxt(MetaString::GENERAL_TXT, 220); - attackedStack->addNameReplacement(text); - GH.statusbar->write(text.toString()); - setAlterText = true; - } - } - else if(setAlterText) - { - GH.statusbar->clear(); - setAlterText = false; - } } void ClickableHex::clickLeft(tribool down, bool previousState) { if(!down && hovered && strictHovered) //we've been really clicked! { - myInterface->actionsController->handleHex(myNumber, LCLICK); + myInterface->actionsController->onHexClicked(myNumber); } } diff --git a/client/battle/BattleInterfaceClasses.h b/client/battle/BattleInterfaceClasses.h index 5413a1399..a785510bd 100644 --- a/client/battle/BattleInterfaceClasses.h +++ b/client/battle/BattleInterfaceClasses.h @@ -175,8 +175,6 @@ public: /// Class which stands for a single hex field on a battlefield class ClickableHex : public CIntObject { -private: - bool setAlterText; //if true, this hex has set alternative text in console and will clean it public: ui32 myNumber; //number of hex in commonly used format bool strictHovered; //for determining if hex is hovered by mouse (this is different problem than hex's graphic hovering) From b1c67fbf470ea80f6378181fc3671aaf8c2eb52c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 21 Jan 2023 16:14:46 +0200 Subject: [PATCH 02/13] Better formatting for enum --- lib/battle/CBattleInfoCallback.h | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 2ef124aeb..fc6283270 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -44,13 +44,27 @@ struct DLL_LINKAGE AttackableTiles enum class PossiblePlayerBattleAction // actions performed at l-click { - INVALID = -1, CREATURE_INFO, - MOVE_TACTICS, CHOOSE_TACTICS_STACK, - MOVE_STACK, ATTACK, WALK_AND_ATTACK, ATTACK_AND_RETURN, SHOOT, //OPEN_GATE, //we can open castle gate during siege - NO_LOCATION, ANY_LOCATION, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL, - FREE_LOCATION, //used with Force Field and Fire Wall - all tiles affected by spell must be free - CATAPULT, HEAL, - AIMED_SPELL_CREATURE + INVALID = -1, + CREATURE_INFO, + MOVE_TACTICS, + CHOOSE_TACTICS_STACK, + + MOVE_STACK, + ATTACK, + WALK_AND_ATTACK, + ATTACK_AND_RETURN, + SHOOT, + CATAPULT, + HEAL, + + NO_LOCATION, // massive spells that affect every possible target, automatic casts + ANY_LOCATION, + OBSTACLE, + TELEPORT, + SACRIFICE, + RANDOM_GENIE_SPELL, // random spell on a friendly creature + FREE_LOCATION, // used with Force Field and Fire Wall - all tiles affected by spell must be free + AIMED_SPELL_CREATURE, // spell targeted at creature }; struct DLL_LINKAGE BattleClientInterfaceData From a2035122e118b31a04a0f40040247d05e54a2f25 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 21 Jan 2023 16:15:20 +0200 Subject: [PATCH 03/13] Removed makingTurn variable --- client/battle/BattleInterface.cpp | 9 ++++++--- client/battle/BattleInterface.h | 3 ++- client/battle/BattleWindow.cpp | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index e78df4e56..3ad3e62fe 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -54,7 +54,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet * , attackerInt(att) , defenderInt(defen) , curInt(att) - , myTurn(false) , moveSoundHander(-1) { for ( auto & event : animationEvents) @@ -258,6 +257,7 @@ void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 addit ba->actionSubtype = additional; sendCommand(ba, actor); + CCS->curh->set(Cursor::Combat::POINTER); } void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor) @@ -267,7 +267,6 @@ void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor) if(!tacticsMode) { logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero")); - myTurn = false; stacksController->setActiveStack(nullptr); givenCommand.setn(command); } @@ -553,7 +552,6 @@ void BattleInterface::activateStack() if(!s) return; - myTurn = true; windowObject->updateQueue(); windowObject->blockUI(false); fieldController->redrawBackgroundWithHexes(); @@ -561,6 +559,11 @@ void BattleInterface::activateStack() GH.fakeMouseMove(); } +bool BattleInterface::makingTurn() const +{ + return stacksController->getActiveStack() != nullptr; +} + void BattleInterface::endAction(const BattleAction* action) { const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber); diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index 394ed3b9c..adec40210 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -146,7 +146,8 @@ public: static CondSh givenCommand; //data != nullptr if we have i.e. moved current unit - bool myTurn; //if true, interface is active (commands can be ordered) + bool makingTurn() const; + int moveSoundHander; // sound handler used when moving a unit BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 4279352bc..9bf24232d 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -391,7 +391,7 @@ void BattleWindow::bSpellf() if (owner.actionsController->spellcastingModeActive()) return; - if (!owner.myTurn) + if (!owner.makingTurn()) return; auto myHero = owner.currentHero(); From 55a58596bcd4a7275d8aa62ecce97cc61f1856a3 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 21 Jan 2023 19:42:44 +0200 Subject: [PATCH 04/13] Mostly finished refactoring of BattleActionsController TODO: test actions TODO: test casters TODO: fix random casters --- client/battle/BattleActionsController.cpp | 1013 +++++++++++---------- client/battle/BattleActionsController.h | 84 +- client/battle/BattleFieldController.cpp | 10 +- client/battle/BattleStacksController.cpp | 39 +- client/battle/BattleStacksController.h | 7 - 5 files changed, 569 insertions(+), 584 deletions(-) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index b37df10d3..d2a5d43cd 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -40,71 +40,66 @@ static std::string formatDmgRange(std::pair dmgRange) return (boost::format("%d") % dmgRange.first).str(); } - BattleActionsController::BattleActionsController(BattleInterface & owner): owner(owner), - creatureCasting(false), - spellDestSelectMode(false), - spellToCast(nullptr), - currentSpell(nullptr) -{ - currentAction = PossiblePlayerBattleAction::INVALID; - selectedAction = PossiblePlayerBattleAction::INVALID; -} + heroSpellToCast(nullptr) +{} void BattleActionsController::endCastingSpell() { - if(spellDestSelectMode) + if(heroSpellToCast) { - spellToCast.reset(); - - currentSpell = nullptr; - spellDestSelectMode = false; - CCS->curh->set(Cursor::Combat::POINTER); + heroSpellToCast.reset(); if(owner.stacksController->getActiveStack()) - { possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared - owner.myTurn = true; - } } else { if(owner.stacksController->getActiveStack()) - { possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); - GH.fakeMouseMove(); - } } + GH.fakeMouseMove(); +} + +bool BattleActionsController::isActiveStackFixedSpellcaster() const +{ + const CStack * casterStack = owner.stacksController->getActiveStack(); + assert(casterStack); + + const auto randomSpellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER)); + return (randomSpellcaster && casterStack->canCast()); +} + +bool BattleActionsController::isActiveStackRandomSpellcaster() const +{ + const CStack * casterStack = owner.stacksController->getActiveStack(); + assert(casterStack); + + const auto randomSpellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER)); + return (randomSpellcaster && casterStack->canCast()); } void BattleActionsController::enterCreatureCastingMode() { //silently check for possible errors - if (!owner.myTurn) - return; - if (owner.tacticsMode) return; //hero is casting a spell - if (spellDestSelectMode) + if (heroSpellToCast) return; if (!owner.stacksController->getActiveStack()) return; - if (!owner.stacksController->activeStackSpellcaster()) - return; - - //random spellcaster - if (owner.stacksController->activeStackSpellToCast() == SpellID::NONE) + if (!isActiveStackFixedSpellcaster()) return; if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION)) { const spells::Caster * caster = owner.stacksController->getActiveStack(); - const CSpell * spell = owner.stacksController->activeStackSpellToCast().toSpell(); + const CSpell * spell = getStackSpellToCast(BattleHex::INVALID); spells::Target target; target.emplace_back(); @@ -118,8 +113,7 @@ void BattleActionsController::enterCreatureCastingMode() if (isCastingPossible) { - owner.myTurn = false; - owner.giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, owner.stacksController->activeStackSpellToCast()); + owner.giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, spell->getId()); owner.stacksController->setSelectedStack(nullptr); CCS->curh->set(Cursor::Combat::POINTER); @@ -144,10 +138,12 @@ void BattleActionsController::enterCreatureCastingMode() std::vector BattleActionsController::getPossibleActionsForStack(const CStack *stack) const { BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass - data.creatureSpellToCast = owner.stacksController->activeStackSpellToCast(); + data.creatureSpellToCast = getStackSpellToCast(BattleHex::INVALID)->getId(); data.tacticsMode = owner.tacticsMode; auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data); + allActions.push_back(PossiblePlayerBattleAction::CREATURE_INFO); + return std::vector(allActions); } @@ -185,39 +181,39 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac return 9; break; case PossiblePlayerBattleAction::HEAL: return 10; break; + case PossiblePlayerBattleAction::CREATURE_INFO: + return 11; break; default: + assert(0); return 200; break; } }; auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs) { - return assignPriority(lhs) > assignPriority(rhs); + return assignPriority(lhs) < assignPriority(rhs); }; - std::make_heap(possibleActions.begin(), possibleActions.end(), comparer); + std::sort(possibleActions.begin(), possibleActions.end(), comparer); } void BattleActionsController::castThisSpell(SpellID spellID) { - spellToCast = std::make_shared(); - spellToCast->actionType = EActionType::HERO_SPELL; - spellToCast->actionSubtype = spellID; //spell number - spellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2; - spellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false; - spellDestSelectMode = true; - creatureCasting = false; + heroSpellToCast = std::make_shared(); + heroSpellToCast->actionType = EActionType::HERO_SPELL; + heroSpellToCast->actionSubtype = spellID; //spell number + heroSpellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2; + heroSpellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false; //choosing possible targets const CGHeroInstance *castingHero = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? owner.attackingHeroInstance : owner.defendingHeroInstance; assert(castingHero); // code below assumes non-null hero - currentSpell = spellID.toSpell(); - PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(currentSpell, castingHero, spells::Mode::HERO); + PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(spellID.toSpell(), castingHero, spells::Mode::HERO); if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location { - spellToCast->aimToHex(BattleHex::INVALID); - owner.curInt->cb->battleMakeAction(spellToCast.get()); + heroSpellToCast->aimToHex(BattleHex::INVALID); + owner.curInt->cb->battleMakeAction(heroSpellToCast.get()); endCastingSpell(); } else @@ -228,499 +224,521 @@ void BattleActionsController::castThisSpell(SpellID spellID) } } - -void BattleActionsController::handleHex(BattleHex myNumber, int eventType) +const CSpell * BattleActionsController::getHeroSpellToCast( ) const { - if (!owner.myTurn) //we are not permit to do anything - return; + if (heroSpellToCast) + return SpellID(heroSpellToCast->actionSubtype).toSpell(); + return nullptr; +} - // This function handles mouse move over hexes and l-clicking on them. - // First we decide what happens if player clicks on this hex and set appropriately - // consoleMsg, cursorFrame/Type and prepare lambda realizeAction. - // - // Then, depending whether it was hover/click we either call the action or set tooltip/cursor. +const CSpell * BattleActionsController::getStackSpellToCast( BattleHex targetHex ) const +{ + return nullptr; +} - std::string newConsoleMsg; - //used when hovering -> tooltip message and cursor to be set - bool setCursor = true; //if we want to suppress setting cursor - bool spellcastingCursor = false; - auto cursorFrame = Cursor::Combat::POINTER; +const CSpell * BattleActionsController::getAnySpellToCast( BattleHex targetHex ) const +{ + if (getHeroSpellToCast()) + return getHeroSpellToCast(); + return getStackSpellToCast(targetHex); +} - //used when l-clicking -> action to be called upon the click - std::function realizeAction; +const CStack * BattleActionsController::getStackForHex(BattleHex hoveredHex) +{ + const CStack * shere = owner.curInt->cb->battleGetStackByPos(hoveredHex, true); + if(shere) + return shere; + return owner.curInt->cb->battleGetStackByPos(hoveredHex, false); +} - //Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks. - const CStack * shere = owner.curInt->cb->battleGetStackByPos(myNumber, true); - if(!shere) - shere = owner.curInt->cb->battleGetStackByPos(myNumber, false); - - if(!owner.stacksController->getActiveStack()) - return; - - bool ourStack = false; - if (shere) - ourStack = shere->owner == owner.curInt->playerID; - - localActions.clear(); - illegalActions.clear(); - - reorderPossibleActionsPriority(owner.stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX); - const bool forcedAction = possibleActions.size() == 1; - - for (PossiblePlayerBattleAction action : possibleActions) +void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action, BattleHex targetHex) +{ + switch (action) { - bool legalAction = false; //this action is legal and can be performed - bool notLegal = false; //this action is not legal and should display message + case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK: + CCS->curh->set(Cursor::Combat::POINTER); + return; - switch (action) - { - case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK: - if (shere && ourStack) - legalAction = true; - break; - case PossiblePlayerBattleAction::MOVE_TACTICS: - case PossiblePlayerBattleAction::MOVE_STACK: - { - if (!(shere && shere->alive())) //we can walk on dead stacks - { - if(canStackMoveHere(owner.stacksController->getActiveStack(), myNumber)) - legalAction = true; - } - break; - } - case PossiblePlayerBattleAction::ATTACK: - case PossiblePlayerBattleAction::WALK_AND_ATTACK: - case PossiblePlayerBattleAction::ATTACK_AND_RETURN: - { - if(owner.curInt->cb->battleCanAttack(owner.stacksController->getActiveStack(), shere, myNumber)) - { - if (owner.fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack? - { - BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber); + case PossiblePlayerBattleAction::MOVE_TACTICS: + case PossiblePlayerBattleAction::MOVE_STACK: + if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING)) + CCS->curh->set(Cursor::Combat::FLY); + else + CCS->curh->set(Cursor::Combat::MOVE); + return; - if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308) - legalAction = true; - } - } - } - break; - case PossiblePlayerBattleAction::SHOOT: - if(owner.curInt->cb->battleCanShoot(owner.stacksController->getActiveStack(), myNumber)) - legalAction = true; - break; - case PossiblePlayerBattleAction::ANY_LOCATION: - if (myNumber > -1) //TODO: this should be checked for all actions - { - if(isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber)) - legalAction = true; - } - break; - case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: - if(shere && isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber)) - legalAction = true; - break; - case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: - { - if(shere && ourStack && shere != owner.stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures - { - int spellID = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE); - if(spellID > -1) - { - legalAction = true; - } - } - } - break; - case PossiblePlayerBattleAction::OBSTACLE: - if(isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber)) - legalAction = true; - break; - case PossiblePlayerBattleAction::TELEPORT: - { - //todo: move to mechanics - ui8 skill = 0; - if (creatureCasting) - skill = owner.stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell()); - else - skill = owner.getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell()); - //TODO: explicitely save power, skill - if (owner.curInt->cb->battleCanTeleportTo(owner.stacksController->getSelectedStack(), myNumber, skill)) - legalAction = true; - else - notLegal = true; - } - break; - case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice - if (shere && shere != owner.stacksController->getSelectedStack() && ourStack && shere->alive()) - legalAction = true; - else - notLegal = true; - break; - case PossiblePlayerBattleAction::FREE_LOCATION: - legalAction = true; - if(!isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber)) - { - legalAction = false; - notLegal = true; - } - break; - case PossiblePlayerBattleAction::CATAPULT: - if (owner.siegeController && owner.siegeController->isAttackableByCatapult(myNumber)) - legalAction = true; - break; - case PossiblePlayerBattleAction::HEAL: - if (shere && ourStack && shere->canBeHealed()) - legalAction = true; - break; - } - if (legalAction) - localActions.push_back (action); - else if (notLegal || forcedAction) - illegalActions.push_back (action); + case PossiblePlayerBattleAction::ATTACK: + case PossiblePlayerBattleAction::WALK_AND_ATTACK: + case PossiblePlayerBattleAction::ATTACK_AND_RETURN: + owner.fieldController->setBattleCursor(targetHex); + return; + + case PossiblePlayerBattleAction::SHOOT: + if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), targetHex)) + CCS->curh->set(Cursor::Combat::SHOOT_PENALTY); + else + CCS->curh->set(Cursor::Combat::SHOOT); + return; + + case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: + case PossiblePlayerBattleAction::ANY_LOCATION: + case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: + case PossiblePlayerBattleAction::FREE_LOCATION: + case PossiblePlayerBattleAction::OBSTACLE: + CCS->curh->set(Cursor::Spellcast::SPELL); + return; + + case PossiblePlayerBattleAction::TELEPORT: + CCS->curh->set(Cursor::Combat::TELEPORT); + return; + + case PossiblePlayerBattleAction::SACRIFICE: + CCS->curh->set(Cursor::Combat::SACRIFICE); + return; + + case PossiblePlayerBattleAction::HEAL: + CCS->curh->set(Cursor::Combat::HEAL); + return; + + case PossiblePlayerBattleAction::CATAPULT: + CCS->curh->set(Cursor::Combat::SHOOT_CATAPULT); + return; + + case PossiblePlayerBattleAction::CREATURE_INFO: + CCS->curh->set(Cursor::Combat::QUERY); + return; } - illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place + assert(0); +} - if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default - currentAction = selectedAction; - else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority) - currentAction = localActions.front(); - else //no legal action possible +void BattleActionsController::actionSetCursorBlocked(PossiblePlayerBattleAction action, BattleHex targetHex) +{ + switch (action) { - currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything - - if (vstd::contains(illegalActions, selectedAction)) - illegalAction = selectedAction; - else if (illegalActions.size()) - illegalAction = illegalActions.front(); - else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature - { - currentAction = PossiblePlayerBattleAction::CREATURE_INFO; - } - else - illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here + case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: + case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: + case PossiblePlayerBattleAction::TELEPORT: + case PossiblePlayerBattleAction::SACRIFICE: + case PossiblePlayerBattleAction::FREE_LOCATION: + CCS->curh->set(Cursor::Combat::BLOCKED); + return; + default: + if (targetHex == -1) + CCS->curh->set(Cursor::Combat::POINTER); + else + CCS->curh->set(Cursor::Combat::BLOCKED); + return; } + assert(0); +} - bool isCastingPossible = false; - bool secondaryTarget = false; +std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattleAction action, BattleHex targetHex) +{ + const CStack * targetStack = getStackForHex(targetHex); - if (currentAction > PossiblePlayerBattleAction::INVALID) + switch (action) //display console message, realize selected action { - switch (currentAction) //display console message, realize selected action - { - case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK: - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s - realizeAction = [=](){ owner.stackActivated(shere); }; - break; - case PossiblePlayerBattleAction::MOVE_TACTICS: - case PossiblePlayerBattleAction::MOVE_STACK: - if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING)) - { - cursorFrame = Cursor::Combat::FLY; - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here - } - else - { - cursorFrame = Cursor::Combat::MOVE; - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here - } + case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK: + return (boost::format(CGI->generaltexth->allTexts[481]) % targetStack->getName()).str(); //Select %s - realizeAction = [=]() - { - if(owner.stacksController->getActiveStack()->doubleWide()) - { - std::vector acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack()); - BattleHex shiftedDest = myNumber.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false); - if(vstd::contains(acc, myNumber)) - owner.giveCommand(EActionType::WALK, myNumber); - else if(vstd::contains(acc, shiftedDest)) - owner.giveCommand(EActionType::WALK, shiftedDest); - } - else - { - owner.giveCommand(EActionType::WALK, myNumber); - } - }; - break; - case PossiblePlayerBattleAction::ATTACK: - case PossiblePlayerBattleAction::WALK_AND_ATTACK: - case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return - { - owner.fieldController->setBattleCursor(myNumber); //handle direction of cursor - setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean? + case PossiblePlayerBattleAction::MOVE_TACTICS: + case PossiblePlayerBattleAction::MOVE_STACK: + if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING)) + return (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here + else + return (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here - bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN; - - realizeAction = [=]() - { - BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber); - if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308) - { - auto command = new BattleAction(BattleAction::makeMeleeAttack(owner.stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack)); - owner.sendCommand(command, owner.stacksController->getActiveStack()); - } - }; - - BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber); - TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere, attackFromHex); - std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage) - } - break; - case PossiblePlayerBattleAction::SHOOT: + case PossiblePlayerBattleAction::ATTACK: + case PossiblePlayerBattleAction::WALK_AND_ATTACK: + case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return { - auto const * shooter = owner.stacksController->getActiveStack(); - - if (owner.curInt->cb->battleHasShootingPenalty(shooter, myNumber)) - cursorFrame = Cursor::Combat::SHOOT_PENALTY; - else - cursorFrame = Cursor::Combat::SHOOT; - - realizeAction = [=](){owner.giveCommand(EActionType::SHOOT, myNumber);}; - TDmgRange damage = owner.curInt->cb->battleEstimateDamage(shooter, shere, shooter->getPosition()); + BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex); + TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex); std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg - //printing - Shoot %s (%d shots left, %s damage) - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % shooter->shots.available() % estDmgText).str(); + return (boost::format(CGI->generaltexth->allTexts[36]) % targetStack->getName() % estDmgText).str(); //Attack %s (%s damage) } - break; - case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: - currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->getNameTranslated() % shere->getName()); //Cast %s on %s - switch (currentSpell->id) + + case PossiblePlayerBattleAction::SHOOT: + { + auto const * shooter = owner.stacksController->getActiveStack(); + + TDmgRange damage = owner.curInt->cb->battleEstimateDamage(shooter, targetStack, shooter->getPosition()); + std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg + //printing - Shoot %s (%d shots left, %s damage) + return (boost::format(CGI->generaltexth->allTexts[296]) % targetStack->getName() % shooter->shots.available() % estDmgText).str(); + } + + case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: + return boost::str(boost::format(CGI->generaltexth->allTexts[27]) % getAnySpellToCast(targetHex)->getNameTranslated() % targetStack->getName()); //Cast %s on %s + + case PossiblePlayerBattleAction::ANY_LOCATION: + return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getAnySpellToCast(targetHex)->getNameTranslated()); //Cast %s + + case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell + return boost::str(boost::format(CGI->generaltexth->allTexts[301]) % targetStack->getName()); //Cast a spell on % + + case PossiblePlayerBattleAction::TELEPORT: + return CGI->generaltexth->allTexts[25]; //Teleport Here + + case PossiblePlayerBattleAction::OBSTACLE: + return CGI->generaltexth->allTexts[550]; + + case PossiblePlayerBattleAction::SACRIFICE: + return (boost::format(CGI->generaltexth->allTexts[549]) % targetStack->getName()).str(); //sacrifice the %s + + case PossiblePlayerBattleAction::FREE_LOCATION: + return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getAnySpellToCast(targetHex)->getNameTranslated()); //Cast %s + + case PossiblePlayerBattleAction::HEAL: + return (boost::format(CGI->generaltexth->allTexts[419]) % targetStack->getName()).str(); //Apply first aid to the %s + + case PossiblePlayerBattleAction::CATAPULT: + return ""; // TODO + + case PossiblePlayerBattleAction::CREATURE_INFO: + return (boost::format(CGI->generaltexth->allTexts[297]) % targetStack->getName()).str(); + } + assert(0); + return ""; +} + +std::string BattleActionsController::actionGetStatusMessageBlocked(PossiblePlayerBattleAction action, BattleHex targetHex) +{ + switch (action) + { + case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: + case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: + return CGI->generaltexth->allTexts[23]; + break; + case PossiblePlayerBattleAction::TELEPORT: + return CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination + break; + case PossiblePlayerBattleAction::SACRIFICE: + return CGI->generaltexth->allTexts[543]; //choose army to sacrifice + break; + case PossiblePlayerBattleAction::FREE_LOCATION: + return boost::str(boost::format(CGI->generaltexth->allTexts[181]) % getAnySpellToCast(targetHex)->getNameTranslated()); //No room to place %s here + break; + default: + return ""; + } +} + +bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, BattleHex targetHex) +{ + const CStack * targetStack = getStackForHex(targetHex); + bool targetStackOwned = targetStack && targetStack->owner == owner.curInt->playerID; + + switch (action) + { + case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK: + case PossiblePlayerBattleAction::CREATURE_INFO: + return (targetStack && targetStackOwned); + + case PossiblePlayerBattleAction::MOVE_TACTICS: + case PossiblePlayerBattleAction::MOVE_STACK: + if (!(targetStack && targetStack->alive())) //we can walk on dead stacks + { + if(canStackMoveHere(owner.stacksController->getActiveStack(), targetHex)) + return true; + } + return false; + + case PossiblePlayerBattleAction::ATTACK: + case PossiblePlayerBattleAction::WALK_AND_ATTACK: + case PossiblePlayerBattleAction::ATTACK_AND_RETURN: + if(owner.curInt->cb->battleCanAttack(owner.stacksController->getActiveStack(), targetStack, targetHex)) + { + if (owner.fieldController->isTileAttackable(targetHex)) // move isTileAttackable to be part of battleCanAttack? + return true; + } + return false; + + case PossiblePlayerBattleAction::SHOOT: + return owner.curInt->cb->battleCanShoot(owner.stacksController->getActiveStack(), targetHex); + + case PossiblePlayerBattleAction::ANY_LOCATION: + return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex); + + case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: + return targetStack && isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex); + + case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: + if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures + { + int spellID = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), targetStack, CBattleInfoCallback::RANDOM_GENIE); + return spellID > -1; + } + return false; + + case PossiblePlayerBattleAction::TELEPORT: + { + ui8 skill = getCurrentSpellcaster()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell()); + return owner.curInt->cb->battleCanTeleportTo(owner.stacksController->getSelectedStack(), targetHex, skill); + } + + case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice + return targetStack && targetStack != owner.stacksController->getSelectedStack() && targetStackOwned && targetStack->alive(); + + case PossiblePlayerBattleAction::OBSTACLE: + case PossiblePlayerBattleAction::FREE_LOCATION: + return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex); + return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex); + + case PossiblePlayerBattleAction::CATAPULT: + return owner.siegeController && owner.siegeController->isAttackableByCatapult(targetHex); + + case PossiblePlayerBattleAction::HEAL: + return targetStack && targetStackOwned && targetStack->canBeHealed(); + } + + assert(0); + return false; +} + +void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, BattleHex targetHex) +{ + const CStack * targetStack = getStackForHex(targetHex); + + switch (action) //display console message, realize selected action + { + case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK: + { + owner.stackActivated(targetStack); + return; + } + + case PossiblePlayerBattleAction::MOVE_TACTICS: + case PossiblePlayerBattleAction::MOVE_STACK: + { + if(owner.stacksController->getActiveStack()->doubleWide()) + { + std::vector acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack()); + BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false); + if(vstd::contains(acc, targetHex)) + owner.giveCommand(EActionType::WALK, targetHex); + else if(vstd::contains(acc, shiftedDest)) + owner.giveCommand(EActionType::WALK, shiftedDest); + } + else + { + owner.giveCommand(EActionType::WALK, targetHex); + } + return; + } + + case PossiblePlayerBattleAction::ATTACK: + case PossiblePlayerBattleAction::WALK_AND_ATTACK: + case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return + { + bool returnAfterAttack = action == PossiblePlayerBattleAction::ATTACK_AND_RETURN; + BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex); + if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308) + { + auto command = new BattleAction(BattleAction::makeMeleeAttack(owner.stacksController->getActiveStack(), targetHex, attackFromHex, returnAfterAttack)); + owner.sendCommand(command, owner.stacksController->getActiveStack()); + } + return; + } + + case PossiblePlayerBattleAction::SHOOT: + { + owner.giveCommand(EActionType::SHOOT, targetHex); + return; + } + + case PossiblePlayerBattleAction::HEAL: + { + owner.giveCommand(EActionType::STACK_HEAL, targetHex); + return; + }; + + case PossiblePlayerBattleAction::CATAPULT: + { + owner.giveCommand(EActionType::CATAPULT, targetHex); + return; + } + + case PossiblePlayerBattleAction::CREATURE_INFO: + { + GH.pushIntT(targetStack, false); + return; + } + + case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: + case PossiblePlayerBattleAction::ANY_LOCATION: + case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell + case PossiblePlayerBattleAction::TELEPORT: + case PossiblePlayerBattleAction::OBSTACLE: + case PossiblePlayerBattleAction::SACRIFICE: + case PossiblePlayerBattleAction::FREE_LOCATION: + { + if (action == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE ) + { + if (getAnySpellToCast(targetHex)->id == SpellID::SACRIFICE) { - case SpellID::SACRIFICE: - case SpellID::TELEPORT: - owner.stacksController->setSelectedStack(shere); //remember first target - secondaryTarget = true; - break; + heroSpellToCast->aimToHex(targetHex); + possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE); + return; } - isCastingPossible = true; - break; - case PossiblePlayerBattleAction::ANY_LOCATION: - currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s - isCastingPossible = true; - break; - case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell - currentSpell = nullptr; - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on % - creatureCasting = true; - isCastingPossible = true; - break; - case PossiblePlayerBattleAction::TELEPORT: - newConsoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here - cursorFrame = Cursor::Combat::TELEPORT; - isCastingPossible = true; - break; - case PossiblePlayerBattleAction::OBSTACLE: - newConsoleMsg = CGI->generaltexth->allTexts[550]; - //TODO: remove obstacle cursor - isCastingPossible = true; - break; - case PossiblePlayerBattleAction::SACRIFICE: - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s - cursorFrame = Cursor::Combat::SACRIFICE; - isCastingPossible = true; - break; - case PossiblePlayerBattleAction::FREE_LOCATION: - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s - isCastingPossible = true; - break; - case PossiblePlayerBattleAction::HEAL: - cursorFrame = Cursor::Combat::HEAL; - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s - realizeAction = [=](){ owner.giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing - break; - case PossiblePlayerBattleAction::CATAPULT: - cursorFrame = Cursor::Combat::SHOOT_CATAPULT; - realizeAction = [=](){ owner.giveCommand(EActionType::CATAPULT, myNumber); }; - break; - case PossiblePlayerBattleAction::CREATURE_INFO: - { - cursorFrame = Cursor::Combat::QUERY; - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str(); - realizeAction = [=](){ GH.pushIntT(shere, false); }; - break; - } - } - } - else //no possible valid action, display message - { - switch (illegalAction) - { - case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: - case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: - cursorFrame = Cursor::Combat::BLOCKED; - newConsoleMsg = CGI->generaltexth->allTexts[23]; - break; - case PossiblePlayerBattleAction::TELEPORT: - cursorFrame = Cursor::Combat::BLOCKED; - newConsoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination - break; - case PossiblePlayerBattleAction::SACRIFICE: - newConsoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice - break; - case PossiblePlayerBattleAction::FREE_LOCATION: - cursorFrame = Cursor::Combat::BLOCKED; - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->getNameTranslated()); //No room to place %s here - break; - default: - if (myNumber == -1) - CCS->curh->set(Cursor::Combat::POINTER); - else - cursorFrame = Cursor::Combat::BLOCKED; - break; - } - } - - if (isCastingPossible) //common part - { - switch (currentAction) //don't use that with teleport / sacrifice - { - case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution? - case PossiblePlayerBattleAction::SACRIFICE: - break; - default: - spellcastingCursor = true; - if (newConsoleMsg.empty() && currentSpell) - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s - break; - } - - realizeAction = [=]() - { - if(secondaryTarget) //select that target now - { - - possibleActions.clear(); - switch (currentSpell->id.toEnum()) + if (getAnySpellToCast(targetHex)->id == SpellID::TELEPORT) { - case SpellID::TELEPORT: //don't cast spell yet, only select target - spellToCast->aimToUnit(shere); - possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT); - break; - case SpellID::SACRIFICE: - spellToCast->aimToHex(myNumber); - possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE); - break; + heroSpellToCast->aimToUnit(targetStack); + possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT); + return; + } + } + + if (spellcastingModeActive()) + { + if (getStackSpellToCast(targetHex)) + { + owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, getStackSpellToCast(targetHex)->getId()); + } + else //unknown random spell + { + owner.giveCommand(EActionType::MONSTER_SPELL, targetHex); } } else { - if (creatureCasting) + assert(getHeroSpellToCast()); + switch (getHeroSpellToCast()->id.toEnum()) { - if (currentSpell) - { - owner.giveCommand(EActionType::MONSTER_SPELL, myNumber, owner.stacksController->activeStackSpellToCast()); - } - else //unknown random spell - { - owner.giveCommand(EActionType::MONSTER_SPELL, myNumber); - } - } - else - { - assert(currentSpell); - switch (currentSpell->id.toEnum()) - { case SpellID::SACRIFICE: - spellToCast->aimToUnit(shere);//victim + heroSpellToCast->aimToUnit(targetStack);//victim break; default: - spellToCast->aimToHex(myNumber); + heroSpellToCast->aimToHex(targetHex); break; - } - owner.curInt->cb->battleMakeAction(spellToCast.get()); - endCastingSpell(); } - owner.stacksController->setSelectedStack(nullptr); + owner.curInt->cb->battleMakeAction(heroSpellToCast.get()); + endCastingSpell(); } - }; - } - - { - if (eventType == CIntObject::MOVE) - { - if (setCursor) - { - if (spellcastingCursor) - CCS->curh->set(Cursor::Spellcast::SPELL); - else - CCS->curh->set(cursorFrame); - } - - if (!currentConsoleMsg.empty()) - GH.statusbar->clearIfMatching(currentConsoleMsg); - if (!newConsoleMsg.empty()) - GH.statusbar->write(newConsoleMsg); - - currentConsoleMsg = newConsoleMsg; - } - if (eventType == CIntObject::LCLICK && realizeAction) - { - //opening creature window shouldn't affect myTurn... - if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget) - { - owner.myTurn = false; //tends to crash with empty calls - } - realizeAction(); - if (!secondaryTarget) //do not replace teleport or sacrifice cursor - CCS->curh->set(Cursor::Combat::POINTER); - GH.statusbar->clear(); + owner.stacksController->setSelectedStack(nullptr); + return; } } + assert(0); + return; } - -bool BattleActionsController::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber) +PossiblePlayerBattleAction BattleActionsController::selectAction(BattleHex targetHex) { - creatureCasting = owner.stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells + assert(owner.stacksController->getActiveStack() != nullptr); + assert(!possibleActions.empty()); + assert(targetHex.isValid()); - bool isCastingPossible = true; + if (owner.stacksController->getActiveStack() == nullptr) + return PossiblePlayerBattleAction::INVALID; - int spellID = -1; - if (creatureCasting) + if (possibleActions.empty()) + return PossiblePlayerBattleAction::INVALID; + + const CStack * targetStack = getStackForHex(targetHex); + + reorderPossibleActionsPriority(owner.stacksController->getActiveStack(), targetStack ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX); + + for (PossiblePlayerBattleAction action : possibleActions) { - if (owner.stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself - spellID = owner.stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast? + if (actionIsLegal(action, targetHex)) + return action; } - else //hero casting + return possibleActions.front(); +} + +void BattleActionsController::onHexHovered(BattleHex hoveredHex) +{ + if (owner.stacksController->getActiveStack() == nullptr) + return; + + auto action = selectAction(hoveredHex); + + std::string newConsoleMsg; + + if (actionIsLegal(action, hoveredHex)) { - spellID = spellToCast->actionSubtype; - } - - - currentSpell = nullptr; - if (spellID >= 0) - currentSpell = CGI->spellh->objects[spellID]; - - if (currentSpell) - { - const spells::Caster *caster = creatureCasting ? static_cast(sactive) : static_cast(owner.curInt->cb->battleGetMyHero()); - if (caster == nullptr) - { - isCastingPossible = false;//just in case - } - else - { - const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO; - - spells::Target target; - target.emplace_back(myNumber); - - spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell); - - auto m = currentSpell->battleMechanics(&cast); - spells::detail::ProblemImpl problem; //todo: display problem in status bar - - isCastingPossible = m->canBeCastAt(target, problem); - } + actionSetCursor(action, hoveredHex); + newConsoleMsg = actionGetStatusMessage(action, hoveredHex); } else - isCastingPossible = false; - if (!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column) - isCastingPossible = false; + { + actionSetCursorBlocked(action, hoveredHex); + newConsoleMsg = actionGetStatusMessageBlocked(action, hoveredHex); + } - return isCastingPossible; + if (!currentConsoleMsg.empty()) + GH.statusbar->clearIfMatching(currentConsoleMsg); + + if (!newConsoleMsg.empty()) + GH.statusbar->write(newConsoleMsg); + + currentConsoleMsg = newConsoleMsg; +} + +void BattleActionsController::onHexClicked(BattleHex clickedHex) +{ + if (owner.stacksController->getActiveStack() == nullptr) + return; + + auto action = selectAction(clickedHex); + + std::string newConsoleMsg; + + if (!actionIsLegal(action, clickedHex)) + return; + + actionRealize(action, clickedHex); + + GH.statusbar->clear(); +} + +void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterStack) +{ + //set casting flag to true if creature can use it to not check it every time + //const auto spellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER)); + //const auto randomSpellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER)); + //if(casterStack->canCast() && (spellcaster || randomSpellcaster)) + //{ + // if(!randomSpellcaster) + // creatureSpellToCast = -1; //spell will be set later on cast + // else + // creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move + // //TODO: what if creature can cast BOTH random genie spell and aimed spell? + // //TODO: faerie dragon type spell should be selected by server + //} +} + +const spells::Caster * BattleActionsController::getCurrentSpellcaster() const +{ + if (heroSpellToCast) + return owner.getActiveHero(); + else + return owner.stacksController->getActiveStack(); + +} + +bool BattleActionsController::isCastingPossibleHere(const CStack *casterStack, const CStack *targetStack, BattleHex targetHex) +{ + auto currentSpell = getAnySpellToCast(targetHex); + assert(currentSpell); + + if (!currentSpell) + return false; + + auto caster = getCurrentSpellcaster(); + + const spells::Mode mode = heroSpellToCast ? spells::Mode::HERO : spells::Mode::CREATURE_ACTIVE; + + spells::Target target; + target.emplace_back(targetHex); + + spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell); + + auto m = currentSpell->battleMechanics(&cast); + spells::detail::ProblemImpl problem; //todo: display problem in status bar + + return m->canBeCastAt(target, problem); } bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const @@ -768,14 +786,7 @@ void BattleActionsController::activateStack() bool BattleActionsController::spellcastingModeActive() const { - return spellDestSelectMode; -} - -SpellID BattleActionsController::selectedSpell() const -{ - if (!spellToCast) - return SpellID::NONE; - return SpellID(spellToCast->actionSubtype); + return heroSpellToCast != nullptr;; } const std::vector & BattleActionsController::getPossibleActions() const diff --git a/client/battle/BattleActionsController.h b/client/battle/BattleActionsController.h index 7b17c2faa..ff58b42ef 100644 --- a/client/battle/BattleActionsController.h +++ b/client/battle/BattleActionsController.h @@ -14,6 +14,9 @@ VCMI_LIB_NAMESPACE_BEGIN class BattleAction; +namespace spells { +class Caster; +} VCMI_LIB_NAMESPACE_END @@ -34,65 +37,76 @@ class BattleActionsController /// all actions possible to call at the moment by player std::vector possibleActions; - /// actions possible to take on hovered hex - std::vector localActions; - - /// these actions display message in case of illegal target - std::vector illegalActions; - - /// action that will be performed on l-click - PossiblePlayerBattleAction currentAction; - - /// last action chosen (and saved) by player - PossiblePlayerBattleAction selectedAction; - - /// if there are not possible actions to choose from, this action should be show as "illegal" in UI - PossiblePlayerBattleAction illegalAction; - - /// if true, stack currently aims to cats a spell - bool creatureCasting; - - /// if true, player is choosing destination for his spell - only for GUI / console - bool spellDestSelectMode; - - /// spell for which player is choosing destination - std::shared_ptr spellToCast; - - /// spell for which player is choosing destination, pointer for convenience - const CSpell *currentSpell; + /// spell for which player's hero is choosing destination + std::shared_ptr heroSpellToCast; /// cached message that was set by this class in status bar std::string currentConsoleMsg; + /// if true, active stack could possibly cast some target spell + const CSpell * creatureSpellToCast; + bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber); bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback std::vector getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context); + bool actionIsLegal(PossiblePlayerBattleAction action, BattleHex hoveredHex); + + void actionSetCursor(PossiblePlayerBattleAction action, BattleHex hoveredHex); + void actionSetCursorBlocked(PossiblePlayerBattleAction action, BattleHex hoveredHex); + + std::string actionGetStatusMessage(PossiblePlayerBattleAction action, BattleHex hoveredHex); + std::string actionGetStatusMessageBlocked(PossiblePlayerBattleAction action, BattleHex hoveredHex); + + void actionRealize(PossiblePlayerBattleAction action, BattleHex hoveredHex); + + PossiblePlayerBattleAction selectAction(BattleHex myNumber); + + const CStack * getStackForHex(BattleHex myNumber); + + /// attempts to initialize spellcasting action for stack + /// will silently return if stack is not a spellcaster + void tryActivateStackSpellcasting(const CStack *casterStack); + + const spells::Caster * getCurrentSpellcaster() const; public: BattleActionsController(BattleInterface & owner); /// initialize list of potential actions for new active stack void activateStack(); - /// initialize potential actions for spells that can be cast by active stack + /// returns true if UI is currently in target selection mode + bool spellcastingModeActive() const; + + /// enter targeted spellcasting mode for creature, e.g. via "F" hotkey void enterCreatureCastingMode(); - /// initialize potential actions for selected spell + /// initialize hero spellcasting mode, e.g. on selecting spell in spellbook void castThisSpell(SpellID spellID); /// ends casting spell (eg. when spell has been cast or canceled) void endCastingSpell(); - /// update UI (e.g. status bar/cursor) according to new active hex - void handleHex(BattleHex myNumber, int eventType); + /// update cursor and status bar according to new active hex + void onHexHovered(BattleHex hoveredHex); - /// returns currently selected spell or SpellID::NONE on error - SpellID selectedSpell() const; + /// performs action according to selected hex + void onHexClicked(BattleHex clickedHex); + + /// returns spell that is currently being cast by hero or nullptr if none + const CSpell * getHeroSpellToCast() const; + + /// if current stack is spellcaster, returns + const CSpell * getStackSpellToCast( BattleHex targetHex ) const; + const CSpell * getAnySpellToCast( BattleHex targetHex ) const; + + /// returns true if current stack is a spellcaster + bool isActiveStackFixedSpellcaster() const; + + /// returns true if current stack is random spellcaster (e.g. Genie) + bool isActiveStackRandomSpellcaster() const; - /// returns true if UI is currently in target selection mode - bool spellcastingModeActive() const; - /// methods to work with array of possible actions, needed to control special creatures abilities const std::vector & getPossibleActions() const; void removePossibleAction(PossiblePlayerBattleAction); diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 46bd6aa5e..f40970323 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -109,7 +109,7 @@ void BattleFieldController::mouseMoved(const SDL_MouseMotionEvent &event) { BattleHex selectedHex = getHoveredHex(); - owner.actionsController->handleHex(selectedHex, MOVE); + owner.actionsController->onHexHovered(selectedHex); } @@ -237,12 +237,12 @@ std::set BattleFieldController::getHighlightedHexesSpellRange() if(owner.actionsController->spellcastingModeActive())//hero casts spell { - spell = owner.actionsController->selectedSpell().toSpell(); + spell = owner.actionsController->getHeroSpellToCast(); caster = owner.getActiveHero(); } - else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell + else if(owner.actionsController->getStackSpellToCast(hoveredHex) != nullptr)//stack casts spell { - spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell(); + spell = owner.actionsController->getStackSpellToCast(hoveredHex); caster = owner.stacksController->getActiveStack(); mode = spells::Mode::CREATURE_ACTIVE; } @@ -520,7 +520,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget) } default: assert(0); - return attackTarget.cloneInDirection(BattleHex::LEFT); + return BattleHex::INVALID; } } } diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 8e3add970..4470e6881 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -73,8 +73,6 @@ BattleStacksController::BattleStacksController(BattleInterface & owner): activeStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), - stackCanCastSpell(false), - creatureSpellToCast(uint32_t(-1)), animIDhelper(0) { //preparing graphics for displaying amounts of creatures @@ -738,25 +736,6 @@ void BattleStacksController::activateStack() const CStack * s = getActiveStack(); if(!s) return; - - //set casting flag to true if creature can use it to not check it every time - const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER)); - const auto randomSpellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER)); - if(s->canCast() && (spellcaster || randomSpellcaster)) - { - stackCanCastSpell = true; - if(randomSpellcaster) - creatureSpellToCast = -1; //spell will be set later on cast - else - creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move - //TODO: what if creature can cast BOTH random genie spell and aimed spell? - //TODO: faerie dragon type spell should be selected by server - } - else - { - stackCanCastSpell = false; - creatureSpellToCast = -1; - } } void BattleStacksController::setSelectedStack(const CStack *stack) @@ -779,18 +758,6 @@ bool BattleStacksController::facingRight(const CStack * stack) const return stackFacingRight.at(stack->ID); } -bool BattleStacksController::activeStackSpellcaster() -{ - return stackCanCastSpell; -} - -SpellID BattleStacksController::activeStackSpellToCast() -{ - if (!stackCanCastSpell) - return SpellID::NONE; - return SpellID(creatureSpellToCast); -} - Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CStack * stack) const { Point ret(-500, -500); //returned value @@ -908,12 +875,12 @@ std::vector BattleStacksController::selectHoveredStacks() if(owner.actionsController->spellcastingModeActive())//hero casts spell { - spell = owner.actionsController->selectedSpell().toSpell(); + spell = owner.actionsController->getHeroSpellToCast(); caster = owner.getActiveHero(); } - else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell + else if(owner.actionsController->getStackSpellToCast(hoveredHex) != nullptr)//stack casts spell { - spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell(); + spell = owner.actionsController->getStackSpellToCast(hoveredHex); caster = owner.stacksController->getActiveStack(); mode = spells::Mode::CREATURE_ACTIVE; } diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index 7963c8c23..67b6ee314 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -79,10 +79,6 @@ class BattleStacksController /// stack that was selected for multi-target spells - Teleport / Sacrifice const CStack *selectedStack; - /// if true, active stack could possibly cast some target spell - bool stackCanCastSpell; - si32 creatureSpellToCast; - /// for giving IDs for animations ui32 animIDhelper; @@ -123,9 +119,6 @@ public: void startAction(const BattleAction* action); void endAction(const BattleAction* action); - bool activeStackSpellcaster(); - SpellID activeStackSpellToCast(); - void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all void setActiveStack(const CStack *stack); From f150ced14c5f4450b8e856295a532b0c450fb60e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 25 Jan 2023 16:39:34 +0200 Subject: [PATCH 05/13] All spellcasters should work correctly now --- client/battle/BattleActionsController.cpp | 74 ++++++++++++----------- client/battle/BattleActionsController.h | 7 +-- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index d2a5d43cd..189be02e4 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -42,44 +42,31 @@ static std::string formatDmgRange(std::pair dmgRange) BattleActionsController::BattleActionsController(BattleInterface & owner): owner(owner), - heroSpellToCast(nullptr) + heroSpellToCast(nullptr), + creatureSpellToCast(nullptr) {} void BattleActionsController::endCastingSpell() { if(heroSpellToCast) - { heroSpellToCast.reset(); - if(owner.stacksController->getActiveStack()) - possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared - } - else - { - if(owner.stacksController->getActiveStack()) - possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); - } + if(owner.stacksController->getActiveStack()) + possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared + GH.fakeMouseMove(); } -bool BattleActionsController::isActiveStackFixedSpellcaster() const +bool BattleActionsController::isActiveStackSpellcaster() const { const CStack * casterStack = owner.stacksController->getActiveStack(); - assert(casterStack); + if (!casterStack) + return false; const auto randomSpellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER)); return (randomSpellcaster && casterStack->canCast()); } -bool BattleActionsController::isActiveStackRandomSpellcaster() const -{ - const CStack * casterStack = owner.stacksController->getActiveStack(); - assert(casterStack); - - const auto randomSpellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER)); - return (randomSpellcaster && casterStack->canCast()); -} - void BattleActionsController::enterCreatureCastingMode() { //silently check for possible errors @@ -93,7 +80,7 @@ void BattleActionsController::enterCreatureCastingMode() if (!owner.stacksController->getActiveStack()) return; - if (!isActiveStackFixedSpellcaster()) + if (!isActiveStackSpellcaster()) return; if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION)) @@ -138,7 +125,12 @@ void BattleActionsController::enterCreatureCastingMode() std::vector BattleActionsController::getPossibleActionsForStack(const CStack *stack) const { BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass - data.creatureSpellToCast = getStackSpellToCast(BattleHex::INVALID)->getId(); + + if (getStackSpellToCast(BattleHex::INVALID)) + data.creatureSpellToCast = getStackSpellToCast(BattleHex::INVALID)->getId(); + else + data.creatureSpellToCast = SpellID::NONE; + data.tacticsMode = owner.tacticsMode; auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data); @@ -233,6 +225,9 @@ const CSpell * BattleActionsController::getHeroSpellToCast( ) const const CSpell * BattleActionsController::getStackSpellToCast( BattleHex targetHex ) const { + if (isActiveStackSpellcaster()) + return creatureSpellToCast; + return nullptr; } @@ -589,7 +584,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B } } - if (spellcastingModeActive()) + if (!spellcastingModeActive()) { if (getStackSpellToCast(targetHex)) { @@ -652,6 +647,15 @@ void BattleActionsController::onHexHovered(BattleHex hoveredHex) if (owner.stacksController->getActiveStack() == nullptr) return; + if (hoveredHex == BattleHex::INVALID) + { + if (!currentConsoleMsg.empty()) + GH.statusbar->clearIfMatching(currentConsoleMsg); + + currentConsoleMsg.clear(); + return; + } + auto action = selectAction(hoveredHex); std::string newConsoleMsg; @@ -695,18 +699,14 @@ void BattleActionsController::onHexClicked(BattleHex clickedHex) void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterStack) { - //set casting flag to true if creature can use it to not check it every time - //const auto spellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER)); - //const auto randomSpellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER)); - //if(casterStack->canCast() && (spellcaster || randomSpellcaster)) - //{ - // if(!randomSpellcaster) - // creatureSpellToCast = -1; //spell will be set later on cast - // else - // creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move - // //TODO: what if creature can cast BOTH random genie spell and aimed spell? - // //TODO: faerie dragon type spell should be selected by server - //} + const auto spellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER)); + if(casterStack->canCast() && spellcaster) + { + // faerie dragon can cast only one, randomly selected spell until their next move + //TODO: what if creature can cast BOTH random genie spell and aimed spell? + //TODO: faerie dragon type spell should be selected by server + creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell(); + } } const spells::Caster * BattleActionsController::getCurrentSpellcaster() const @@ -759,6 +759,8 @@ void BattleActionsController::activateStack() const CStack * s = owner.stacksController->getActiveStack(); if(s) { + tryActivateStackSpellcasting(s); + possibleActions = getPossibleActionsForStack(s); std::list actionsToSelect; if(!possibleActions.empty()) diff --git a/client/battle/BattleActionsController.h b/client/battle/BattleActionsController.h index ff58b42ef..47855b35e 100644 --- a/client/battle/BattleActionsController.h +++ b/client/battle/BattleActionsController.h @@ -97,15 +97,12 @@ public: /// returns spell that is currently being cast by hero or nullptr if none const CSpell * getHeroSpellToCast() const; - /// if current stack is spellcaster, returns + /// if current stack is spellcaster, returns spell being cast, or null othervice const CSpell * getStackSpellToCast( BattleHex targetHex ) const; const CSpell * getAnySpellToCast( BattleHex targetHex ) const; /// returns true if current stack is a spellcaster - bool isActiveStackFixedSpellcaster() const; - - /// returns true if current stack is random spellcaster (e.g. Genie) - bool isActiveStackRandomSpellcaster() const; + bool isActiveStackSpellcaster() const; /// methods to work with array of possible actions, needed to control special creatures abilities const std::vector & getPossibleActions() const; From dbbbba5f2da92ef96f87bba0a79ae1e3007ee6fd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 29 Jan 2023 13:59:24 +0200 Subject: [PATCH 06/13] Fix typo --- server/CGameHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 57e3ee87c..b429b4ab9 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4448,7 +4448,7 @@ void CGameHandler::updateGateState() bool hasForceFieldOnBridge = !battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), true).empty(); bool hasStackAtGateInner = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr; bool hasStackAtGateOuter = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr; - bool hasStackAtGateBridge = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr; + bool hasStackAtGateBridge = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false) != nullptr; bool hasLongBridge = gs->curB->town->subID == ETownType::FORTRESS; BattleUpdateGateState db; From c3b79c786ba1bf6e9f5c786ade192af4710e7530 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 29 Jan 2023 14:27:57 +0200 Subject: [PATCH 07/13] Spellcasters UI now shares handling with hero spellcasting --- client/battle/BattleActionsController.cpp | 61 +++++++++++++++++------ client/battle/BattleActionsController.h | 31 ++++++++---- client/battle/BattleFieldController.cpp | 45 +++++++---------- client/battle/BattleStacksController.cpp | 18 ++----- 4 files changed, 88 insertions(+), 67 deletions(-) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 189be02e4..d4351565d 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -86,7 +86,7 @@ void BattleActionsController::enterCreatureCastingMode() if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION)) { const spells::Caster * caster = owner.stacksController->getActiveStack(); - const CSpell * spell = getStackSpellToCast(BattleHex::INVALID); + const CSpell * spell = getStackSpellToCast(); spells::Target target; target.emplace_back(); @@ -126,8 +126,8 @@ std::vector BattleActionsController::getPossibleActi { BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass - if (getStackSpellToCast(BattleHex::INVALID)) - data.creatureSpellToCast = getStackSpellToCast(BattleHex::INVALID)->getId(); + if (getStackSpellToCast()) + data.creatureSpellToCast = getStackSpellToCast()->getId(); else data.creatureSpellToCast = SpellID::NONE; @@ -175,6 +175,8 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac return 10; break; case PossiblePlayerBattleAction::CREATURE_INFO: return 11; break; + case PossiblePlayerBattleAction::TELEPORT: + return 12; break; default: assert(0); return 200; break; @@ -223,7 +225,7 @@ const CSpell * BattleActionsController::getHeroSpellToCast( ) const return nullptr; } -const CSpell * BattleActionsController::getStackSpellToCast( BattleHex targetHex ) const +const CSpell * BattleActionsController::getStackSpellToCast( ) const { if (isActiveStackSpellcaster()) return creatureSpellToCast; @@ -231,11 +233,11 @@ const CSpell * BattleActionsController::getStackSpellToCast( BattleHex targetHex return nullptr; } -const CSpell * BattleActionsController::getAnySpellToCast( BattleHex targetHex ) const +const CSpell * BattleActionsController::getCurrentSpell( ) const { if (getHeroSpellToCast()) return getHeroSpellToCast(); - return getStackSpellToCast(targetHex); + return getStackSpellToCast(); } const CStack * BattleActionsController::getStackForHex(BattleHex hoveredHex) @@ -364,10 +366,10 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle } case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: - return boost::str(boost::format(CGI->generaltexth->allTexts[27]) % getAnySpellToCast(targetHex)->getNameTranslated() % targetStack->getName()); //Cast %s on %s + return boost::str(boost::format(CGI->generaltexth->allTexts[27]) % getCurrentSpell()->getNameTranslated() % targetStack->getName()); //Cast %s on %s case PossiblePlayerBattleAction::ANY_LOCATION: - return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getAnySpellToCast(targetHex)->getNameTranslated()); //Cast %s + return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getCurrentSpell()->getNameTranslated()); //Cast %s case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell return boost::str(boost::format(CGI->generaltexth->allTexts[301]) % targetStack->getName()); //Cast a spell on % @@ -382,7 +384,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle return (boost::format(CGI->generaltexth->allTexts[549]) % targetStack->getName()).str(); //sacrifice the %s case PossiblePlayerBattleAction::FREE_LOCATION: - return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getAnySpellToCast(targetHex)->getNameTranslated()); //Cast %s + return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getCurrentSpell()->getNameTranslated()); //Cast %s case PossiblePlayerBattleAction::HEAL: return (boost::format(CGI->generaltexth->allTexts[419]) % targetStack->getName()).str(); //Apply first aid to the %s @@ -412,7 +414,7 @@ std::string BattleActionsController::actionGetStatusMessageBlocked(PossiblePlaye return CGI->generaltexth->allTexts[543]; //choose army to sacrifice break; case PossiblePlayerBattleAction::FREE_LOCATION: - return boost::str(boost::format(CGI->generaltexth->allTexts[181]) % getAnySpellToCast(targetHex)->getNameTranslated()); //No room to place %s here + return boost::str(boost::format(CGI->generaltexth->allTexts[181]) % getCurrentSpell()->getNameTranslated()); //No room to place %s here break; default: return ""; @@ -570,25 +572,27 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B { if (action == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE ) { - if (getAnySpellToCast(targetHex)->id == SpellID::SACRIFICE) + if (getCurrentSpell()->id == SpellID::SACRIFICE) { heroSpellToCast->aimToHex(targetHex); possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE); + owner.stacksController->setSelectedStack(targetStack); return; } - if (getAnySpellToCast(targetHex)->id == SpellID::TELEPORT) + if (getCurrentSpell()->id == SpellID::TELEPORT) { heroSpellToCast->aimToUnit(targetStack); possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT); + owner.stacksController->setSelectedStack(targetStack); return; } } if (!spellcastingModeActive()) { - if (getStackSpellToCast(targetHex)) + if (getStackSpellToCast()) { - owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, getStackSpellToCast(targetHex)->getId()); + owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, getStackSpellToCast()->getId()); } else //unknown random spell { @@ -703,7 +707,6 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS if(casterStack->canCast() && spellcaster) { // faerie dragon can cast only one, randomly selected spell until their next move - //TODO: what if creature can cast BOTH random genie spell and aimed spell? //TODO: faerie dragon type spell should be selected by server creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell(); } @@ -715,12 +718,20 @@ const spells::Caster * BattleActionsController::getCurrentSpellcaster() const return owner.getActiveHero(); else return owner.stacksController->getActiveStack(); +} + +spells::Mode BattleActionsController::getCurrentCastMode() const +{ + if (heroSpellToCast) + return spells::Mode::HERO; + else + return spells::Mode::CREATURE_ACTIVE; } bool BattleActionsController::isCastingPossibleHere(const CStack *casterStack, const CStack *targetStack, BattleHex targetHex) { - auto currentSpell = getAnySpellToCast(targetHex); + auto currentSpell = getCurrentSpell(); assert(currentSpell); if (!currentSpell) @@ -791,6 +802,24 @@ bool BattleActionsController::spellcastingModeActive() const return heroSpellToCast != nullptr;; } +bool BattleActionsController::currentActionSpellcasting(BattleHex hoveredHex) +{ + if (heroSpellToCast) + return true; + + if (!owner.stacksController->getActiveStack()) + return false; + + auto action = selectAction(hoveredHex); + + return + action == PossiblePlayerBattleAction::ANY_LOCATION || + action == PossiblePlayerBattleAction::NO_LOCATION || + action == PossiblePlayerBattleAction::FREE_LOCATION || + action == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE || + action == PossiblePlayerBattleAction::OBSTACLE; +} + const std::vector & BattleActionsController::getPossibleActions() const { return possibleActions; diff --git a/client/battle/BattleActionsController.h b/client/battle/BattleActionsController.h index 47855b35e..3cd25b059 100644 --- a/client/battle/BattleActionsController.h +++ b/client/battle/BattleActionsController.h @@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN class BattleAction; namespace spells { class Caster; +enum class Mode; } VCMI_LIB_NAMESPACE_END @@ -63,13 +64,21 @@ class BattleActionsController PossiblePlayerBattleAction selectAction(BattleHex myNumber); - const CStack * getStackForHex(BattleHex myNumber); + const CStack * getStackForHex(BattleHex myNumber) ; /// attempts to initialize spellcasting action for stack /// will silently return if stack is not a spellcaster void tryActivateStackSpellcasting(const CStack *casterStack); - const spells::Caster * getCurrentSpellcaster() const; + /// returns spell that is currently being cast by hero or nullptr if none + const CSpell * getHeroSpellToCast() const; + + /// if current stack is spellcaster, returns spell being cast, or null othervice + const CSpell * getStackSpellToCast( ) const; + + /// returns true if current stack is a spellcaster + bool isActiveStackSpellcaster() const; + public: BattleActionsController(BattleInterface & owner); @@ -79,6 +88,12 @@ public: /// returns true if UI is currently in target selection mode bool spellcastingModeActive() const; + /// returns true if one of the following is true: + /// - we are casting spell by hero + /// - we are casting spell by creature in targeted mode (F hotkey) + /// - current creature is spellcaster and preferred action for current hex is spellcast + bool currentActionSpellcasting(BattleHex hoveredHex); + /// enter targeted spellcasting mode for creature, e.g. via "F" hotkey void enterCreatureCastingMode(); @@ -94,15 +109,9 @@ public: /// performs action according to selected hex void onHexClicked(BattleHex clickedHex); - /// returns spell that is currently being cast by hero or nullptr if none - const CSpell * getHeroSpellToCast() const; - - /// if current stack is spellcaster, returns spell being cast, or null othervice - const CSpell * getStackSpellToCast( BattleHex targetHex ) const; - const CSpell * getAnySpellToCast( BattleHex targetHex ) const; - - /// returns true if current stack is a spellcaster - bool isActiveStackSpellcaster() const; + const spells::Caster * getCurrentSpellcaster() const; + const CSpell * getCurrentSpell() const; + spells::Mode getCurrentCastMode() const; /// methods to work with array of possible actions, needed to control special creatures abilities const std::vector & getPossibleActions() const; diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index f40970323..12c6c50c3 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -230,25 +230,15 @@ std::set BattleFieldController::getHighlightedHexesSpellRange() if(!settings["battle"]["mouseShadow"].Bool()) return result; - const spells::Caster *caster = nullptr; - const CSpell *spell = nullptr; - - spells::Mode mode = spells::Mode::HERO; - - if(owner.actionsController->spellcastingModeActive())//hero casts spell - { - spell = owner.actionsController->getHeroSpellToCast(); - caster = owner.getActiveHero(); - } - else if(owner.actionsController->getStackSpellToCast(hoveredHex) != nullptr)//stack casts spell - { - spell = owner.actionsController->getStackSpellToCast(hoveredHex); - caster = owner.stacksController->getActiveStack(); - mode = spells::Mode::CREATURE_ACTIVE; - } - - if(caster && spell) //when casting spell - { + const spells::Caster *caster = nullptr; + const CSpell *spell = nullptr; + + spells::Mode mode = owner.actionsController->getCurrentCastMode(); + spell = owner.actionsController->getCurrentSpell(); + caster = owner.actionsController->getCurrentSpellcaster(); + + if(caster && spell) //when casting spell + { // printing shaded hex(es) spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell); auto shaded = spell->battleMechanics(&event)->rangeInHexes(hoveredHex); @@ -307,13 +297,16 @@ std::set BattleFieldController::getHighlightedHexesMovementTarget() void BattleFieldController::showHighlightedHexes(Canvas & canvas) { std::set hoveredStack = getHighlightedHexesStackRange(); - std::set hoveredSpell = getHighlightedHexesSpellRange(); - std::set hoveredMove = getHighlightedHexesMovementTarget(); - - auto const & hoveredMouse = owner.actionsController->spellcastingModeActive() ? hoveredSpell : hoveredMove; - - for(int b=0; b hoveredSpell = getHighlightedHexesSpellRange(); + std::set hoveredMove = getHighlightedHexesMovementTarget(); + + if (getHoveredHex() == BattleHex::INVALID) + return; + + auto const & hoveredMouse = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpell : hoveredMove; + + for(int b=0; b BattleStacksController::selectHoveredStacks() const spells::Caster *caster = nullptr; const CSpell *spell = nullptr; - spells::Mode mode = spells::Mode::HERO; + spells::Mode mode = owner.actionsController->getCurrentCastMode(); + spell = owner.actionsController->getCurrentSpell(); + caster = owner.actionsController->getCurrentSpellcaster(); - if(owner.actionsController->spellcastingModeActive())//hero casts spell - { - spell = owner.actionsController->getHeroSpellToCast(); - caster = owner.getActiveHero(); - } - else if(owner.actionsController->getStackSpellToCast(hoveredHex) != nullptr)//stack casts spell - { - spell = owner.actionsController->getStackSpellToCast(hoveredHex); - caster = owner.stacksController->getActiveStack(); - mode = spells::Mode::CREATURE_ACTIVE; - } - - if(caster && spell) //when casting spell + if(caster && spell && owner.actionsController->currentActionSpellcasting(hoveredHex) ) //when casting spell { spells::Target target; target.emplace_back(hoveredHex); From d3c85a19cbfe831e319d5632f8e6a7e57acff8fb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 29 Jan 2023 14:34:47 +0200 Subject: [PATCH 08/13] Merged CClickableHex class into BattleFieldController --- client/battle/BattleFieldController.cpp | 88 +++++++++++++++--------- client/battle/BattleFieldController.h | 7 +- client/battle/BattleInterface.h | 1 - client/battle/BattleInterfaceClasses.cpp | 41 ----------- client/battle/BattleInterfaceClasses.h | 16 ----- 5 files changed, 59 insertions(+), 94 deletions(-) diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 12c6c50c3..77b514f72 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -27,6 +27,7 @@ #include "../gui/CGuiHandler.h" #include "../gui/CursorHandler.h" #include "../adventureMap/CInGameConsole.h" +#include "../windows/CCreatureWindow.h" #include "../../CCallback.h" #include "../../lib/BattleFieldHandler.h" @@ -76,20 +77,11 @@ BattleFieldController::BattleFieldController(BattleInterface & owner): backgroundWithHexes = std::make_unique(Point(background->width(), background->height())); - for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h) - { - auto hex = std::make_shared(); - hex->myNumber = h; - hex->pos = hexPositionAbsolute(h); - hex->myInterface = &owner; - bfield.push_back(hex); - } - auto accessibility = owner.curInt->cb->getAccesibility(); for(int i = 0; i < accessibility.size(); i++) stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE); - addUsedEvents(MOVE); + addUsedEvents(LCLICK | RCLICK | MOVE); LOCPLINT->cingconsole->pos = this->pos; } @@ -112,6 +104,29 @@ void BattleFieldController::mouseMoved(const SDL_MouseMotionEvent &event) owner.actionsController->onHexHovered(selectedHex); } +void BattleFieldController::clickLeft(tribool down, bool previousState) +{ + if(!down) + { + BattleHex selectedHex = getHoveredHex(); + + if (selectedHex != BattleHex::INVALID) + owner.actionsController->onHexClicked(selectedHex); + } +} + +void BattleFieldController::clickRight(tribool down, bool previousState) +{ + if(down) + { + BattleHex selectedHex = getHoveredHex(); + + auto selectedStack = owner.curInt->cb->battleGetStackByPos(selectedHex, true); + + if (selectedStack != nullptr) + GH.pushIntT(selectedStack, true); + } +} void BattleFieldController::renderBattlefield(Canvas & canvas) { @@ -230,15 +245,15 @@ std::set BattleFieldController::getHighlightedHexesSpellRange() if(!settings["battle"]["mouseShadow"].Bool()) return result; - const spells::Caster *caster = nullptr; - const CSpell *spell = nullptr; - - spells::Mode mode = owner.actionsController->getCurrentCastMode(); - spell = owner.actionsController->getCurrentSpell(); - caster = owner.actionsController->getCurrentSpellcaster(); - - if(caster && spell) //when casting spell - { + const spells::Caster *caster = nullptr; + const CSpell *spell = nullptr; + + spells::Mode mode = owner.actionsController->getCurrentCastMode(); + spell = owner.actionsController->getCurrentSpell(); + caster = owner.actionsController->getCurrentSpellcaster(); + + if(caster && spell) //when casting spell + { // printing shaded hex(es) spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell); auto shaded = spell->battleMechanics(&event)->rangeInHexes(hoveredHex); @@ -297,16 +312,16 @@ std::set BattleFieldController::getHighlightedHexesMovementTarget() void BattleFieldController::showHighlightedHexes(Canvas & canvas) { std::set hoveredStack = getHighlightedHexesStackRange(); - std::set hoveredSpell = getHighlightedHexesSpellRange(); - std::set hoveredMove = getHighlightedHexesMovementTarget(); - - if (getHoveredHex() == BattleHex::INVALID) - return; - - auto const & hoveredMouse = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpell : hoveredMove; - - for(int b=0; b hoveredSpell = getHighlightedHexesSpellRange(); + std::set hoveredMove = getHighlightedHexesMovementTarget(); + + if (getHoveredHex() == BattleHex::INVALID) + return; + + auto const & hoveredMouse = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpell : hoveredMove; + + for(int b=0; bhovered && hex->strictHovered) - return hex->myNumber; + Point hoverPos = GH.getCursorPosition(); + + for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h) + { + Rect hexPosition = hexPositionAbsolute(h); + + if (!hexPosition.isInside(hoverPos)) + continue; + + if (isPixelInHex(hoverPos - hexPosition.topLeft())) + return h; + } return BattleHex::INVALID; } diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index bcb16b361..0fa0e2910 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -20,7 +20,6 @@ class Point; VCMI_LIB_NAMESPACE_END -class ClickableHex; class BattleHero; class Canvas; class IImage; @@ -50,8 +49,6 @@ class BattleFieldController : public CIntObject /// hexes that when in front of a unit cause it's amount box to move back std::array stackCountOutsideHexes; - std::vector> bfield; - void showHighlightedHex(Canvas & to, BattleHex hex, bool darkBorder); std::set getHighlightedHexesStackRange(); @@ -66,9 +63,11 @@ class BattleFieldController : public CIntObject BattleHex::EDir selectAttackDirection(BattleHex myNumber, const Point & point); void mouseMoved(const SDL_MouseMotionEvent &event) override; + void clickLeft(tribool down, bool previousState) override; + void clickRight(tribool down, bool previousState) override; + void showAll(SDL_Surface * to) override; void show(SDL_Surface * to) override; - public: BattleFieldController(BattleInterface & owner); diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index adec40210..55cce087f 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -37,7 +37,6 @@ class Canvas; class BattleResultWindow; class StackQueue; class CPlayerInterface; -class ClickableHex; class CAnimation; struct BattleEffect; class IImage; diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 30b7ff447..2d396f0d5 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -30,7 +30,6 @@ #include "../widgets/Images.h" #include "../widgets/TextControls.h" #include "../windows/CMessage.h" -#include "../windows/CCreatureWindow.h" #include "../windows/CSpellWindow.h" #include "../render/CAnimation.h" #include "../adventureMap/CInGameConsole.h" @@ -681,46 +680,6 @@ void BattleResultWindow::bExitf() CCS->videoh->close(); } -void ClickableHex::hover(bool on) -{ - hovered = on; - //Hoverable::hover(on); -} - -ClickableHex::ClickableHex() - : myNumber(-1) - , strictHovered(false) - , myInterface(nullptr) -{ - addUsedEvents(LCLICK | RCLICK | HOVER | MOVE); -} - -void ClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent) -{ - strictHovered = myInterface->fieldController->isPixelInHex(Point(sEvent.x-pos.x, sEvent.y-pos.y)); -} - -void ClickableHex::clickLeft(tribool down, bool previousState) -{ - if(!down && hovered && strictHovered) //we've been really clicked! - { - myInterface->actionsController->onHexClicked(myNumber); - } -} - -void ClickableHex::clickRight(tribool down, bool previousState) -{ - const CStack * myst = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber); //stack info - if(hovered && strictHovered && myst!=nullptr) - { - if(!myst->alive()) return; - if(down) - { - GH.pushIntT(myst, true); - } - } -} - StackQueue::StackQueue(bool Embedded, BattleInterface & owner) : embedded(Embedded), owner(owner) diff --git a/client/battle/BattleInterfaceClasses.h b/client/battle/BattleInterfaceClasses.h index a785510bd..f22484a5a 100644 --- a/client/battle/BattleInterfaceClasses.h +++ b/client/battle/BattleInterfaceClasses.h @@ -172,22 +172,6 @@ public: void show(SDL_Surface * to = 0) override; }; -/// Class which stands for a single hex field on a battlefield -class ClickableHex : public CIntObject -{ -public: - ui32 myNumber; //number of hex in commonly used format - bool strictHovered; //for determining if hex is hovered by mouse (this is different problem than hex's graphic hovering) - BattleInterface * myInterface; //interface that owns me - - //for user interactions - void hover (bool on) override; - void mouseMoved (const SDL_MouseMotionEvent &sEvent) override; - void clickLeft(tribool down, bool previousState) override; - void clickRight(tribool down, bool previousState) override; - ClickableHex(); -}; - /// Shows the stack queue class StackQueue : public CIntObject { From 967829687529472438471e491efad631b3b987c2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 29 Jan 2023 17:52:19 +0200 Subject: [PATCH 09/13] Hero interaction is now battle action --- client/battle/BattleActionsController.cpp | 58 ++++++++++++++++++++++- client/battle/BattleActionsController.h | 8 +++- client/battle/BattleFieldController.cpp | 30 +++++++++--- client/battle/BattleInterfaceClasses.cpp | 25 ++-------- client/battle/BattleInterfaceClasses.h | 6 +-- lib/battle/BattleHex.h | 4 ++ lib/battle/CBattleInfoCallback.h | 1 + 7 files changed, 99 insertions(+), 33 deletions(-) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index d4351565d..940e9752b 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -134,6 +134,7 @@ std::vector BattleActionsController::getPossibleActi data.tacticsMode = owner.tacticsMode; auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data); + allActions.push_back(PossiblePlayerBattleAction::HERO_INFO); allActions.push_back(PossiblePlayerBattleAction::CREATURE_INFO); return std::vector(allActions); @@ -175,8 +176,10 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac return 10; break; case PossiblePlayerBattleAction::CREATURE_INFO: return 11; break; - case PossiblePlayerBattleAction::TELEPORT: + case PossiblePlayerBattleAction::HERO_INFO: return 12; break; + case PossiblePlayerBattleAction::TELEPORT: + return 13; break; default: assert(0); return 200; break; @@ -304,6 +307,9 @@ void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action, case PossiblePlayerBattleAction::CREATURE_INFO: CCS->curh->set(Cursor::Combat::QUERY); return; + case PossiblePlayerBattleAction::HERO_INFO: + CCS->curh->set(Cursor::Combat::HERO); + return; } assert(0); } @@ -394,6 +400,9 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle case PossiblePlayerBattleAction::CREATURE_INFO: return (boost::format(CGI->generaltexth->allTexts[297]) % targetStack->getName()).str(); + + case PossiblePlayerBattleAction::HERO_INFO: + return ""; //TODO: "View Hero Stats" } assert(0); return ""; @@ -432,6 +441,15 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B case PossiblePlayerBattleAction::CREATURE_INFO: return (targetStack && targetStackOwned); + case PossiblePlayerBattleAction::HERO_INFO: + if (targetHex == BattleHex::HERO_ATTACKER) + return owner.attackingHero != nullptr; + + if (targetHex == BattleHex::HERO_DEFENDER) + return owner.defendingHero != nullptr; + + return false; + case PossiblePlayerBattleAction::MOVE_TACTICS: case PossiblePlayerBattleAction::MOVE_STACK: if (!(targetStack && targetStack->alive())) //we can walk on dead stacks @@ -562,6 +580,17 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B return; } + case PossiblePlayerBattleAction::HERO_INFO: + { + if (targetHex == BattleHex::HERO_ATTACKER) + owner.attackingHero->heroLeftClicked(); + + if (targetHex == BattleHex::HERO_DEFENDER) + owner.defendingHero->heroLeftClicked(); + + return; + } + case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: case PossiblePlayerBattleAction::ANY_LOCATION: case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell @@ -657,6 +686,7 @@ void BattleActionsController::onHexHovered(BattleHex hoveredHex) GH.statusbar->clearIfMatching(currentConsoleMsg); currentConsoleMsg.clear(); + CCS->curh->set(Cursor::Combat::BLOCKED); return; } @@ -684,7 +714,17 @@ void BattleActionsController::onHexHovered(BattleHex hoveredHex) currentConsoleMsg = newConsoleMsg; } -void BattleActionsController::onHexClicked(BattleHex clickedHex) +void BattleActionsController::onHoverEnded() +{ + CCS->curh->set(Cursor::Combat::POINTER); + + if (!currentConsoleMsg.empty()) + GH.statusbar->clearIfMatching(currentConsoleMsg); + + currentConsoleMsg.clear(); +} + +void BattleActionsController::onHexLeftClicked(BattleHex clickedHex) { if (owner.stacksController->getActiveStack() == nullptr) return; @@ -797,6 +837,20 @@ void BattleActionsController::activateStack() } } +void BattleActionsController::onHexRightClicked(BattleHex clickedHex) +{ + auto selectedStack = owner.curInt->cb->battleGetStackByPos(clickedHex, true); + + if (selectedStack != nullptr) + GH.pushIntT(selectedStack, true); + + if (clickedHex == BattleHex::HERO_ATTACKER && owner.attackingHero) + owner.attackingHero->heroRightClicked(); + + if (clickedHex == BattleHex::HERO_DEFENDER && owner.defendingHero) + owner.defendingHero->heroRightClicked(); +} + bool BattleActionsController::spellcastingModeActive() const { return heroSpellToCast != nullptr;; diff --git a/client/battle/BattleActionsController.h b/client/battle/BattleActionsController.h index 3cd25b059..9ce9be196 100644 --- a/client/battle/BattleActionsController.h +++ b/client/battle/BattleActionsController.h @@ -106,8 +106,14 @@ public: /// update cursor and status bar according to new active hex void onHexHovered(BattleHex hoveredHex); + /// called when cursor is no longer over battlefield and cursor/battle log should be reset + void onHoverEnded(); + /// performs action according to selected hex - void onHexClicked(BattleHex clickedHex); + void onHexLeftClicked(BattleHex clickedHex); + + /// performs action according to selected hex + void onHexRightClicked(BattleHex clickedHex); const spells::Caster * getCurrentSpellcaster() const; const CSpell * getCurrentSpell() const; diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 77b514f72..522e506d3 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -27,7 +27,6 @@ #include "../gui/CGuiHandler.h" #include "../gui/CursorHandler.h" #include "../adventureMap/CInGameConsole.h" -#include "../windows/CCreatureWindow.h" #include "../../CCallback.h" #include "../../lib/BattleFieldHandler.h" @@ -35,6 +34,8 @@ #include "../../lib/CStack.h" #include "../../lib/spells/ISpellMechanics.h" +#include + BattleFieldController::BattleFieldController(BattleInterface & owner): owner(owner) { @@ -99,8 +100,13 @@ void BattleFieldController::createHeroes() void BattleFieldController::mouseMoved(const SDL_MouseMotionEvent &event) { - BattleHex selectedHex = getHoveredHex(); + if (!pos.isInside(event.x, event.y)) + { + owner.actionsController->onHoverEnded(); + return; + } + BattleHex selectedHex = getHoveredHex(); owner.actionsController->onHexHovered(selectedHex); } @@ -111,7 +117,7 @@ void BattleFieldController::clickLeft(tribool down, bool previousState) BattleHex selectedHex = getHoveredHex(); if (selectedHex != BattleHex::INVALID) - owner.actionsController->onHexClicked(selectedHex); + owner.actionsController->onHexLeftClicked(selectedHex); } } @@ -121,10 +127,9 @@ void BattleFieldController::clickRight(tribool down, bool previousState) { BattleHex selectedHex = getHoveredHex(); - auto selectedStack = owner.curInt->cb->battleGetStackByPos(selectedHex, true); + if (selectedHex != BattleHex::INVALID) + owner.actionsController->onHexRightClicked(selectedHex); - if (selectedStack != nullptr) - GH.pushIntT(selectedStack, true); } } @@ -365,6 +370,19 @@ BattleHex BattleFieldController::getHoveredHex() { Point hoverPos = GH.getCursorPosition(); + if (owner.attackingHero) + { + if (owner.attackingHero->pos.isInside(hoverPos)) + return BattleHex::HERO_ATTACKER; + } + + if (owner.defendingHero) + { + if (owner.attackingHero->pos.isInside(hoverPos)) + return BattleHex::HERO_DEFENDER; + } + + for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h) { Rect hexPosition = hexPositionAbsolute(h); diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 2d396f0d5..b95f44428 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -273,24 +273,12 @@ void BattleHero::setPhase(EHeroAnimType newPhase) nextPhase = EHeroAnimType::HOLDING; } -void BattleHero::hover(bool on) -{ - //TODO: BROKEN CODE - if (on) - CCS->curh->set(Cursor::Combat::HERO); - else - CCS->curh->set(Cursor::Combat::POINTER); -} - -void BattleHero::clickLeft(tribool down, bool previousState) +void BattleHero::heroLeftClicked() { if(owner.actionsController->spellcastingModeActive()) //we are casting a spell return; - if(boost::logic::indeterminate(down)) - return; - - if(!hero || down || !owner.makingTurn()) + if(!hero || !owner.makingTurn()) return; if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions @@ -306,17 +294,14 @@ void BattleHero::clickLeft(tribool down, bool previousState) } } -void BattleHero::clickRight(tribool down, bool previousState) +void BattleHero::heroRightClicked() { - if(boost::logic::indeterminate(down)) - return; - Point windowPosition; windowPosition.x = (!defender) ? owner.fieldController->pos.left() + 1 : owner.fieldController->pos.right() - 79; windowPosition.y = owner.fieldController->pos.y + 135; InfoAboutHero targetHero; - if(down && (owner.makingTurn() || settings["session"]["spectate"].Bool())) + if(owner.makingTurn() || settings["session"]["spectate"].Bool()) { auto h = defender ? owner.defendingHeroInstance : owner.attackingHeroInstance; targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE); @@ -373,8 +358,6 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her flagAnimation->preload(); flagAnimation->playerColored(hero->tempOwner); - addUsedEvents(LCLICK | RCLICK | HOVER); - switchToNextPhase(); play(); } diff --git a/client/battle/BattleInterfaceClasses.h b/client/battle/BattleInterfaceClasses.h index f22484a5a..5fd34cbd8 100644 --- a/client/battle/BattleInterfaceClasses.h +++ b/client/battle/BattleInterfaceClasses.h @@ -122,9 +122,9 @@ public: void pause(); void play(); - void hover(bool on) override; - void clickLeft(tribool down, bool previousState) override; //call-in - void clickRight(tribool down, bool previousState) override; //call-in + void heroLeftClicked(); + void heroRightClicked(); + BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender); }; diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index af0f51c64..eeaef2b5e 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -39,6 +39,10 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f static const si16 CASTLE_BOTTOM_TOWER = -3; static const si16 CASTLE_UPPER_TOWER = -4; + // hexes for interaction with heroes + static const si16 HERO_ATTACKER = 0; + static const si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1; + // helpers for rendering static const si16 HEX_BEFORE_ALL = std::numeric_limits::min(); static const si16 HEX_AFTER_ALL = std::numeric_limits::max(); diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index fc6283270..6789b9504 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -46,6 +46,7 @@ enum class PossiblePlayerBattleAction // actions performed at l-click { INVALID = -1, CREATURE_INFO, + HERO_INFO, MOVE_TACTICS, CHOOSE_TACTICS_STACK, From 3c8a0b9e70a944e0a78af366214b5cae907b545b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 29 Jan 2023 17:58:55 +0200 Subject: [PATCH 10/13] Add missing text --- client/battle/BattleActionsController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 940e9752b..ddbeb4b27 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -402,7 +402,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle return (boost::format(CGI->generaltexth->allTexts[297]) % targetStack->getName()).str(); case PossiblePlayerBattleAction::HERO_INFO: - return ""; //TODO: "View Hero Stats" + return CGI->generaltexth->translate("core.genrltxt.417"); // "View Hero Stats" } assert(0); return ""; From 1e09c39c0e794a0ad33ad5bbb083e27c19ac5980 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 2 Feb 2023 14:36:58 +0200 Subject: [PATCH 11/13] Fix battle cursor after attack command --- client/battle/BattleInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index 3ad3e62fe..7529588d2 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -257,7 +257,6 @@ void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 addit ba->actionSubtype = additional; sendCommand(ba, actor); - CCS->curh->set(Cursor::Combat::POINTER); } void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor) @@ -277,6 +276,7 @@ void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor) stacksController->setActiveStack(nullptr); //next stack will be activated when action ends } + CCS->curh->set(Cursor::Combat::POINTER); } const CGHeroInstance * BattleInterface::getActiveHero() From 731f7b05787be395f5282fca31e5049d69e2c192 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 2 Feb 2023 14:37:16 +0200 Subject: [PATCH 12/13] Fix opening spellbook via clicking on hero --- client/battle/BattleInterfaceClasses.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index b95f44428..c6e3b6c68 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -283,13 +283,7 @@ void BattleHero::heroLeftClicked() if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions { - BattleHex hoveredHex = owner.fieldController->getHoveredHex(); - //do nothing when any hex is hovered - hero's animation overlaps battlefield - if ( hoveredHex != BattleHex::INVALID ) - return; - CCS->curh->set(Cursor::Map::POINTER); - GH.pushIntT(hero, owner.getCurrentPlayerInterface()); } } From 2ac997d8a99c7bf97ac6aa5c0171236dda6bd123 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 2 Feb 2023 14:37:34 +0200 Subject: [PATCH 13/13] Fix "holding" animation speed --- client/battle/CreatureAnimation.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/battle/CreatureAnimation.cpp b/client/battle/CreatureAnimation.cpp index f6a827468..daad96096 100644 --- a/client/battle/CreatureAnimation.cpp +++ b/client/battle/CreatureAnimation.cpp @@ -78,10 +78,7 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c return baseSpeed; case ECreatureAnimType::HOLDING: - if ( creature->animation.idleAnimationTime > 0.01) - return speed / creature->animation.idleAnimationTime; - else - return 0.f; // this animation is disabled for current creature + return creature->animation.idleAnimationTime; case ECreatureAnimType::SHOOT_UP: case ECreatureAnimType::SHOOT_FRONT: