mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-20 20:23:03 +02:00
Feature: Army Management Shortcuts should work as in HD+ Mod
This commit is contained in:
parent
7faa691f42
commit
8cae3398ba
@ -109,6 +109,7 @@ int CCallback::mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, S
|
||||
sendRequest(&pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val)
|
||||
{
|
||||
ArrangeStacks pack(3,p1,p2,s1->id,s2->id,val);
|
||||
@ -116,6 +117,34 @@ int CCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, Sl
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot)
|
||||
{
|
||||
BulkMoveArmy pack(srcArmy, destArmy, srcSlot);
|
||||
sendRequest(&pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany)
|
||||
{
|
||||
BulkSplitStack pack(armyId, srcSlot, howMany);
|
||||
sendRequest(&pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot)
|
||||
{
|
||||
BulkSmartSplitStack pack(armyId, srcSlot);
|
||||
sendRequest(&pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot)
|
||||
{
|
||||
BulkMergeStacks pack(armyId, srcSlot);
|
||||
sendRequest(&pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CCallback::dismissHero(const CGHeroInstance *hero)
|
||||
{
|
||||
if(player!=hero->tempOwner) return false;
|
||||
|
14
CCallback.h
14
CCallback.h
@ -78,6 +78,12 @@ public:
|
||||
virtual void save(const std::string &fname) = 0;
|
||||
virtual void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) = 0;
|
||||
virtual void buildBoat(const IShipyard *obj) = 0;
|
||||
|
||||
// To implement high-level army management bulk actions
|
||||
virtual int bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) = 0;
|
||||
virtual int bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany = 1) = 0;
|
||||
virtual int bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot) = 0;
|
||||
virtual int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) = 0;
|
||||
};
|
||||
|
||||
struct CPackForServer;
|
||||
@ -99,7 +105,9 @@ public:
|
||||
friend class CClient;
|
||||
};
|
||||
|
||||
class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback
|
||||
class CCallback : public CPlayerSpecificInfoCallback,
|
||||
public IGameActionCallback,
|
||||
public CBattleCallback
|
||||
{
|
||||
public:
|
||||
CCallback(CGameState * GS, boost::optional<PlayerColor> Player, CClient *C);
|
||||
@ -125,6 +133,10 @@ public:
|
||||
int mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; //first goes to the second
|
||||
int mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; //first goes to the second
|
||||
int splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val) override;
|
||||
int bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) override;
|
||||
int bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany = 1) override;
|
||||
int bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot) override;
|
||||
int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) override;
|
||||
bool dismissHero(const CGHeroInstance * hero) override;
|
||||
bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override;
|
||||
bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
|
||||
|
@ -35,4 +35,4 @@ Platform support is constantly tested by continuous integration and CMake config
|
||||
VCMI Project source code is licensed under GPL version 2 or later.
|
||||
VCMI Project assets are licensed under CC-BY-SA 4.0. Assets sources and information about contributors are available under following link: [https://github.com/vcmi/vcmi-assets]
|
||||
|
||||
Copyright (C) 2007-2020 VCMI Team (check AUTHORS file for the contributors list)
|
||||
Copyright (C) 2007-2022 VCMI Team (check AUTHORS file for the contributors list)
|
||||
|
@ -238,6 +238,30 @@ void RebalanceStacks::applyCl(CClient * cl)
|
||||
dispatchGarrisonChange(cl, srcArmy, dstArmy);
|
||||
}
|
||||
|
||||
void BulkRebalanceStacks::applyCl(CClient * cl)
|
||||
{
|
||||
if(!moves.empty())
|
||||
{
|
||||
auto destArmy = moves[0].srcArmy == moves[0].dstArmy
|
||||
? ObjectInstanceID()
|
||||
: moves[0].dstArmy;
|
||||
dispatchGarrisonChange(cl, moves[0].srcArmy, destArmy);
|
||||
}
|
||||
}
|
||||
|
||||
void BulkSmartRebalanceStacks::applyCl(CClient * cl)
|
||||
{
|
||||
if(!moves.empty())
|
||||
{
|
||||
assert(moves[0].srcArmy == moves[0].dstArmy);
|
||||
dispatchGarrisonChange(cl, moves[0].srcArmy, ObjectInstanceID());
|
||||
}
|
||||
else if(!changes.empty())
|
||||
{
|
||||
dispatchGarrisonChange(cl, changes[0].army, ObjectInstanceID());
|
||||
}
|
||||
}
|
||||
|
||||
void PutArtifact::applyCl(CClient *cl)
|
||||
{
|
||||
callInterfaceIfPresent(cl, al.owningPlayer(), &IGameEventsReceiver::artifactPut, al);
|
||||
|
@ -74,18 +74,23 @@ void CGarrisonSlot::hover (bool on)
|
||||
}
|
||||
else
|
||||
{
|
||||
if(upg == EGarrisonType::UP)
|
||||
const bool isHeroOnMap = owner->armedObjs[0] // Hero is not a visitor and not a garrison defender
|
||||
&& owner->armedObjs[0]->ID == Obj::HERO
|
||||
&& (!owner->armedObjs[1] || owner->armedObjs[1]->ID == Obj::HERO) // one hero or we are in the Heroes exchange window
|
||||
&& !(static_cast<const CGHeroInstance*>(owner->armedObjs[0]))->inTownGarrison;
|
||||
|
||||
if(isHeroOnMap)
|
||||
{
|
||||
temp = CGI->generaltexth->allTexts[481]; //Select %s
|
||||
}
|
||||
else if(upg == EGarrisonType::UP)
|
||||
{
|
||||
temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison)
|
||||
}
|
||||
else if(owner->armedObjs[0] && (owner->armedObjs[0]->ID == Obj::TOWN || owner->armedObjs[0]->ID == Obj::HERO))
|
||||
else // Hero is visiting some object (town, mine, etc)
|
||||
{
|
||||
temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting)
|
||||
}
|
||||
else
|
||||
{
|
||||
temp = CGI->generaltexth->allTexts[481]; //Select %s
|
||||
}
|
||||
boost::algorithm::replace_first(temp,"%s",creature->nameSing);
|
||||
}
|
||||
}
|
||||
@ -140,6 +145,18 @@ bool CGarrisonSlot::ally() const
|
||||
return PlayerRelations::ALLIES == LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, getObj()->tempOwner);
|
||||
}
|
||||
|
||||
std::function<void()> CGarrisonSlot::getDismiss() const
|
||||
{
|
||||
const bool canDismiss = getObj()->tempOwner == LOCPLINT->playerID
|
||||
&& (getObj()->stacksCount() > 1 ||
|
||||
!getObj()->needsLastStack());
|
||||
|
||||
return canDismiss ? [=]()
|
||||
{
|
||||
LOCPLINT->cb->dismissCreature(getObj(), ID);
|
||||
} : (std::function<void()>)nullptr;
|
||||
}
|
||||
|
||||
/// The creature slot has been clicked twice, therefore the creature info should be shown
|
||||
/// @return Whether the view should be refreshed
|
||||
bool CGarrisonSlot::viewInfo()
|
||||
@ -148,11 +165,9 @@ bool CGarrisonSlot::viewInfo()
|
||||
LOCPLINT->cb->getUpgradeInfo(getObj(), ID, pom);
|
||||
|
||||
bool canUpgrade = getObj()->tempOwner == LOCPLINT->playerID && pom.oldID>=0; //upgrade is possible
|
||||
bool canDismiss = getObj()->tempOwner == LOCPLINT->playerID && (getObj()->stacksCount()>1 || !getObj()->needsLastStack());
|
||||
std::function<void(CreatureID)> upgr = nullptr;
|
||||
std::function<void()> dism = nullptr;
|
||||
auto dism = getDismiss();
|
||||
if(canUpgrade) upgr = [=] (CreatureID newID) { LOCPLINT->cb->upgradeCreature(getObj(), ID, newID); };
|
||||
if(canDismiss) dism = [=](){ LOCPLINT->cb->dismissCreature(getObj(), ID); };
|
||||
|
||||
owner->selectSlot(nullptr);
|
||||
owner->setSplittingMode(false);
|
||||
@ -288,13 +303,17 @@ void CGarrisonSlot::clickLeft(tribool down, bool previousState)
|
||||
{
|
||||
bool refr = false;
|
||||
const CGarrisonSlot * selection = owner->getSelection();
|
||||
|
||||
if(!selection)
|
||||
{
|
||||
refr = highlightOrDropArtifact();
|
||||
refr = highlightOrDropArtifact(); // Affects selection
|
||||
handleSplittingShortcuts();
|
||||
}
|
||||
else if(selection == this)
|
||||
refr = viewInfo();
|
||||
{
|
||||
if(!handleSplittingShortcuts())
|
||||
refr = viewInfo(); // Affects selection
|
||||
}
|
||||
// Re-highlight if troops aren't removable or not ours.
|
||||
else if (mustForceReselection())
|
||||
{
|
||||
@ -403,34 +422,67 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, CGa
|
||||
update();
|
||||
}
|
||||
|
||||
void CGarrisonSlot::splitIntoParts(CGarrisonSlot::EGarrisonType type, int amount, int maxOfSplittedSlots)
|
||||
void CGarrisonSlot::splitIntoParts(CGarrisonSlot::EGarrisonType type, int amount)
|
||||
{
|
||||
auto empty = owner->getEmptySlot(type);
|
||||
|
||||
if(empty == SlotID())
|
||||
return;
|
||||
|
||||
owner->pb = type;
|
||||
for(CGarrisonSlot * slot : owner->getEmptySlots(type))
|
||||
{
|
||||
owner->p2 = slot->ID;
|
||||
owner->splitStacks(1, amount);
|
||||
maxOfSplittedSlots--;
|
||||
if(!maxOfSplittedSlots || owner->getSelection()->myStack->count <= 1)
|
||||
break;
|
||||
}
|
||||
owner->p2 = empty;
|
||||
owner->splitStacks(1, amount);
|
||||
}
|
||||
|
||||
void CGarrisonSlot::handleSplittingShortcuts()
|
||||
bool CGarrisonSlot::handleSplittingShortcuts()
|
||||
{
|
||||
const Uint8 * state = SDL_GetKeyboardState(NULL);
|
||||
if(owner->getSelection() && owner->getEmptySlots(owner->getSelection()->upg).size() && owner->getSelection()->myStack->count > 1)
|
||||
const bool isAlt = !!state[SDL_SCANCODE_LALT];
|
||||
const bool isLShift = !!state[SDL_SCANCODE_LSHIFT];
|
||||
const bool isLCtrl = !!state[SDL_SCANCODE_LCTRL];
|
||||
|
||||
if(!isAlt && !isLShift && !isLCtrl)
|
||||
return false; // This is only case when return false
|
||||
|
||||
auto selected = owner->getSelection();
|
||||
if(!selected)
|
||||
return true; // Some Shortcusts are pressed but there are no appropriate actions
|
||||
|
||||
auto units = selected->myStack->count;
|
||||
if(units < 1)
|
||||
return true;
|
||||
|
||||
if (isLShift && isLCtrl && isAlt)
|
||||
{
|
||||
if(state[SDL_SCANCODE_LCTRL] && state[SDL_SCANCODE_LSHIFT])
|
||||
splitIntoParts(owner->getSelection()->upg, 1, 7);
|
||||
else if(state[SDL_SCANCODE_LCTRL])
|
||||
splitIntoParts(owner->getSelection()->upg, 1, 1);
|
||||
else if(state[SDL_SCANCODE_LSHIFT])
|
||||
splitIntoParts(owner->getSelection()->upg, owner->getSelection()->myStack->count/2 , 1);
|
||||
else
|
||||
return;
|
||||
owner->selectSlot(nullptr);
|
||||
owner->bulkMoveArmy(selected);
|
||||
}
|
||||
else if(isLCtrl && isAlt)
|
||||
{
|
||||
owner->moveStackToAnotherArmy(selected);
|
||||
}
|
||||
else if(isLShift && isAlt)
|
||||
{
|
||||
auto dismiss = getDismiss();
|
||||
if(dismiss)
|
||||
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[12], dismiss, nullptr);
|
||||
}
|
||||
else if(isAlt)
|
||||
{
|
||||
owner->bulkMergeStacks(selected);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(units <= 1)
|
||||
return true;
|
||||
|
||||
if(isLCtrl && isLShift)
|
||||
owner->bulkSplitStack(selected);
|
||||
else if(isLShift)
|
||||
owner->bulkSmartSplitStack(selected);
|
||||
else
|
||||
splitIntoParts(selected->upg, 1); // LCtrl
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGarrisonInt::addSplitBtn(std::shared_ptr<CButton> button)
|
||||
@ -492,6 +544,115 @@ void CGarrisonInt::splitStacks(int, int amountRight)
|
||||
LOCPLINT->cb->splitStack(armedObjs[getSelection()->upg], armedObjs[pb], getSelection()->ID, p2, amountRight);
|
||||
}
|
||||
|
||||
bool CGarrisonInt::checkSelected(const CGarrisonSlot * selected, TQuantity min) const
|
||||
{
|
||||
return selected && selected->myStack && selected->myStack->count > min && selected->creature;
|
||||
}
|
||||
|
||||
void CGarrisonInt::moveStackToAnotherArmy(const CGarrisonSlot * selected)
|
||||
{
|
||||
if(!checkSelected(selected))
|
||||
return;
|
||||
|
||||
const auto srcArmyType = selected->upg;
|
||||
const auto destArmyType = srcArmyType == CGarrisonSlot::UP
|
||||
? CGarrisonSlot::DOWN
|
||||
: CGarrisonSlot::UP;
|
||||
|
||||
auto srcArmy = armedObjs[srcArmyType];
|
||||
auto destArmy = armedObjs[destArmyType];
|
||||
|
||||
if(!destArmy)
|
||||
return;
|
||||
|
||||
auto destSlot = destArmy->getSlotFor(selected->creature);
|
||||
|
||||
if(destSlot == SlotID())
|
||||
return;
|
||||
|
||||
const auto srcSlot = selected->ID;
|
||||
const bool isDestSlotEmpty = !destArmy->getStackCount(destSlot);
|
||||
|
||||
if(isDestSlotEmpty && !destArmy->getStackCount(srcSlot))
|
||||
destSlot = srcSlot; // Same place is more preferable
|
||||
|
||||
const bool isLastStack = srcArmy->stacksCount() == 1 && srcArmy->needsLastStack();
|
||||
auto srcAmount = selected->myStack->count - (isLastStack ? 1 : 0);
|
||||
|
||||
if(!srcAmount)
|
||||
return;
|
||||
|
||||
if(!isDestSlotEmpty || isLastStack)
|
||||
{
|
||||
srcAmount += destArmy->getStackCount(destSlot); // Due to 'split' implementation in the 'CGameHandler::arrangeStacks'
|
||||
LOCPLINT->cb->splitStack(srcArmy, destArmy, srcSlot, destSlot, srcAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOCPLINT->cb->swapCreatures(srcArmy, destArmy, srcSlot, destSlot);
|
||||
}
|
||||
}
|
||||
|
||||
void CGarrisonInt::bulkMoveArmy(const CGarrisonSlot * selected)
|
||||
{
|
||||
if(!checkSelected(selected))
|
||||
return;
|
||||
|
||||
const auto srcArmyType = selected->upg;
|
||||
const auto destArmyType = (srcArmyType == CGarrisonSlot::UP)
|
||||
? CGarrisonSlot::DOWN
|
||||
: CGarrisonSlot::UP;
|
||||
|
||||
auto srcArmy = armedObjs[srcArmyType];
|
||||
auto destArmy = armedObjs[destArmyType];
|
||||
|
||||
if(!destArmy)
|
||||
return;
|
||||
|
||||
const auto srcSlot = selected->ID;
|
||||
LOCPLINT->cb->bulkMoveArmy(srcArmy->id, destArmy->id, srcSlot);
|
||||
}
|
||||
|
||||
void CGarrisonInt::bulkMergeStacks(const CGarrisonSlot * selected)
|
||||
{
|
||||
if(!checkSelected(selected))
|
||||
return;
|
||||
|
||||
const auto type = selected->upg;
|
||||
|
||||
if(!armedObjs[type]->hasCreatureSlots(selected->creature, selected->ID))
|
||||
return;
|
||||
|
||||
LOCPLINT->cb->bulkMergeStacks(armedObjs[type]->id, selected->ID);
|
||||
}
|
||||
|
||||
void CGarrisonInt::bulkSplitStack(const CGarrisonSlot * selected)
|
||||
{
|
||||
if(!checkSelected(selected, 1)) // check if > 1
|
||||
return;
|
||||
|
||||
const auto type = selected->upg;
|
||||
|
||||
if(!hasEmptySlot(type))
|
||||
return;
|
||||
|
||||
LOCPLINT->cb->bulkSplitStack(armedObjs[type]->id, selected->ID);
|
||||
}
|
||||
|
||||
void CGarrisonInt::bulkSmartSplitStack(const CGarrisonSlot * selected)
|
||||
{
|
||||
if(!checkSelected(selected, 1))
|
||||
return;
|
||||
|
||||
const auto type = selected->upg;
|
||||
|
||||
// Do not disturb the server if the creature is already balanced
|
||||
if(!hasEmptySlot(type) && armedObjs[type]->isCreatureBalanced(selected->creature))
|
||||
return;
|
||||
|
||||
LOCPLINT->cb->bulkSmartSplitStack(armedObjs[type]->id, selected->ID);
|
||||
}
|
||||
|
||||
CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point & garsOffset,
|
||||
const CArmedInstance * s1, const CArmedInstance * s2,
|
||||
bool _removableUnits, bool smallImgs, bool _twoRows)
|
||||
@ -513,7 +674,7 @@ CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point & garsOffset,
|
||||
createSlots();
|
||||
}
|
||||
|
||||
const CGarrisonSlot * CGarrisonInt::getSelection()
|
||||
const CGarrisonSlot * CGarrisonInt::getSelection() const
|
||||
{
|
||||
return highlighted;
|
||||
}
|
||||
@ -554,15 +715,15 @@ bool CGarrisonInt::getSplittingMode()
|
||||
return inSplittingMode;
|
||||
}
|
||||
|
||||
std::vector<CGarrisonSlot *> CGarrisonInt::getEmptySlots(CGarrisonSlot::EGarrisonType type)
|
||||
SlotID CGarrisonInt::getEmptySlot(CGarrisonSlot::EGarrisonType type) const
|
||||
{
|
||||
std::vector<CGarrisonSlot *> emptySlots;
|
||||
for(auto slot : availableSlots)
|
||||
{
|
||||
if(type == slot->upg && ((slot->our() || slot->ally()) && slot->creature == nullptr))
|
||||
emptySlots.push_back(slot.get());
|
||||
}
|
||||
return emptySlots;
|
||||
assert(armedObjs[type]);
|
||||
return armedObjs[type] ? armedObjs[type]->getFreeSlot() : SlotID();
|
||||
}
|
||||
|
||||
bool CGarrisonInt::hasEmptySlot(CGarrisonSlot::EGarrisonType type) const
|
||||
{
|
||||
return getEmptySlot(type) != SlotID();
|
||||
}
|
||||
|
||||
void CGarrisonInt::setArmy(const CArmedInstance * army, bool bottomGarrison)
|
||||
|
@ -45,6 +45,8 @@ class CGarrisonSlot : public CIntObject
|
||||
bool mustForceReselection() const;
|
||||
|
||||
void setHighlight(bool on);
|
||||
std::function<void()> getDismiss() const;
|
||||
|
||||
public:
|
||||
virtual void hover (bool on) override; //call-in
|
||||
const CArmedInstance * getObj() const;
|
||||
@ -55,8 +57,8 @@ public:
|
||||
void update();
|
||||
CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, EGarrisonType Upg=EGarrisonType::UP, const CStackInstance * creature_ = nullptr);
|
||||
|
||||
void splitIntoParts(EGarrisonType type, int amount, int maxOfSplittedSlots);
|
||||
void handleSplittingShortcuts();
|
||||
void splitIntoParts(EGarrisonType type, int amount);
|
||||
bool handleSplittingShortcuts(); /// Returns true when some shortcut is pressed, false otherwise
|
||||
|
||||
friend class CGarrisonInt;
|
||||
};
|
||||
@ -70,6 +72,8 @@ class CGarrisonInt :public CIntObject
|
||||
std::vector<std::shared_ptr<CGarrisonSlot>> availableSlots; ///< Slots of upper and lower garrison
|
||||
|
||||
void createSlots();
|
||||
bool checkSelected(const CGarrisonSlot * selected, TQuantity min = 0) const;
|
||||
|
||||
public:
|
||||
int interx; ///< Space between slots
|
||||
Point garOffset; ///< Offset between garrisons (not used if only one hero)
|
||||
@ -83,12 +87,13 @@ public:
|
||||
owned[2]; ///< player Owns up or down army ([0] upper, [1] lower)
|
||||
|
||||
void selectSlot(CGarrisonSlot * slot); ///< @param slot null = deselect
|
||||
const CGarrisonSlot * getSelection();
|
||||
const CGarrisonSlot * getSelection() const;
|
||||
|
||||
void setSplittingMode(bool on);
|
||||
bool getSplittingMode();
|
||||
|
||||
std::vector<CGarrisonSlot *> getEmptySlots(CGarrisonSlot::EGarrisonType type);
|
||||
bool hasEmptySlot(CGarrisonSlot::EGarrisonType type) const;
|
||||
SlotID getEmptySlot(CGarrisonSlot::EGarrisonType type) const;
|
||||
|
||||
const CArmedInstance * armedObjs[2]; ///< [0] is upper, [1] is down
|
||||
|
||||
@ -99,6 +104,11 @@ public:
|
||||
|
||||
void splitClick(); ///< handles click on split button
|
||||
void splitStacks(int amountLeft, int amountRight); ///< TODO: comment me
|
||||
void moveStackToAnotherArmy(const CGarrisonSlot * selected);
|
||||
void bulkMoveArmy(const CGarrisonSlot * selected);
|
||||
void bulkMergeStacks(const CGarrisonSlot * selected); // Gather all creatures of selected type to the selected slot from other hero/garrison slots
|
||||
void bulkSplitStack(const CGarrisonSlot * selected); // Used to separate one-creature troops from main stack
|
||||
void bulkSmartSplitStack(const CGarrisonSlot * selected);
|
||||
|
||||
/// Constructor
|
||||
/// @param x, y Position
|
||||
|
@ -23,6 +23,12 @@
|
||||
#include "serializer/JsonSerializeFormat.h"
|
||||
#include "NetPacksBase.h"
|
||||
|
||||
|
||||
bool CreatureSlotComparer::operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs)
|
||||
{
|
||||
return lhs.first->getAIValue() < rhs.first->getAIValue(); // Descendant order sorting
|
||||
}
|
||||
|
||||
const CStackInstance &CCreatureSet::operator[](SlotID slot) const
|
||||
{
|
||||
auto i = stacks.find(slot);
|
||||
@ -72,7 +78,7 @@ SlotID CCreatureSet::getSlotFor(CreatureID creature, ui32 slotsAmount) const /*r
|
||||
|
||||
SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const
|
||||
{
|
||||
assert(c->valid());
|
||||
assert(c && c->valid());
|
||||
for(auto & elem : stacks)
|
||||
{
|
||||
assert(elem.second->type->valid());
|
||||
@ -84,6 +90,75 @@ SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const
|
||||
return getFreeSlot(slotsAmount);
|
||||
}
|
||||
|
||||
bool CCreatureSet::hasCreatureSlots(const CCreature * c, SlotID exclude) const
|
||||
{
|
||||
assert(c && c->valid());
|
||||
for(auto & elem : stacks) // elem is const
|
||||
{
|
||||
if(elem.first == exclude) // Check slot
|
||||
continue;
|
||||
|
||||
if(!elem.second || !elem.second->type) // Check creature
|
||||
continue;
|
||||
|
||||
assert(elem.second->type->valid());
|
||||
|
||||
if(elem.second->type == c)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<SlotID> CCreatureSet::getCreatureSlots(const CCreature * c, SlotID exclude, TQuantity ignoreAmount) const
|
||||
{
|
||||
assert(c && c->valid());
|
||||
std::vector<SlotID> result;
|
||||
|
||||
for(auto & elem : stacks)
|
||||
{
|
||||
if(elem.first == exclude)
|
||||
continue;
|
||||
|
||||
if(!elem.second || !elem.second->type || elem.second->type != c)
|
||||
continue;
|
||||
|
||||
if(elem.second->count == ignoreAmount || elem.second->count < 1)
|
||||
continue;
|
||||
|
||||
assert(elem.second->type->valid());
|
||||
result.push_back(elem.first);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmount) const
|
||||
{
|
||||
assert(c && c->valid());
|
||||
TQuantity max = 0;
|
||||
TQuantity min = std::numeric_limits<TQuantity>::max();
|
||||
|
||||
for(auto & elem : stacks)
|
||||
{
|
||||
if(!elem.second || !elem.second->type || elem.second->type != c)
|
||||
continue;
|
||||
|
||||
const auto count = elem.second->count;
|
||||
|
||||
if(count == ignoreAmount || count < 1)
|
||||
continue;
|
||||
|
||||
assert(elem.second->type->valid());
|
||||
|
||||
if(count > max)
|
||||
max = count;
|
||||
if(count < min)
|
||||
min = count;
|
||||
if(max - min > 1)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SlotID CCreatureSet::getFreeSlot(ui32 slotsAmount) const
|
||||
{
|
||||
for(ui32 i=0; i<slotsAmount; i++)
|
||||
@ -96,6 +171,68 @@ SlotID CCreatureSet::getFreeSlot(ui32 slotsAmount) const
|
||||
return SlotID(); //no slot available
|
||||
}
|
||||
|
||||
std::vector<SlotID> CCreatureSet::getFreeSlots(ui32 slotsAmount) const
|
||||
{
|
||||
std::vector<SlotID> freeSlots;
|
||||
|
||||
for(ui32 i = 0; i < slotsAmount; i++)
|
||||
{
|
||||
auto slot = SlotID(i);
|
||||
|
||||
if(!vstd::contains(stacks, slot))
|
||||
freeSlots.push_back(slot);
|
||||
}
|
||||
return freeSlots;
|
||||
}
|
||||
|
||||
std::queue<SlotID> CCreatureSet::getFreeSlotsQueue(ui32 slotsAmount) const
|
||||
{
|
||||
std::queue<SlotID> freeSlots;
|
||||
|
||||
for (ui32 i = 0; i < slotsAmount; i++)
|
||||
{
|
||||
auto slot = SlotID(i);
|
||||
|
||||
if(!vstd::contains(stacks, slot))
|
||||
freeSlots.push(slot);
|
||||
}
|
||||
return freeSlots;
|
||||
}
|
||||
|
||||
TMapCreatureSlot CCreatureSet::getCreatureMap() const
|
||||
{
|
||||
TMapCreatureSlot creatureMap;
|
||||
TMapCreatureSlot::key_compare keyComp = creatureMap.key_comp();
|
||||
|
||||
// https://stackoverflow.com/questions/97050/stdmap-insert-or-stdmap-find
|
||||
// https://www.cplusplus.com/reference/map/map/key_comp/
|
||||
for(auto pair : stacks)
|
||||
{
|
||||
auto creature = pair.second->type;
|
||||
auto slot = pair.first;
|
||||
TMapCreatureSlot::iterator lb = creatureMap.lower_bound(creature);
|
||||
|
||||
if(lb != creatureMap.end() && !(keyComp(creature, lb->first)))
|
||||
continue;
|
||||
|
||||
creatureMap.insert(lb, TMapCreatureSlot::value_type(creature, slot));
|
||||
}
|
||||
return creatureMap;
|
||||
}
|
||||
|
||||
TCreatureQueue CCreatureSet::getCreatureQueue(SlotID exclude) const
|
||||
{
|
||||
TCreatureQueue creatureQueue;
|
||||
|
||||
for(auto pair : stacks)
|
||||
{
|
||||
if(pair.first == exclude)
|
||||
continue;
|
||||
creatureQueue.push(std::make_pair(pair.second->type, pair.first));
|
||||
}
|
||||
return creatureQueue;
|
||||
}
|
||||
|
||||
TQuantity CCreatureSet::getStackCount(SlotID slot) const
|
||||
{
|
||||
auto i = stacks.find(slot);
|
||||
|
@ -142,6 +142,20 @@ public:
|
||||
typedef std::map<SlotID, CStackInstance*> TSlots;
|
||||
typedef std::map<SlotID, std::pair<CreatureID, TQuantity>> TSimpleSlots;
|
||||
|
||||
typedef std::pair<const CCreature*, SlotID> TPairCreatureSlot;
|
||||
typedef std::map<const CCreature*, SlotID> TMapCreatureSlot;
|
||||
|
||||
struct DLL_LINKAGE CreatureSlotComparer
|
||||
{
|
||||
bool operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs);
|
||||
};
|
||||
|
||||
typedef std::priority_queue<
|
||||
TPairCreatureSlot,
|
||||
std::vector<TPairCreatureSlot>,
|
||||
CreatureSlotComparer
|
||||
> TCreatureQueue;
|
||||
|
||||
class IArmyDescriptor
|
||||
{
|
||||
public:
|
||||
@ -209,7 +223,17 @@ public:
|
||||
SlotID findStack(const CStackInstance *stack) const; //-1 if none
|
||||
SlotID getSlotFor(CreatureID creature, ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns -1 if no slot available
|
||||
SlotID getSlotFor(const CCreature *c, ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns -1 if no slot available
|
||||
SlotID getFreeSlot(ui32 slotsAmount = GameConstants::ARMY_SIZE) const;
|
||||
bool hasCreatureSlots(const CCreature * c, SlotID exclude) const;
|
||||
std::vector<SlotID> getCreatureSlots(const CCreature * c, SlotID exclude, TQuantity ignoreAmount = -1) const;
|
||||
bool isCreatureBalanced(const CCreature* c, TQuantity ignoreAmount = 1) const; // Check if the creature is evenly distributed across slots
|
||||
|
||||
SlotID getFreeSlot(ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns first free slot
|
||||
std::vector<SlotID> getFreeSlots(ui32 slotsAmount = GameConstants::ARMY_SIZE) const;
|
||||
std::queue<SlotID> getFreeSlotsQueue(ui32 slotsAmount = GameConstants::ARMY_SIZE) const;
|
||||
|
||||
TMapCreatureSlot getCreatureMap() const;
|
||||
TCreatureQueue getCreatureQueue(SlotID exclude) const;
|
||||
|
||||
bool mergableStacks(std::pair<SlotID, SlotID> &out, SlotID preferable = SlotID()) const; //looks for two same stacks, returns slot positions;
|
||||
bool validTypes(bool allowUnrandomized = false) const; //checks if all types of creatures are set properly
|
||||
bool slotEmpty(SlotID slot) const;
|
||||
|
128
lib/NetPacks.h
128
lib/NetPacks.h
@ -886,7 +886,7 @@ struct RebalanceStacks : CGarrisonOperationPack
|
||||
TQuantity count;
|
||||
|
||||
void applyCl(CClient *cl);
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
DLL_LINKAGE void applyGs(CGameState * gs);
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
@ -898,6 +898,36 @@ struct RebalanceStacks : CGarrisonOperationPack
|
||||
}
|
||||
};
|
||||
|
||||
struct BulkRebalanceStacks : CGarrisonOperationPack
|
||||
{
|
||||
std::vector<RebalanceStacks> moves;
|
||||
|
||||
void applyCl(CClient * cl);
|
||||
DLL_LINKAGE void applyGs(CGameState * gs);
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & moves;
|
||||
}
|
||||
};
|
||||
|
||||
struct BulkSmartRebalanceStacks : CGarrisonOperationPack
|
||||
{
|
||||
std::vector<RebalanceStacks> moves;
|
||||
std::vector<ChangeStackCount> changes;
|
||||
|
||||
void applyCl(CClient * cl);
|
||||
DLL_LINKAGE void applyGs(CGameState * gs);
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & moves;
|
||||
h & changes;
|
||||
}
|
||||
};
|
||||
|
||||
struct GetEngagedHeroIds : boost::static_visitor<boost::optional<ObjectInstanceID>>
|
||||
{
|
||||
boost::optional<ObjectInstanceID> operator()(const ConstTransitivePtr<CGHeroInstance> &h) const
|
||||
@ -1946,6 +1976,102 @@ struct ArrangeStacks : public CPackForServer
|
||||
}
|
||||
};
|
||||
|
||||
struct BulkMoveArmy : public CPackForServer
|
||||
{
|
||||
SlotID srcSlot;
|
||||
ObjectInstanceID srcArmy;
|
||||
ObjectInstanceID destArmy;
|
||||
|
||||
BulkMoveArmy()
|
||||
{};
|
||||
|
||||
BulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot)
|
||||
: srcArmy(srcArmy), destArmy(destArmy), srcSlot(srcSlot)
|
||||
{};
|
||||
|
||||
bool applyGh(CGameHandler * gh);
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & static_cast<CPackForServer&>(*this);
|
||||
h & srcSlot;
|
||||
h & srcArmy;
|
||||
h & destArmy;
|
||||
}
|
||||
};
|
||||
|
||||
struct BulkSplitStack : public CPackForServer
|
||||
{
|
||||
SlotID src;
|
||||
ObjectInstanceID srcOwner;
|
||||
si32 amount;
|
||||
|
||||
BulkSplitStack() : amount(0)
|
||||
{};
|
||||
|
||||
BulkSplitStack(ObjectInstanceID srcOwner, SlotID src, si32 howMany)
|
||||
: src(src), srcOwner(srcOwner), amount(howMany)
|
||||
{};
|
||||
|
||||
bool applyGh(CGameHandler * gh);
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & static_cast<CPackForServer&>(*this);
|
||||
h & src;
|
||||
h & srcOwner;
|
||||
h & amount;
|
||||
}
|
||||
};
|
||||
|
||||
struct BulkMergeStacks : public CPackForServer
|
||||
{
|
||||
SlotID src;
|
||||
ObjectInstanceID srcOwner;
|
||||
|
||||
BulkMergeStacks()
|
||||
{};
|
||||
|
||||
BulkMergeStacks(ObjectInstanceID srcOwner, SlotID src)
|
||||
: src(src), srcOwner(srcOwner)
|
||||
{};
|
||||
|
||||
bool applyGh(CGameHandler * gh);
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & static_cast<CPackForServer&>(*this);
|
||||
h & src;
|
||||
h & srcOwner;
|
||||
}
|
||||
};
|
||||
|
||||
struct BulkSmartSplitStack : public CPackForServer
|
||||
{
|
||||
SlotID src;
|
||||
ObjectInstanceID srcOwner;
|
||||
|
||||
BulkSmartSplitStack()
|
||||
{};
|
||||
|
||||
BulkSmartSplitStack(ObjectInstanceID srcOwner, SlotID src)
|
||||
: src(src), srcOwner(srcOwner)
|
||||
{};
|
||||
|
||||
bool applyGh(CGameHandler * gh);
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & static_cast<CPackForServer&>(*this);
|
||||
h & src;
|
||||
h & srcOwner;
|
||||
}
|
||||
};
|
||||
|
||||
struct DisbandCreature : public CPackForServer
|
||||
{
|
||||
DisbandCreature(){};
|
||||
|
@ -1020,6 +1020,21 @@ DLL_LINKAGE void RebalanceStacks::applyGs(CGameState * gs)
|
||||
CBonusSystemNode::treeHasChanged();
|
||||
}
|
||||
|
||||
DLL_LINKAGE void BulkRebalanceStacks::applyGs(CGameState * gs)
|
||||
{
|
||||
for(auto & move : moves)
|
||||
move.applyGs(gs);
|
||||
}
|
||||
|
||||
DLL_LINKAGE void BulkSmartRebalanceStacks::applyGs(CGameState * gs)
|
||||
{
|
||||
for(auto & move : moves)
|
||||
move.applyGs(gs);
|
||||
|
||||
for(auto & change : changes)
|
||||
change.applyGs(gs);
|
||||
}
|
||||
|
||||
DLL_LINKAGE void PutArtifact::applyGs(CGameState *gs)
|
||||
{
|
||||
assert(art->canBePutAt(al));
|
||||
|
@ -317,6 +317,8 @@ void registerTypesClientPacks2(Serializer &s)
|
||||
|
||||
s.template registerType<CPackForClient, SaveGameClient>();
|
||||
s.template registerType<CPackForClient, PlayerMessageClient>();
|
||||
s.template registerType<CGarrisonOperationPack, BulkRebalanceStacks>();
|
||||
s.template registerType<CGarrisonOperationPack, BulkSmartRebalanceStacks>();
|
||||
}
|
||||
|
||||
template<typename Serializer>
|
||||
@ -347,6 +349,10 @@ void registerTypesServerPacks(Serializer &s)
|
||||
s.template registerType<CPackForServer, CastleTeleportHero>();
|
||||
s.template registerType<CPackForServer, SaveGame>();
|
||||
s.template registerType<CPackForServer, PlayerMessage>();
|
||||
s.template registerType<CPackForServer, BulkSplitStack>();
|
||||
s.template registerType<CPackForServer, BulkMergeStacks>();
|
||||
s.template registerType<CPackForServer, BulkSmartSplitStack>();
|
||||
s.template registerType<CPackForServer, BulkMoveArmy>();
|
||||
}
|
||||
|
||||
template<typename Serializer>
|
||||
|
@ -1629,6 +1629,9 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
|
||||
|
||||
CGameHandler::CGameHandler(CVCMIServer * lobby)
|
||||
: lobby(lobby)
|
||||
, complainNoCreatures("No creatures to split")
|
||||
, complainNotEnoughCreatures("Cannot split that stack, not enough creatures!")
|
||||
, complainInvalidSlot("Invalid slot accessed!")
|
||||
{
|
||||
QID = 1;
|
||||
IObjectInterface::cb = this;
|
||||
@ -2962,6 +2965,259 @@ void CGameHandler::load(const std::string & filename)
|
||||
gs->updateOnLoad(lobby->si.get());
|
||||
}
|
||||
|
||||
bool CGameHandler::bulkSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner, si32 howMany)
|
||||
{
|
||||
if(!slotSrc.validSlot() && complain(complainInvalidSlot))
|
||||
return false;
|
||||
|
||||
const CArmedInstance * army = static_cast<const CArmedInstance*>(getObjInstance(srcOwner));
|
||||
const CCreatureSet & creatureSet = *army;
|
||||
|
||||
if((!vstd::contains(creatureSet.stacks, slotSrc) && complain(complainNoCreatures))
|
||||
|| (howMany < 1 && complain("Invalid split parameter!")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto actualAmount = army->getStackCount(slotSrc);
|
||||
|
||||
if(actualAmount <= howMany && complain(complainNotEnoughCreatures)) // '<=' because it's not intended just for moving a stack
|
||||
return false;
|
||||
|
||||
auto freeSlots = creatureSet.getFreeSlots();
|
||||
|
||||
if(freeSlots.empty() && complain("No empty stacks"))
|
||||
return false;
|
||||
|
||||
BulkRebalanceStacks bulkRS;
|
||||
|
||||
for(auto slot : freeSlots)
|
||||
{
|
||||
RebalanceStacks rs;
|
||||
rs.srcArmy = army->id;
|
||||
rs.dstArmy = army->id;
|
||||
rs.srcSlot = slotSrc;
|
||||
rs.dstSlot = slot;
|
||||
rs.count = howMany;
|
||||
|
||||
bulkRS.moves.push_back(rs);
|
||||
actualAmount -= howMany;
|
||||
|
||||
if(actualAmount <= howMany)
|
||||
break;
|
||||
}
|
||||
sendAndApply(&bulkRS);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGameHandler::bulkMergeStacks(SlotID slotSrc, ObjectInstanceID srcOwner)
|
||||
{
|
||||
if(!slotSrc.validSlot() && complain(complainInvalidSlot))
|
||||
return false;
|
||||
|
||||
const CArmedInstance * army = static_cast<const CArmedInstance*>(getObjInstance(srcOwner));
|
||||
const CCreatureSet & creatureSet = *army;
|
||||
|
||||
if(!vstd::contains(creatureSet.stacks, slotSrc) && complain(complainNoCreatures))
|
||||
return false;
|
||||
|
||||
auto actualAmount = creatureSet.getStackCount(slotSrc);
|
||||
|
||||
if(actualAmount < 1 && complain(complainNoCreatures))
|
||||
return false;
|
||||
|
||||
auto currentCreature = creatureSet.getCreature(slotSrc);
|
||||
|
||||
if(!currentCreature && complain(complainNoCreatures))
|
||||
return false;
|
||||
|
||||
auto creatureSlots = creatureSet.getCreatureSlots(currentCreature, slotSrc);
|
||||
|
||||
if(!creatureSlots.size())
|
||||
return false;
|
||||
|
||||
BulkRebalanceStacks bulkRS;
|
||||
|
||||
for(auto slot : creatureSlots)
|
||||
{
|
||||
RebalanceStacks rs;
|
||||
rs.srcArmy = army->id;
|
||||
rs.dstArmy = army->id;
|
||||
rs.srcSlot = slot;
|
||||
rs.dstSlot = slotSrc;
|
||||
rs.count = creatureSet.getStackCount(slot);
|
||||
bulkRS.moves.push_back(rs);
|
||||
}
|
||||
sendAndApply(&bulkRS);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGameHandler::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot)
|
||||
{
|
||||
if(!srcSlot.validSlot() && complain(complainInvalidSlot))
|
||||
return false;
|
||||
|
||||
const CArmedInstance * armySrc = static_cast<const CArmedInstance*>(getObjInstance(srcArmy));
|
||||
const CCreatureSet & setSrc = *armySrc;
|
||||
|
||||
if(!vstd::contains(setSrc.stacks, srcSlot) && complain(complainNoCreatures))
|
||||
return false;
|
||||
|
||||
const CArmedInstance * armyDest = static_cast<const CArmedInstance*>(getObjInstance(destArmy));
|
||||
const CCreatureSet & setDest = *armyDest;
|
||||
auto freeSlots = setDest.getFreeSlotsQueue();
|
||||
|
||||
typedef std::map<SlotID, std::pair<SlotID, TQuantity>> TRebalanceMap;
|
||||
TRebalanceMap moves;
|
||||
|
||||
auto srcQueue = setSrc.getCreatureQueue(srcSlot); // Exclude srcSlot, it should be moved last
|
||||
auto slotsLeft = setSrc.stacksCount();
|
||||
auto destMap = setDest.getCreatureMap();
|
||||
TMapCreatureSlot::key_compare keyComp = destMap.key_comp();
|
||||
|
||||
while(!srcQueue.empty())
|
||||
{
|
||||
auto pair = srcQueue.top();
|
||||
srcQueue.pop();
|
||||
|
||||
auto currCreature = pair.first;
|
||||
auto currSlot = pair.second;
|
||||
const auto quantity = setSrc.getStackCount(currSlot);
|
||||
|
||||
TMapCreatureSlot::iterator lb = destMap.lower_bound(currCreature);
|
||||
const bool alreadyExists = (lb != destMap.end() && !(keyComp(currCreature, lb->first)));
|
||||
|
||||
if(!alreadyExists)
|
||||
{
|
||||
if(freeSlots.empty())
|
||||
continue;
|
||||
|
||||
auto currFreeSlot = freeSlots.front();
|
||||
freeSlots.pop();
|
||||
destMap.insert(lb, TMapCreatureSlot::value_type(currCreature, currFreeSlot));
|
||||
}
|
||||
moves.insert(std::make_pair(currSlot, std::make_pair(destMap[currCreature], quantity)));
|
||||
slotsLeft--;
|
||||
}
|
||||
if(slotsLeft == 1)
|
||||
{
|
||||
auto lastCreature = setSrc.getCreature(srcSlot);
|
||||
auto slotToMove = SlotID();
|
||||
// Try to find a slot for last creature
|
||||
if(destMap.find(lastCreature) == destMap.end())
|
||||
{
|
||||
if(!freeSlots.empty())
|
||||
slotToMove = freeSlots.front();
|
||||
}
|
||||
else
|
||||
{
|
||||
slotToMove = destMap[lastCreature];
|
||||
}
|
||||
|
||||
if(slotToMove != SlotID())
|
||||
{
|
||||
const bool needsLastStack = armySrc->needsLastStack();
|
||||
const auto quantity = setSrc.getStackCount(srcSlot) - (needsLastStack ? 1 : 0);
|
||||
moves.insert(std::make_pair(srcSlot, std::make_pair(slotToMove, quantity)));
|
||||
}
|
||||
}
|
||||
BulkRebalanceStacks bulkRS;
|
||||
|
||||
for(auto & move : moves)
|
||||
{
|
||||
RebalanceStacks rs;
|
||||
rs.srcArmy = armySrc->id;
|
||||
rs.dstArmy = armyDest->id;
|
||||
rs.srcSlot = move.first;
|
||||
rs.dstSlot = move.second.first;
|
||||
rs.count = move.second.second;
|
||||
bulkRS.moves.push_back(rs);
|
||||
}
|
||||
sendAndApply(&bulkRS);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGameHandler::bulkSmartSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner)
|
||||
{
|
||||
if(!slotSrc.validSlot() && complain(complainInvalidSlot))
|
||||
return false;
|
||||
|
||||
const CArmedInstance * army = static_cast<const CArmedInstance*>(getObjInstance(srcOwner));
|
||||
const CCreatureSet & creatureSet = *army;
|
||||
|
||||
if(!vstd::contains(creatureSet.stacks, slotSrc) && complain(complainNoCreatures))
|
||||
return false;
|
||||
|
||||
auto actualAmount = creatureSet.getStackCount(slotSrc);
|
||||
|
||||
if(actualAmount <= 1 && complain(complainNoCreatures))
|
||||
return false;
|
||||
|
||||
auto freeSlot = creatureSet.getFreeSlot();
|
||||
auto currentCreature = creatureSet.getCreature(slotSrc);
|
||||
|
||||
if(freeSlot == SlotID() && creatureSet.isCreatureBalanced(currentCreature))
|
||||
return true;
|
||||
|
||||
auto creatureSlots = creatureSet.getCreatureSlots(currentCreature, SlotID(-1), 1); // Ignore slots where's only 1 creature, don't ignore slotSrc
|
||||
TQuantity totalCreatures = 0;
|
||||
|
||||
for(auto slot : creatureSlots)
|
||||
totalCreatures += creatureSet.getStackCount(slot);
|
||||
|
||||
if(totalCreatures <= 1 && complain("Total creatures number is invalid"))
|
||||
return false;
|
||||
|
||||
if(freeSlot != SlotID())
|
||||
creatureSlots.push_back(freeSlot);
|
||||
|
||||
if(creatureSlots.empty() && complain("No available slots for smart rebalancing"))
|
||||
return false;
|
||||
|
||||
const auto totalCreatureSlots = creatureSlots.size();
|
||||
const auto rem = totalCreatures % totalCreatureSlots;
|
||||
const auto quotient = totalCreatures / totalCreatureSlots;
|
||||
|
||||
// totalCreatures == rem * (quotient + 1) + (totalCreatureSlots - rem) * quotient;
|
||||
// Proof: r(q+1)+(s-r)q = rq+r+qs-rq = r+qs = total, where total/s = q+r/s
|
||||
|
||||
BulkSmartRebalanceStacks bulkSRS;
|
||||
|
||||
if(freeSlot != SlotID())
|
||||
{
|
||||
RebalanceStacks rs;
|
||||
rs.srcArmy = rs.dstArmy = army->id;
|
||||
rs.srcSlot = slotSrc;
|
||||
rs.dstSlot = freeSlot;
|
||||
rs.count = 1;
|
||||
bulkSRS.moves.push_back(rs);
|
||||
}
|
||||
auto currSlot = 0;
|
||||
auto check = 0;
|
||||
|
||||
for(auto slot : creatureSlots)
|
||||
{
|
||||
ChangeStackCount csc;
|
||||
|
||||
csc.army = army->id;
|
||||
csc.slot = slot;
|
||||
csc.count = (currSlot < rem)
|
||||
? quotient + 1
|
||||
: quotient;
|
||||
csc.absoluteValue = true;
|
||||
bulkSRS.changes.push_back(csc);
|
||||
currSlot++;
|
||||
check += csc.count;
|
||||
}
|
||||
|
||||
if(check != totalCreatures)
|
||||
{
|
||||
complain((boost::format("Failure: totalCreatures=%d but check=%d") % totalCreatures % check).str());
|
||||
return false;
|
||||
}
|
||||
sendAndApply(&bulkSRS);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player)
|
||||
{
|
||||
const CArmedInstance * s1 = static_cast<const CArmedInstance *>(getObjInstance(id1)),
|
||||
@ -2970,7 +3226,7 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
|
||||
StackLocation sl1(s1, p1), sl2(s2, p2);
|
||||
if (!sl1.slot.validSlot() || !sl2.slot.validSlot())
|
||||
{
|
||||
complain("Invalid slot accessed!");
|
||||
complain(complainInvalidSlot);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3051,8 +3307,8 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
|
||||
}
|
||||
|
||||
//general conditions checking
|
||||
if ((!vstd::contains(S1.stacks,p1) && complain("no creatures to split"))
|
||||
|| (val<1 && complain("no creatures to split")) )
|
||||
if ((!vstd::contains(S1.stacks,p1) && complain(complainNoCreatures))
|
||||
|| (val<1 && complain(complainNoCreatures)) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -3087,7 +3343,7 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
|
||||
{
|
||||
if (s1->getStackCount(p1) < val)//not enough creatures
|
||||
{
|
||||
complain("Cannot split that stack, not enough creatures!");
|
||||
complain(complainNotEnoughCreatures);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -6793,7 +7049,11 @@ bool CGameHandler::isBlockedByQueries(const CPack *pack, PlayerColor player)
|
||||
auto query = queries.topQuery(player);
|
||||
if (query && query->blocksPack(pack))
|
||||
{
|
||||
complain(boost::str(boost::format("Player %s has to answer queries before attempting any further actions. Top query is %s!") % player % query->toString()));
|
||||
complain(boost::str(boost::format(
|
||||
"\r\n| Player \"%s\" has to answer queries before attempting any further actions.\r\n| Top Query: \"%s\"\r\n")
|
||||
% boost::to_upper_copy<std::string>(player.getStr())
|
||||
% query->toString()
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -249,6 +249,10 @@ public:
|
||||
bool razeStructure(ObjectInstanceID tid, BuildingID bid);
|
||||
bool disbandCreature( ObjectInstanceID id, SlotID pos );
|
||||
bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player);
|
||||
bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot);
|
||||
bool bulkSplitStack(SlotID src, ObjectInstanceID srcOwner, si32 howMany);
|
||||
bool bulkMergeStacks(SlotID slotSrc, ObjectInstanceID srcOwner);
|
||||
bool bulkSmartSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner);
|
||||
void save(const std::string &fname);
|
||||
void load(const std::string &fname);
|
||||
|
||||
@ -353,7 +357,9 @@ private:
|
||||
void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors);
|
||||
void checkVictoryLossConditionsForAll();
|
||||
|
||||
|
||||
const std::string complainNoCreatures;
|
||||
const std::string complainNotEnoughCreatures;
|
||||
const std::string complainInvalidSlot;
|
||||
};
|
||||
|
||||
class ExceptionNotAllowedAction : public std::exception
|
||||
|
@ -69,7 +69,25 @@ void CQuery::addPlayer(PlayerColor color)
|
||||
|
||||
std::string CQuery::toString() const
|
||||
{
|
||||
std::string ret = boost::str(boost::format("A query of type %s and qid=%d affecting players %s") % typeid(*this).name() % queryID % formatContainer(players));
|
||||
const auto size = players.size();
|
||||
const std::string plural = size > 1 ? "s" : "";
|
||||
std::string names;
|
||||
|
||||
for(size_t i = 0; i < size; i++)
|
||||
{
|
||||
names += boost::to_upper_copy<std::string>(players[i].getStr());
|
||||
|
||||
if(i < size - 2)
|
||||
names += ", ";
|
||||
else if(size > 1 && i == size - 2)
|
||||
names += " and ";
|
||||
}
|
||||
std::string ret = boost::str(boost::format("A query of type '%s' and qid = %d affecting player%s %s")
|
||||
% typeid(*this).name()
|
||||
% queryID
|
||||
% plural
|
||||
% names
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -327,6 +345,18 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const
|
||||
if(auto stacks = dynamic_ptr_cast<ArrangeStacks>(pack))
|
||||
return !vstd::contains(ourIds, stacks->id1) || !vstd::contains(ourIds, stacks->id2);
|
||||
|
||||
if(auto stacks = dynamic_ptr_cast<BulkSplitStack>(pack))
|
||||
return !vstd::contains(ourIds, stacks->srcOwner);
|
||||
|
||||
if(auto stacks = dynamic_ptr_cast<BulkMergeStacks>(pack))
|
||||
return !vstd::contains(ourIds, stacks->srcOwner);
|
||||
|
||||
if(auto stacks = dynamic_ptr_cast<BulkSmartSplitStack>(pack))
|
||||
return !vstd::contains(ourIds, stacks->srcOwner);
|
||||
|
||||
if(auto stacks = dynamic_ptr_cast<BulkMoveArmy>(pack))
|
||||
return !vstd::contains(ourIds, stacks->srcArmy) || !vstd::contains(ourIds, stacks->destArmy);
|
||||
|
||||
if(auto arts = dynamic_ptr_cast<ExchangeArtifacts>(pack))
|
||||
{
|
||||
if(auto id1 = boost::apply_visitor(GetEngagedHeroIds(), arts->src.artHolder))
|
||||
|
@ -123,6 +123,26 @@ bool ArrangeStacks::applyGh(CGameHandler * gh)
|
||||
return gh->arrangeStacks(id1, id2, what, p1, p2, val, gh->getPlayerAt(c));
|
||||
}
|
||||
|
||||
bool BulkMoveArmy::applyGh(CGameHandler * gh)
|
||||
{
|
||||
return gh->bulkMoveArmy(srcArmy, destArmy, srcSlot);
|
||||
}
|
||||
|
||||
bool BulkSplitStack::applyGh(CGameHandler * gh)
|
||||
{
|
||||
return gh->bulkSplitStack(src, srcOwner, amount);
|
||||
}
|
||||
|
||||
bool BulkMergeStacks::applyGh(CGameHandler* gh)
|
||||
{
|
||||
return gh->bulkMergeStacks(src, srcOwner);
|
||||
}
|
||||
|
||||
bool BulkSmartSplitStack::applyGh(CGameHandler * gh)
|
||||
{
|
||||
return gh->bulkSmartSplitStack(src, srcOwner);
|
||||
}
|
||||
|
||||
bool DisbandCreature::applyGh(CGameHandler * gh)
|
||||
{
|
||||
throwOnWrongOwner(gh, id);
|
||||
|
Loading…
Reference in New Issue
Block a user