Implemented new unit actions panel in combat
Alternative actions submod from extras is now deprecated and will have no effect. As long as screen width allows, game will now display additional panel with all possible unit actions. Panel will also display spells that can be cast by unit, allowing small version of unit spellbook (total limit of actions is 12, but some are used for creature actions, so unit spells are limited to 7-9)
BIN
Mods/vcmi/Content/Sprites/battle/actionAttack.png
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
Mods/vcmi/Content/Sprites/battle/actionInfo.png
Normal file
|
After Width: | Height: | Size: 674 B |
BIN
Mods/vcmi/Content/Sprites/battle/actionMove.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Mods/vcmi/Content/Sprites/battle/actionReturn.png
Normal file
|
After Width: | Height: | Size: 903 B |
BIN
Mods/vcmi/Content/Sprites/battle/actionShoot.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 602 B |
BIN
Mods/vcmi/Content/Sprites2x/battle/actionAttack.png
Normal file
|
After Width: | Height: | Size: 1002 B |
BIN
Mods/vcmi/Content/Sprites2x/battle/actionInfo.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Mods/vcmi/Content/Sprites2x/battle/actionMove.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Mods/vcmi/Content/Sprites2x/battle/actionReturn.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Mods/vcmi/Content/Sprites2x/battle/actionShoot.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 717 B |
@@ -32,6 +32,12 @@
|
||||
"vcmi.adventureMap.dwelling3" : "{%s}\n\nWould you like to recruit %s, %s, or %s?",
|
||||
|
||||
"vcmi.artifact.charges" : "Charges",
|
||||
|
||||
"vcmi.battle.action.move" : "Move unit to specified location",
|
||||
"vcmi.battle.action.info" : "Show unit information",
|
||||
"vcmi.battle.action.shoot" : "Use ranged attack",
|
||||
"vcmi.battle.action.attack" : "Use melee attack",
|
||||
"vcmi.battle.action.return" : "Use melee attack and return",
|
||||
|
||||
"vcmi.bonusSource.artifact" : "Artifact",
|
||||
"vcmi.bonusSource.creature" : "Ability",
|
||||
|
||||
@@ -33,6 +33,7 @@ set(vcmiclientcommon_SRCS
|
||||
battle/QuickSpellPanel.cpp
|
||||
battle/StackInfoBasicPanel.cpp
|
||||
battle/StackQueue.cpp
|
||||
battle/UnitActionPanel.cpp
|
||||
|
||||
eventsSDL/NotificationHandler.cpp
|
||||
eventsSDL/InputHandler.cpp
|
||||
@@ -235,6 +236,7 @@ set(vcmiclientcommon_HEADERS
|
||||
battle/QuickSpellPanel.h
|
||||
battle/StackInfoBasicPanel.h
|
||||
battle/StackQueue.h
|
||||
battle/UnitActionPanel.h
|
||||
|
||||
eventsSDL/NotificationHandler.h
|
||||
eventsSDL/InputHandler.h
|
||||
|
||||
@@ -148,7 +148,10 @@ void BattleActionsController::endCastingSpell()
|
||||
}
|
||||
|
||||
if(owner.stacksController->getActiveStack())
|
||||
{
|
||||
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared
|
||||
owner.windowObject->setPossibleActions(possibleActions);
|
||||
}
|
||||
|
||||
selectedStack = nullptr;
|
||||
ENGINE->fakeMouseMove();
|
||||
@@ -624,7 +627,7 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, c
|
||||
return (targetStack && targetStackOwned && targetStack->getMovementRange() > 0);
|
||||
|
||||
case PossiblePlayerBattleAction::CREATURE_INFO:
|
||||
return (targetStack && targetStackOwned && targetStack->alive());
|
||||
return (targetStack && targetStack->alive());
|
||||
|
||||
case PossiblePlayerBattleAction::HERO_INFO:
|
||||
if (targetHex == BattleHex::HERO_ATTACKER)
|
||||
@@ -1037,33 +1040,7 @@ void BattleActionsController::activateStack()
|
||||
tryActivateStackSpellcasting(s);
|
||||
|
||||
possibleActions = getPossibleActionsForStack(s);
|
||||
std::list<PossiblePlayerBattleAction> actionsToSelect;
|
||||
if(!possibleActions.empty())
|
||||
{
|
||||
auto primaryAction = possibleActions.front().get();
|
||||
|
||||
if(primaryAction == PossiblePlayerBattleAction::SHOOT || primaryAction == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE
|
||||
|| primaryAction == PossiblePlayerBattleAction::ANY_LOCATION || primaryAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN)
|
||||
{
|
||||
actionsToSelect.push_back(possibleActions.front());
|
||||
|
||||
auto shootActionPredicate = [](const PossiblePlayerBattleAction& action)
|
||||
{
|
||||
return action.get() == PossiblePlayerBattleAction::SHOOT;
|
||||
};
|
||||
bool hasShootSecondaryAction = std::any_of(possibleActions.begin() + 1, possibleActions.end(), shootActionPredicate);
|
||||
|
||||
if(hasShootSecondaryAction) //casters may have shooting capabilities, for example storm elementals
|
||||
actionsToSelect.emplace_back(PossiblePlayerBattleAction::SHOOT);
|
||||
|
||||
/* TODO: maybe it would also make sense to check spellcast as non-top priority action ("NO_SPELLCAST_BY_DEFAULT" bonus)?
|
||||
* it would require going beyond this "if" block for melee casters
|
||||
* F button helps, but some mod creatures may have that bonus and more than 1 castable spell */
|
||||
|
||||
actionsToSelect.emplace_back(PossiblePlayerBattleAction::ATTACK); //always allow melee attack as last option
|
||||
}
|
||||
}
|
||||
owner.windowObject->setAlternativeActions(actionsToSelect);
|
||||
owner.windowObject->setPossibleActions(possibleActions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1123,14 +1100,9 @@ const std::vector<PossiblePlayerBattleAction> & BattleActionsController::getPoss
|
||||
return possibleActions;
|
||||
}
|
||||
|
||||
void BattleActionsController::removePossibleAction(PossiblePlayerBattleAction action)
|
||||
void BattleActionsController::setPriorityActions(const std::vector<PossiblePlayerBattleAction> & actions)
|
||||
{
|
||||
vstd::erase(possibleActions, action);
|
||||
}
|
||||
|
||||
void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction action)
|
||||
{
|
||||
possibleActions.insert(possibleActions.begin(), action);
|
||||
possibleActions = actions;
|
||||
}
|
||||
|
||||
void BattleActionsController::resetCurrentStackPossibleActions()
|
||||
|
||||
@@ -120,10 +120,9 @@ public:
|
||||
|
||||
/// methods to work with array of possible actions, needed to control special creatures abilities
|
||||
const std::vector<PossiblePlayerBattleAction> & getPossibleActions() const;
|
||||
void removePossibleAction(PossiblePlayerBattleAction);
|
||||
|
||||
/// inserts possible action in the beginning in order to prioritize it
|
||||
void pushFrontPossibleAction(PossiblePlayerBattleAction);
|
||||
/// sets list of high-priority actions that should be selected before any other actions
|
||||
void setPriorityActions(const std::vector<PossiblePlayerBattleAction> &);
|
||||
|
||||
/// resets possible actions to original state
|
||||
void resetCurrentStackPossibleActions();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "QuickSpellPanel.h"
|
||||
#include "StackInfoBasicPanel.h"
|
||||
#include "StackQueue.h"
|
||||
#include "UnitActionPanel.h"
|
||||
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../GameEngine.h"
|
||||
@@ -53,9 +54,8 @@
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
|
||||
BattleWindow::BattleWindow(BattleInterface & Owner):
|
||||
owner(Owner),
|
||||
lastAlternativeAction(PossiblePlayerBattleAction::INVALID)
|
||||
BattleWindow::BattleWindow(BattleInterface & Owner)
|
||||
: owner(Owner)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos.w = 800;
|
||||
@@ -98,7 +98,6 @@ BattleWindow::BattleWindow(BattleInterface & Owner):
|
||||
addShortcut(EShortcut::BATTLE_CONSOLE_DOWN, std::bind(&BattleWindow::bConsoleDownf, this));
|
||||
addShortcut(EShortcut::BATTLE_TACTICS_NEXT, std::bind(&BattleWindow::bTacticNextStack, this));
|
||||
addShortcut(EShortcut::BATTLE_TACTICS_END, std::bind(&BattleWindow::bTacticPhaseEnd, this));
|
||||
addShortcut(EShortcut::BATTLE_SELECT_ACTION, std::bind(&BattleWindow::bSwitchActionf, this));
|
||||
addShortcut(EShortcut::BATTLE_OPEN_ACTIVE_UNIT, std::bind(&BattleWindow::bOpenActiveUnit, this));
|
||||
addShortcut(EShortcut::BATTLE_OPEN_HOVERED_UNIT, std::bind(&BattleWindow::bOpenHoveredUnit, this));
|
||||
|
||||
@@ -194,6 +193,9 @@ void BattleWindow::createQuickSpellWindow()
|
||||
quickSpellWindow = std::make_shared<QuickSpellPanel>(owner);
|
||||
quickSpellWindow->moveTo(Point(pos.x - 67, pos.y));
|
||||
|
||||
unitActionWindow = std::make_shared<UnitActionPanel>(owner);
|
||||
unitActionWindow->moveTo(Point(pos.x + pos.w + 15, pos.y));
|
||||
|
||||
if(settings["battle"]["enableQuickSpellPanel"].Bool())
|
||||
showStickyQuickSpellWindow();
|
||||
else
|
||||
@@ -214,6 +216,7 @@ void BattleWindow::hideStickyQuickSpellWindow()
|
||||
showStickyQuickSpellWindow->Bool() = false;
|
||||
|
||||
quickSpellWindow->disable();
|
||||
unitActionWindow->disable();
|
||||
|
||||
setPositionInfoWindow();
|
||||
createTimerInfoWindows();
|
||||
@@ -227,10 +230,11 @@ void BattleWindow::showStickyQuickSpellWindow()
|
||||
|
||||
auto hero = owner.getBattle()->battleGetMyHero();
|
||||
|
||||
if(ENGINE->screenDimensions().x >= 1050 && hero != nullptr && hero->hasSpellbook())
|
||||
quickSpellWindow->enable();
|
||||
else
|
||||
quickSpellWindow->disable();
|
||||
bool quickSpellWindowVisible = ENGINE->screenDimensions().x >= 1050 && hero != nullptr && hero->hasSpellbook();
|
||||
bool unitActionWindowVisible = ENGINE->screenDimensions().x >= 1050;
|
||||
|
||||
quickSpellWindow->setEnabled(quickSpellWindowVisible);
|
||||
unitActionWindow->setEnabled(unitActionWindowVisible);
|
||||
|
||||
setPositionInfoWindow();
|
||||
createTimerInfoWindows();
|
||||
@@ -242,6 +246,7 @@ void BattleWindow::createTimerInfoWindows()
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
int xOffsetAttacker = quickSpellWindow->isDisabled() ? 0 : -53;
|
||||
int xOffsetDefender = unitActionWindow->isDisabled() ? 0 : 53;
|
||||
|
||||
if(GAME->interface()->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || GAME->interface()->cb->getStartInfo()->turnTimerInfo.unitTimer != 0)
|
||||
{
|
||||
@@ -259,7 +264,7 @@ void BattleWindow::createTimerInfoWindows()
|
||||
if (defender.isValidPlayer())
|
||||
{
|
||||
if (ENGINE->screenDimensions().x >= 1000)
|
||||
defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w + 16, 1), defender);
|
||||
defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w + 16 + xOffsetDefender, 1), defender);
|
||||
else
|
||||
defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w - 78, 135), defender);
|
||||
}
|
||||
@@ -379,10 +384,12 @@ void BattleWindow::updateQueue()
|
||||
void BattleWindow::setPositionInfoWindow()
|
||||
{
|
||||
int xOffsetAttacker = quickSpellWindow->isDisabled() ? 0 : -53;
|
||||
int xOffsetDefender = unitActionWindow->isDisabled() ? 0 : 53;
|
||||
|
||||
if(defenderHeroWindow)
|
||||
{
|
||||
Point position = (ENGINE->screenDimensions().x >= 1000)
|
||||
? Point(pos.x + pos.w + 15, pos.y + 60)
|
||||
? Point(pos.x + pos.w + 15 + xOffsetDefender, pos.y + 60)
|
||||
: Point(pos.x + pos.w -79, pos.y + 195);
|
||||
defenderHeroWindow->moveTo(position);
|
||||
}
|
||||
@@ -396,7 +403,7 @@ void BattleWindow::setPositionInfoWindow()
|
||||
if(defenderStackWindow)
|
||||
{
|
||||
Point position = (ENGINE->screenDimensions().x >= 1000)
|
||||
? Point(pos.x + pos.w + 15, defenderHeroWindow ? defenderHeroWindow->pos.y + 210 : pos.y + 60)
|
||||
? Point(pos.x + pos.w + 15 + xOffsetDefender, defenderHeroWindow ? defenderHeroWindow->pos.y + 210 : pos.y + 60)
|
||||
: Point(pos.x + pos.w -79, defenderHeroWindow ? defenderHeroWindow->pos.y : pos.y + 195);
|
||||
defenderStackWindow->moveTo(position);
|
||||
}
|
||||
@@ -502,12 +509,9 @@ void BattleWindow::tacticPhaseStarted()
|
||||
auto menuTactics = widget<CIntObject>("menuTactics");
|
||||
auto tacticNext = widget<CIntObject>("tacticNext");
|
||||
auto tacticEnd = widget<CIntObject>("tacticEnd");
|
||||
auto alternativeAction = widget<CIntObject>("alternativeAction");
|
||||
|
||||
menuBattle->disable();
|
||||
console->disable();
|
||||
if (alternativeAction)
|
||||
alternativeAction->disable();
|
||||
|
||||
menuTactics->enable();
|
||||
tacticNext->enable();
|
||||
@@ -523,12 +527,9 @@ void BattleWindow::tacticPhaseEnded()
|
||||
auto menuTactics = widget<CIntObject>("menuTactics");
|
||||
auto tacticNext = widget<CIntObject>("tacticNext");
|
||||
auto tacticEnd = widget<CIntObject>("tacticEnd");
|
||||
auto alternativeAction = widget<CIntObject>("alternativeAction");
|
||||
|
||||
menuBattle->enable();
|
||||
console->enable();
|
||||
if (alternativeAction)
|
||||
alternativeAction->enable();
|
||||
|
||||
menuTactics->disable();
|
||||
tacticNext->disable();
|
||||
@@ -615,63 +616,9 @@ void BattleWindow::reallySurrender()
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
|
||||
void BattleWindow::setPossibleActions(const std::vector<PossiblePlayerBattleAction> & actions)
|
||||
{
|
||||
auto w = widget<CButton>("alternativeAction");
|
||||
if(!w)
|
||||
return;
|
||||
|
||||
AnimationPath iconName = AnimationPath::fromJson(variables["actionIconDefault"]);
|
||||
switch(action.get())
|
||||
{
|
||||
case PossiblePlayerBattleAction::ATTACK:
|
||||
iconName = AnimationPath::fromJson(variables["actionIconAttack"]);
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
iconName = AnimationPath::fromJson(variables["actionIconShoot"]);
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
iconName = AnimationPath::fromJson(variables["actionIconSpell"]);
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
iconName = AnimationPath::fromJson(variables["actionIconSpell"]);
|
||||
break;
|
||||
|
||||
//TODO: figure out purpose of this icon
|
||||
//case PossiblePlayerBattleAction::???:
|
||||
//iconName = variables["actionIconWalk"].String();
|
||||
//break;
|
||||
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||
iconName = AnimationPath::fromJson(variables["actionIconReturn"]);
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
iconName = AnimationPath::fromJson(variables["actionIconNoReturn"]);
|
||||
break;
|
||||
}
|
||||
|
||||
w->setImage(iconName);
|
||||
w->redraw();
|
||||
}
|
||||
|
||||
void BattleWindow::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
|
||||
{
|
||||
assert(actions.size() != 1);
|
||||
|
||||
alternativeActions = actions;
|
||||
lastAlternativeAction = PossiblePlayerBattleAction::INVALID;
|
||||
|
||||
if(alternativeActions.size() > 1)
|
||||
{
|
||||
lastAlternativeAction = alternativeActions.back();
|
||||
showAlternativeActionIcon(alternativeActions.front());
|
||||
}
|
||||
else
|
||||
showAlternativeActionIcon(PossiblePlayerBattleAction::INVALID);
|
||||
unitActionWindow->setPossibleActions(actions);
|
||||
}
|
||||
|
||||
void BattleWindow::bAutofightf()
|
||||
@@ -761,28 +708,6 @@ void BattleWindow::bSpellf()
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::bSwitchActionf()
|
||||
{
|
||||
if(alternativeActions.empty())
|
||||
return;
|
||||
|
||||
auto actions = owner.actionsController->getPossibleActions();
|
||||
|
||||
if(!actions.empty() && actions.front() != lastAlternativeAction)
|
||||
{
|
||||
owner.actionsController->removePossibleAction(alternativeActions.front());
|
||||
showAlternativeActionIcon(*std::next(alternativeActions.begin()));
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.actionsController->resetCurrentStackPossibleActions();
|
||||
showAlternativeActionIcon(owner.actionsController->getPossibleActions().front());
|
||||
}
|
||||
|
||||
alternativeActions.push_back(alternativeActions.front());
|
||||
alternativeActions.pop_front();
|
||||
}
|
||||
|
||||
void BattleWindow::bWaitf()
|
||||
{
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
@@ -850,7 +775,6 @@ void BattleWindow::blockUI(bool on)
|
||||
setShortcutBlocked(EShortcut::BATTLE_CAST_SPELL, on || owner.tacticsMode || !canCastSpells);
|
||||
setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait);
|
||||
setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode);
|
||||
setShortcutBlocked(EShortcut::BATTLE_SELECT_ACTION, on || owner.tacticsMode);
|
||||
setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, (settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman) ? on || owner.tacticsMode || owner.actionsController->heroSpellcastingModeActive() : owner.actionsController->heroSpellcastingModeActive());
|
||||
setShortcutBlocked(EShortcut::BATTLE_END_WITH_AUTOCOMBAT, on || !onlyOnePlayerHuman || owner.actionsController->heroSpellcastingModeActive());
|
||||
setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on || !owner.tacticsMode);
|
||||
@@ -859,6 +783,7 @@ void BattleWindow::blockUI(bool on)
|
||||
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
|
||||
|
||||
quickSpellWindow->setInputEnabled(!on);
|
||||
unitActionWindow->setInputEnabled(!on);
|
||||
}
|
||||
|
||||
void BattleWindow::bOpenActiveUnit()
|
||||
|
||||
@@ -28,6 +28,7 @@ class TurnTimerWidget;
|
||||
class HeroInfoBasicPanel;
|
||||
class StackInfoBasicPanel;
|
||||
class QuickSpellPanel;
|
||||
class UnitActionPanel;
|
||||
|
||||
/// GUI object that handles functionality of panel at the bottom of combat screen
|
||||
class BattleWindow : public InterfaceObjectConfigurable
|
||||
@@ -42,6 +43,7 @@ class BattleWindow : public InterfaceObjectConfigurable
|
||||
std::shared_ptr<StackInfoBasicPanel> defenderStackWindow;
|
||||
|
||||
std::shared_ptr<QuickSpellPanel> quickSpellWindow;
|
||||
std::shared_ptr<UnitActionPanel> unitActionWindow;
|
||||
|
||||
std::shared_ptr<TurnTimerWidget> attackerTimerWidget;
|
||||
std::shared_ptr<TurnTimerWidget> defenderTimerWidget;
|
||||
@@ -53,7 +55,6 @@ class BattleWindow : public InterfaceObjectConfigurable
|
||||
void bAutofightf();
|
||||
void bSpellf();
|
||||
void bWaitf();
|
||||
void bSwitchActionf();
|
||||
void bDefencef();
|
||||
void bConsoleUpf();
|
||||
void bConsoleDownf();
|
||||
@@ -66,11 +67,6 @@ class BattleWindow : public InterfaceObjectConfigurable
|
||||
void reallyFlee();
|
||||
void reallySurrender();
|
||||
|
||||
/// management of alternative actions
|
||||
std::list<PossiblePlayerBattleAction> alternativeActions;
|
||||
PossiblePlayerBattleAction lastAlternativeAction;
|
||||
void showAlternativeActionIcon(PossiblePlayerBattleAction);
|
||||
|
||||
void useSpellIfPossible(int slot);
|
||||
|
||||
/// flip battle queue visibility to opposite
|
||||
@@ -140,8 +136,8 @@ public:
|
||||
/// Toggle UI to displaying battle log in place of tactics UI
|
||||
void tacticPhaseEnded();
|
||||
|
||||
/// Set possible alternative options. If more than 1 - the last will be considered as default option
|
||||
void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
|
||||
/// Set possible alternative options to fill unit actions panel
|
||||
void setPossibleActions(const std::vector<PossiblePlayerBattleAction> & allActions);
|
||||
|
||||
/// ends battle with autocombat
|
||||
void endWithAutocombat();
|
||||
|
||||
152
client/battle/UnitActionPanel.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* UnitActionPanel.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "UnitActionPanel.h"
|
||||
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleActionsController.h"
|
||||
|
||||
#include "../GameEngine.h"
|
||||
#include "../eventsSDL/InputHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../windows/CSpellWindow.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/GameLibrary.h"
|
||||
#include "../../lib/battle/CPlayerBattleCallback.h"
|
||||
#include "../../lib/json/JsonUtils.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
|
||||
UnitActionPanel::UnitActionPanel(BattleInterface & owner)
|
||||
: CIntObject(0)
|
||||
, owner(owner)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
addUsedEvents(LCLICK | SHOW_POPUP | MOVE | INPUT_MODE_CHANGE);
|
||||
|
||||
pos = Rect(0, 0, 52, 600);
|
||||
background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), pos);
|
||||
rect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w + 1, pos.h + 1), ColorRGBA(0, 0, 0, 0), ColorRGBA(241, 216, 120, 255));
|
||||
}
|
||||
|
||||
void UnitActionPanel::restoreAllActions()
|
||||
{
|
||||
owner.actionsController->resetCurrentStackPossibleActions();
|
||||
}
|
||||
|
||||
void UnitActionPanel::setActions(int buttonIndex, const std::vector<PossiblePlayerBattleAction> & filteredActions)
|
||||
{
|
||||
for (const auto & button : buttons)
|
||||
if (button != buttons.at(buttonIndex))
|
||||
button->setSelectedSilent(false);
|
||||
|
||||
owner.actionsController->setPriorityActions(filteredActions);
|
||||
if (filteredActions.front().spellcast())
|
||||
owner.actionsController->enterCreatureCastingMode();
|
||||
owner.actionsController->setPriorityActions(filteredActions);
|
||||
}
|
||||
|
||||
void UnitActionPanel::testAndAddAction(const std::vector<PossiblePlayerBattleAction> & allActions, const std::vector<PossiblePlayerBattleAction::Actions> & actionFilter, const ImagePath & iconPath, const std::string & descriptionTextID)
|
||||
{
|
||||
std::vector<PossiblePlayerBattleAction> filteredActions;
|
||||
|
||||
for (const auto & action : allActions)
|
||||
if (vstd::contains(actionFilter, action.get()))
|
||||
filteredActions.push_back(action);
|
||||
|
||||
if (filteredActions.empty())
|
||||
return;
|
||||
|
||||
int index = buttons.size();
|
||||
|
||||
const auto & callback = [this, filteredActions, index](bool isSelected){ if (isSelected) setActions(index, filteredActions); else restoreAllActions(); };
|
||||
|
||||
MetaString tooltip;
|
||||
tooltip.appendTextID(descriptionTextID);
|
||||
|
||||
auto button = std::make_shared<CToggleButton>(Point(2, 7 + 50 * index), AnimationPath::builtin("battleUnitAction"), CButton::tooltip(tooltip.toString()), callback);
|
||||
button->setOverlay(std::make_shared<CPicture>(iconPath));
|
||||
button->setHighlightedBorderColor(Colors::WHITE);
|
||||
button->setAllowDeselection(true);
|
||||
buttons.push_back(button);
|
||||
}
|
||||
|
||||
void UnitActionPanel::testAndAddSpell(const std::vector<PossiblePlayerBattleAction> & allActions, const SpellID & spellFilter)
|
||||
{
|
||||
std::vector<PossiblePlayerBattleAction> filteredActions;
|
||||
|
||||
for (const auto & action : allActions)
|
||||
if (action.spellcast() && action.spell() == spellFilter)
|
||||
filteredActions.push_back(action);
|
||||
|
||||
if (filteredActions.empty())
|
||||
return;
|
||||
|
||||
int index = buttons.size();
|
||||
const auto & callback = [this, filteredActions, index](bool isSelected){ if (isSelected) setActions(index, filteredActions); else restoreAllActions();};
|
||||
|
||||
MetaString tooltip;
|
||||
tooltip.appendTextID("core.genrltxt.26");
|
||||
tooltip.replaceName(spellFilter);
|
||||
|
||||
std::string hoverText = tooltip.toString();
|
||||
std::string description = spellFilter.toSpell()->getDescriptionTranslated(0);
|
||||
|
||||
|
||||
auto button = std::make_shared<CToggleButton>(Point(2, 7 + 50 * index), AnimationPath::builtin("battleUnitAction"), CButton::tooltip(hoverText, description), callback);
|
||||
button->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("spellint"), spellFilter.getNum() + 1));
|
||||
button->setHighlightedBorderColor(Colors::WHITE);
|
||||
buttons.push_back(button);
|
||||
}
|
||||
|
||||
void UnitActionPanel::setPossibleActions(const std::vector<PossiblePlayerBattleAction> & newActions)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
buttons.clear();
|
||||
|
||||
static const std::vector actionsMove = { PossiblePlayerBattleAction::MOVE_STACK };
|
||||
static const std::vector actionsInfo = { PossiblePlayerBattleAction::CREATURE_INFO, PossiblePlayerBattleAction::HERO_INFO };
|
||||
static const std::vector actionsShoot = { PossiblePlayerBattleAction::SHOOT };
|
||||
static const std::vector actionsAttack = { PossiblePlayerBattleAction::ATTACK, PossiblePlayerBattleAction::WALK_AND_ATTACK };
|
||||
static const std::vector actionsReturn = { PossiblePlayerBattleAction::ATTACK_AND_RETURN };
|
||||
|
||||
testAndAddAction(newActions, actionsMove, ImagePath::builtin("battle/actionMove"), "vcmi.battle.action.move");
|
||||
testAndAddAction(newActions, actionsReturn, ImagePath::builtin("battle/actionReturn"), "vcmi.battle.action.return");
|
||||
testAndAddAction(newActions, actionsAttack, ImagePath::builtin("battle/actionAttack"), "vcmi.battle.action.attack");
|
||||
testAndAddAction(newActions, actionsShoot, ImagePath::builtin("battle/actionShoot"), "vcmi.battle.action.shoot");
|
||||
|
||||
std::vector<SpellID> spells;
|
||||
|
||||
for (const auto & action : newActions)
|
||||
if (action.spellcast())
|
||||
spells.push_back(action.spell());
|
||||
|
||||
|
||||
for (const auto & spell : spells)
|
||||
testAndAddSpell(newActions, spell);
|
||||
|
||||
// Not really a unit action, so place it at the end
|
||||
testAndAddAction(newActions, actionsInfo, ImagePath::builtin("battle/actionInfo"), "vcmi.battle.action.info");
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
void UnitActionPanel::show(Canvas & to)
|
||||
{
|
||||
showAll(to);
|
||||
CIntObject::show(to);
|
||||
}
|
||||
46
client/battle/UnitActionPanel.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* UnitActionPanel.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../gui/CIntObject.h"
|
||||
#include "../../lib/battle/PossiblePlayerBattleAction.h"
|
||||
#include "../../lib/filesystem/ResourcePath.h"
|
||||
|
||||
class CFilledTexture;
|
||||
class TransparentFilledRectangle;
|
||||
class CToggleButton;
|
||||
class CLabel;
|
||||
class BattleInterface;
|
||||
|
||||
class UnitActionPanel : public CIntObject
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<CFilledTexture> background;
|
||||
std::shared_ptr<TransparentFilledRectangle> rect;
|
||||
std::vector<std::shared_ptr<CToggleButton>> buttons;
|
||||
|
||||
BattleInterface & owner;
|
||||
|
||||
void testAndAddAction(const std::vector<PossiblePlayerBattleAction> & allActions, const std::vector<PossiblePlayerBattleAction::Actions> & actionFilter, const ImagePath & iconPath, const std::string & descriptionTextID );
|
||||
void testAndAddSpell(const std::vector<PossiblePlayerBattleAction> & allActions, const SpellID & spellFilter );
|
||||
|
||||
void restoreAllActions();
|
||||
void setActions(int buttonIndex, const std::vector<PossiblePlayerBattleAction> & newActions);
|
||||
public:
|
||||
static constexpr int ACTION_SLOTS = 12;
|
||||
|
||||
UnitActionPanel(BattleInterface & owner);
|
||||
|
||||
void setPossibleActions(const std::vector<PossiblePlayerBattleAction> & actions);
|
||||
|
||||
std::vector<std::tuple<SpellID, bool>> getSpells() const;
|
||||
|
||||
void show(Canvas & to) override;
|
||||
};
|
||||
@@ -192,7 +192,6 @@ enum class EShortcut
|
||||
BATTLE_CONSOLE_DOWN,
|
||||
BATTLE_TACTICS_NEXT,
|
||||
BATTLE_TACTICS_END,
|
||||
BATTLE_SELECT_ACTION, // Alternative actions toggle
|
||||
BATTLE_TOGGLE_HEROES_STATS,
|
||||
BATTLE_OPEN_ACTIVE_UNIT,
|
||||
BATTLE_OPEN_HOVERED_UNIT,
|
||||
|
||||
@@ -229,7 +229,6 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
|
||||
{"battleConsoleDown", EShortcut::BATTLE_CONSOLE_DOWN },
|
||||
{"battleTacticsNext", EShortcut::BATTLE_TACTICS_NEXT },
|
||||
{"battleTacticsEnd", EShortcut::BATTLE_TACTICS_END },
|
||||
{"battleSelectAction", EShortcut::BATTLE_SELECT_ACTION },
|
||||
{"battleToggleQuickSpell", EShortcut::BATTLE_TOGGLE_QUICKSPELL },
|
||||
{"battleSpellShortcut0", EShortcut::BATTLE_SPELL_SHORTCUT_0 },
|
||||
{"battleSpellShortcut1", EShortcut::BATTLE_SPELL_SHORTCUT_1 },
|
||||
|
||||
@@ -62,6 +62,11 @@ void CButton::setBorderColor(std::optional<ColorRGBA> newBorderColor)
|
||||
borderColor = newBorderColor;
|
||||
}
|
||||
|
||||
void CButton::setHighlightedBorderColor(std::optional<ColorRGBA> newBorderColor)
|
||||
{
|
||||
highlightedBorderColor = newBorderColor;
|
||||
}
|
||||
|
||||
void CButton::addCallback(const std::function<void()> & callback)
|
||||
{
|
||||
this->callback += callback;
|
||||
@@ -378,6 +383,12 @@ void CButton::showAll(Canvas & to)
|
||||
|
||||
if (borderColor)
|
||||
to.drawBorder(Rect::createAround(pos, 1), *borderColor);
|
||||
|
||||
if (highlightedBorderColor && isHighlighted())
|
||||
{
|
||||
to.drawBorder(pos, *highlightedBorderColor);
|
||||
to.drawBorder(Rect(pos.topLeft() + Point(1,1), pos.dimensions() - Point(1,1)), *highlightedBorderColor);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> CButton::tooltip()
|
||||
|
||||
@@ -73,6 +73,7 @@ class CButton : public ButtonBase
|
||||
|
||||
std::array<std::string, 4> hoverTexts; //texts for statusbar, if empty - first entry will be used
|
||||
std::optional<ColorRGBA> borderColor; // mapping of button state to border color
|
||||
std::optional<ColorRGBA> highlightedBorderColor; // mapping of button state to border color
|
||||
std::string helpBox; //for right-click help
|
||||
|
||||
bool actOnDown; //runs when mouse is pressed down over it, not when up
|
||||
@@ -88,6 +89,7 @@ protected:
|
||||
public:
|
||||
// sets the same border color for all button states.
|
||||
void setBorderColor(std::optional<ColorRGBA> borderColor);
|
||||
void setHighlightedBorderColor(std::optional<ColorRGBA> borderColor);
|
||||
|
||||
/// adds one more callback to on-click actions
|
||||
void addCallback(const std::function<void()> & callback);
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
"battleOpenActiveUnit": "I",
|
||||
"battleOpenHoveredUnit": "V",
|
||||
"battleRetreat": "R",
|
||||
"battleSelectAction": "S",
|
||||
"battleToggleQuickSpell": "T",
|
||||
"battleSpellShortcut0": "1",
|
||||
"battleSpellShortcut1": "2",
|
||||
|
||||
114
config/widgets/buttons/battleUnitAction.json
Normal file
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"normal" : {
|
||||
"width": 48,
|
||||
"height": 36,
|
||||
"items" : [
|
||||
{
|
||||
"type": "texture",
|
||||
"image": "DiBoxBck",
|
||||
"rect": {"x": 0, "y": 0, "w": 48, "h": 36}
|
||||
},
|
||||
{
|
||||
"type": "graphicalPrimitive",
|
||||
"rect": {"x": 0, "y": 0, "w": 48, "h": 36},
|
||||
"primitives" : [
|
||||
{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] },
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] },
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
|
||||
{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
|
||||
{ "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"pressed" : {
|
||||
"width": 48,
|
||||
"height": 36,
|
||||
"items" : [
|
||||
{
|
||||
"type": "texture",
|
||||
"image": "DiBoxBck",
|
||||
"rect": {"x": 1, "y": 1, "w": 47, "h": 35}
|
||||
},
|
||||
{
|
||||
"type": "graphicalPrimitive",
|
||||
"rect": {"x": 0, "y": 0, "w": 48, "h": 36},
|
||||
"primitives" : [
|
||||
{ "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] },
|
||||
|
||||
{ "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] },
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] },
|
||||
{ "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] },
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"blocked" : {
|
||||
"width": 48,
|
||||
"height": 36,
|
||||
"items" : [
|
||||
{
|
||||
"type": "texture",
|
||||
"image": "DiBoxBck",
|
||||
"rect": {"x": 0, "y": 0, "w": 48, "h": 36}
|
||||
},
|
||||
{
|
||||
"type": "graphicalPrimitive",
|
||||
"rect": {"x": 0, "y": 0, "w": 48, "h": 36},
|
||||
"primitives" : [
|
||||
{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] },
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] },
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
|
||||
{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
|
||||
{ "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"highlighted" : {
|
||||
"width": 48,
|
||||
"height": 36,
|
||||
"items" : [
|
||||
{
|
||||
"type": "texture",
|
||||
"image": "DiBoxBck",
|
||||
"rect": {"x": 0, "y": 0, "w": 48, "h": 36}
|
||||
},
|
||||
{
|
||||
"type": "graphicalPrimitive",
|
||||
"rect": {"x": 0, "y": 0, "w": 48, "h": 36},
|
||||
"primitives" : [
|
||||
{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] },
|
||||
|
||||
{ "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 255 ] },
|
||||
{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 255 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] },
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
@@ -100,13 +100,17 @@
|
||||
"primitives" : [
|
||||
{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] },
|
||||
|
||||
{ "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 255 ] },
|
||||
{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 255 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] },
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] },
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] },
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
|
||||
{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
|
||||
|
||||
{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
|
||||
{ "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1901,7 +1901,7 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(vstd::RNG & rand,const CStack
|
||||
if (!bl->size())
|
||||
return SpellID::NONE;
|
||||
|
||||
if(bl->size() == 1)
|
||||
if(bl->size() == 1 && bl->front()->additionalInfo[0] > 0) // there is one random spell -> select it
|
||||
return bl->front()->subtype.as<SpellID>();
|
||||
|
||||
int totalWeight = 0;
|
||||
|
||||