1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-21 21:17:49 +02:00

Merge branch 'develop' into unlimited-autosave

This commit is contained in:
Dydzio 2023-07-16 15:19:04 +02:00
commit 5882cbae78
76 changed files with 1971 additions and 925 deletions

View File

@ -270,17 +270,10 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn
{ {
assert(townOrTavern); assert(townOrTavern);
assert(hero); assert(hero);
ui8 i=0;
for(; i<gs->players[*player].availableHeroes.size(); i++) HireHero pack(HeroTypeID(hero->subID), townOrTavern->id);
{ pack.player = *player;
if(gs->players[*player].availableHeroes[i] == hero) sendRequest(&pack);
{
HireHero pack(i, townOrTavern->id);
pack.player = *player;
sendRequest(&pack);
return;
}
}
} }
void CCallback::save( const std::string &fname ) void CCallback::save( const std::string &fname )

View File

@ -1,6 +1,129 @@
# 1.2.1 -> 1.3.0 # 1.2.1 -> 1.3.0
(unreleased) (unreleased)
### GENERAL:
* Implemented automatic interface scaling to any resolution supported by monitor
* Implemented UI scaling option to scale game interface
* Game resolution and UI scaling can now be changed without game restart
* Fixed multiple issues with borderless fullscreen mode
* On mobile systems game will now always run at native resolution with configurable UI scaling
* Implemented support for Horn of the Abyss map format
* Implemented option to replay results of quick combat
* Added translations to French and Chinese
* All in-game cheats are now case-insensitive
* Added high-definition icon for Windows
* Fix crash on connecting to server on FreeBSD and Flatpak builds
### TOUCHSCREEN SUPPORT:
* VCMI will now properly recognizes touch screen input
* Implemented long tap gesture that shows popup window. Tap once more to close popup
* Long tap gesture duration can now be configured in settings
* Implemented swipe gesture for scrolling through lists
* All windows that have sliders in UI can now be scrolled using swipe gesture
* Implemented swipe gesture for attack direction selection: swipe from enemy position to position you want to attack from
* Implemented pinch gesture for zooming adventure map
* Implemented haptic feedback (vibration) for long press gesture
### LAUNCHER:
* Launcher will now attempt to automatically detect language of OS on first launch
* Added "About" tab with information about project and environment
* Added separate options for Allied AI and Enemy AI for adventure map
* Patially fixed displaying of download progress for mods
* Fixed potential crash on opening mod information for mods with a changelog
### MAP EDITOR:
* Fixed crash on cutting random town
* Added option to export entire map as an image
* Added validation for placing multiple heroes into starting town
* It is now possible to have single player on a map
* It is now possible to configure teams in editor
### AI PLAYER:
* Fixed potential crash on accessing market (VCAI)
* Fixed potentially infinite turns (VCAI)
### GAME MECHANICS
* Implemented hero backpack limit (disabled by default)
* Fixed Admiral's Hat movement points calculation
* It is now possible to access Shipwrecks from coast
* Hero path will now be correctly updated on equipping/unequipping Levitation Boots or Angel Wings
* It is no longer possible to abort movement while hero is flying over water
* Fixed digging for Grail
* Implemented "Survive beyond a time limit" victory condition
* Implemented "Defeat all monsters" victory condition
* 100% damage resistance or damage reduction will make unit immune to a spell
* Game will now randomly select obligatory skill for hero on levelup instead of always picking Fire Magic
* Fixed duration of bonuses from visitable object such as Idol of Fortune
* Rescued hero from prison will now correctly reveal map around him
* Lighthouses will no longer give movement bonus on land
### CAMPAIGNS:
* Fixed transfer of artifacts into next scenario
* Fixed crash on advancing to next scenario with heroes from mods
* Fixed handling of "Start with building" campaign bonus
* Fixed incorrect starting level of heroes in campaigns
* Game will now play correct music track on scenario selection window
* Dracon woll now correctly start without spellbook in Dragon Slayer campaign
* Fixed frequent crash on moving to next scenario during campaign
### RANDOM MAP GENERATOR:
* Improved zone placement, shape and connections
* Improved zone passability for better gameplay
* Improved treasure distribution and treasure values to match SoD closely
* RMG will now respect road settings set in menu
* Tweaked many original templates so they allow new terrains and factions
* Added "bannedTowns", "bannedTerrains", "bannedMonsters" zone properties
* Added "road" property to connections
* Support for "wide" connections
* Support for new "fictive" and "repulsive" connections
* RMG will now run faster, utilizing many CPU cores
### INTERFACE:
* Adventure map is now scalable and can be used with any resolution without mods
* Adventure map interface is now correctly blocked during enemy turn
* It is now possible to zoom in or out using mouse wheel or pinch gesture
* It is now possible to reset zoom via Backspace hotkey
* Receiving a message in chat will now play sound
* Map grid will now correctly display on map start
* Fixed multiple issues with incorrect updates of save/load game screen
* Fixed missing fortifications level icon in town tooltip
* Fixed positioning of resource label in Blacksmith window
* Status bar on inactive windows will no longer show any tooltip from active window
* Fixed highlighting of possible artifact placements when exchanging with allied hero
* Implemented sound of flying movement (for Fly spell or Angel Wings)
* Last symbol of entered cheat/chat message will no longer trigger hotkey
### BATTLES:
* Implemented Tower moat (Land Mines)
* Implemented defence reduction for units in moat
* Fixed movement through moat of double-hexed units
* Fixed removal of Land Mines and Fire Walls
* Obstacles will now corectly show up either below or above unit
* It is now possible to teleport a unit through destroyed walls
* Added distinct overlay image for showing movement range of highlighted unit
* Added overlay for displaying shooting range penalties of units
### MODDING:
* Implemented initial version of VCMI campaign format
* Implemented spell cast as possible reward for configurable object
* Implemented support for configurable buildings in towns
* Implemented support for placing prison, tavern and heroes on water
* Implemented support for new boat types
* It is now possible for boats to use other movement layers, such as "air"
* It is now possible to use growing artifacts on artifacts that can be used by hero
* It is now possible to configure town moat
* Palette-cycling animation of terrains and rivers can now be configured in json
* Game will now correctly resolve identifier in unexpected form (e.g. 'bless' vs 'spell.bless' vs 'core:bless')
* Creature specialties that use short form ( "creature" : "pikeman" ) will now correctly affect all creature upgrades
* It is now possible to configure spells for Shrines
* It is now possible to configure upgrade costs per level for Hill Forts
* It is now possible to configure boat type for Shipyards on adventure map and in town
* Implemented support for HotA-style adventure map images for monsters, with offset
* Replaced (SCHOOL)_SPELL_DMG_PREMY with SPELL_DAMAGE bonus (uses school as subtype).
* Removed bonuses (SCHOOL)_SPELLS - replaced with SPELLS_OF_SCHOOL
* Removed DIRECT_DAMAGE_IMMUNITY bonus - replaced by 100% spell damage resistance
* MAGIC_SCHOOL_SKILL subtype has been changed for consistency with other spell school bonuses
# 1.2.0 -> 1.2.1 # 1.2.0 -> 1.2.1
### GENERAL: ### GENERAL:

View File

@ -434,7 +434,8 @@ void playIntro()
{ {
if(CCS->videoh->openAndPlayVideo("3DOLOGO.SMK", 0, 1, true, true)) if(CCS->videoh->openAndPlayVideo("3DOLOGO.SMK", 0, 1, true, true))
{ {
CCS->videoh->openAndPlayVideo("AZVS.SMK", 0, 1, true, true); if (CCS->videoh->openAndPlayVideo("NWCLOGO.SMK", 0, 1, true, true))
CCS->videoh->openAndPlayVideo("H3INTRO.SMK", 0, 1, true, true);
} }
} }

View File

@ -415,7 +415,10 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
EVENT_HANDLER_CALLED_BY_CLIENT; EVENT_HANDLER_CALLED_BY_CLIENT;
LOG_TRACE_PARAMS(logGlobal, "Hero %s killed handler for player %s", hero->getNameTranslated() % playerID); LOG_TRACE_PARAMS(logGlobal, "Hero %s killed handler for player %s", hero->getNameTranslated() % playerID);
localState->removeWanderingHero(hero); // if hero is not in town garrison
if (vstd::contains(localState->getWanderingHeroes(), hero))
localState->removeWanderingHero(hero);
adventureInt->onHeroChanged(hero); adventureInt->onHeroChanged(hero);
localState->erasePath(hero); localState->erasePath(hero);
} }
@ -517,7 +520,7 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
if(town->garrisonHero) //wandering hero moved to the garrison if(town->garrisonHero) //wandering hero moved to the garrison
{ {
// This method also gets called on hero recruitment -> garrisoned hero is already in garrison // This method also gets called on hero recruitment -> garrisoned hero is already in garrison
if(town->garrisonHero->tempOwner == playerID && !vstd::contains(localState->getWanderingHeroes(), town->visitingHero)) if(town->garrisonHero->tempOwner == playerID && vstd::contains(localState->getWanderingHeroes(), town->garrisonHero))
localState->removeWanderingHero(town->garrisonHero); localState->removeWanderingHero(town->garrisonHero);
} }
@ -537,7 +540,9 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
castleInt->garr->setArmy(town->visitingHero, 1); castleInt->garr->setArmy(town->visitingHero, 1);
castleInt->garr->recreateSlots(); castleInt->garr->recreateSlots();
castleInt->heroes->update(); castleInt->heroes->update();
castleInt->redraw();
// Perform totalRedraw to update hero list on adventure map
GH.windows().totalRedraw();
} }
for (auto ki : GH.windows().findWindows<CKingdomInterface>()) for (auto ki : GH.windows().findWindows<CKingdomInterface>())
@ -604,9 +609,11 @@ void CPlayerInterface::garrisonsChanged(std::vector<const CGObjectInstance *> ob
void CPlayerInterface::buildChanged(const CGTownInstance *town, BuildingID buildingID, int what) //what: 1 - built, 2 - demolished void CPlayerInterface::buildChanged(const CGTownInstance *town, BuildingID buildingID, int what) //what: 1 - built, 2 - demolished
{ {
EVENT_HANDLER_CALLED_BY_CLIENT; EVENT_HANDLER_CALLED_BY_CLIENT;
adventureInt->onTownChanged(town);
if (castleInt) if (castleInt)
{ {
castleInt->townlist->update(town); castleInt->townlist->updateElement(town);
if (castleInt->town == town) if (castleInt->town == town)
{ {
@ -621,8 +628,10 @@ void CPlayerInterface::buildChanged(const CGTownInstance *town, BuildingID build
break; break;
} }
} }
// Perform totalRedraw in order to force redraw of updated town list icon from adventure map
GH.windows().totalRedraw();
} }
adventureInt->onTownChanged(town);
} }
void CPlayerInterface::battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) void CPlayerInterface::battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2)
@ -1490,6 +1499,13 @@ void CPlayerInterface::objectRemovedAfter()
{ {
EVENT_HANDLER_CALLED_BY_CLIENT; EVENT_HANDLER_CALLED_BY_CLIENT;
adventureInt->onMapTilesChanged(boost::none); adventureInt->onMapTilesChanged(boost::none);
// visiting or garrisoned hero removed - recreate castle window
if (castleInt)
openTownWindow(castleInt->town);
for (auto ki : GH.windows().findWindows<CKingdomInterface>())
ki->heroRemoved();
} }
void CPlayerInterface::playerBlocked(int reason, bool start) void CPlayerInterface::playerBlocked(int reason, bool start)
@ -1988,8 +2004,17 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
int soundChannel = -1; int soundChannel = -1;
std::string soundName; std::string soundName;
auto getMovementSoundFor = [&](const CGHeroInstance * hero, int3 posPrev, int3 posNext) -> std::string auto getMovementSoundFor = [&](const CGHeroInstance * hero, int3 posPrev, int3 posNext, EPathNodeAction moveType) -> std::string
{ {
if (moveType == EPathNodeAction::TELEPORT_BATTLE || moveType == EPathNodeAction::TELEPORT_BLOCKING_VISIT || moveType == EPathNodeAction::TELEPORT_NORMAL)
return "";
if (moveType == EPathNodeAction::EMBARK || moveType == EPathNodeAction::DISEMBARK)
return "";
if (moveType == EPathNodeAction::BLOCKING_VISIT)
return "";
// flying movement sound // flying movement sound
if (hero->hasBonusOfType(BonusType::FLYING_MOVEMENT)) if (hero->hasBonusOfType(BonusType::FLYING_MOVEMENT))
return "HORSE10.wav"; return "HORSE10.wav";
@ -2041,8 +2066,11 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
} }
if(i != path.nodes.size() - 1) if(i != path.nodes.size() - 1)
{ {
soundName = getMovementSoundFor(h, prevCoord, nextCoord); soundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
soundChannel = CCS->soundh->playSound(soundName, -1); if (!soundName.empty())
soundChannel = CCS->soundh->playSound(soundName, -1);
else
soundChannel = -1;
} }
continue; continue;
} }
@ -2055,14 +2083,17 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
{ {
// Start a new sound for the hero movement or let the existing one carry on. // Start a new sound for the hero movement or let the existing one carry on.
std::string newSoundName = getMovementSoundFor(h, prevCoord, nextCoord); std::string newSoundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
if(newSoundName != soundName) if(newSoundName != soundName)
{ {
soundName = newSoundName; soundName = newSoundName;
CCS->soundh->stopSound(soundChannel); CCS->soundh->stopSound(soundChannel);
soundChannel = CCS->soundh->playSound(soundName, -1); if (!soundName.empty())
soundChannel = CCS->soundh->playSound(soundName, -1);
else
soundChannel = -1;
} }
} }

View File

@ -92,7 +92,7 @@ void AdventureMapInterface::onHeroMovementStarted(const CGHeroInstance * hero)
void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h) void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
{ {
widget->getHeroList()->update(h); widget->getHeroList()->updateElement(h);
if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents()) if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents())
widget->getInfoBar()->showSelection(); widget->getInfoBar()->showSelection();
@ -102,7 +102,7 @@ void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
void AdventureMapInterface::onTownChanged(const CGTownInstance * town) void AdventureMapInterface::onTownChanged(const CGTownInstance * town)
{ {
widget->getTownList()->update(town); widget->getTownList()->updateElement(town);
if (town && town == LOCPLINT->localState->getCurrentTown() && !widget->getInfoBar()->showingComponents()) if (town && town == LOCPLINT->localState->getCurrentTown() && !widget->getInfoBar()->showingComponents())
widget->getInfoBar()->showSelection(); widget->getInfoBar()->showSelection();
@ -365,8 +365,8 @@ void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
widget->getInfoBar()->showSelection(); widget->getInfoBar()->showSelection();
} }
widget->getHeroList()->update(); widget->getHeroList()->updateWidget();
widget->getTownList()->update(); widget->getTownList()->updateWidget();
const CGHeroInstance * heroToSelect = nullptr; const CGHeroInstance * heroToSelect = nullptr;
@ -833,5 +833,8 @@ void AdventureMapInterface::onScreenResize()
widget->getMinimap()->update(); widget->getMinimap()->update();
widget->getInfoBar()->showSelection(); widget->getInfoBar()->showSelection();
if (LOCPLINT && LOCPLINT->localState->getCurrentArmy())
widget->getMapView()->onCenteredObject(LOCPLINT->localState->getCurrentArmy());
adjustActiveness(); adjustActiveness();
} }

View File

@ -280,21 +280,15 @@ void CHeroList::select(const CGHeroInstance * hero)
selectIndex(vstd::find_pos(LOCPLINT->localState->getWanderingHeroes(), hero)); selectIndex(vstd::find_pos(LOCPLINT->localState->getWanderingHeroes(), hero));
} }
void CHeroList::update(const CGHeroInstance * hero) void CHeroList::updateElement(const CGHeroInstance * hero)
{ {
//this hero is already present, update its status updateWidget();
for(auto & elem : listBox->getItems()) }
{
auto item = std::dynamic_pointer_cast<CHeroItem>(elem);
if(item && item->hero == hero && vstd::contains(LOCPLINT->localState->getWanderingHeroes(), hero))
{
item->update();
return;
}
}
//simplest solution for now: reset list and restore selection
void CHeroList::updateWidget()
{
listBox->resize(LOCPLINT->localState->getWanderingHeroes().size()); listBox->resize(LOCPLINT->localState->getWanderingHeroes().size());
listBox->reset();
if (LOCPLINT->localState->getCurrentHero()) if (LOCPLINT->localState->getCurrentHero())
select(LOCPLINT->localState->getCurrentHero()); select(LOCPLINT->localState->getCurrentHero());
@ -363,14 +357,17 @@ void CTownList::select(const CGTownInstance * town)
selectIndex(vstd::find_pos(LOCPLINT->localState->getOwnedTowns(), town)); selectIndex(vstd::find_pos(LOCPLINT->localState->getOwnedTowns(), town));
} }
void CTownList::update(const CGTownInstance *) void CTownList::updateElement(const CGTownInstance * town)
{ {
//simplest solution for now: reset list and restore selection updateWidget();
}
void CTownList::updateWidget()
{
listBox->resize(LOCPLINT->localState->getOwnedTowns().size()); listBox->resize(LOCPLINT->localState->getOwnedTowns().size());
listBox->reset();
if (LOCPLINT->localState->getCurrentTown()) if (LOCPLINT->localState->getCurrentTown())
select(LOCPLINT->localState->getCurrentTown()); select(LOCPLINT->localState->getCurrentTown());
CList::update(); CList::update();
} }

View File

@ -77,6 +77,9 @@ protected:
virtual std::shared_ptr<CIntObject> createItem(size_t index) = 0; virtual std::shared_ptr<CIntObject> createItem(size_t index) = 0;
/// should be called when list is invalidated
void update();
public: public:
/// functions that will be called when selection changes /// functions that will be called when selection changes
CFunctionList<void()> onSelect; CFunctionList<void()> onSelect;
@ -87,8 +90,6 @@ public:
void setScrollUpButton(std::shared_ptr<CButton> button); void setScrollUpButton(std::shared_ptr<CButton> button);
void setScrollDownButton(std::shared_ptr<CButton> button); void setScrollDownButton(std::shared_ptr<CButton> button);
/// should be called when list is invalidated
void update();
/// set of methods to switch selection /// set of methods to switch selection
void selectIndex(int which); void selectIndex(int which);
@ -137,7 +138,10 @@ public:
void select(const CGHeroInstance * hero = nullptr); void select(const CGHeroInstance * hero = nullptr);
/// Update hero. Will add or remove it from the list if needed /// Update hero. Will add or remove it from the list if needed
void update(const CGHeroInstance * hero = nullptr); void updateElement(const CGHeroInstance * hero);
/// Update all heroes
void updateWidget();
}; };
/// List of towns which is shown at the right of the adventure map screen or in the town screen /// List of towns which is shown at the right of the adventure map screen or in the town screen
@ -167,6 +171,9 @@ public:
void select(const CGTownInstance * town = nullptr); void select(const CGTownInstance * town = nullptr);
/// Update town. Will add or remove it from the list if needed /// Update town. Will add or remove it from the list if needed
void update(const CGTownInstance * town = nullptr); void updateElement(const CGTownInstance * town);
/// Update all towns
void updateWidget();
}; };

View File

@ -223,10 +223,12 @@ void InputSourceTouch::handleUpdate()
if (currentTime > lastTapTimeTicks + params.longTouchTimeMilliseconds) if (currentTime > lastTapTimeTicks + params.longTouchTimeMilliseconds)
{ {
GH.events().dispatchShowPopup(GH.getCursorPosition()); GH.events().dispatchShowPopup(GH.getCursorPosition());
hapticFeedback();
if (GH.windows().isTopWindowPopup()) if (GH.windows().isTopWindowPopup())
{
hapticFeedback();
state = TouchState::TAP_DOWN_LONG; state = TouchState::TAP_DOWN_LONG;
}
} }
} }
} }

View File

@ -305,6 +305,7 @@ CKeyShortcut::CKeyShortcut()
CKeyShortcut::CKeyShortcut(EShortcut key) CKeyShortcut::CKeyShortcut(EShortcut key)
: assignedKey(key) : assignedKey(key)
, shortcutPressed(false)
{ {
} }

View File

@ -37,4 +37,7 @@ public:
/// Converts provided rect from logical coordinates into coordinates within window, accounting for scaling and viewport /// Converts provided rect from logical coordinates into coordinates within window, accounting for scaling and viewport
virtual Rect convertLogicalPointsToWindow(const Rect & input) const = 0; virtual Rect convertLogicalPointsToWindow(const Rect & input) const = 0;
/// Dimensions of render output
virtual Point getRenderResolution() const = 0;
}; };

View File

