1
0
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:
Dmitry Orlov 2021-11-28 15:57:38 +03:00 committed by Andrii Danylchenko
parent 7faa691f42
commit 8cae3398ba
15 changed files with 916 additions and 56 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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(){};

View File

@ -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));

View File

@ -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>

View File

@ -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;
}

View File

@ -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

View File

@ -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))

View File

@ -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);