@ -46,7 +46,7 @@ std::tuple<int, int> ScreenHandler::getSupportedScalingRange() const
// arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices // arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices
static const double minimalScaling = 50; static const double minimalScaling = 50;
Point renderResolution = getActualRenderResolution(); Point renderResolution = getRenderResolution();
double reservedAreaWidth = settings["video"]["reservedWidth"].Float(); double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y); Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
@ -85,7 +85,7 @@ Rect ScreenHandler::convertLogicalPointsToWindow(const Rect & input) const
Point ScreenHandler::getPreferredLogicalResolution() const Point ScreenHandler::getPreferredLogicalResolution() const
{ {
Point renderResolution = getActualRenderResolution(); Point renderResolution = getRenderResolution();
double reservedAreaWidth = settings["video"]["reservedWidth"].Float(); double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y); Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
@ -99,7 +99,7 @@ Point ScreenHandler::getPreferredLogicalResolution() const
return logicalResolution; return logicalResolution;
} }
Point ScreenHandler::getActualRenderResolution() const Point ScreenHandler::getRenderResolution() const
{ {
assert(mainRenderer != nullptr); assert(mainRenderer != nullptr);

View File

@ -39,9 +39,6 @@ class ScreenHandler final : public IScreenHandler
/// This value is what player views as window size /// This value is what player views as window size
Point getPreferredWindowResolution() const; Point getPreferredWindowResolution() const;
/// Dimensions of render output, usually same as window size except for high-DPI screens on macOS / iOS
Point getActualRenderResolution() const;
EWindowMode getPreferredWindowMode() const; EWindowMode getPreferredWindowMode() const;
/// Returns index of display on which window should be created /// Returns index of display on which window should be created
@ -86,6 +83,9 @@ public:
/// Fills screen with black color, erasing any existing content /// Fills screen with black color, erasing any existing content
void clearScreen() final; void clearScreen() final;
/// Dimensions of render output, usually same as window size except for high-DPI screens on macOS / iOS
Point getRenderResolution() const final;
std::vector<Point> getSupportedResolutions() const final; std::vector<Point> getSupportedResolutions() const final;
std::vector<Point> getSupportedResolutions(int displayIndex) const; std::vector<Point> getSupportedResolutions(int displayIndex) const;
std::tuple<int, int> getSupportedScalingRange() const final; std::tuple<int, int> getSupportedScalingRange() const final;

View File

@ -319,9 +319,6 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
noDismiss = true; noDismiss = true;
} }
for(auto ki : GH.windows().findWindows<CKingdomInterface>())
noDismiss = true;
//if player only have one hero and no towns //if player only have one hero and no towns
if(!LOCPLINT->cb->howManyTowns() && LOCPLINT->cb->howManyHeroes() == 1) if(!LOCPLINT->cb->howManyTowns() && LOCPLINT->cb->howManyHeroes() == 1)
noDismiss = true; noDismiss = true;
@ -329,7 +326,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
if(curHero->isMissionCritical()) if(curHero->isMissionCritical())
noDismiss = true; noDismiss = true;
dismissButton->block(!!curHero->visitedTown || noDismiss); dismissButton->block(noDismiss);
if(curHero->valOfBonuses(Selector::type()(BonusType::BEFORE_BATTLE_REPOSITION)) == 0) if(curHero->valOfBonuses(Selector::type()(BonusType::BEFORE_BATTLE_REPOSITION)) == 0)
{ {

View File

@ -15,6 +15,7 @@
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../adventureMap/CResDataBar.h" #include "../adventureMap/CResDataBar.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h" #include "../gui/Shortcut.h"
@ -638,6 +639,11 @@ void CKingdomInterface::townChanged(const CGTownInstance *town)
townList->townChanged(town); townList->townChanged(town);
} }
void CKingdomInterface::heroRemoved()
{
tabArea->reset();
}
void CKingdomInterface::updateGarrisons() void CKingdomInterface::updateGarrisons()
{ {
if(auto garrison = std::dynamic_pointer_cast<CGarrisonHolder>(tabArea->getItem())) if(auto garrison = std::dynamic_pointer_cast<CGarrisonHolder>(tabArea->getItem()))
@ -694,11 +700,12 @@ void CKingdHeroList::updateGarrisons()
std::shared_ptr<CIntObject> CKingdHeroList::createHeroItem(size_t index) std::shared_ptr<CIntObject> CKingdHeroList::createHeroItem(size_t index)
{ {
ui32 picCount = 4; // OVSLOT contains 4 images ui32 picCount = 4; // OVSLOT contains 4 images
size_t heroesCount = LOCPLINT->cb->howManyHeroes(false);
if(index < heroesCount) auto heroesList = LOCPLINT->localState->getWanderingHeroes();
if(index < heroesList.size())
{ {
auto hero = std::make_shared<CHeroItem>(LOCPLINT->cb->getHeroBySerial((int)index, false)); auto hero = std::make_shared<CHeroItem>(heroesList[index]);
addSet(hero->heroArts); addSet(hero->heroArts);
return hero; return hero;
} }
@ -745,10 +752,11 @@ void CKingdTownList::updateGarrisons()
std::shared_ptr<CIntObject> CKingdTownList::createTownItem(size_t index) std::shared_ptr<CIntObject> CKingdTownList::createTownItem(size_t index)
{ {
ui32 picCount = 4; // OVSLOT contains 4 images ui32 picCount = 4; // OVSLOT contains 4 images
size_t townsCount = LOCPLINT->cb->howManyTowns();
if(index < townsCount) auto townsList = LOCPLINT->localState->getOwnedTowns();
return std::make_shared<CTownItem>(LOCPLINT->cb->getTownBySerial((int)index));
if(index < townsList.size())
return std::make_shared<CTownItem>(townsList[index]);
else else
return std::make_shared<CAnimImage>("OVSLOT", (index-2) % picCount ); return std::make_shared<CAnimImage>("OVSLOT", (index-2) % picCount );
} }

View File

@ -246,6 +246,7 @@ public:
CKingdomInterface(); CKingdomInterface();
void townChanged(const CGTownInstance *town); void townChanged(const CGTownInstance *town);
void heroRemoved();
void updateGarrisons() override; void updateGarrisons() override;
void artifactRemoved(const ArtifactLocation &artLoc) override; void artifactRemoved(const ArtifactLocation &artLoc) override;
void artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc, bool withRedraw) override; void artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc, bool withRedraw) override;

View File

@ -158,23 +158,33 @@ void CRecruitmentWindow::select(std::shared_ptr<CCreatureCard> card)
void CRecruitmentWindow::buy() void CRecruitmentWindow::buy()
{ {
CreatureID crid = selected->creature->getId(); CreatureID crid = selected->creature->getId();
SlotID dstslot = dst-> getSlotFor(crid); SlotID dstslot = dst->getSlotFor(crid);
if(!dstslot.validSlot() && (selected->creature->warMachine == ArtifactID::NONE)) //no available slot if(!dstslot.validSlot() && (selected->creature->warMachine == ArtifactID::NONE)) //no available slot
{ {
std::string txt; std::pair<SlotID, SlotID> toMerge;
if(dst->ID == Obj::HERO) bool allowMerge = CGI->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED);
if (allowMerge && dst->mergableStacks(toMerge))
{ {
txt = CGI->generaltexth->allTexts[425]; //The %s would join your hero, but there aren't enough provisions to support them. LOCPLINT->cb->mergeStacks( dst, dst, toMerge.first, toMerge.second);
boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->objects[crid]->getNamePluralTranslated() : CGI->creh->objects[crid]->getNameSingularTranslated());
} }
else else
{ {
txt = CGI->generaltexth->allTexts[17]; //There is no room in the garrison for this army. std::string txt;
} if(dst->ID == Obj::HERO)
{
txt = CGI->generaltexth->allTexts[425]; //The %s would join your hero, but there aren't enough provisions to support them.
boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->objects[crid]->getNamePluralTranslated() : CGI->creh->objects[crid]->getNameSingularTranslated());
}
else
{
txt = CGI->generaltexth->allTexts[17]; //There is no room in the garrison for this army.
}
LOCPLINT->showInfoDialog(txt); LOCPLINT->showInfoDialog(txt);
return; return;
}
} }
onRecruit(crid, slider->getValue()); onRecruit(crid, slider->getValue());

View File

@ -224,25 +224,19 @@ void GeneralOptionsTab::updateResolutionSelector()
std::shared_ptr<CButton> resolutionButton = widget<CButton>("resolutionButton"); std::shared_ptr<CButton> resolutionButton = widget<CButton>("resolutionButton");
std::shared_ptr<CLabel> resolutionLabel = widget<CLabel>("resolutionLabel"); std::shared_ptr<CLabel> resolutionLabel = widget<CLabel>("resolutionLabel");
if (settings["video"]["fullscreen"].Bool() && !settings["video"]["realFullscreen"].Bool()) if (resolutionButton)
{ {
if (resolutionButton) if (settings["video"]["fullscreen"].Bool() && !settings["video"]["realFullscreen"].Bool())
resolutionButton->disable(); resolutionButton->disable();
else
if (resolutionLabel)
resolutionLabel->setText(resolutionToLabelString(GH.screenDimensions().x, GH.screenDimensions().y));
}
else
{
const auto & currentResolution = settings["video"]["resolution"];
if (resolutionButton)
resolutionButton->enable(); resolutionButton->enable();
if (resolutionLabel)
resolutionLabel->setText(resolutionToLabelString(currentResolution["width"].Integer(), currentResolution["height"].Integer()));
} }
if (resolutionLabel)
{
Point resolution = GH.screenHandler().getRenderResolution();
resolutionLabel->setText(resolutionToLabelString(resolution.x, resolution.y));
}
} }
void GeneralOptionsTab::selectGameResolution() void GeneralOptionsTab::selectGameResolution()
@ -370,6 +364,11 @@ void GeneralOptionsTab::setGameScaling(int index)
gameRes["scaling"].Float() = scaling; gameRes["scaling"].Float() = scaling;
widget<CLabel>("scalingLabel")->setText(scalingToLabelString(scaling)); widget<CLabel>("scalingLabel")->setText(scalingToLabelString(scaling));
GH.dispatchMainThread([](){
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.onScreenResize();
});
} }
void GeneralOptionsTab::selectLongTouchDuration() void GeneralOptionsTab::selectLongTouchDuration()

View File

@ -68,6 +68,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/gameState/CGameState.cpp ${MAIN_LIB_DIR}/gameState/CGameState.cpp
${MAIN_LIB_DIR}/gameState/CGameStateCampaign.cpp ${MAIN_LIB_DIR}/gameState/CGameStateCampaign.cpp
${MAIN_LIB_DIR}/gameState/InfoAboutArmy.cpp ${MAIN_LIB_DIR}/gameState/InfoAboutArmy.cpp
${MAIN_LIB_DIR}/gameState/TavernHeroesPool.cpp
${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.cpp ${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.cpp
${MAIN_LIB_DIR}/logging/CLogger.cpp ${MAIN_LIB_DIR}/logging/CLogger.cpp
@ -394,6 +395,8 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/gameState/EVictoryLossCheckResult.h ${MAIN_LIB_DIR}/gameState/EVictoryLossCheckResult.h
${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h ${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h
${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h ${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h
${MAIN_LIB_DIR}/gameState/TavernHeroesPool.h
${MAIN_LIB_DIR}/gameState/TavernSlot.h
${MAIN_LIB_DIR}/gameState/QuestInfo.h ${MAIN_LIB_DIR}/gameState/QuestInfo.h
${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h ${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h

View File

@ -4,12 +4,12 @@
{ {
"name" : "attackerLoose", // loose formation, attacker "name" : "attackerLoose", // loose formation, attacker
"levels": [ "levels": [
[ 86 ], [ 86 ],
[ 35, 137 ], [ 35, 137 ],
[ 35, 86, 137 ], [ 35, 86, 137 ],
[ 1, 69, 103, 171 ], [ 1, 69, 103, 171 ],
[ 1, 35, 86, 137, 171 ], [ 1, 35, 86, 137, 171 ],
[ 1, 35, 69, 103, 137, 171 ], [ 1, 35, 69, 103, 137, 171 ],
[ 1, 35, 69, 86, 103, 137, 171 ] [ 1, 35, 69, 86, 103, 137, 171 ]
] ]
}, },
@ -17,12 +17,12 @@
{ {
"name" : "defenderLoose", // loose formation, defender "name" : "defenderLoose", // loose formation, defender
"levels": [ "levels": [
[ 100 ], [ 100 ],
[ 49, 151 ], [ 49, 151 ],
[ 49, 100, 151 ], [ 49, 100, 151 ],
[ 15, 83, 117, 185 ], [ 15, 83, 117, 185 ],
[ 15, 49, 100, 151, 185 ], [ 15, 49, 100, 151, 185 ],
[ 15, 49, 83, 117, 151, 185 ], [ 15, 49, 83, 117, 151, 185 ],
[ 15, 49, 83, 100, 117, 151, 185 ] [ 15, 49, 83, 100, 117, 151, 185 ]
] ]
}, },
@ -30,26 +30,26 @@
{ {
"name" : "attackerTight", // tight formation, attacker "name" : "attackerTight", // tight formation, attacker
"levels": [ "levels": [
[ 86 ], [ 86 ],
[ 69, 103 ], [ 69, 103 ],
[ 69, 86, 103 ], [ 69, 86, 103 ],
[ 52, 69, 103, 120 ], [ 35, 69, 103, 137 ],
[ 52, 69, 86, 103, 120 ], [ 35, 69, 86, 103, 137 ],
[ 35, 52, 69, 103, 120, 137 ], [ 1, 35, 69, 103, 137, 171 ],
[ 35, 52, 69, 86, 103, 120, 137 ] [ 1, 35, 69, 86, 103, 137, 171 ]
] ]
}, },
{ {
"name" : "defenderTight", // tight formation, defender "name" : "defenderTight", // tight formation, defender
"levels": [ "levels": [
[ 100 ], [ 100 ],
[ 83, 117 ], [ 83, 117 ],
[ 83, 100, 117 ], [ 83, 100, 117 ],
[ 66, 83, 117, 134 ], [ 49, 83, 117, 151 ],
[ 66, 83, 100, 117, 134 ], [ 49, 83, 100, 117, 151 ],
[ 49, 66, 83, 117, 134, 151 ], [ 15, 49, 83, 117, 151, 185 ],
[ 49, 66, 83, 100, 117, 134, 151 ] [ 15, 49, 83, 100, 117, 151, 185 ]
] ]
}, },

View File

@ -9,25 +9,25 @@
{ {
"DATA/" : "DATA/" :
[ [
{"type" : "lod", "path" : "Data/H3ab_bmp.lod"}, {"type" : "lod", "path" : "Data/H3ab_bmp.lod"}, // Contains H3:AB data
{"type" : "lod", "path" : "Data/H3bitmap.lod"}, {"type" : "lod", "path" : "Data/h3abp_bm.lod"}, // Localized versions only, contains H3:AB patch data
{"type" : "lod", "path" : "Data/h3abp_bm.lod"}, // Polish version of H3 only {"type" : "lod", "path" : "Data/H3bitmap.lod"}, // Contains H3:SoD data (overrides H3:AB data)
{"type" : "lod", "path" : "Data/H3pbitma.lod"}, // Polish version of H3 only {"type" : "lod", "path" : "Data/H3pbitma.lod"}, // Localized versions only, contains H3:SoD patch data
{"type" : "dir", "path" : "Data"} {"type" : "dir", "path" : "Data"}
], ],
"SPRITES/": "SPRITES/":
[ [
{"type" : "lod", "path" : "Data/H3ab_spr.lod"}, {"type" : "lod", "path" : "Data/H3ab_spr.lod"}, // Contains H3:AB data
{"type" : "lod", "path" : "Data/H3sprite.lod"}, {"type" : "lod", "path" : "Data/H3sprite.lod"}, // Localized versions only, contains H3:AB patch data
{"type" : "lod", "path" : "Data/h3abp_sp.lod"}, // Polish version of H3 only {"type" : "lod", "path" : "Data/h3abp_sp.lod"}, // Contains H3:SoD data (overrides H3:AB data)
{"type" : "lod", "path" : "Data/H3psprit.lod"}, // Polish version of H3 only // {"type" : "lod", "path" : "Data/H3psprit.lod"}, // Localized versions only, contains H3:SoD patch data. Unused? Has corrupted data, e.g. lock icon for artifacts
{"type" : "dir", "path" : "Sprites"} {"type" : "dir", "path" : "Sprites"}
], ],
"SOUNDS/": "SOUNDS/":
[ [
{"type" : "snd", "path" : "Data/H3ab_ahd.snd"}, {"type" : "snd", "path" : "Data/H3ab_ahd.snd"},
{"type" : "snd", "path" : "Data/Heroes3-cd2.snd"},
{"type" : "snd", "path" : "Data/Heroes3.snd"}, {"type" : "snd", "path" : "Data/Heroes3.snd"},
{"type" : "snd", "path" : "Data/Heroes3-cd2.snd"},
//WoG have overriden sounds with .82m extension in Data //WoG have overriden sounds with .82m extension in Data
{"type" : "dir", "path" : "Data", "depth": 0} {"type" : "dir", "path" : "Data", "depth": 0}
], ],

View File

@ -311,6 +311,8 @@
"accumulateWhenNeutral" : false, "accumulateWhenNeutral" : false,
// if enabled, dwellings owned by players will accumulate creatures // if enabled, dwellings owned by players will accumulate creatures
"accumulateWhenOwned" : false "accumulateWhenOwned" : false
// if enabled, game will attempt to merge slots in army on recruit if all slots in hero army are in use
"mergeOnRecruit" : true
}, },
"markets" : "markets" :

View File

@ -63,12 +63,12 @@
}, },
"language" : { "language" : {
"type" : "string", "type" : "string",
"enum" : [ "english", "czech", "chinese", "german", "hungarian", "italian", "korean", "polish", "russian", "spanish", "ukrainian" ], "enum" : [ "english", "czech", "chinese", "french", "german", "hungarian", "italian", "korean", "polish", "russian", "spanish", "ukrainian" ],
"default" : "english" "default" : "english"
}, },
"gameDataLanguage" : { "gameDataLanguage" : {
"type" : "string", "type" : "string",
"enum" : [ "auto", "english", "czech", "chinese", "german", "hungarian", "italian", "korean", "polish", "russian", "spanish", "ukrainian", "other_cp1250", "other_cp1251", "other_cp1252" ], "enum" : [ "auto", "english", "czech", "chinese", "french", "german", "hungarian", "italian", "korean", "polish", "russian", "spanish", "ukrainian", "other_cp1250", "other_cp1251", "other_cp1252" ],
"default" : "auto" "default" : "auto"
}, },
"lastSave" : { "lastSave" : {

View File

@ -245,7 +245,7 @@
] ]
}, },
{ {
"index": 12, "index": 9,
"type": "toggleButton", "type": "toggleButton",
"image": "settingsWindow/button46", "image": "settingsWindow/button46",
"help": "vcmi.battleOptions.animationsSpeed5", "help": "vcmi.battleOptions.animationsSpeed5",
@ -261,7 +261,7 @@
] ]
}, },
{ {
"index": 24, "index": 18,
"type": "toggleButton", "type": "toggleButton",
"image": "settingsWindow/button46", "image": "settingsWindow/button46",
"help": "vcmi.battleOptions.animationsSpeed6", "help": "vcmi.battleOptions.animationsSpeed6",

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>745</width> <width>745</width>
<height>389</height> <height>397</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -96,7 +96,7 @@
<item> <item>
<widget class="QStackedWidget" name="installerTabs"> <widget class="QStackedWidget" name="installerTabs">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>2</number>
</property> </property>
<widget class="QWidget" name="pageLanguageSelect"> <widget class="QWidget" name="pageLanguageSelect">
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
@ -616,7 +616,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
<string>Horn of the Abyss</string> <string>Horn of the Abyss</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -638,7 +638,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
<string>Heroes III Translation</string> <string>Heroes III Translation</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -699,10 +699,10 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
</font> </font>
</property> </property>
<property name="text"> <property name="text">
<string>High Definition Support</string> <string>Interface Improvements</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -724,7 +724,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
<string>In The Wake of Gods</string> <string>In The Wake of Gods</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -769,7 +769,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Install support for playing Heroes III in resolutions higher than 800x600</string> <string>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View File

@ -153,6 +153,7 @@ void MainWindow::enterSetup()
ui->startEditorButton->setEnabled(false); ui->startEditorButton->setEnabled(false);
ui->lobbyButton->setEnabled(false); ui->lobbyButton->setEnabled(false);
ui->settingsButton->setEnabled(false); ui->settingsButton->setEnabled(false);
ui->aboutButton->setEnabled(false);
ui->modslistButton->setEnabled(false); ui->modslistButton->setEnabled(false);
ui->tabListWidget->setCurrentIndex(TabRows::SETUP); ui->tabListWidget->setCurrentIndex(TabRows::SETUP);
} }
@ -166,6 +167,7 @@ void MainWindow::exitSetup()
ui->startEditorButton->setEnabled(true); ui->startEditorButton->setEnabled(true);
ui->lobbyButton->setEnabled(true); ui->lobbyButton->setEnabled(true);
ui->settingsButton->setEnabled(true); ui->settingsButton->setEnabled(true);
ui->aboutButton->setEnabled(true);
ui->modslistButton->setEnabled(true); ui->modslistButton->setEnabled(true);
ui->tabListWidget->setCurrentIndex(TabRows::MODS); ui->tabListWidget->setCurrentIndex(TabRows::MODS);
} }

View File

@ -280,12 +280,6 @@ void CSettingsView::on_comboBoxDisplayIndex_currentIndexChanged(int index)
fillValidResolutionsForScreen(index); fillValidResolutionsForScreen(index);
} }
void CSettingsView::on_comboBoxPlayerAI_currentTextChanged(const QString & arg1)
{
Settings node = settings.write["server"]["playerAI"];
node->String() = arg1.toUtf8().data();
}
void CSettingsView::on_comboBoxFriendlyAI_currentTextChanged(const QString & arg1) void CSettingsView::on_comboBoxFriendlyAI_currentTextChanged(const QString & arg1)
{ {
Settings node = settings.write["server"]["friendlyAI"]; Settings node = settings.write["server"]["friendlyAI"];
@ -500,6 +494,19 @@ void CSettingsView::on_spinBoxFramerateLimit_valueChanged(int arg1)
node->Float() = arg1; node->Float() = arg1;
} }
void CSettingsView::on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1)
{
Settings node = settings.write["server"]["playerAI"];
node->String() = arg1.toUtf8().data();
}
void CSettingsView::on_comboBoxAlliedPlayerAI_currentTextChanged(const QString &arg1)
{
Settings node = settings.write["server"]["alliedAI"];
node->String() = arg1.toUtf8().data();
}
void CSettingsView::on_checkBoxAutoSavePrefix_stateChanged(int arg1) void CSettingsView::on_checkBoxAutoSavePrefix_stateChanged(int arg1)
{ {

View File

@ -35,7 +35,6 @@ public slots:
private slots: private slots:
void on_comboBoxResolution_currentTextChanged(const QString & arg1); void on_comboBoxResolution_currentTextChanged(const QString & arg1);
void on_comboBoxFullScreen_currentIndexChanged(int index); void on_comboBoxFullScreen_currentIndexChanged(int index);
void on_comboBoxPlayerAI_currentTextChanged(const QString & arg1);
void on_comboBoxFriendlyAI_currentTextChanged(const QString & arg1); void on_comboBoxFriendlyAI_currentTextChanged(const QString & arg1);
void on_comboBoxNeutralAI_currentTextChanged(const QString & arg1); void on_comboBoxNeutralAI_currentTextChanged(const QString & arg1);
void on_comboBoxEnemyAI_currentTextChanged(const QString & arg1); void on_comboBoxEnemyAI_currentTextChanged(const QString & arg1);
@ -63,6 +62,10 @@ private slots:
void on_spinBoxFramerateLimit_valueChanged(int arg1); void on_spinBoxFramerateLimit_valueChanged(int arg1);
void on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1);
void on_comboBoxAlliedPlayerAI_currentTextChanged(const QString &arg1);
void on_checkBoxAutoSavePrefix_stateChanged(int arg1); void on_checkBoxAutoSavePrefix_stateChanged(int arg1);
void on_spinBoxAutoSaveLimit_valueChanged(int arg1); void on_spinBoxAutoSaveLimit_valueChanged(int arg1);

View File

@ -710,6 +710,11 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
<source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source> <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
<translation>33</translation> <translation>33</translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
<source>Install a translation of Heroes III in your preferred language</source> <source>Install a translation of Heroes III in your preferred language</source>
@ -722,8 +727,12 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Install support for playing Heroes III in resolutions higher than 800x600</source> <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
<translation>3800x600以上分辨率支持</translation> <translation type="vanished">3800x600以上分辨率支持</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
@ -813,9 +822,8 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
<translation>3</translation> <translation>3</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source> <source>High Definition Support</source>
<translation></translation> <translation type="vanished"></translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>

View File

@ -678,6 +678,11 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
<source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source> <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
<source>Install a translation of Heroes III in your preferred language</source> <source>Install a translation of Heroes III in your preferred language</source>
@ -690,7 +695,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install support for playing Heroes III in resolutions higher than 800x600</source> <source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
@ -780,11 +785,6 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
<source>Heroes III Translation</source> <source>Heroes III Translation</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>
<source>In The Wake of Gods</source> <source>In The Wake of Gods</source>

View File

@ -257,7 +257,7 @@
<translation>Impressions écran</translation> <translation>Impressions écran</translation>
</message> </message>
<message> <message>
<location filename="../modManager/cmodlistview_moc.ui" line="323"/> <location filename="../modManager/cmodlistview_moc.ui" line="324"/>
<source> %p% (%v KB out of %m KB)</source> <source> %p% (%v KB out of %m KB)</source>
<translation> %p% (%v Ko sur %m Ko)</translation> <translation> %p% (%v Ko sur %m Ko)</translation>
</message> </message>
@ -780,9 +780,8 @@ Mode exclusif plein écran - le jeu couvrira l&quot;intégralité de votre écra
</translation> </translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install support for playing Heroes III in resolutions higher than 800x600</source> <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
<translation>Installer un support pour jouer à Heroes III avec des résolutions supérieures à 800x600 <translation type="vanished">Installer un support pour jouer à Heroes III avec des résolutions supérieures à 800x600
</translation> </translation>
</message> </message>
<message> <message>
@ -860,7 +859,7 @@ Heroes® of Might and Magic® III HD n&quot;est actuellement pas pris en charge
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/>
<source>If you don&apos;t have a copy of Heroes III installed, you can use our automatic installation tool &apos;vcmibuilder&apos;, which only requires the GoG.com Heroes III installer. Please visit our wiki for detailed instructions.</source> <source>If you don&apos;t have a copy of Heroes III installed, you can use our automatic installation tool &apos;vcmibuilder&apos;, which only requires the GoG.com Heroes III installer. Please visit our wiki for detailed instructions.</source>
<translation>Si vous n&quot;avez pas installé de copie de Heroes III, vous pouvez utiliser notre outil d&quot;installation automatique "vcmibuilder", qui ne nécessite que le programme d&quot;installation de GoG.com Heroes III. Veuillez visiter notre wiki pour des instructions détaillées.</translation> <translation>Si vous n&quot;avez pas installé de copie de Heroes III, vous pouvez utiliser notre outil d&quot;installation automatique &quot;vcmibuilder&quot;, qui ne nécessite que le programme d&quot;installation de GoG.com Heroes III. Veuillez visiter notre wiki pour des instructions détaillées.</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/>
@ -920,8 +919,12 @@ Heroes® of Might and Magic® III HD n&quot;est actuellement pas pris en charge
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>High Definition Support</source> <source>High Definition Support</source>
<translation>Support de Haute Définition</translation> <translation type="vanished">Support de Haute Définition</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>
@ -933,6 +936,11 @@ Heroes® of Might and Magic® III HD n&quot;est actuellement pas pris en charge
<source>Optionally, you can install additional mods either now, or at any point later, using the VCMI Launcher</source> <source>Optionally, you can install additional mods either now, or at any point later, using the VCMI Launcher</source>
<translation>En option, vous pouvez installer des mods supplémentaires soit maintenant, soit à tout moment plus tard, à l&quot;aide du lanceur VCMI</translation> <translation>En option, vous pouvez installer des mods supplémentaires soit maintenant, soit à tout moment plus tard, à l&quot;aide du lanceur VCMI</translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
<source>Install compatible version of &quot;Horn of the Abyss&quot;, a fan-made Heroes III expansion ported by the VCMI team</source> <source>Install compatible version of &quot;Horn of the Abyss&quot;, a fan-made Heroes III expansion ported by the VCMI team</source>

View File

@ -714,6 +714,11 @@ Heroes III: HD Edition wird derzeit nicht unterstützt</translation>
<source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source> <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
<translation>Automatische Erkennung der Sprache fehlgeschlagen. Bitte wählen Sie die Sprache Ihrer Heroes III Kopie</translation> <translation>Automatische Erkennung der Sprache fehlgeschlagen. Bitte wählen Sie die Sprache Ihrer Heroes III Kopie</translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
<source>Install a translation of Heroes III in your preferred language</source> <source>Install a translation of Heroes III in your preferred language</source>
@ -726,8 +731,12 @@ Heroes III: HD Edition wird derzeit nicht unterstützt</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Install support for playing Heroes III in resolutions higher than 800x600</source> <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
<translation>Installieren Sie Unterstützung für das Spielen von Heroes III in anderen Auflösungen als 800x600</translation> <translation type="vanished">Installieren Sie Unterstützung für das Spielen von Heroes III in anderen Auflösungen als 800x600</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
@ -817,9 +826,8 @@ Heroes III: HD Edition wird derzeit nicht unterstützt</translation>
<translation>Heroes III Übersetzung</translation> <translation>Heroes III Übersetzung</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source> <source>High Definition Support</source>
<translation>Unterstützung für hohe Auflösungen</translation> <translation type="vanished">Unterstützung für hohe Auflösungen</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>

View File

@ -714,6 +714,11 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
<source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source> <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
<translation>Automatyczna detekcja języka nie powiodła się. Proszę wybrać język twojego Heroes III</translation> <translation>Automatyczna detekcja języka nie powiodła się. Proszę wybrać język twojego Heroes III</translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
<source>Install a translation of Heroes III in your preferred language</source> <source>Install a translation of Heroes III in your preferred language</source>
@ -726,8 +731,12 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Install support for playing Heroes III in resolutions higher than 800x600</source> <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
<translation>Zainstaluj wsparcie dla grania w Heroes III w rozdzielczości innej niż 800x600</translation> <translation type="vanished">Zainstaluj wsparcie dla grania w Heroes III w rozdzielczości innej niż 800x600</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
@ -817,9 +826,8 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
<translation>Tłumaczenie Heroes III</translation> <translation>Tłumaczenie Heroes III</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source> <source>High Definition Support</source>
<translation>Wsparcie High Definition</translation> <translation type="vanished">Wsparcie High Definition</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>

View File

@ -708,6 +708,11 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
<source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source> <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
<translation>Язык Героев III не был определен. Пожалуйста, выберите язык вашей копии Героев III</translation> <translation>Язык Героев III не был определен. Пожалуйста, выберите язык вашей копии Героев III</translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
<source>Install a translation of Heroes III in your preferred language</source> <source>Install a translation of Heroes III in your preferred language</source>
@ -720,8 +725,12 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Install support for playing Heroes III in resolutions higher than 800x600</source> <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
<translation>Установить поддержку запуска Героев III в разрешениях, отличных от 800x600</translation> <translation type="vanished">Установить поддержку запуска Героев III в разрешениях, отличных от 800x600</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
@ -811,9 +820,8 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
<translation>Перевод Героев III</translation> <translation>Перевод Героев III</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source> <source>High Definition Support</source>
<translation>Поддержка высоких разрешений</translation> <translation type="vanished">Поддержка высоких разрешений</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>

View File

@ -702,6 +702,16 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
<source>Your Heroes III language has been successfully detected.</source> <source>Your Heroes III language has been successfully detected.</source>
<translation>Se ha detectado con éxito el idioma de tu Heroes III.</translation> <translation>Se ha detectado con éxito el idioma de tu Heroes III.</translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="127"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="127"/>
<source>Select your language</source> <source>Select your language</source>
@ -781,9 +791,8 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
<translation>Traducción de Heroes III.</translation> <translation>Traducción de Heroes III.</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source> <source>High Definition Support</source>
<translation>Soporte para resoluciones en Alta Definición</translation> <translation type="vanished">Soporte para resoluciones en Alta Definición</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>
@ -801,9 +810,8 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi
<translation>Opcionalmente, puedes instalar mods adicionales ya sea ahora o en cualquier momento posterior, utilizando el lanzador de VCMI.</translation> <translation>Opcionalmente, puedes instalar mods adicionales ya sea ahora o en cualquier momento posterior, utilizando el lanzador de VCMI.</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install support for playing Heroes III in resolutions higher than 800x600</source> <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
<translation>Instalar soporte para jugar Heroes III en resoluciones superiores a 800x600</translation> <translation type="vanished">Instalar soporte para jugar Heroes III en resoluciones superiores a 800x600</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>

View File

@ -718,6 +718,11 @@ Heroes® of Might and Magic® III HD наразі не підтримуєтьс
<source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source> <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
<translation>Не вдалося визначити мову гри. Будь ласка, виберіть мову вашої копії Heroes III</translation> <translation>Не вдалося визначити мову гри. Будь ласка, виберіть мову вашої копії Heroes III</translation>
</message> </message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation>Удосконалення нтерфейсу</translation>
</message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
<source>Install a translation of Heroes III in your preferred language</source> <source>Install a translation of Heroes III in your preferred language</source>
@ -730,8 +735,12 @@ Heroes® of Might and Magic® III HD наразі не підтримуєтьс
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
<translation>Встановити різноманітні покращення інтерфейсу, такі як покращений інтерфейс випадкових карт та вибір варіантів дій у боях</translation>
</message>
<message>
<source>Install support for playing Heroes III in resolutions higher than 800x600</source> <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
<translation>Встановити підтримку для гри в Heroes III у роздільних здатностях, більших за 800x600</translation> <translation type="vanished">Встановити підтримку для гри в Heroes III у роздільних здатностях, більших за 800x600</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
@ -821,9 +830,8 @@ Heroes® of Might and Magic® III HD наразі не підтримуєтьс
<translation>Переклад Heroes III</translation> <translation>Переклад Heroes III</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source> <source>High Definition Support</source>
<translation>Підтримка високих роздільних здатностей</translation> <translation type="vanished">Підтримка високих роздільних здатностей</translation>
</message> </message>
<message> <message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/> <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>

View File

@ -1097,7 +1097,7 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
case 'F': case 'F':
b.type = BonusType::FLYING; break; b.type = BonusType::FLYING; break;
case 'm': case 'm':
b.type = BonusType::MORALE; break; b.type = BonusType::MORALE;
b.val = 1; b.val = 1;
b.valType = BonusValueType::INDEPENDENT_MAX; b.valType = BonusValueType::INDEPENDENT_MAX;
break; break;

View File

@ -13,6 +13,7 @@
#include "gameState/CGameState.h" #include "gameState/CGameState.h"
#include "gameState/InfoAboutArmy.h" #include "gameState/InfoAboutArmy.h"
#include "gameState/SThievesGuildInfo.h" #include "gameState/SThievesGuildInfo.h"
#include "gameState/TavernHeroesPool.h"
#include "CGeneralTextHandler.h" #include "CGeneralTextHandler.h"
#include "StartInfo.h" // for StartInfo #include "StartInfo.h" // for StartInfo
#include "battle/BattleInfo.h" // for BattleInfo #include "battle/BattleInfo.h" // for BattleInfo
@ -99,13 +100,6 @@ const PlayerState * CGameInfoCallback::getPlayerState(PlayerColor color, bool ve
} }
} }
const CTown * CGameInfoCallback::getNativeTown(PlayerColor color) const
{
const PlayerSettings *ps = getPlayerSettings(color);
ERROR_RET_VAL_IF(!ps, "There is no such player!", nullptr);
return (*VLC->townh)[ps->castle]->town;
}
const CGObjectInstance * CGameInfoCallback::getObjByQuestIdentifier(int identifier) const const CGObjectInstance * CGameInfoCallback::getObjByQuestIdentifier(int identifier) const
{ {
if(gs->map->questIdentifierToId.empty()) if(gs->map->questIdentifierToId.empty())
@ -486,13 +480,10 @@ std::vector<const CGHeroInstance *> CGameInfoCallback::getAvailableHeroes(const
//ERROR_RET_VAL_IF(!isOwnedOrVisited(townOrTavern), "Town or tavern must be owned or visited!", ret); //ERROR_RET_VAL_IF(!isOwnedOrVisited(townOrTavern), "Town or tavern must be owned or visited!", ret);
//TODO: town needs to be owned, advmap tavern needs to be visited; to be reimplemented when visit tracking is done //TODO: town needs to be owned, advmap tavern needs to be visited; to be reimplemented when visit tracking is done
const CGTownInstance * town = getTown(townOrTavern->id); const CGTownInstance * town = getTown(townOrTavern->id);
if(townOrTavern->ID == Obj::TAVERN || (town && town->hasBuilt(BuildingID::TAVERN))) if(townOrTavern->ID == Obj::TAVERN || (town && town->hasBuilt(BuildingID::TAVERN)))
{ return gs->heroesPool->getHeroesFor(*player);
range::copy(gs->players[*player].availableHeroes, std::back_inserter(ret));
vstd::erase_if(ret, [](const CGHeroInstance * h) {
return h == nullptr;
});
}
return ret; return ret;
} }

View File

@ -108,7 +108,6 @@ public:
// std::string getTavernRumor(const CGObjectInstance * townOrTavern) const; // std::string getTavernRumor(const CGObjectInstance * townOrTavern) const;
// EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements // EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
// virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const; // virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
// const CTown *getNativeTown(PlayerColor color) const;
//from gs //from gs
// const TeamState *getTeam(TeamID teamID) const; // const TeamState *getTeam(TeamID teamID) const;
@ -206,7 +205,6 @@ public:
virtual std::string getTavernRumor(const CGObjectInstance * townOrTavern) const; virtual std::string getTavernRumor(const CGObjectInstance * townOrTavern) const;
virtual EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements virtual EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const; virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
virtual const CTown *getNativeTown(PlayerColor color) const;
//from gs //from gs
virtual const TeamState *getTeam(TeamID teamID) const; virtual const TeamState *getTeam(TeamID teamID) const;

View File

@ -35,7 +35,6 @@ PlayerState::PlayerState(PlayerState && other) noexcept:
std::swap(visitedObjects, other.visitedObjects); std::swap(visitedObjects, other.visitedObjects);
std::swap(heroes, other.heroes); std::swap(heroes, other.heroes);
std::swap(towns, other.towns); std::swap(towns, other.towns);
std::swap(availableHeroes, other.availableHeroes);
std::swap(dwellings, other.dwellings); std::swap(dwellings, other.dwellings);
std::swap(quests, other.quests); std::swap(quests, other.quests);
} }

View File

@ -33,7 +33,6 @@ public:
std::set<ObjectInstanceID> visitedObjects; // as a std::set, since most accesses here will be from visited status checks std::set<ObjectInstanceID> visitedObjects; // as a std::set, since most accesses here will be from visited status checks
std::vector<ConstTransitivePtr<CGHeroInstance> > heroes; std::vector<ConstTransitivePtr<CGHeroInstance> > heroes;
std::vector<ConstTransitivePtr<CGTownInstance> > towns; std::vector<ConstTransitivePtr<CGTownInstance> > towns;
std::vector<ConstTransitivePtr<CGHeroInstance> > availableHeroes; //heroes available in taverns
std::vector<ConstTransitivePtr<CGDwelling> > dwellings; //used for town growth std::vector<ConstTransitivePtr<CGDwelling> > dwellings; //used for town growth
std::vector<QuestInfo> quests; //store info about all received quests std::vector<QuestInfo> quests; //store info about all received quests
@ -74,7 +73,6 @@ public:
h & status; h & status;
h & heroes; h & heroes;
h & towns; h & towns;
h & availableHeroes;
h & dwellings; h & dwellings;
h & quests; h & quests;
h & visitedObjects; h & visitedObjects;

View File

@ -20,6 +20,11 @@ CRandomGenerator::CRandomGenerator()
resetSeed(); resetSeed();
} }
CRandomGenerator::CRandomGenerator(int seed)
{
setSeed(seed);
}
void CRandomGenerator::setSeed(int seed) void CRandomGenerator::setSeed(int seed)
{ {
rand.seed(seed); rand.seed(seed);

View File

@ -30,6 +30,9 @@ public:
/// current thread ID. /// current thread ID.
CRandomGenerator(); CRandomGenerator();
/// Seeds the generator with provided initial seed
explicit CRandomGenerator(int seed);
void setSeed(int seed); void setSeed(int seed);
/// Resets the seed to the product of the current time in milliseconds and the /// Resets the seed to the product of the current time in milliseconds and the

View File

@ -40,6 +40,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
const HeroTypeID HeroTypeID::NONE = HeroTypeID(-1); const HeroTypeID HeroTypeID::NONE = HeroTypeID(-1);
const ObjectInstanceID ObjectInstanceID::NONE = ObjectInstanceID(-1);
const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2); const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3); const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);

View File

@ -313,6 +313,8 @@ class ObjectInstanceID : public BaseForID<ObjectInstanceID, si32>
{ {
INSTID_LIKE_CLASS_COMMON(ObjectInstanceID, si32) INSTID_LIKE_CLASS_COMMON(ObjectInstanceID, si32)
DLL_LINKAGE static const ObjectInstanceID NONE;
friend class CGameInfoCallback; friend class CGameInfoCallback;
friend class CNonConstInfoCallback; friend class CNonConstInfoCallback;
}; };
@ -357,9 +359,11 @@ class PlayerColor : public BaseForID<PlayerColor, ui8>
enum EPlayerColor enum EPlayerColor
{ {
PLAYER_LIMIT_I = 8 PLAYER_LIMIT_I = 8,
}; };
using Mask = uint8_t;
DLL_LINKAGE static const PlayerColor SPECTATOR; //252 DLL_LINKAGE static const PlayerColor SPECTATOR; //252
DLL_LINKAGE static const PlayerColor CANNOT_DETERMINE; //253 DLL_LINKAGE static const PlayerColor CANNOT_DETERMINE; //253
DLL_LINKAGE static const PlayerColor UNFLAGGABLE; //254 - neutral objects (pandora, banks) DLL_LINKAGE static const PlayerColor UNFLAGGABLE; //254 - neutral objects (pandora, banks)

View File

@ -68,6 +68,7 @@ void GameSettings::load(const JsonNode & input)
{EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" },
{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" },
{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" },
{EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" },
{EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" },
{EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" },
{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" },

View File

@ -32,6 +32,7 @@ enum class EGameSettings
CREATURES_WEEKLY_GROWTH_PERCENT, CREATURES_WEEKLY_GROWTH_PERCENT,
DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,
DWELLINGS_ACCUMULATE_WHEN_OWNED, DWELLINGS_ACCUMULATE_WHEN_OWNED,
DWELLINGS_MERGE_ON_RECRUIT,
HEROES_PER_PLAYER_ON_MAP_CAP, HEROES_PER_PLAYER_ON_MAP_CAP,
HEROES_PER_PLAYER_TOTAL_CAP, HEROES_PER_PLAYER_TOTAL_CAP,
HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,

View File

@ -36,6 +36,7 @@
#include "StartInfo.h" #include "StartInfo.h"
#include "gameState/CGameState.h" #include "gameState/CGameState.h"
#include "gameState/CGameStateCampaign.h" #include "gameState/CGameStateCampaign.h"
#include "gameState/TavernHeroesPool.h"
#include "mapping/CMap.h" #include "mapping/CMap.h"
#include "CPlayerState.h" #include "CPlayerState.h"
#include "GameSettings.h" #include "GameSettings.h"

View File

@ -36,7 +36,7 @@ public:
virtual void visitSetMana(SetMana & pack) {} virtual void visitSetMana(SetMana & pack) {}
virtual void visitSetMovePoints(SetMovePoints & pack) {} virtual void visitSetMovePoints(SetMovePoints & pack) {}
virtual void visitFoWChange(FoWChange & pack) {} virtual void visitFoWChange(FoWChange & pack) {}
virtual void visitSetAvailableHeroes(SetAvailableHeroes & pack) {} virtual void visitSetAvailableHeroes(SetAvailableHero & pack) {}
virtual void visitGiveBonus(GiveBonus & pack) {} virtual void visitGiveBonus(GiveBonus & pack) {}
virtual void visitChangeObjPos(ChangeObjPos & pack) {} virtual void visitChangeObjPos(ChangeObjPos & pack) {}
virtual void visitPlayerEndsGame(PlayerEndsGame & pack) {} virtual void visitPlayerEndsGame(PlayerEndsGame & pack) {}
@ -162,4 +162,4 @@ public:
virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {} virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {}
}; };
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -19,6 +19,7 @@
#include "battle/BattleAction.h" #include "battle/BattleAction.h"
#include "battle/CObstacleInstance.h" #include "battle/CObstacleInstance.h"
#include "gameState/EVictoryLossCheckResult.h" #include "gameState/EVictoryLossCheckResult.h"
#include "gameState/TavernSlot.h"
#include "gameState/QuestInfo.h" #include "gameState/QuestInfo.h"
#include "mapObjects/CGHeroInstance.h" #include "mapObjects/CGHeroInstance.h"
#include "mapping/CMapDefines.h" #include "mapping/CMapDefines.h"
@ -330,23 +331,26 @@ struct DLL_LINKAGE FoWChange : public CPackForClient
} }
}; };
struct DLL_LINKAGE SetAvailableHeroes : public CPackForClient struct DLL_LINKAGE SetAvailableHero : public CPackForClient
{ {
SetAvailableHeroes() SetAvailableHero()
{ {
for(auto & i : army) army.clear();
i.clear();
} }
void applyGs(CGameState * gs); void applyGs(CGameState * gs);
TavernHeroSlot slotID;
TavernSlotRole roleID;
PlayerColor player; PlayerColor player;
si32 hid[GameConstants::AVAILABLE_HEROES_PER_PLAYER]; //-1 if no hero HeroTypeID hid; //HeroTypeID::NONE if no hero
CSimpleArmy army[GameConstants::AVAILABLE_HEROES_PER_PLAYER]; CSimpleArmy army;
virtual void visitTyped(ICPackVisitor & visitor) override; virtual void visitTyped(ICPackVisitor & visitor) override;
template <typename Handler> void serialize(Handler & h, const int version) template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & slotID;
h & roleID;
h & player; h & player;
h & hid; h & hid;
h & army; h & army;
@ -692,7 +696,7 @@ struct DLL_LINKAGE HeroRecruited : public CPackForClient
{ {
void applyGs(CGameState * gs) const; void applyGs(CGameState * gs) const;
si32 hid = -1; //subID of hero HeroTypeID hid; //subID of hero
ObjectInstanceID tid; ObjectInstanceID tid;
ObjectInstanceID boatId; ObjectInstanceID boatId;
int3 tile; int3 tile;
@ -2437,12 +2441,12 @@ struct DLL_LINKAGE SetFormation : public CPackForServer
struct DLL_LINKAGE HireHero : public CPackForServer struct DLL_LINKAGE HireHero : public CPackForServer
{ {
HireHero() = default; HireHero() = default;
HireHero(si32 HID, const ObjectInstanceID & TID) HireHero(HeroTypeID HID, const ObjectInstanceID & TID)
: hid(HID) : hid(HID)
, tid(TID) , tid(TID)
{ {
} }
si32 hid = 0; //available hero serial HeroTypeID hid; //available hero serial
ObjectInstanceID tid; //town (tavern) id ObjectInstanceID tid; //town (tavern) id
PlayerColor player; PlayerColor player;

View File

@ -20,6 +20,7 @@
#include "spells/CSpellHandler.h" #include "spells/CSpellHandler.h"
#include "CCreatureHandler.h" #include "CCreatureHandler.h"
#include "gameState/CGameState.h" #include "gameState/CGameState.h"
#include "gameState/TavernHeroesPool.h"
#include "CStack.h" #include "CStack.h"
#include "battle/BattleInfo.h" #include "battle/BattleInfo.h"
#include "CTownHandler.h" #include "CTownHandler.h"
@ -151,7 +152,7 @@ void FoWChange::visitTyped(ICPackVisitor & visitor)
visitor.visitFoWChange(*this); visitor.visitFoWChange(*this);
} }
void SetAvailableHeroes::visitTyped(ICPackVisitor & visitor) void SetAvailableHero::visitTyped(ICPackVisitor & visitor)
{ {
visitor.visitSetAvailableHeroes(*this); visitor.visitSetAvailableHeroes(*this);
} }
@ -939,18 +940,9 @@ void FoWChange::applyGs(CGameState *gs)
} }
} }
void SetAvailableHeroes::applyGs(CGameState *gs) void SetAvailableHero::applyGs(CGameState *gs)
{ {
PlayerState *p = gs->getPlayerState(player); gs->heroesPool->setHeroForPlayer(player, slotID, hid, army, roleID);
p->availableHeroes.clear();
for (int i = 0; i < GameConstants::AVAILABLE_HEROES_PER_PLAYER; i++)
{
CGHeroInstance *h = (hid[i]>=0 ? gs->hpool.heroesPool[hid[i]].get() : nullptr);
if(h && army[i])
h->setToArmy(army[i]);
p->availableHeroes.emplace_back(h);
}
} }
void GiveBonus::applyGs(CGameState *gs) void GiveBonus::applyGs(CGameState *gs)
@ -1132,7 +1124,16 @@ void RemoveObject::applyGs(CGameState *gs)
PlayerState * p = gs->getPlayerState(beatenHero->tempOwner); PlayerState * p = gs->getPlayerState(beatenHero->tempOwner);
gs->map->heroesOnMap -= beatenHero; gs->map->heroesOnMap -= beatenHero;
p->heroes -= beatenHero; p->heroes -= beatenHero;
beatenHero->detachFrom(*beatenHero->whereShouldBeAttachedOnSiege(gs));
auto * siegeNode = beatenHero->whereShouldBeAttachedOnSiege(gs);
// FIXME: workaround:
// hero should be attached to siegeNode after battle
// however this code might also be called on dismissing hero while in town
if (siegeNode && vstd::contains(beatenHero->getParentNodes(), siegeNode))
beatenHero->detachFrom(*siegeNode);
beatenHero->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero beatenHero->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero
vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi) vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi)
{ {
@ -1150,11 +1151,8 @@ void RemoveObject::applyGs(CGameState *gs)
beatenHero->inTownGarrison = false; beatenHero->inTownGarrison = false;
} }
//return hero to the pool, so he may reappear in tavern //return hero to the pool, so he may reappear in tavern
gs->hpool.heroesPool[beatenHero->subID] = beatenHero;
if(!vstd::contains(gs->hpool.pavailable, beatenHero->subID))
gs->hpool.pavailable[beatenHero->subID] = 0xff;
gs->heroesPool->addHeroToPool(beatenHero);
gs->map->objects[id.getNum()] = nullptr; gs->map->objects[id.getNum()] = nullptr;
//If hero on Boat is removed, the Boat disappears //If hero on Boat is removed, the Boat disappears
@ -1379,8 +1377,7 @@ void SetHeroesInTown::applyGs(CGameState * gs) const
void HeroRecruited::applyGs(CGameState * gs) const void HeroRecruited::applyGs(CGameState * gs) const
{ {
assert(vstd::contains(gs->hpool.heroesPool, hid)); CGHeroInstance *h = gs->heroesPool->takeHeroFromPool(hid);
CGHeroInstance *h = gs->hpool.heroesPool[hid];
CGTownInstance *t = gs->getTown(tid); CGTownInstance *t = gs->getTown(tid);
PlayerState *p = gs->getPlayerState(player); PlayerState *p = gs->getPlayerState(player);
@ -1411,7 +1408,6 @@ void HeroRecruited::applyGs(CGameState * gs) const
} }
} }
gs->hpool.heroesPool.erase(hid);
if(h->id == ObjectInstanceID()) if(h->id == ObjectInstanceID())
{ {
h->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size())); h->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
@ -2021,26 +2017,17 @@ void NewTurn::applyGs(CGameState *gs)
{ {
CGHeroInstance *hero = gs->getHero(h.id); CGHeroInstance *hero = gs->getHero(h.id);
if(!hero) if(!hero)
{
// retreated or surrendered hero who has not been reset yet
for(auto& hp : gs->hpool.heroesPool)
{
if(hp.second->id == h.id)
{
hero = hp.second;
break;
}
}
}
if(!hero)
{ {
logGlobal->error("Hero %d not found in NewTurn::applyGs", h.id.getNum()); logGlobal->error("Hero %d not found in NewTurn::applyGs", h.id.getNum());
continue; continue;
} }
hero->setMovementPoints(h.move); hero->setMovementPoints(h.move);
hero->mana = h.mana; hero->mana = h.mana;
} }
gs->heroesPool->onNewDay();
for(const auto & re : res) for(const auto & re : res)
{ {
assert(re.first < PlayerColor::PLAYER_LIMIT); assert(re.first < PlayerColor::PLAYER_LIMIT);

View File

@ -160,7 +160,7 @@ public:
h & x; h & x;
h & y; h & y;
h & w; h & w;
h & h; h & this->h;
} }
}; };

View File

@ -35,9 +35,9 @@ struct DLL_LINKAGE PlayerSettings
}; };
Ebonus bonus; Ebonus bonus;
si16 castle; FactionID castle;
si32 hero, HeroTypeID hero;
heroPortrait; //-1 if default, else ID HeroTypeID heroPortrait; //-1 if default, else ID
std::string heroName; std::string heroName;
PlayerColor color; //from 0 - PlayerColor color; //from 0 -

View File

@ -12,6 +12,7 @@
#include "EVictoryLossCheckResult.h" #include "EVictoryLossCheckResult.h"
#include "InfoAboutArmy.h" #include "InfoAboutArmy.h"
#include "TavernHeroesPool.h"
#include "CGameStateCampaign.h" #include "CGameStateCampaign.h"
#include "SThievesGuildInfo.h" #include "SThievesGuildInfo.h"
@ -102,81 +103,6 @@ static CGObjectInstance * createObject(const Obj & id, int subid, const int3 & p
return nobj; return nobj;
} }
CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native,
const PlayerColor & player,
const CTown * town,
std::map<ui32, ConstTransitivePtr<CGHeroInstance>> & available,
CRandomGenerator & rand,
const CHeroClass * bannedClass) const
{
CGHeroInstance *ret = nullptr;
if(player>=PlayerColor::PLAYER_LIMIT)
{
logGlobal->error("Cannot pick hero for faction %s. Wrong owner!", town->faction->getJsonKey());
return nullptr;
}
std::vector<CGHeroInstance *> pool;
if(native)
{
for(auto & elem : available)
{
if(pavailable.find(elem.first)->second & 1<<player.getNum()
&& elem.second->type->heroClass->faction == town->faction->getIndex())
{
pool.push_back(elem.second); //get all available heroes
}
}
if(pool.empty())
{
logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr());
return pickHeroFor(false, player, town, available, rand);
}
else
{
ret = *RandomGeneratorUtil::nextItem(pool, rand);
}
}
else
{
int sum = 0;
int r;
for(auto & elem : available)
{
if (pavailable.find(elem.first)->second & (1<<player.getNum()) && // hero is available
( !bannedClass || elem.second->type->heroClass != bannedClass) ) // and his class is not same as other hero
{
pool.push_back(elem.second);
sum += elem.second->type->heroClass->selectionProbability[town->faction->getId()]; //total weight
}
}
if(pool.empty() || sum == 0)
{
logGlobal->error("There are no heroes available for player %s!", player.getStr());
return nullptr;
}
r = rand.nextInt(sum - 1);
for (auto & elem : pool)
{
r -= elem->type->heroClass->selectionProbability[town->faction->getId()];
if(r < 0)
{
ret = elem;
break;
}
}
if(!ret)
ret = pool.back();
}
available.erase(ret->subID);
return ret;
}
HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner) HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
{ {
const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner); const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
@ -459,6 +385,7 @@ int CGameState::getDate(Date::EDateType mode) const
CGameState::CGameState() CGameState::CGameState()
{ {
gs = this; gs = this;
heroesPool = std::make_unique<TavernHeroesPool>();
applier = std::make_shared<CApplier<CBaseForGSApply>>(); applier = std::make_shared<CApplier<CBaseForGSApply>>();
registerTypesClientPacks1(*applier); registerTypesClientPacks1(*applier);
registerTypesClientPacks2(*applier); registerTypesClientPacks2(*applier);
@ -469,9 +396,6 @@ CGameState::~CGameState()
{ {
map.dellNull(); map.dellNull();
curB.dellNull(); curB.dellNull();
for(auto ptr : hpool.heroesPool) // clean hero pool
ptr.second.dellNull();
} }
void CGameState::preInit(Services * services) void CGameState::preInit(Services * services)
@ -951,8 +875,7 @@ void CGameState::initHeroes()
if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID))) if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID)))
continue; continue;
ph->initHero(getRandomGenerator()); ph->initHero(getRandomGenerator());
hpool.heroesPool[ph->subID] = ph; heroesPool->addHeroToPool(ph);
hpool.pavailable[ph->subID] = 0xff;
heroesToCreate.erase(ph->type->getId()); heroesToCreate.erase(ph->type->getId());
map->allHeroes[ph->subID] = ph; map->allHeroes[ph->subID] = ph;
@ -965,14 +888,11 @@ void CGameState::initHeroes()
int typeID = htype.getNum(); int typeID = htype.getNum();
map->allHeroes[typeID] = vhi; map->allHeroes[typeID] = vhi;
hpool.heroesPool[typeID] = vhi; heroesPool->addHeroToPool(vhi);
hpool.pavailable[typeID] = 0xff;
} }
for(auto & elem : map->disposedHeroes) for(auto & elem : map->disposedHeroes)
{ heroesPool->setAvailability(elem.heroId, elem.players);
hpool.pavailable[elem.heroId] = elem.players;
}
if (campaign) if (campaign)
campaign->initHeroes(); campaign->initHeroes();
@ -2067,17 +1987,6 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
#undef FILL_FIELD #undef FILL_FIELD
} }
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > CGameState::unusedHeroesFromPool()
{
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = hpool.heroesPool;
for(const auto & player : players)
for(auto availableHero : player.second.availableHeroes)
if(availableHero)
pool.erase((*availableHero).subID);
return pool;
}
void CGameState::buildBonusSystemTree() void CGameState::buildBonusSystemTree()
{ {
buildGlobalTeamPlayerTree(); buildGlobalTeamPlayerTree();

View File

@ -29,6 +29,7 @@ struct EventCondition;
struct CampaignTravel; struct CampaignTravel;
class CStackInstance; class CStackInstance;
class CGameStateCampaign; class CGameStateCampaign;
class TavernHeroesPool;
struct SThievesGuildInfo; struct SThievesGuildInfo;
template<typename T> class CApplier; template<typename T> class CApplier;
@ -78,25 +79,10 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheck
class DLL_LINKAGE CGameState : public CNonConstInfoCallback class DLL_LINKAGE CGameState : public CNonConstInfoCallback
{ {
friend class CGameStateCampaign; friend class CGameStateCampaign;
public: public:
struct DLL_LINKAGE HeroesPool //we have here all heroes available on this map that are not hired
{ std::unique_ptr<TavernHeroesPool> heroesPool;
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > heroesPool; //[subID] - heroes available to buy; nullptr if not available
std::map<ui32,ui8> pavailable; // [subid] -> which players can recruit hero (binary flags)
CGHeroInstance * pickHeroFor(bool native,
const PlayerColor & player,
const CTown * town,
std::map<ui32, ConstTransitivePtr<CGHeroInstance>> & available,
CRandomGenerator & rand,
const CHeroClass * bannedClass = nullptr) const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & heroesPool;
h & pavailable;
}
} hpool; //we have here all heroes available on this map that are not hired
CGameState(); CGameState();
virtual ~CGameState(); virtual ~CGameState();
@ -142,7 +128,6 @@ public:
bool checkForStandardLoss(const PlayerColor & player) const; //checks if given player lost the game bool checkForStandardLoss(const PlayerColor & player) const; //checks if given player lost the game
void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
bool isVisible(int3 pos, const std::optional<PlayerColor> & player) const override; bool isVisible(int3 pos, const std::optional<PlayerColor> & player) const override;
bool isVisible(const CGObjectInstance * obj, const std::optional<PlayerColor> & player) const override; bool isVisible(const CGObjectInstance * obj, const std::optional<PlayerColor> & player) const override;
@ -169,7 +154,7 @@ public:
h & map; h & map;
h & players; h & players;
h & teams; h & teams;
h & hpool; h & heroesPool;
h & globalEffects; h & globalEffects;
h & rand; h & rand;
h & rumor; h & rumor;

View File

@ -0,0 +1,142 @@
/*
* TavernHeroesPool.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 "TavernHeroesPool.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../CHeroHandler.h"
VCMI_LIB_NAMESPACE_BEGIN
TavernHeroesPool::~TavernHeroesPool()
{
for(const auto & ptr : heroesPool) // clean hero pool
delete ptr.second;
}
std::map<HeroTypeID, CGHeroInstance*> TavernHeroesPool::unusedHeroesFromPool() const
{
std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool;
for(const auto & slot : currentTavern)
pool.erase(HeroTypeID(slot.hero->subID));
return pool;
}
TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const
{
for (auto const & slot : currentTavern)
{
if (HeroTypeID(slot.hero->subID) == hero)
return slot.role;
}
return TavernSlotRole::NONE;
}
void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role)
{
vstd::erase_if(currentTavern, [&](const TavernSlot & entry){
return entry.player == player && entry.slot == slot;
});
if (hero == HeroTypeID::NONE)
return;
CGHeroInstance * h = heroesPool[hero];
if (h && army)
h->setToArmy(army);
TavernSlot newSlot;
newSlot.hero = h;
newSlot.player = player;
newSlot.role = role;
newSlot.slot = slot;
currentTavern.push_back(newSlot);
boost::range::sort(currentTavern, [](const TavernSlot & left, const TavernSlot & right)
{
if (left.slot == right.slot)
return left.player < right.player;
else
return left.slot < right.slot;
});
}
bool TavernHeroesPool::isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const
{
if (perPlayerAvailability.count(hero))
return perPlayerAvailability.at(hero) & (1 << color.getNum());
return true;
}
std::vector<const CGHeroInstance *> TavernHeroesPool::getHeroesFor(PlayerColor color) const
{
std::vector<const CGHeroInstance *> result;
for(const auto & slot : currentTavern)
{
if (slot.player == color)
result.push_back(slot.hero);
}
return result;
}
CGHeroInstance * TavernHeroesPool::takeHeroFromPool(HeroTypeID hero)
{
assert(heroesPool.count(hero));
CGHeroInstance * result = heroesPool[hero];
heroesPool.erase(hero);
vstd::erase_if(currentTavern, [&](const TavernSlot & entry){
return entry.hero->type->getId() == hero;
});
assert(result);
return result;
}
void TavernHeroesPool::onNewDay()
{
for(auto & hero : heroesPool)
{
assert(hero.second);
if(!hero.second)
continue;
hero.second->setMovementPoints(hero.second->movementPointsLimit(true));
hero.second->mana = hero.second->manaLimit();
}
for (auto & slot : currentTavern)
{
if (slot.role == TavernSlotRole::RETREATED_TODAY)
slot.role = TavernSlotRole::RETREATED;
if (slot.role == TavernSlotRole::SURRENDERED_TODAY)
slot.role = TavernSlotRole::SURRENDERED;
}
}
void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero)
{
heroesPool[HeroTypeID(hero->subID)] = hero;
}
void TavernHeroesPool::setAvailability(HeroTypeID hero, PlayerColor::Mask mask)
{
perPlayerAvailability[hero] = mask;
}
VCMI_LIB_NAMESPACE_END

View File

@ -0,0 +1,87 @@
/*
* TavernHeroesPool.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 "../GameConstants.h"
#include "TavernSlot.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
class CTown;
class CRandomGenerator;
class CHeroClass;
class CGameState;
class CSimpleArmy;
class DLL_LINKAGE TavernHeroesPool
{
struct TavernSlot
{
CGHeroInstance * hero;
TavernHeroSlot slot;
TavernSlotRole role;
PlayerColor player;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & hero;
h & slot;
h & role;
h & player;
}
};
/// list of all heroes in pool, including those currently present in taverns
std::map<HeroTypeID, CGHeroInstance* > heroesPool;
/// list of which players are able to purchase specific hero
/// if hero is not present in list, he is available for everyone
std::map<HeroTypeID, PlayerColor::Mask> perPlayerAvailability;
/// list of heroes currently available in taverns
std::vector<TavernSlot> currentTavern;
public:
~TavernHeroesPool();
/// Returns heroes currently availabe in tavern of a specific player
std::vector<const CGHeroInstance *> getHeroesFor(PlayerColor color) const;
/// returns heroes in pool without heroes that are available in taverns
std::map<HeroTypeID, CGHeroInstance* > unusedHeroesFromPool() const;
/// Returns true if hero is available to a specific player
bool isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const;
TavernSlotRole getSlotRole(HeroTypeID hero) const;
CGHeroInstance * takeHeroFromPool(HeroTypeID hero);
/// reset mana and movement points for all heroes in pool
void onNewDay();
void addHeroToPool(CGHeroInstance * hero);
/// Marks hero as available to only specific set of players
void setAvailability(HeroTypeID hero, PlayerColor::Mask mask);
/// Makes hero available in tavern of specified player
void setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & heroesPool;
h & perPlayerAvailability;
h & currentTavern;
}
};
VCMI_LIB_NAMESPACE_END

View File

@ -0,0 +1,35 @@
/*
* TavernSlot.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
VCMI_LIB_NAMESPACE_BEGIN
enum class TavernHeroSlot : int8_t
{
NONE = -1,
NATIVE, // 1st / left slot in tavern, contains hero native to player's faction on new week
RANDOM // 2nd / right slot in tavern, contains hero of random class
};
enum class TavernSlotRole : int8_t
{
NONE = -1,
SINGLE_UNIT, // hero was added after buying hero from this slot, and only has 1 creature in army
FULL_ARMY, // hero was added to tavern on new week and still has full army
RETREATED, // hero was owned by player before, but have retreated from battle and only has 1 creature in army
RETREATED_TODAY,
SURRENDERED, // hero was owned by player before, but have surrendered in battle and kept some troops
SURRENDERED_TODAY,
};
VCMI_LIB_NAMESPACE_END

View File

@ -57,6 +57,8 @@ CObjectClassesHandler::CObjectClassesHandler()
SET_HANDLER_CLASS("shrine", ShrineInstanceConstructor); SET_HANDLER_CLASS("shrine", ShrineInstanceConstructor);
SET_HANDLER_CLASS("hillFort", HillFortInstanceConstructor); SET_HANDLER_CLASS("hillFort", HillFortInstanceConstructor);
SET_HANDLER_CLASS("shipyard", ShipyardInstanceConstructor); SET_HANDLER_CLASS("shipyard", ShipyardInstanceConstructor);
SET_HANDLER_CLASS("monster", CreatureInstanceConstructor);
SET_HANDLER_CLASS("resource", ResourceInstanceConstructor);
SET_HANDLER_CLASS("static", CObstacleConstructor); SET_HANDLER_CLASS("static", CObstacleConstructor);
SET_HANDLER_CLASS("", CObstacleConstructor); SET_HANDLER_CLASS("", CObstacleConstructor);
@ -73,7 +75,6 @@ CObjectClassesHandler::CObjectClassesHandler()
SET_HANDLER("artifact", CGArtifact); SET_HANDLER("artifact", CGArtifact);
SET_HANDLER("borderGate", CGBorderGate); SET_HANDLER("borderGate", CGBorderGate);
SET_HANDLER("borderGuard", CGBorderGuard); SET_HANDLER("borderGuard", CGBorderGuard);
SET_HANDLER("monster", CGCreature);
SET_HANDLER("denOfThieves", CGDenOfthieves); SET_HANDLER("denOfThieves", CGDenOfthieves);
SET_HANDLER("event", CGEvent); SET_HANDLER("event", CGEvent);
SET_HANDLER("garrison", CGGarrison); SET_HANDLER("garrison", CGGarrison);
@ -87,7 +88,6 @@ CObjectClassesHandler::CObjectClassesHandler()
SET_HANDLER("pandora", CGPandoraBox); SET_HANDLER("pandora", CGPandoraBox);
SET_HANDLER("prison", CGHeroInstance); SET_HANDLER("prison", CGHeroInstance);
SET_HANDLER("questGuard", CGQuestGuard); SET_HANDLER("questGuard", CGQuestGuard);
SET_HANDLER("resource", CGResource);
SET_HANDLER("scholar", CGScholar); SET_HANDLER("scholar", CGScholar);
SET_HANDLER("seerHut", CGSeerHut); SET_HANDLER("seerHut", CGSeerHut);
SET_HANDLER("sign", CGSignBottle); SET_HANDLER("sign", CGSignBottle);

View File

@ -35,6 +35,26 @@ bool CObstacleConstructor::isStaticObject()
return true; return true;
} }
bool CreatureInstanceConstructor::hasNameTextID() const
{
return true;
}
std::string CreatureInstanceConstructor::getNameTextID() const
{
return VLC->creatures()->getByIndex(getSubIndex())->getNamePluralTextID();
}
bool ResourceInstanceConstructor::hasNameTextID() const
{
return true;
}
std::string ResourceInstanceConstructor::getNameTextID() const
{
return TextIdentifier("core", "restypes", getSubIndex()).get();
}
void CTownInstanceConstructor::initTypeData(const JsonNode & input) void CTownInstanceConstructor::initTypeData(const JsonNode & input)
{ {
VLC->modh->identifiers.requestIdentifier("faction", input["faction"], [&](si32 index) VLC->modh->identifiers.requestIdentifier("faction", input["faction"], [&](si32 index)
@ -86,6 +106,16 @@ void CTownInstanceConstructor::randomizeObject(CGTownInstance * object, CRandomG
object->appearance = templ; object->appearance = templ;
} }
bool CTownInstanceConstructor::hasNameTextID() const
{
return true;
}
std::string CTownInstanceConstructor::getNameTextID() const
{
return faction->getNameTextID();
}
void CHeroInstanceConstructor::initTypeData(const JsonNode & input) void CHeroInstanceConstructor::initTypeData(const JsonNode & input)
{ {
VLC->modh->identifiers.requestIdentifier( VLC->modh->identifiers.requestIdentifier(
@ -133,6 +163,16 @@ void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, CRandomG
} }
bool CHeroInstanceConstructor::hasNameTextID() const
{
return true;
}
std::string CHeroInstanceConstructor::getNameTextID() const
{
return heroClass->getNameTextID();
}
void BoatInstanceConstructor::initTypeData(const JsonNode & input) void BoatInstanceConstructor::initTypeData(const JsonNode & input)
{ {
layer = EPathfindingLayer::SAIL; layer = EPathfindingLayer::SAIL;

View File

@ -13,6 +13,7 @@
#include "../LogicalExpression.h" #include "../LogicalExpression.h"
#include "../mapObjects/MiscObjects.h" #include "../mapObjects/MiscObjects.h"
#include "../mapObjects/CGCreature.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -22,6 +23,7 @@ class CGTownInstance;
class CGHeroInstance; class CGHeroInstance;
class CGMarket; class CGMarket;
class CHeroClass; class CHeroClass;
class CGCreature;
class CBank; class CBank;
class CGBoat; class CGBoat;
class CFaction; class CFaction;
@ -33,6 +35,20 @@ public:
bool isStaticObject() override; bool isStaticObject() override;
}; };
class CreatureInstanceConstructor : public CDefaultObjectTypeHandler<CGCreature>
{
public:
bool hasNameTextID() const override;
std::string getNameTextID() const override;
};
class ResourceInstanceConstructor : public CDefaultObjectTypeHandler<CGResource>
{
public:
bool hasNameTextID() const override;
std::string getNameTextID() const override;
};
class CTownInstanceConstructor : public CDefaultObjectTypeHandler<CGTownInstance> class CTownInstanceConstructor : public CDefaultObjectTypeHandler<CGTownInstance>
{ {
JsonNode filtersJson; JsonNode filtersJson;
@ -48,6 +64,9 @@ public:
void randomizeObject(CGTownInstance * object, CRandomGenerator & rng) const override; void randomizeObject(CGTownInstance * object, CRandomGenerator & rng) const override;
void afterLoadFinalization() override; void afterLoadFinalization() override;
bool hasNameTextID() const override;
std::string getNameTextID() const override;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & filtersJson; h & filtersJson;
@ -72,6 +91,9 @@ public:
void randomizeObject(CGHeroInstance * object, CRandomGenerator & rng) const override; void randomizeObject(CGHeroInstance * object, CRandomGenerator & rng) const override;
void afterLoadFinalization() override; void afterLoadFinalization() override;
bool hasNameTextID() const override;
std::string getNameTextID() const override;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & filtersJson; h & filtersJson;

View File

@ -324,6 +324,21 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
{ {
if(count) //there are available creatures if(count) //there are available creatures
{ {
if (VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED))
{
SlotID testSlot = h->getSlotFor(crid);
if(!testSlot.validSlot()) //no available slot - try merging army of visiting hero
{
std::pair<SlotID, SlotID> toMerge;
if (h->mergableStacks(toMerge))
{
cb->moveStack(StackLocation(h, toMerge.first), StackLocation(h, toMerge.second), -1); //merge toMerge.first into toMerge.second
assert(!h->hasStackAtSlot(toMerge.first)); //we have now a new free slot
}
}
}
SlotID slot = h->getSlotFor(crid); SlotID slot = h->getSlotFor(crid);
if(!slot.validSlot()) //no available slot if(!slot.validSlot()) //no available slot
{ {

View File

@ -56,10 +56,10 @@ struct DLL_LINKAGE DisposedHero
{ {
DisposedHero(); DisposedHero();
ui32 heroId; HeroTypeID heroId;
ui32 portrait; /// The portrait id of the hero, -1 is default. HeroTypeID portrait; /// The portrait id of the hero, -1 is default.
std::string name; std::string name;
ui8 players; /// Who can hire this hero (bitfield). PlayerColor::Mask players; /// Who can hire this hero (bitfield).
template <typename Handler> template <typename Handler>
void serialize(Handler & h, const int version) void serialize(Handler & h, const int version)

View File

@ -107,6 +107,8 @@ void registerTypesMapObjectTypes(Serializer &s)
s.template registerType<AObjectTypeHandler, ShrineInstanceConstructor>(); s.template registerType<AObjectTypeHandler, ShrineInstanceConstructor>();
s.template registerType<AObjectTypeHandler, ShipyardInstanceConstructor>(); s.template registerType<AObjectTypeHandler, ShipyardInstanceConstructor>();
s.template registerType<AObjectTypeHandler, HillFortInstanceConstructor>(); s.template registerType<AObjectTypeHandler, HillFortInstanceConstructor>();
s.template registerType<AObjectTypeHandler, CreatureInstanceConstructor>();
s.template registerType<AObjectTypeHandler, ResourceInstanceConstructor>();
#define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType<AObjectTypeHandler, CDefaultObjectTypeHandler<TYPENAME> >() #define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType<AObjectTypeHandler, CDefaultObjectTypeHandler<TYPENAME> >()
@ -239,7 +241,7 @@ void registerTypesClientPacks1(Serializer &s)
s.template registerType<CPackForClient, SetMana>(); s.template registerType<CPackForClient, SetMana>();
s.template registerType<CPackForClient, SetMovePoints>(); s.template registerType<CPackForClient, SetMovePoints>();
s.template registerType<CPackForClient, FoWChange>(); s.template registerType<CPackForClient, FoWChange>();
s.template registerType<CPackForClient, SetAvailableHeroes>(); s.template registerType<CPackForClient, SetAvailableHero>();
s.template registerType<CPackForClient, GiveBonus>(); s.template registerType<CPackForClient, GiveBonus>();
s.template registerType<CPackForClient, ChangeObjPos>(); s.template registerType<CPackForClient, ChangeObjPos>();
s.template registerType<CPackForClient, PlayerEndsGame>(); s.template registerType<CPackForClient, PlayerEndsGame>();

View File

@ -14,6 +14,7 @@
#include "../StartInfo.h" #include "../StartInfo.h"
#include "../gameState/CGameState.h" #include "../gameState/CGameState.h"
#include "../gameState/CGameStateCampaign.h" #include "../gameState/CGameStateCampaign.h"
#include "../gameState/TavernHeroesPool.h"
#include "../mapping/CMap.h" #include "../mapping/CMap.h"
#include "../CModHandler.h" #include "../CModHandler.h"
#include "../mapObjects/CObjectHandler.h" #include "../mapObjects/CObjectHandler.h"

View File

@ -606,6 +606,8 @@ bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard
auto & instance = object.addInstance(*guard); auto & instance = object.addInstance(*guard);
instance.setPosition(guardPos - object.getPosition()); instance.setPosition(guardPos - object.getPosition());
instance.setAnyTemplate(); //terrain is irrelevant for monsters, but monsters need some template now instance.setAnyTemplate(); //terrain is irrelevant for monsters, but monsters need some template now
//Make up for extra offset in HotA creature templates
instance.setPosition(instance.getPosition() + instance.object().getVisitableOffset());
return true; return true;
} }

View File

@ -541,7 +541,7 @@ void TargetCondition::loadConditions(const JsonNode & source, bool exclusive, bo
CModHandler::parseIdentifier(keyValue.first, scope, type, identifier); CModHandler::parseIdentifier(keyValue.first, scope, type, identifier);
item = itemFactory->createConfigurable(scope, type, identifier); item = itemFactory->createConfigurable(keyValue.second.meta, type, identifier);
} }
if(item) if(item)

View File

@ -250,7 +250,10 @@ void Timed::serializeJsonUnitEffect(JsonSerializeFormat & handler)
auto guard = handler.enterStruct(p.first); auto guard = handler.enterStruct(p.first);
const JsonNode & bonusNode = handler.getCurrent(); const JsonNode & bonusNode = handler.getCurrent();
auto b = JsonUtils::parseBonus(bonusNode); auto b = JsonUtils::parseBonus(bonusNode);
bonus.push_back(b); if (b)
bonus.push_back(b);
else
logMod->error("Failed to parse bonus '%s'!", p.first);
} }
} }
} }

View File

@ -8,6 +8,14 @@
* *
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "CGameHandler.h"
#include "HeroPoolProcessor.h"
#include "ServerNetPackVisitors.h"
#include "ServerSpellCastEnvironment.h"
#include "CVCMIServer.h"
#include "PlayerMessageProcessor.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileInfo.h" #include "../lib/filesystem/FileInfo.h"
@ -35,7 +43,6 @@
#include "../lib/GameSettings.h" #include "../lib/GameSettings.h"
#include "../lib/battle/BattleInfo.h" #include "../lib/battle/BattleInfo.h"
#include "../lib/CondSh.h" #include "../lib/CondSh.h"
#include "ServerNetPackVisitors.h"
#include "../lib/VCMI_Lib.h" #include "../lib/VCMI_Lib.h"
#include "../lib/mapping/CMap.h" #include "../lib/mapping/CMap.h"
#include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMapService.h"
@ -44,9 +51,6 @@
#include "../lib/ScopeGuard.h" #include "../lib/ScopeGuard.h"
#include "../lib/CSoundBase.h" #include "../lib/CSoundBase.h"
#include "../lib/TerrainHandler.h" #include "../lib/TerrainHandler.h"
#include "CGameHandler.h"
#include "ServerSpellCastEnvironment.h"
#include "CVCMIServer.h"
#include "../lib/CCreatureSet.h" #include "../lib/CCreatureSet.h"
#include "../lib/CThreadHelper.h" #include "../lib/CThreadHelper.h"
#include "../lib/GameConstants.h" #include "../lib/GameConstants.h"
@ -294,6 +298,11 @@ events::EventBus * CGameHandler::eventBus() const
return serverEventBus.get(); return serverEventBus.get();
} }
CVCMIServer * CGameHandler::gameLobby() const
{
return lobby;
}
void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill) void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill)
{ {
changeSecSkill(hero, skill, 1, 0); changeSecSkill(hero, skill, 1, 0);
@ -868,24 +877,12 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
std::set<PlayerColor> playerColors = {finishingBattle->loser, finishingBattle->victor}; std::set<PlayerColor> playerColors = {finishingBattle->loser, finishingBattle->victor};
checkVictoryLossConditions(playerColors); checkVictoryLossConditions(playerColors);
if (result.result == BattleResult::SURRENDER || result.result == BattleResult::ESCAPE) //loser has escaped or surrendered if (result.result == BattleResult::SURRENDER)
{ heroPool->onHeroSurrendered(finishingBattle->loser, finishingBattle->loserHero);
SetAvailableHeroes sah;
sah.player = finishingBattle->loser;
sah.hid[0] = finishingBattle->loserHero->subID;
if (result.result == BattleResult::ESCAPE) //retreat
{
sah.army[0].clear();
sah.army[0].setCreature(SlotID(0), finishingBattle->loserHero->type->initialArmy.at(0).creature, 1);
}
if (const CGHeroInstance *another = getPlayerState(finishingBattle->loser)->availableHeroes.at(0)) if (result.result == BattleResult::ESCAPE)
sah.hid[1] = another->subID; heroPool->onHeroEscaped(finishingBattle->loser, finishingBattle->loserHero);
else
sah.hid[1] = -1;
sendAndApply(&sah);
}
if (result.winner != 2 && finishingBattle->winnerHero && finishingBattle->winnerHero->stacks.empty() if (result.winner != 2 && finishingBattle->winnerHero && finishingBattle->winnerHero->stacks.empty()
&& (!finishingBattle->winnerHero->commander || !finishingBattle->winnerHero->commander->alive)) && (!finishingBattle->winnerHero->commander || !finishingBattle->winnerHero->commander->alive))
{ {
@ -893,20 +890,7 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
sendAndApply(&ro); sendAndApply(&ro);
if (VLC->settings()->getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS)) if (VLC->settings()->getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS))
{ heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero);
SetAvailableHeroes sah;
sah.player = finishingBattle->victor;
sah.hid[0] = finishingBattle->winnerHero->subID;
sah.army[0].clear();
sah.army[0].setCreature(SlotID(0), finishingBattle->winnerHero->type->initialArmy.at(0).creature, 1);
if (const CGHeroInstance *another = getPlayerState(finishingBattle->victor)->availableHeroes.at(0))
sah.hid[1] = another->subID;
else
sah.hid[1] = -1;
sendAndApply(&sah);
}
} }
finishingBattle.reset(); finishingBattle.reset();
@ -1240,7 +1224,7 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
if(playerConnection != playerConnections.second.end()) if(playerConnection != playerConnections.second.end())
{ {
std::string messageText = boost::str(boost::format("%s (cid %d) was disconnected") % playerSettings->name % c->connectionID); std::string messageText = boost::str(boost::format("%s (cid %d) was disconnected") % playerSettings->name % c->connectionID);
playerMessage(playerId, messageText, ObjectInstanceID{}); playerMessages->broadcastMessage(playerId, messageText);
} }
} }
} }
@ -1576,6 +1560,8 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
CGameHandler::CGameHandler(CVCMIServer * lobby) CGameHandler::CGameHandler(CVCMIServer * lobby)
: lobby(lobby) : lobby(lobby)
, heroPool(std::make_unique<HeroPoolProcessor>(this))
, playerMessages(std::make_unique<PlayerMessageProcessor>(this))
, complainNoCreatures("No creatures to split") , complainNoCreatures("No creatures to split")
, complainNotEnoughCreatures("Cannot split that stack, not enough creatures!") , complainNotEnoughCreatures("Cannot split that stack, not enough creatures!")
, complainInvalidSlot("Invalid slot accessed!") , complainInvalidSlot("Invalid slot accessed!")
@ -1765,27 +1751,6 @@ void CGameHandler::newTurn()
} }
} }
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->hpool.heroesPool;
for (auto& hp : pool)
{
auto hero = hp.second;
if (hero->isInitialized() && hero->stacks.size())
{
// reset retreated or surrendered heroes
auto maxmove = hero->movementPointsLimit(true);
// if movement is greater than maxmove, we should decrease it
if (hero->movementPointsRemaining() != maxmove || hero->mana < hero->manaLimit())
{
NewTurn::Hero hth;
hth.id = hero->id;
hth.move = maxmove;
hth.mana = hero->getManaNewTurn();
n.heroes.insert(hth);
}
}
}
for (auto & elem : gs->players) for (auto & elem : gs->players)
{ {
if (elem.first == PlayerColor::NEUTRAL) if (elem.first == PlayerColor::NEUTRAL)
@ -1797,29 +1762,7 @@ void CGameHandler::newTurn()
hadGold.insert(playerGold); hadGold.insert(playerGold);
if (newWeek) //new heroes in tavern if (newWeek) //new heroes in tavern
{ heroPool->onNewWeek(elem.first);
SetAvailableHeroes sah;
sah.player = elem.first;
//pick heroes and their armies
CHeroClass *banned = nullptr;
for (int j = 0; j < GameConstants::AVAILABLE_HEROES_PER_PLAYER; j++)
{
//first hero - native if possible, second hero -> any other class
if (CGHeroInstance *h = gs->hpool.pickHeroFor(j == 0, elem.first, getNativeTown(elem.first), pool, getRandomGenerator(), banned))
{
sah.hid[j] = h->subID;
h->initArmy(getRandomGenerator(), &sah.army[j]);
banned = h->type->heroClass;
}
else
{
sah.hid[j] = -1;
}
}
sendAndApply(&sah);
}
n.res[elem.first] = elem.second.resources; n.res[elem.first] = elem.second.resources;
@ -2709,14 +2652,6 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st
sendAndApply(&cs); sendAndApply(&cs);
} }
void CGameHandler::sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message)
{
SystemMessage sm;
sm.text = message;
boost::unique_lock<boost::mutex> lock(*c->mutexWrite);
*(c.get()) << &sm;
}
void CGameHandler::giveHeroBonus(GiveBonus * bonus) void CGameHandler::giveHeroBonus(GiveBonus * bonus)
{ {
sendAndApply(bonus); sendAndApply(bonus);
@ -2927,10 +2862,8 @@ bool CGameHandler::isPlayerOwns(CPackForServer * pack, ObjectInstanceID id)
void CGameHandler::throwNotAllowedAction(CPackForServer * pack) void CGameHandler::throwNotAllowedAction(CPackForServer * pack)
{ {
if(pack->c) if(pack->c)
{ playerMessages->sendSystemMessage(pack->c, "You are not allowed to perform this action!");
SystemMessage temp_message("You are not allowed to perform this action!");
pack->c->sendPack(&temp_message);
}
logNetwork->error("Player is not allowed to perform this action!"); logNetwork->error("Player is not allowed to perform this action!");
throw ExceptionNotAllowedAction(); throw ExceptionNotAllowedAction();
} }
@ -2940,11 +2873,9 @@ void CGameHandler::wrongPlayerMessage(CPackForServer * pack, PlayerColor expecte
std::ostringstream oss; std::ostringstream oss;
oss << "You were identified as player " << getPlayerAt(pack->c) << " while expecting " << expectedplayer; oss << "You were identified as player " << getPlayerAt(pack->c) << " while expecting " << expectedplayer;
logNetwork->error(oss.str()); logNetwork->error(oss.str());
if(pack->c) if(pack->c)
{ playerMessages->sendSystemMessage(pack->c, oss.str());
SystemMessage temp_message(oss.str());
pack->c->sendPack(&temp_message);
}
} }
void CGameHandler::throwOnWrongOwner(CPackForServer * pack, ObjectInstanceID id) void CGameHandler::throwOnWrongOwner(CPackForServer * pack, ObjectInstanceID id)
@ -3628,7 +3559,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
sendAndApply(&fw); sendAndApply(&fw);
if(t->visitingHero) if(t->visitingHero)
objectVisited(t, t->visitingHero); visitCastleObjects(t, t->visitingHero);
if(t->garrisonHero) if(t->garrisonHero)
visitCastleObjects(t, t->garrisonHero); visitCastleObjects(t, t->garrisonHero);
@ -3659,13 +3590,6 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
return true; return true;
} }
void CGameHandler::sendMessageToAll(const std::string &message)
{
SystemMessage sm;
sm.text = message;
sendToAllClients(&sm);
}
bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl) bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl)
{ {
const CGDwelling * dw = static_cast<const CGDwelling *>(getObj(objid)); const CGDwelling * dw = static_cast<const CGDwelling *>(getObj(objid));
@ -4383,93 +4307,6 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, ui8 formation)
return true; return true;
} }
bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor player)
{
const PlayerState * p = getPlayerState(player);
const CGTownInstance * t = getTown(obj->id);
//common preconditions
// if ((p->resources.at(EGameResID::GOLD)<GOLD_NEEDED && complain("Not enough gold for buying hero!"))
// || (getHeroCount(player, false) >= GameConstants::MAX_HEROES_PER_PLAYER && complain("Cannot hire hero, only 8 wandering heroes are allowed!")))
if ((p->resources[EGameResID::GOLD] < GameConstants::HERO_GOLD_COST && complain("Not enough gold for buying hero!"))
|| ((getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && complain("Cannot hire hero, too many wandering heroes already!")))
|| ((getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))))
{
return false;
}
if (t) //tavern in town
{
if ((!t->hasBuilt(BuildingID::TAVERN) && complain("No tavern!"))
|| (t->visitingHero && complain("There is visiting hero - no place!")))
{
return false;
}
}
else if (obj->ID == Obj::TAVERN)
{
if (getTile(obj->visitablePos())->visitableObjects.back() != obj && complain("Tavern entry must be unoccupied!"))
{
return false;
}
}
const CGHeroInstance *nh = p->availableHeroes.at(hid);
if (!nh)
{
complain ("Hero is not available for hiring!");
return false;
}
HeroRecruited hr;
hr.tid = obj->id;
hr.hid = nh->subID;
hr.player = player;
hr.tile = nh->convertFromVisitablePos(obj->visitablePos());
if (getTile(hr.tile)->isWater())
{
//Create a new boat for hero
createObject(obj->visitablePos(), Obj::BOAT, nh->getBoatType().getNum());
hr.boatId = getTopObj(hr.tile)->id;
}
sendAndApply(&hr);
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->unusedHeroesFromPool();
const CGHeroInstance *theOtherHero = p->availableHeroes.at(!hid);
const CGHeroInstance *newHero = nullptr;
if (theOtherHero) //on XXL maps all heroes can be imprisoned :(
{
newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, getRandomGenerator(), theOtherHero->type->heroClass);
}
SetAvailableHeroes sah;
sah.player = player;
if (newHero)
{
sah.hid[hid] = newHero->subID;
sah.army[hid].clear();
sah.army[hid].setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1);
}
else
{
sah.hid[hid] = -1;
}
sah.hid[!hid] = theOtherHero ? theOtherHero->subID : -1;
sendAndApply(&sah);
giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST);
if(t)
{
objectVisited(t, nh);
}
return true;
}
bool CGameHandler::queryReply(QueryID qid, const JsonNode & answer, PlayerColor player) bool CGameHandler::queryReply(QueryID qid, const JsonNode & answer, PlayerColor player)
{ {
boost::unique_lock<boost::recursive_mutex> lock(gsm); boost::unique_lock<boost::recursive_mutex> lock(gsm);
@ -4977,140 +4814,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
return ok; return ok;
} }
void CGameHandler::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
{
bool cheated = false;
std::vector<std::string> words;
boost::split(words, message, boost::is_any_of(" "));
bool isHost = false;
for(auto & c : connections[player])
if(lobby->isClientHost(c->connectionID))
isHost = true;
if(isHost && words.size() >= 2 && words[0] == "game")
{
if(words[1] == "exit" || words[1] == "quit" || words[1] == "end")
{
SystemMessage temp_message("game was terminated");
sendAndApply(&temp_message);
lobby->state = EServerState::SHUTDOWN;
return;
}
if(words.size() == 3 && words[1] == "save")
{
save("Saves/" + words[2]);
SystemMessage temp_message("game saved as " + words[2]);
sendAndApply(&temp_message);
return;
}
if(words.size() == 3 && words[1] == "kick")
{
auto playername = words[2];
PlayerColor playerToKick(PlayerColor::CANNOT_DETERMINE);
if(std::all_of(playername.begin(), playername.end(), ::isdigit))
playerToKick = PlayerColor(std::stoi(playername));
else
{
for(auto & c : connections)
{
if(c.first.getStr(false) == playername)
playerToKick = c.first;
}
}
if(playerToKick != PlayerColor::CANNOT_DETERMINE)
{
PlayerCheated pc;
pc.player = playerToKick;
pc.losingCheatCode = true;
sendAndApply(&pc);
checkVictoryLossConditionsForPlayer(playerToKick);
}
return;
}
}
int obj = 0;
if (words.size() == 2 && words[0] != "vcmiexp" && words[0] != "vcmiolorin")
{
obj = std::atoi(words[1].c_str());
if (obj)
currObj = ObjectInstanceID(obj);
}
const CGHeroInstance * hero = getHero(currObj);
const CGTownInstance * town = getTown(currObj);
if (!town && hero)
town = hero->visitedTown;
if(words.size() > 1 && (words[0] == "vcmiarmy" || words[0] == "vcminissi" || words[0] == "vcmiexp" || words[0] == "vcmiolorin"))
{
std::string cheatCodeWithOneParameter = std::string(words[0]) + " " + words[1];
handleCheatCode(cheatCodeWithOneParameter, player, hero, town, cheated);
}
else if (words.size() == 1 || obj)
{
handleCheatCode(words[0], player, hero, town, cheated);
}
else
{
for (const auto & i : gs->players)
{
if (i.first == PlayerColor::NEUTRAL)
continue;
if (words[1] == "ai")
{
if (i.second.human)
continue;
}
else if (words[1] != "all" && words[1] != i.first.getStr())
continue;
if (words[0] == "vcmiformenos" || words[0] == "vcmieagles" || words[0] == "vcmiungoliant"
|| words[0] == "vcmiresources" || words[0] == "vcmimap" || words[0] == "vcmihidemap")
{
handleCheatCode(words[0], i.first, nullptr, nullptr, cheated);
}
else if (words[0] == "vcmiarmenelos" || words[0] == "vcmibuild")
{
for (const auto & t : i.second.towns)
{
handleCheatCode(words[0], i.first, nullptr, t, cheated);
}
}
else
{
for (const auto & h : i.second.heroes)
{
handleCheatCode(words[0], i.first, h, nullptr, cheated);
}
}
}
}
if (cheated)
{
if(!getPlayerSettings(player)->isControlledByAI())
{
SystemMessage temp_message(VLC->generaltexth->allTexts[260]);
sendAndApply(&temp_message);
}
if(!player.isSpectator())
checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
}
else
{
if(!getPlayerSettings(player)->isControlledByAI())
{
PlayerMessageClient temp_message(player, message);
sendAndApply(&temp_message);
}
}
}
bool CGameHandler::makeCustomAction(BattleAction & ba) bool CGameHandler::makeCustomAction(BattleAction & ba)
{ {
switch(ba.actionType) switch(ba.actionType)
@ -5473,7 +5176,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n)
bool CGameHandler::complain(const std::string &problem) bool CGameHandler::complain(const std::string &problem)
{ {
sendMessageToAll("Server encountered a problem: " + problem); playerMessages->broadcastSystemMessage("Server encountered a problem: " + problem);
logGlobal->error(problem); logGlobal->error(problem);
return true; return true;
} }
@ -6865,225 +6568,6 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID)
} }
} }
void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, const CGHeroInstance * hero, const CGTownInstance * town, bool & cheated)
{
//Make cheat case-insensitive
std::transform(cheat.begin(), cheat.end(), cheat.begin(), [](unsigned char c){ return std::tolower(c); });
if (cheat == "vcmiistari" || cheat == "vcmispells")
{
cheated = true;
if (!hero) return;
///Give hero spellbook
if (!hero->hasSpellbook())
giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
///Give all spells with bonus (to allow banned spells)
GiveBonus giveBonus(GiveBonus::ETarget::HERO);
giveBonus.id = hero->id.getNum();
giveBonus.bonus = Bonus(BonusDuration::PERMANENT, BonusType::SPELLS_OF_LEVEL, BonusSource::OTHER, 0, 0);
//start with level 0 to skip abilities
for (int level = 1; level <= GameConstants::SPELL_LEVELS; level++)
{
giveBonus.bonus.subtype = level;
sendAndApply(&giveBonus);
}
///Give mana
SetMana sm;
sm.hid = hero->id;
sm.val = 999;
sm.absolute = true;
sendAndApply(&sm);
}
else if (cheat == "vcmiarmenelos" || cheat == "vcmibuild")
{
cheated = true;
if (!town) return;
///Build all buildings in selected town
for (auto & build : town->town->buildings)
{
if (!town->hasBuilt(build.first)
&& !build.second->getNameTranslated().empty()
&& build.first != BuildingID::SHIP)
{
buildStructure(town->id, build.first, true);
}
}
}
else if (cheat == "vcmiainur" || cheat == "vcmiangband" || cheat == "vcmiglaurung" || cheat == "vcmiarchangel"
|| cheat == "vcmiblackknight" || cheat == "vcmicrystal" || cheat == "vcmiazure" || cheat == "vcmifaerie")
{
cheated = true;
if (!hero) return;
///Gives N creatures into each slot
std::map<std::string, std::pair<std::string, int>> creatures;
creatures.insert(std::make_pair("vcmiainur", std::make_pair("archangel", 5))); //5 archangels
creatures.insert(std::make_pair("vcmiangband", std::make_pair("blackKnight", 10))); //10 black knights
creatures.insert(std::make_pair("vcmiglaurung", std::make_pair("crystalDragon", 5000))); //5000 crystal dragons
creatures.insert(std::make_pair("vcmiarchangel", std::make_pair("archangel", 5))); //5 archangels
creatures.insert(std::make_pair("vcmiblackknight", std::make_pair("blackKnight", 10))); //10 black knights
creatures.insert(std::make_pair("vcmicrystal", std::make_pair("crystalDragon", 5000))); //5000 crystal dragons
creatures.insert(std::make_pair("vcmiazure", std::make_pair("azureDragon", 5000))); //5000 azure dragons
creatures.insert(std::make_pair("vcmifaerie", std::make_pair("fairieDragon", 5000))); //5000 faerie dragons
const int32_t creatureIdentifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatures[cheat].first, false).value();
const CCreature * creature = VLC->creh->objects.at(creatureIdentifier);
for (int i = 0; i < GameConstants::ARMY_SIZE; i++)
if (!hero->hasStackAtSlot(SlotID(i)))
insertNewStack(StackLocation(hero, SlotID(i)), creature, creatures[cheat].second);
}
else if (boost::starts_with(cheat, "vcmiarmy") || boost::starts_with(cheat, "vcminissi"))
{
cheated = true;
if (!hero) return;
std::vector<std::string> words;
boost::split(words, cheat, boost::is_any_of(" "));
if(words.size() < 2)
return;
std::string creatureIdentifier = words[1];
std::optional<int32_t> creatureId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatureIdentifier, false);
if(creatureId.has_value())
{
const auto * creature = CreatureID(creatureId.value()).toCreature();
for (int i = 0; i < GameConstants::ARMY_SIZE; i++)
if (!hero->hasStackAtSlot(SlotID(i)))
insertNewStack(StackLocation(hero, SlotID(i)), creature, 5 * std::pow(10, i));
}
}
else if (cheat == "vcminoldor" || cheat == "vcmimachines")
{
cheated = true;
if (!hero) return;
///Give all war machines to hero
if (!hero->getArt(ArtifactPosition::MACH1))
giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::BALLISTA], ArtifactPosition::MACH1);
if (!hero->getArt(ArtifactPosition::MACH2))
giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::AMMO_CART], ArtifactPosition::MACH2);
if (!hero->getArt(ArtifactPosition::MACH3))
giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
}
else if (cheat == "vcmiforgeofnoldorking" || cheat == "vcmiartifacts")
{
cheated = true;
if (!hero) return;
///Give hero all artifacts except war machines, spell scrolls and spell book
for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods
{
if(VLC->arth->objects[g]->canBePutAt(hero))
giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE);
}
}
else if (cheat == "vcmiglorfindel" || cheat == "vcmilevel")
{
cheated = true;
if (!hero) return;
///selected hero gains a new level
changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level + 1) - VLC->heroh->reqExp(hero->level));
}
else if (boost::starts_with(cheat, "vcmiexp") || boost::starts_with(cheat, "vcmiolorin"))
{
cheated = true;
if (!hero) return;
std::vector<std::string> words;
boost::split(words, cheat, boost::is_any_of(" "));
if(words.size() < 2)
return;
std::string expAmount = words[1];
long expAmountProcessed = 0;
try
{
expAmountProcessed = std::stol(expAmount);
}
catch(std::exception&)
{
logGlobal->error("Could not parse experience amount for vcmiexp cheat");
}
if(expAmountProcessed > 1)
{
changePrimSkill(hero, PrimarySkill::EXPERIENCE, expAmountProcessed);
}
}
else if (cheat == "vcminahar" || cheat == "vcmimove")
{
cheated = true;
if (!hero) return;
///Give 1000000 movement points to hero
SetMovePoints smp;
smp.hid = hero->id;
smp.val = 1000000;
sendAndApply(&smp);
GiveBonus gb(GiveBonus::ETarget::HERO);
gb.bonus.type = BonusType::FREE_SHIP_BOARDING;
gb.bonus.duration = BonusDuration::ONE_DAY;
gb.bonus.source = BonusSource::OTHER;
gb.id = hero->id.getNum();
giveHeroBonus(&gb);
}
else if (cheat == "vcmiformenos" || cheat == "vcmiresources")
{
cheated = true;
///Give resources to player
TResources resources;
resources[EGameResID::GOLD] = 100000;
for (GameResID i = EGameResID::WOOD; i < EGameResID::GOLD; ++i)
resources[i] = 100;
giveResources(player, resources);
}
else if (cheat == "vcmisilmaril" || cheat == "vcmiwin")
{
cheated = true;
///Player wins
PlayerCheated pc;
pc.player = player;
pc.winningCheatCode = true;
sendAndApply(&pc);
}
else if (cheat == "vcmimelkor" || cheat == "vcmilose")
{
cheated = true;
///Player looses
PlayerCheated pc;
pc.player = player;
pc.losingCheatCode = true;
sendAndApply(&pc);
}
else if (cheat == "vcmieagles" || cheat == "vcmiungoliant" || cheat == "vcmimap" || cheat == "vcmihidemap")
{
cheated = true;
///Reveal or conceal FoW
FoWChange fc;
fc.mode = ((cheat == "vcmieagles" || cheat == "vcmimap") ? 1 : 0);
fc.player = player;
const auto & fowMap = gs->getPlayerTeam(player)->fogOfWarMap;
auto hlp_tab = new int3[gs->map->width * gs->map->height * (gs->map->levels())];
int lastUnc = 0;
for(int z = 0; z < gs->map->levels(); z++)
for(int x = 0; x < gs->map->width; x++)
for(int y = 0; y < gs->map->height; y++)
if(!(*fowMap)[z][x][y] || !fc.mode)
hlp_tab[lastUnc++] = int3(x, y, z);
fc.tiles.insert(hlp_tab, hlp_tab + lastUnc);
delete [] hlp_tab;
sendAndApply(&fc);
}
}
void CGameHandler::removeObstacle(const CObstacleInstance & obstacle) void CGameHandler::removeObstacle(const CObstacleInstance & obstacle)
{ {
BattleObstaclesChanged obsRem; BattleObstaclesChanged obsRem;
@ -7394,3 +6878,11 @@ void CGameHandler::createObject(const int3 & visitablePosition, Obj type, int32_
no.targetPos = visitablePosition; no.targetPos = visitablePosition;
sendAndApply(&no); sendAndApply(&no);
} }
void CGameHandler::deserializationFix()
{
//FIXME: pointer to GameHandler itself can't be deserialized at the moment since GameHandler is top-level entity in serialization
// restore any places that requires such pointer manually
heroPool->gameHandler = this;
playerMessages->gameHandler = this;
}

View File

@ -46,9 +46,11 @@ template<typename T> class CApplier;
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END
class HeroPoolProcessor;
class CGameHandler; class CGameHandler;
class CVCMIServer; class CVCMIServer;
class CBaseForGHApply; class CBaseForGHApply;
class PlayerMessageProcessor;
struct PlayerStatus struct PlayerStatus
{ {
@ -97,13 +99,18 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En
CVCMIServer * lobby; CVCMIServer * lobby;
std::shared_ptr<CApplier<CBaseForGHApply>> applier; std::shared_ptr<CApplier<CBaseForGHApply>> applier;
std::unique_ptr<boost::thread> battleThread; std::unique_ptr<boost::thread> battleThread;
public: public:
std::unique_ptr<HeroPoolProcessor> heroPool;
using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>; using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
//use enums as parameters, because doMove(sth, true, false, true) is not readable //use enums as parameters, because doMove(sth, true, false, true) is not readable
enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS}; enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS};
enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST}; enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST};
enum ELEaveTile {LEAVING_TILE, REMAINING_ON_TILE}; enum ELEaveTile {LEAVING_TILE, REMAINING_ON_TILE};
std::unique_ptr<PlayerMessageProcessor> playerMessages;
std::map<PlayerColor, std::set<std::shared_ptr<CConnection>>> connections; //player color -> connection to client with interface of that player std::map<PlayerColor, std::set<std::shared_ptr<CConnection>>> connections; //player color -> connection to client with interface of that player
PlayerStatuses states; //player color -> player state PlayerStatuses states; //player color -> player state
@ -119,6 +126,7 @@ public:
const GameCb * game() const override; const GameCb * game() const override;
vstd::CLoggerBase * logger() const override; vstd::CLoggerBase * logger() const override;
events::EventBus * eventBus() const override; events::EventBus * eventBus() const override;
CVCMIServer * gameLobby() const;
bool isValidObject(const CGObjectInstance *obj) const; bool isValidObject(const CGObjectInstance *obj) const;
bool isBlockedByQueries(const CPack *pack, PlayerColor player); bool isBlockedByQueries(const CPack *pack, PlayerColor player);
@ -145,6 +153,7 @@ public:
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town); void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
void setBattleResult(BattleResult::EResult resultType, int victoriusSide); void setBattleResult(BattleResult::EResult resultType, int victoriusSide);
CGameHandler() = default;
CGameHandler(CVCMIServer * lobby); CGameHandler(CVCMIServer * lobby);
~CGameHandler(); ~CGameHandler();
@ -230,7 +239,6 @@ public:
PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const; PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;
bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const; bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const;
void playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj);
void updateGateState(); void updateGateState();
bool makeBattleAction(BattleAction &ba); bool makeBattleAction(BattleAction &ba);
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
@ -240,7 +248,6 @@ public:
void removeObstacle(const CObstacleInstance &obstacle); void removeObstacle(const CObstacleInstance &obstacle);
bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player ); bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player );
bool hireHero( const CGObjectInstance *obj, ui8 hid, PlayerColor player );
bool buildBoat( ObjectInstanceID objid, PlayerColor player ); bool buildBoat( ObjectInstanceID objid, PlayerColor player );
bool setFormation( ObjectInstanceID hid, ui8 formation ); bool setFormation( ObjectInstanceID hid, ui8 formation );
bool tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2); bool tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2);
@ -283,7 +290,12 @@ public:
h & QID; h & QID;
h & states; h & states;
h & finishingBattle; h & finishingBattle;
h & heroPool;
h & getRandomGenerator(); h & getRandomGenerator();
h & playerMessages;
if (!h.saving)
deserializationFix();
#if SCRIPTING_ENABLED #if SCRIPTING_ENABLED
JsonNode scriptsState; JsonNode scriptsState;
@ -295,8 +307,6 @@ public:
#endif #endif
} }
void sendMessageToAll(const std::string &message);
void sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message);
void sendToAllClients(CPackForClient * pack); void sendToAllClients(CPackForClient * pack);
void sendAndApply(CPackForClient * pack) override; void sendAndApply(CPackForClient * pack) override;
void applyAndSend(CPackForClient * pack); void applyAndSend(CPackForClient * pack);
@ -346,7 +356,11 @@ public:
void attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender); void attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender);
bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot); bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot);
void spawnWanderingMonsters(CreatureID creatureID); void spawnWanderingMonsters(CreatureID creatureID);
void handleCheatCode(std::string & cheat, PlayerColor player, const CGHeroInstance * hero, const CGTownInstance * town, bool & cheated);
// Check for victory and loss conditions
void checkVictoryLossConditionsForPlayer(PlayerColor player);
void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors);
void checkVictoryLossConditionsForAll();
CRandomGenerator & getRandomGenerator(); CRandomGenerator & getRandomGenerator();
@ -355,6 +369,8 @@ public:
scripting::Pool * getContextPool() const override; scripting::Pool * getContextPool() const override;
#endif #endif
std::list<PlayerColor> generatePlayerTurnOrder() const;
friend class CVCMIServer; friend class CVCMIServer;
private: private:
std::unique_ptr<events::EventBus> serverEventBus; std::unique_ptr<events::EventBus> serverEventBus;
@ -363,16 +379,12 @@ private:
#endif #endif
void reinitScripting(); void reinitScripting();
void deserializationFix();
std::list<PlayerColor> generatePlayerTurnOrder() const;
void makeStackDoNothing(const CStack * next); void makeStackDoNothing(const CStack * next);
void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const; void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;
// Check for victory and loss conditions
void checkVictoryLossConditionsForPlayer(PlayerColor player);
void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors);
void checkVictoryLossConditionsForAll();
const std::string complainNoCreatures; const std::string complainNoCreatures;
const std::string complainNotEnoughCreatures; const std::string complainNotEnoughCreatures;
const std::string complainInvalidSlot; const std::string complainInvalidSlot;

View File

@ -2,6 +2,8 @@ set(server_SRCS
StdInc.cpp StdInc.cpp
CGameHandler.cpp CGameHandler.cpp
HeroPoolProcessor.cpp
PlayerMessageProcessor.cpp
ServerSpellCastEnvironment.cpp ServerSpellCastEnvironment.cpp
CQuery.cpp CQuery.cpp
CVCMIServer.cpp CVCMIServer.cpp
@ -13,6 +15,8 @@ set(server_HEADERS
StdInc.h StdInc.h
CGameHandler.h CGameHandler.h
HeroPoolProcessor.h
PlayerMessageProcessor.h
ServerSpellCastEnvironment.h ServerSpellCastEnvironment.h
CQuery.h CQuery.h
CVCMIServer.h CVCMIServer.h

View File

@ -39,6 +39,7 @@
#include "../lib/VCMI_Lib.h" #include "../lib/VCMI_Lib.h"
#include "../lib/VCMIDirs.h" #include "../lib/VCMIDirs.h"
#include "CGameHandler.h" #include "CGameHandler.h"
#include "PlayerMessageProcessor.h"
#include "../lib/mapping/CMapInfo.h" #include "../lib/mapping/CMapInfo.h"
#include "../lib/GameConstants.h" #include "../lib/GameConstants.h"
#include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/logging/CBasicLogConfigurator.h"
@ -605,7 +606,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
if(gh && si && state == EServerState::GAMEPLAY) if(gh && si && state == EServerState::GAMEPLAY)
{ {
gh->playerMessage(playerSettings->color, playerLeftMsgText, ObjectInstanceID{}); gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText);
gh->connections[playerSettings->color].insert(hostClient); gh->connections[playerSettings->color].insert(hostClient);
startAiPack.players.push_back(playerSettings->color); startAiPack.players.push_back(playerSettings->color);
} }
@ -633,7 +634,7 @@ void CVCMIServer::reconnectPlayer(int connId)
continue; continue;
std::string messageText = boost::str(boost::format("%s (cid %d) is connected") % playerSettings->name % connId); std::string messageText = boost::str(boost::format("%s (cid %d) is connected") % playerSettings->name % connId);
gh->playerMessage(playerSettings->color, messageText, ObjectInstanceID{}); gh->playerMessages->broadcastMessage(playerSettings->color, messageText);
startAiPack.players.push_back(playerSettings->color); startAiPack.players.push_back(playerSettings->color);
} }
@ -822,7 +823,7 @@ void CVCMIServer::setPlayer(PlayerColor clickedColor)
void CVCMIServer::optionNextCastle(PlayerColor player, int dir) void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
{ {
PlayerSettings & s = si->playerInfos[player]; PlayerSettings & s = si->playerInfos[player];
si16 & cur = s.castle; FactionID & cur = s.castle;
auto & allowed = getPlayerInfo(player.getNum()).allowedFactions; auto & allowed = getPlayerInfo(player.getNum()).allowedFactions;
const bool allowRandomTown = getPlayerInfo(player.getNum()).isFactionRandom; const bool allowRandomTown = getPlayerInfo(player.getNum()).isFactionRandom;
@ -856,7 +857,7 @@ void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
else else
{ {
assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range
auto iter = allowed.find(FactionID(cur)); auto iter = allowed.find(cur);
std::advance(iter, dir); std::advance(iter, dir);
cur = *iter; cur = *iter;
} }

View File

@ -0,0 +1,397 @@
/*
* HeroPoolProcessor.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 "HeroPoolProcessor.h"
#include "CGameHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CPlayerState.h"
#include "../lib/GameSettings.h"
#include "../lib/NetPacks.h"
#include "../lib/StartInfo.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/gameState/CGameState.h"
#include "../lib/gameState/TavernHeroesPool.h"
#include "../lib/gameState/TavernSlot.h"
HeroPoolProcessor::HeroPoolProcessor()
: gameHandler(nullptr)
{
}
HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler)
: gameHandler(gameHandler)
{
}
bool HeroPoolProcessor::playerEndedTurn(const PlayerColor & player)
{
// our player is acting right now and have not ended turn
if (player == gameHandler->gameState()->currentPlayer)
return false;
auto turnOrder = gameHandler->generatePlayerTurnOrder();
for (auto const & entry : turnOrder)
{
// our player is yet to start turn
if (entry == gameHandler->gameState()->currentPlayer)
return false;
// our player have finished turn
if (entry == player)
return true;
}
assert(false);
return false;
}
TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID)
{
const auto & heroesPool = gameHandler->gameState()->heroesPool;
const auto & heroes = heroesPool->getHeroesFor(player);
// if tavern has empty slot - use it
if (heroes.size() == 0)
return TavernHeroSlot::NATIVE;
if (heroes.size() == 1)
return TavernHeroSlot::RANDOM;
// try to find "better" slot to overwrite
// we want to avoid overwriting retreated heroes when tavern still has slot with random hero
// as well as avoid overwriting surrendered heroes if we can overwrite retreated hero
auto roleLeft = heroesPool->getSlotRole(HeroTypeID(heroes[0]->subID));
auto roleRight = heroesPool->getSlotRole(HeroTypeID(heroes[1]->subID));
if (roleLeft > roleRight)
return TavernHeroSlot::RANDOM;
if (roleLeft < roleRight)
return TavernHeroSlot::NATIVE;
// both slots are equal in "value", so select randomly
if (getRandomGenerator(player).nextInt(100) > 50)
return TavernHeroSlot::RANDOM;
else
return TavernHeroSlot::NATIVE;
}
void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero)
{
SetAvailableHero sah;
if (playerEndedTurn(color))
sah.roleID = TavernSlotRole::SURRENDERED_TODAY;
else
sah.roleID = TavernSlotRole::SURRENDERED;
sah.slotID = selectSlotForRole(color, sah.roleID);
sah.player = color;
sah.hid = hero->subID;
gameHandler->sendAndApply(&sah);
}
void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero)
{
SetAvailableHero sah;
if (playerEndedTurn(color))
sah.roleID = TavernSlotRole::RETREATED_TODAY;
else
sah.roleID = TavernSlotRole::RETREATED;
sah.slotID = selectSlotForRole(color, sah.roleID);
sah.player = color;
sah.hid = hero->subID;
sah.army.clear();
sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1);
gameHandler->sendAndApply(&sah);
}
void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot)
{
SetAvailableHero sah;
sah.player = color;
sah.roleID = TavernSlotRole::NONE;
sah.slotID = slot;
sah.hid = HeroTypeID::NONE;
gameHandler->sendAndApply(&sah);
}
void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveArmy)
{
SetAvailableHero sah;
sah.player = color;
sah.slotID = slot;
CGHeroInstance *newHero = pickHeroFor(needNativeHero, color);
if (newHero)
{
sah.hid = newHero->subID;
if (giveArmy)
{
sah.roleID = TavernSlotRole::FULL_ARMY;
newHero->initArmy(getRandomGenerator(color), &sah.army);
}
else
{
sah.roleID = TavernSlotRole::SINGLE_UNIT;
sah.army.clear();
sah.army.setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1);
}
}
else
{
sah.hid = -1;
}
gameHandler->sendAndApply(&sah);
}
void HeroPoolProcessor::onNewWeek(const PlayerColor & color)
{
const auto & heroesPool = gameHandler->gameState()->heroesPool;
const auto & heroes = heroesPool->getHeroesFor(color);
const auto nativeSlotRole = heroes.size() < 1 ? TavernSlotRole::NONE : heroesPool->getSlotRole(heroes[0]->type->getId());
const auto randomSlotRole = heroes.size() < 2 ? TavernSlotRole::NONE : heroesPool->getSlotRole(heroes[1]->type->getId());
bool resetNativeSlot = nativeSlotRole != TavernSlotRole::RETREATED_TODAY && nativeSlotRole != TavernSlotRole::SURRENDERED_TODAY;
bool resetRandomSlot = randomSlotRole != TavernSlotRole::RETREATED_TODAY && randomSlotRole != TavernSlotRole::SURRENDERED_TODAY;
if (resetNativeSlot)
clearHeroFromSlot(color, TavernHeroSlot::NATIVE);
if (resetRandomSlot)
clearHeroFromSlot(color, TavernHeroSlot::RANDOM);
if (resetNativeSlot)
selectNewHeroForSlot(color, TavernHeroSlot::NATIVE, true, true);
if (resetRandomSlot)
selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true);
}
bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTypeID & heroToRecruit, const PlayerColor & player)
{
const PlayerState * playerState = gameHandler->getPlayerState(player);
const CGObjectInstance * mapObject = gameHandler->getObj(objectID);
const CGTownInstance * town = gameHandler->getTown(objectID);
if (!mapObject && gameHandler->complain("Invalid map object!"))
return false;
if (!playerState && gameHandler->complain("Invalid player!"))
return false;
if (playerState->resources[EGameResID::GOLD] < GameConstants::HERO_GOLD_COST && gameHandler->complain("Not enough gold for buying hero!"))
return false;
if (gameHandler->getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && gameHandler->complain("Cannot hire hero, too many wandering heroes already!"))
return false;
if (gameHandler->getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && gameHandler->complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))
return false;
if(town) //tavern in town
{
if(gameHandler->getPlayerRelations(mapObject->tempOwner, player) == PlayerRelations::ENEMIES && gameHandler->complain("Can't buy hero in enemy town!"))
return false;
if(!town->hasBuilt(BuildingID::TAVERN) && gameHandler->complain("No tavern!"))
return false;
if(town->visitingHero && gameHandler->complain("There is visiting hero - no place!"))
return false;
}
if(mapObject->ID == Obj::TAVERN)
{
if(gameHandler->getTile(mapObject->visitablePos())->visitableObjects.back() != mapObject && gameHandler->complain("Tavern entry must be unoccupied!"))
return false;
}
auto recruitableHeroes = gameHandler->gameState()->heroesPool->getHeroesFor(player);
const CGHeroInstance * recruitedHero = nullptr;
for(const auto & hero : recruitableHeroes)
{
if(hero->subID == heroToRecruit)
recruitedHero = hero;
}
if(!recruitedHero)
{
gameHandler->complain("Hero is not available for hiring!");
return false;
}
HeroRecruited hr;
hr.tid = mapObject->id;
hr.hid = recruitedHero->subID;
hr.player = player;
hr.tile = recruitedHero->convertFromVisitablePos(mapObject->visitablePos());
if(gameHandler->getTile(hr.tile)->isWater())
{
//Create a new boat for hero
gameHandler->createObject(mapObject->visitablePos(), Obj::BOAT, recruitedHero->getBoatType().getNum());
hr.boatId = gameHandler->getTopObj(hr.tile)->id;
}
// apply netpack -> this will remove hired hero from pool
gameHandler->sendAndApply(&hr);
if(recruitableHeroes[0] == recruitedHero)
selectNewHeroForSlot(player, TavernHeroSlot::NATIVE, false, false);
else
selectNewHeroForSlot(player, TavernHeroSlot::RANDOM, false, false);
gameHandler->giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST);
if(town)
{
gameHandler->visitCastleObjects(town, recruitedHero);
gameHandler->giveSpells(town, recruitedHero);
}
return true;
}
std::vector<const CHeroClass *> HeroPoolProcessor::findAvailableClassesFor(const PlayerColor & player) const
{
std::vector<const CHeroClass *> result;
const auto & heroesPool = gameHandler->gameState()->heroesPool;
FactionID factionID = gameHandler->getPlayerSettings(player)->castle;
for(auto & elem : heroesPool->unusedHeroesFromPool())
{
if (vstd::contains(result, elem.second->type->heroClass))
continue;
bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player);
bool heroClassBanned = elem.second->type->heroClass->selectionProbability[factionID] == 0;
if(heroAvailable && !heroClassBanned)
result.push_back(elem.second->type->heroClass);
}
return result;
}
std::vector<CGHeroInstance *> HeroPoolProcessor::findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const
{
std::vector<CGHeroInstance *> result;
const auto & heroesPool = gameHandler->gameState()->heroesPool;
for(auto & elem : heroesPool->unusedHeroesFromPool())
{
assert(!vstd::contains(result, elem.second));
bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player);
bool heroClassMatches = elem.second->type->heroClass == heroClass;
if(heroAvailable && heroClassMatches)
result.push_back(elem.second);
}
return result;
}
const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerColor & player)
{
if(player >= PlayerColor::PLAYER_LIMIT)
{
logGlobal->error("Cannot pick hero for player %d. Wrong owner!", player.getStr());
return nullptr;
}
FactionID factionID = gameHandler->getPlayerSettings(player)->castle;
const auto & heroesPool = gameHandler->gameState()->heroesPool;
const auto & currentTavern = heroesPool->getHeroesFor(player);
std::vector<const CHeroClass *> potentialClasses = findAvailableClassesFor(player);
std::vector<const CHeroClass *> possibleClasses;
if(potentialClasses.empty())
{
logGlobal->error("There are no heroes available for player %s!", player.getStr());
return nullptr;
}
for(const auto & heroClass : potentialClasses)
{
if (isNative && heroClass->faction != factionID)
continue;
bool hasSameClass = vstd::contains_if(currentTavern, [&](const CGHeroInstance * hero){
return hero->type->heroClass == heroClass;
});
if (hasSameClass)
continue;
possibleClasses.push_back(heroClass);
}
if (possibleClasses.empty())
{
logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr());
possibleClasses = potentialClasses;
}
int totalWeight = 0;
for(const auto & heroClass : possibleClasses)
totalWeight += heroClass->selectionProbability.at(factionID);
int roll = getRandomGenerator(player).nextInt(totalWeight - 1);
for(const auto & heroClass : possibleClasses)
{
roll -= heroClass->selectionProbability.at(factionID);
if(roll < 0)
return heroClass;
}
return *possibleClasses.rbegin();
}
CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, const PlayerColor & player)
{
const CHeroClass * heroClass = pickClassFor(isNative, player);
if(!heroClass)
return nullptr;
std::vector<CGHeroInstance *> possibleHeroes = findAvailableHeroesFor(player, heroClass);
assert(!possibleHeroes.empty());
if(possibleHeroes.empty())
return nullptr;
return *RandomGeneratorUtil::nextItem(possibleHeroes, getRandomGenerator(player));
}
CRandomGenerator & HeroPoolProcessor::getRandomGenerator(const PlayerColor & player)
{
if (playerSeed.count(player) == 0)
{
int seed = gameHandler->getRandomGenerator().nextInt();
playerSeed.emplace(player, std::make_unique<CRandomGenerator>(seed));
}
return *playerSeed.at(player);
}

View File

@ -0,0 +1,66 @@
/*
* HeroPoolProcessor.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
VCMI_LIB_NAMESPACE_BEGIN
enum class TavernHeroSlot : int8_t;
enum class TavernSlotRole : int8_t;
class PlayerColor;
class CGHeroInstance;
class HeroTypeID;
class ObjectInstanceID;
class CRandomGenerator;
class CHeroClass;
VCMI_LIB_NAMESPACE_END
class CGameHandler;
class HeroPoolProcessor : boost::noncopyable
{
/// per-player random generators
std::map<PlayerColor, std::unique_ptr<CRandomGenerator>> playerSeed;
void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot);
void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy);
std::vector<const CHeroClass *> findAvailableClassesFor(const PlayerColor & player) const;
std::vector<CGHeroInstance *> findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const;
const CHeroClass * pickClassFor(bool isNative, const PlayerColor & player);
CGHeroInstance * pickHeroFor(bool isNative, const PlayerColor & player);
CRandomGenerator & getRandomGenerator(const PlayerColor & player);
TavernHeroSlot selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID);
bool playerEndedTurn(const PlayerColor & player);
public:
CGameHandler * gameHandler;
HeroPoolProcessor();
HeroPoolProcessor(CGameHandler * gameHandler);
void onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero);
void onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero);
void onNewWeek(const PlayerColor & color);
/// Incoming net pack handling
bool hireHero(const ObjectInstanceID & objectID, const HeroTypeID & hid, const PlayerColor & player);
template <typename Handler> void serialize(Handler &h, const int version)
{
// h & gameHandler; // FIXME: make this work instead of using deserializationFix in gameHandler
h & playerSeed;
}
};

View File

@ -11,6 +11,9 @@
#include "ServerNetPackVisitors.h" #include "ServerNetPackVisitors.h"
#include "CGameHandler.h" #include "CGameHandler.h"
#include "HeroPoolProcessor.h"
#include "PlayerMessageProcessor.h"
#include "../lib/IGameCallback.h" #include "../lib/IGameCallback.h"
#include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/gameState/CGameState.h" #include "../lib/gameState/CGameState.h"
@ -246,12 +249,10 @@ void ApplyGhNetPackVisitor::visitSetFormation(SetFormation & pack)
void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack) void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack)
{ {
const CGObjectInstance * obj = gh.getObj(pack.tid); if (!gh.hasPlayerAt(pack.player, pack.c))
const CGTownInstance * town = dynamic_ptr_cast<CGTownInstance>(obj); gh.throwAndComplain(&pack, "No such pack.player!");
if(town && PlayerRelations::ENEMIES == gh.getPlayerRelations(obj->tempOwner, gh.getPlayerAt(pack.c)))
gh.throwAndComplain(&pack, "Can't buy hero in enemy town!");
result = gh.hireHero(obj, pack.hid, pack.player); result = gh.heroPool->hireHero(pack.tid, pack.hid, pack.player);
} }
void ApplyGhNetPackVisitor::visitBuildBoat(BuildBoat & pack) void ApplyGhNetPackVisitor::visitBuildBoat(BuildBoat & pack)
@ -352,6 +353,6 @@ void ApplyGhNetPackVisitor::visitPlayerMessage(PlayerMessage & pack)
if(!pack.player.isSpectator()) // TODO: clearly not a great way to verify permissions if(!pack.player.isSpectator()) // TODO: clearly not a great way to verify permissions
gh.throwOnWrongPlayer(&pack, pack.player); gh.throwOnWrongPlayer(&pack, pack.player);
gh.playerMessage(pack.player, pack.text, pack.currObj); gh.playerMessages->playerMessage(pack.player, pack.text, pack.currObj);
result = true; result = true;
} }

View File

@ -0,0 +1,523 @@
/*
* CGameHandler.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 "PlayerMessageProcessor.h"
#include "CGameHandler.h"
#include "CVCMIServer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CModHandler.h"
#include "../lib/CPlayerState.h"
#include "../lib/GameConstants.h"
#include "../lib/NetPacks.h"
#include "../lib/StartInfo.h"
#include "../lib/gameState/CGameState.h"
#include "../lib/mapObjects/CGTownInstance.h"
PlayerMessageProcessor::PlayerMessageProcessor()
:gameHandler(nullptr)
{
}
PlayerMessageProcessor::PlayerMessageProcessor(CGameHandler * gameHandler)
:gameHandler(gameHandler)
{
}
void PlayerMessageProcessor::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
{
if (handleHostCommand(player, message))
return;
if (handleCheatCode(message, player, currObj))
{
if(!gameHandler->getPlayerSettings(player)->isControlledByAI())
broadcastSystemMessage(VLC->generaltexth->allTexts[260]);
if(!player.isSpectator())
gameHandler->checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
return;
}
broadcastMessage(player, message);
}
bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::string &message)
{
std::vector<std::string> words;
boost::split(words, message, boost::is_any_of(" "));
bool isHost = false;
for(auto & c : gameHandler->connections[player])
if(gameHandler->gameLobby()->isClientHost(c->connectionID))
isHost = true;
if(!isHost || words.size() < 2 || words[0] != "game")
return false;
if(words[1] == "exit" || words[1] == "quit" || words[1] == "end")
{
broadcastSystemMessage("game was terminated");
gameHandler->gameLobby()->state = EServerState::SHUTDOWN;
return true;
}
if(words.size() == 3 && words[1] == "save")
{
gameHandler->save("Saves/" + words[2]);
broadcastSystemMessage("game saved as " + words[2]);
return true;
}
if(words.size() == 3 && words[1] == "kick")
{
auto playername = words[2];
PlayerColor playerToKick(PlayerColor::CANNOT_DETERMINE);
if(std::all_of(playername.begin(), playername.end(), ::isdigit))
playerToKick = PlayerColor(std::stoi(playername));
else
{
for(auto & c : gameHandler->connections)
{
if(c.first.getStr(false) == playername)
playerToKick = c.first;
}
}
if(playerToKick != PlayerColor::CANNOT_DETERMINE)
{
PlayerCheated pc;
pc.player = playerToKick;
pc.losingCheatCode = true;
gameHandler->sendAndApply(&pc);
gameHandler->checkVictoryLossConditionsForPlayer(playerToKick);
}
return true;
}
if(words.size() == 2 && words[1] == "cheaters")
{
if (cheaters.empty())
broadcastSystemMessage("No cheaters registered!");
for (auto const & entry : cheaters)
broadcastSystemMessage("Player " + entry.getStr() + " is cheater!");
return true;
}
return false;
}
void PlayerMessageProcessor::cheatGiveSpells(PlayerColor player, const CGHeroInstance * hero)
{
if (!hero)
return;
///Give hero spellbook
if (!hero->hasSpellbook())
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
///Give all spells with bonus (to allow banned spells)
GiveBonus giveBonus(GiveBonus::ETarget::HERO);
giveBonus.id = hero->id.getNum();
giveBonus.bonus = Bonus(BonusDuration::PERMANENT, BonusType::SPELLS_OF_LEVEL, BonusSource::OTHER, 0, 0);
//start with level 0 to skip abilities
for (int level = 1; level <= GameConstants::SPELL_LEVELS; level++)
{
giveBonus.bonus.subtype = level;
gameHandler->sendAndApply(&giveBonus);
}
///Give mana
SetMana sm;
sm.hid = hero->id;
sm.val = 999;
sm.absolute = true;
gameHandler->sendAndApply(&sm);
}
void PlayerMessageProcessor::cheatBuildTown(PlayerColor player, const CGTownInstance * town)
{
if (!town)
return;
for (auto & build : town->town->buildings)
{
if (!town->hasBuilt(build.first)
&& !build.second->getNameTranslated().empty()
&& build.first != BuildingID::SHIP)
{
gameHandler->buildStructure(town->id, build.first, true);
}
}
}
void PlayerMessageProcessor::cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
{
if (!hero)
return;
std::string creatureIdentifier = words.empty() ? "archangel" : words[0];
std::optional<int> amountPerSlot;
try
{
amountPerSlot = std::stol(words.at(1));
}
catch(std::exception&)
{
}
std::optional<int32_t> creatureId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatureIdentifier, false);
if(creatureId.has_value())
{
const auto * creature = CreatureID(creatureId.value()).toCreature();
for (int i = 0; i < GameConstants::ARMY_SIZE; i++)
{
if (!hero->hasStackAtSlot(SlotID(i)))
{
if (amountPerSlot.has_value())
gameHandler->insertNewStack(StackLocation(hero, SlotID(i)), creature, *amountPerSlot);
else
gameHandler->insertNewStack(StackLocation(hero, SlotID(i)), creature, 5 * std::pow(10, i));
}
}
}
}
void PlayerMessageProcessor::cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero)
{
if (!hero)
return;
if (!hero->getArt(ArtifactPosition::MACH1))
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::BALLISTA], ArtifactPosition::MACH1);
if (!hero->getArt(ArtifactPosition::MACH2))
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::AMMO_CART], ArtifactPosition::MACH2);
if (!hero->getArt(ArtifactPosition::MACH3))
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
}
void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero)
{
if (!hero)
return;
for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods
{
if(VLC->arth->objects[g]->canBePutAt(hero))
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE);
}
}
void PlayerMessageProcessor::cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
{
if (!hero)
return;
int levelsToGain;
try
{
levelsToGain = std::stol(words.at(0));
}
catch(std::exception&)
{
levelsToGain = 1;
}
gameHandler->changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level + levelsToGain) - VLC->heroh->reqExp(hero->level));
}
void PlayerMessageProcessor::cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
{
if (!hero)
return;
int expAmountProcessed;
try
{
expAmountProcessed = std::stol(words.at(0));
}
catch(std::exception&)
{
expAmountProcessed = 10000;
}
gameHandler->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expAmountProcessed);
}
void PlayerMessageProcessor::cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
{
if (!hero)
return;
SetMovePoints smp;
smp.hid = hero->id;
try
{
smp.val = std::stol(words.at(0));;
}
catch(std::exception&)
{
smp.val = 1000000;
}
gameHandler->sendAndApply(&smp);
GiveBonus gb(GiveBonus::ETarget::HERO);
gb.bonus.type = BonusType::FREE_SHIP_BOARDING;
gb.bonus.duration = BonusDuration::ONE_DAY;
gb.bonus.source = BonusSource::OTHER;
gb.id = hero->id.getNum();
gameHandler->giveHeroBonus(&gb);
}
void PlayerMessageProcessor::cheatResources(PlayerColor player, std::vector<std::string> words)
{
int baseResourceAmount;
try
{
baseResourceAmount = std::stol(words.at(0));;
}
catch(std::exception&)
{
baseResourceAmount = 100;
}
TResources resources;
resources[EGameResID::GOLD] = baseResourceAmount * 100;
for (GameResID i = EGameResID::WOOD; i < EGameResID::GOLD; ++i)
resources[i] = baseResourceAmount;
gameHandler->giveResources(player, resources);
}
void PlayerMessageProcessor::cheatVictory(PlayerColor player)
{
PlayerCheated pc;
pc.player = player;
pc.winningCheatCode = true;
gameHandler->sendAndApply(&pc);
}
void PlayerMessageProcessor::cheatDefeat(PlayerColor player)
{
PlayerCheated pc;
pc.player = player;
pc.losingCheatCode = true;
gameHandler->sendAndApply(&pc);
}
void PlayerMessageProcessor::cheatMapReveal(PlayerColor player, bool reveal)
{
FoWChange fc;
fc.mode = reveal;
fc.player = player;
const auto & fowMap = gameHandler->gameState()->getPlayerTeam(player)->fogOfWarMap;
const auto & mapSize = gameHandler->gameState()->getMapSize();
auto hlp_tab = new int3[mapSize.x * mapSize.y * mapSize.z];
int lastUnc = 0;
for(int z = 0; z < mapSize.z; z++)
for(int x = 0; x < mapSize.x; x++)
for(int y = 0; y < mapSize.y; y++)
if(!(*fowMap)[z][x][y] || !fc.mode)
hlp_tab[lastUnc++] = int3(x, y, z);
fc.tiles.insert(hlp_tab, hlp_tab + lastUnc);
delete [] hlp_tab;
gameHandler->sendAndApply(&fc);
}
bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerColor player, ObjectInstanceID currObj)
{
std::vector<std::string> words;
boost::split(words, cheat, boost::is_any_of("\t\r\n "));
if (words.empty())
return false;
//Make cheat name case-insensitive, but keep words/parameters (e.g. creature name) as it
std::string cheatName = boost::to_lower_copy(words[0]);
words.erase(words.begin());
std::vector<std::string> townTargetedCheats = { "vcmiarmenelos", "vcmibuild", "nwczion" };
std::vector<std::string> playerTargetedCheats = {
"vcmiformenos", "vcmiresources", "nwctheconstruct",
"vcmimelkor", "vcmilose", "nwcbluepill",
"vcmisilmaril", "vcmiwin", "nwcredpill",
"vcmieagles", "vcmimap", "nwcwhatisthematrix",
"vcmiungoliant", "vcmihidemap", "nwcignoranceisbliss"
};
std::vector<std::string> heroTargetedCheats = {
"vcmiainur", "vcmiarchangel", "nwctrinity",
"vcmiangband", "vcmiblackknight", "nwcagents",
"vcmiglaurung", "vcmicrystal", "vcmiazure",
"vcmifaerie", "vcmiarmy", "vcminissi",
"vcmiistari", "vcmispells", "nwcthereisnospoon",
"vcminoldor", "vcmimachines", "nwclotsofguns",
"vcmiglorfindel", "vcmilevel", "nwcneo",
"vcminahar", "vcmimove", "nwcnebuchadnezzar",
"vcmiforgeofnoldorking", "vcmiartifacts",
"vcmiolorin", "vcmiexp",
};
if (!vstd::contains(townTargetedCheats, cheatName) && !vstd::contains(playerTargetedCheats, cheatName) && !vstd::contains(heroTargetedCheats, cheatName))
return false;
bool playerTargetedCheat = false;
for (const auto & i : gameHandler->gameState()->players)
{
if (words.empty())
break;
if (i.first == PlayerColor::NEUTRAL)
continue;
if (words.front() == "ai" && i.second.human)
continue;
if (words.front() != "all" && words.front() != i.first.getStr())
continue;
std::vector<std::string> parameters = words;
cheaters.insert(i.first);
playerTargetedCheat = true;
parameters.erase(parameters.begin());
if (vstd::contains(playerTargetedCheats, cheatName))
executeCheatCode(cheatName, i.first, ObjectInstanceID::NONE, parameters);
if (vstd::contains(townTargetedCheats, cheatName))
for (const auto & t : i.second.towns)
executeCheatCode(cheatName, i.first, t->id, parameters);
if (vstd::contains(heroTargetedCheats, cheatName))
for (const auto & h : i.second.heroes)
executeCheatCode(cheatName, i.first, h->id, parameters);
}
if (!playerTargetedCheat)
executeCheatCode(cheatName, player, currObj, words);
cheaters.insert(player);
return true;
}
void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, PlayerColor player, ObjectInstanceID currObj, const std::vector<std::string> & words)
{
const CGHeroInstance * hero = gameHandler->getHero(currObj);
const CGTownInstance * town = gameHandler->getTown(currObj);
if (!town && hero)
town = hero->visitedTown;
const auto & doCheatGiveSpells = [&]() { cheatGiveSpells(player, hero); };
const auto & doCheatBuildTown = [&]() { cheatBuildTown(player, town); };
const auto & doCheatGiveArmyCustom = [&]() { cheatGiveArmy(player, hero, words); };
const auto & doCheatGiveArmyFixed = [&](std::vector<std::string> customWords) { cheatGiveArmy(player, hero, customWords); };
const auto & doCheatGiveMachines = [&]() { cheatGiveMachines(player, hero); };
const auto & doCheatGiveArtifacts = [&]() { cheatGiveArtifacts(player, hero); };
const auto & doCheatLevelup = [&]() { cheatLevelup(player, hero, words); };
const auto & doCheatExperience = [&]() { cheatExperience(player, hero, words); };
const auto & doCheatMovement = [&]() { cheatMovement(player, hero, words); };
const auto & doCheatResources = [&]() { cheatResources(player, words); };
const auto & doCheatVictory = [&]() { cheatVictory(player); };
const auto & doCheatDefeat = [&]() { cheatDefeat(player); };
const auto & doCheatMapReveal = [&]() { cheatMapReveal(player, true); };
const auto & doCheatMapHide = [&]() { cheatMapReveal(player, false); };
// Unimplemented H3 cheats:
// nwcfollowthewhiterabbit - The currently selected hero permanently gains maximum luck.
// nwcmorpheus - The currently selected hero permanently gains maximum morale.
// nwcoracle - The puzzle map is permanently revealed.
// nwcphisherprice - Changes and brightens the game colors.
std::map<std::string, std::function<void()>> callbacks = {
{"vcmiainur", [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} },
{"nwctrinity", [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} },
{"vcmiangband", [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} },
{"vcmiglaurung", [&] () {doCheatGiveArmyFixed({ "crystalDragon", "5000" });} },
{"vcmiarchangel", [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} },
{"nwcagents", [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} },
{"vcmiblackknight", [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} },
{"vcmicrystal", [&] () {doCheatGiveArmyFixed({ "crystalDragon", "5000" });} },
{"vcmiazure", [&] () {doCheatGiveArmyFixed({ "azureDragon", "5000" });} },
{"vcmifaerie", [&] () {doCheatGiveArmyFixed({ "fairieDragon", "5000" });} },
{"vcmiarmy", doCheatGiveArmyCustom },
{"vcminissi", doCheatGiveArmyCustom },
{"vcmiistari", doCheatGiveSpells },
{"vcmispells", doCheatGiveSpells },
{"nwcthereisnospoon", doCheatGiveSpells },
{"vcmiarmenelos", doCheatBuildTown },
{"vcmibuild", doCheatBuildTown },
{"nwczion", doCheatBuildTown },
{"vcminoldor", doCheatGiveMachines },
{"vcmimachines", doCheatGiveMachines },
{"nwclotsofguns", doCheatGiveMachines },
{"vcmiforgeofnoldorking", doCheatGiveArtifacts },
{"vcmiartifacts", doCheatGiveArtifacts },
{"vcmiglorfindel", doCheatLevelup },
{"vcmilevel", doCheatLevelup },
{"nwcneo", doCheatLevelup },
{"vcmiolorin", doCheatExperience },
{"vcmiexp", doCheatExperience },
{"vcminahar", doCheatMovement },
{"vcmimove", doCheatMovement },
{"nwcnebuchadnezzar", doCheatMovement },
{"vcmiformenos", doCheatResources },
{"vcmiresources", doCheatResources },
{"nwctheconstruct", doCheatResources },
{"nwcbluepill", doCheatDefeat },
{"vcmimelkor", doCheatDefeat },
{"vcmilose", doCheatDefeat },
{"nwcredpill", doCheatVictory },
{"vcmisilmaril", doCheatVictory },
{"vcmiwin", doCheatVictory },
{"nwcwhatisthematrix", doCheatMapReveal },
{"vcmieagles", doCheatMapReveal },
{"vcmimap", doCheatMapReveal },
{"vcmiungoliant", doCheatMapHide },
{"vcmihidemap", doCheatMapHide },
{"nwcignoranceisbliss", doCheatMapHide },
};
assert(callbacks.count(cheatName));
if (callbacks.count(cheatName))
callbacks.at(cheatName)();
}
void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message)
{
SystemMessage sm;
sm.text = message;
connection->sendPack(&sm);
}
void PlayerMessageProcessor::broadcastSystemMessage(const std::string & message)
{
SystemMessage sm;
sm.text = message;
gameHandler->sendToAllClients(&sm);
}
void PlayerMessageProcessor::broadcastMessage(PlayerColor playerSender, const std::string & message)
{
PlayerMessageClient temp_message(playerSender, message);
gameHandler->sendAndApply(&temp_message);
}

View File

@ -0,0 +1,65 @@
/*
* CGameHandler.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 "../lib/GameConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
class CGTownInstance;
class CConnection;
VCMI_LIB_NAMESPACE_END
class CGameHandler;
class PlayerMessageProcessor
{
std::set<PlayerColor> cheaters;
void executeCheatCode(const std::string & cheatName, PlayerColor player, ObjectInstanceID currObj, const std::vector<std::string> & arguments );
bool handleCheatCode(const std::string & cheatFullCommand, PlayerColor player, ObjectInstanceID currObj);
bool handleHostCommand(PlayerColor player, const std::string & message);
void cheatGiveSpells(PlayerColor player, const CGHeroInstance * hero);
void cheatBuildTown(PlayerColor player, const CGTownInstance * town);
void cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
void cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero);
void cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero);
void cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
void cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
void cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
void cheatResources(PlayerColor player, std::vector<std::string> words);
void cheatVictory(PlayerColor player);
void cheatDefeat(PlayerColor player);
void cheatMapReveal(PlayerColor player, bool reveal);
public:
CGameHandler * gameHandler;
PlayerMessageProcessor();
PlayerMessageProcessor(CGameHandler * gameHandler);
/// incoming NetPack handling
void playerMessage(PlayerColor player, const std::string & message, ObjectInstanceID currObj);
/// Send message to specific client with "System" as sender
void sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message);
/// Send message to all players with "System" as sender
void broadcastSystemMessage(const std::string & message);
/// Send message from specific player to all other players
void broadcastMessage(PlayerColor playerSender, const std::string & message);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & cheaters;
}
};