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(hero);
ui8 i=0;
for(; i<gs->players[*player].availableHeroes.size(); i++)
{
if(gs->players[*player].availableHeroes[i] == hero)
{
HireHero pack(i, townOrTavern->id);
pack.player = *player;
sendRequest(&pack);
return;
}
}
HireHero pack(HeroTypeID(hero->subID), townOrTavern->id);
pack.player = *player;
sendRequest(&pack);
}
void CCallback::save( const std::string &fname )

View File

@ -1,6 +1,129 @@
# 1.2.1 -> 1.3.0
(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
### GENERAL:

View File

@ -434,7 +434,8 @@ void playIntro()
{
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;
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);
localState->erasePath(hero);
}
@ -517,7 +520,7 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
if(town->garrisonHero) //wandering hero moved to the 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);
}
@ -537,7 +540,9 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
castleInt->garr->setArmy(town->visitingHero, 1);
castleInt->garr->recreateSlots();
castleInt->heroes->update();
castleInt->redraw();
// Perform totalRedraw to update hero list on adventure map
GH.windows().totalRedraw();
}
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
{
EVENT_HANDLER_CALLED_BY_CLIENT;
adventureInt->onTownChanged(town);
if (castleInt)
{
castleInt->townlist->update(town);
castleInt->townlist->updateElement(town);
if (castleInt->town == town)
{
@ -621,8 +628,10 @@ void CPlayerInterface::buildChanged(const CGTownInstance *town, BuildingID build
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)
@ -1490,6 +1499,13 @@ void CPlayerInterface::objectRemovedAfter()
{
EVENT_HANDLER_CALLED_BY_CLIENT;
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)
@ -1988,8 +2004,17 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
int soundChannel = -1;
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
if (hero->hasBonusOfType(BonusType::FLYING_MOVEMENT))
return "HORSE10.wav";
@ -2041,8 +2066,11 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
}
if(i != path.nodes.size() - 1)
{
soundName = getMovementSoundFor(h, prevCoord, nextCoord);
soundChannel = CCS->soundh->playSound(soundName, -1);
soundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
if (!soundName.empty())
soundChannel = CCS->soundh->playSound(soundName, -1);
else
soundChannel = -1;
}
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.
std::string newSoundName = getMovementSoundFor(h, prevCoord, nextCoord);
std::string newSoundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
if(newSoundName != soundName)
{
soundName = newSoundName;
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)
{
widget->getHeroList()->update(h);
widget->getHeroList()->updateElement(h);
if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents())
widget->getInfoBar()->showSelection();
@ -102,7 +102,7 @@ void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
void AdventureMapInterface::onTownChanged(const CGTownInstance * town)
{
widget->getTownList()->update(town);
widget->getTownList()->updateElement(town);
if (town && town == LOCPLINT->localState->getCurrentTown() && !widget->getInfoBar()->showingComponents())
widget->getInfoBar()->showSelection();
@ -365,8 +365,8 @@ void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
widget->getInfoBar()->showSelection();
}
widget->getHeroList()->update();
widget->getTownList()->update();
widget->getHeroList()->updateWidget();
widget->getTownList()->updateWidget();
const CGHeroInstance * heroToSelect = nullptr;
@ -833,5 +833,8 @@ void AdventureMapInterface::onScreenResize()
widget->getMinimap()->update();
widget->getInfoBar()->showSelection();
if (LOCPLINT && LOCPLINT->localState->getCurrentArmy())
widget->getMapView()->onCenteredObject(LOCPLINT->localState->getCurrentArmy());
adjustActiveness();
}

View File

@ -280,21 +280,15 @@ void CHeroList::select(const CGHeroInstance * 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
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
updateWidget();
}
void CHeroList::updateWidget()
{
listBox->resize(LOCPLINT->localState->getWanderingHeroes().size());
listBox->reset();
if (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));
}
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->reset();
if (LOCPLINT->localState->getCurrentTown())
select(LOCPLINT->localState->getCurrentTown());
CList::update();
}

View File

@ -77,6 +77,9 @@ protected:
virtual std::shared_ptr<CIntObject> createItem(size_t index) = 0;
/// should be called when list is invalidated
void update();
public:
/// functions that will be called when selection changes
CFunctionList<void()> onSelect;
@ -87,8 +90,6 @@ public:
void setScrollUpButton(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
void selectIndex(int which);
@ -137,7 +138,10 @@ public:
void select(const CGHeroInstance * hero = nullptr);
/// 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
@ -167,6 +171,9 @@ public:
void select(const CGTownInstance * town = nullptr);
/// 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)
{
GH.events().dispatchShowPopup(GH.getCursorPosition());
hapticFeedback();
if (GH.windows().isTopWindowPopup())
{
hapticFeedback();
state = TouchState::TAP_DOWN_LONG;
}
}
}
}

View File

@ -305,6 +305,7 @@ CKeyShortcut::CKeyShortcut()
CKeyShortcut::CKeyShortcut(EShortcut 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
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
static const double minimalScaling = 50;
Point renderResolution = getActualRenderResolution();
Point renderResolution = getRenderResolution();
double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
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 renderResolution = getActualRenderResolution();
Point renderResolution = getRenderResolution();
double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
@ -99,7 +99,7 @@ Point ScreenHandler::getPreferredLogicalResolution() const
return logicalResolution;
}
Point ScreenHandler::getActualRenderResolution() const
Point ScreenHandler::getRenderResolution() const
{
assert(mainRenderer != nullptr);

View File

@ -39,9 +39,6 @@ class ScreenHandler final : public IScreenHandler
/// This value is what player views as window size
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;
/// Returns index of display on which window should be created
@ -86,6 +83,9 @@ public:
/// Fills screen with black color, erasing any existing content
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(int displayIndex) const;
std::tuple<int, int> getSupportedScalingRange() const final;

View File

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

View File

@ -15,6 +15,7 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../adventureMap/CResDataBar.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
@ -638,6 +639,11 @@ void CKingdomInterface::townChanged(const CGTownInstance *town)
townList->townChanged(town);
}
void CKingdomInterface::heroRemoved()
{
tabArea->reset();
}
void CKingdomInterface::updateGarrisons()
{
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)
{
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);
return hero;
}
@ -745,10 +752,11 @@ void CKingdTownList::updateGarrisons()
std::shared_ptr<CIntObject> CKingdTownList::createTownItem(size_t index)
{
ui32 picCount = 4; // OVSLOT contains 4 images
size_t townsCount = LOCPLINT->cb->howManyTowns();
if(index < townsCount)
return std::make_shared<CTownItem>(LOCPLINT->cb->getTownBySerial((int)index));
auto townsList = LOCPLINT->localState->getOwnedTowns();
if(index < townsList.size())
return std::make_shared<CTownItem>(townsList[index]);
else
return std::make_shared<CAnimImage>("OVSLOT", (index-2) % picCount );
}

View File

@ -246,6 +246,7 @@ public:
CKingdomInterface();
void townChanged(const CGTownInstance *town);
void heroRemoved();
void updateGarrisons() override;
void artifactRemoved(const ArtifactLocation &artLoc) 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()
{
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
{
std::string txt;
if(dst->ID == Obj::HERO)
std::pair<SlotID, SlotID> toMerge;
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.
boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->objects[crid]->getNamePluralTranslated() : CGI->creh->objects[crid]->getNameSingularTranslated());
LOCPLINT->cb->mergeStacks( dst, dst, toMerge.first, toMerge.second);
}
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);
return;
LOCPLINT->showInfoDialog(txt);
return;
}
}
onRecruit(crid, slider->getValue());

View File

@ -224,25 +224,19 @@ void GeneralOptionsTab::updateResolutionSelector()
std::shared_ptr<CButton> resolutionButton = widget<CButton>("resolutionButton");
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();
if (resolutionLabel)
resolutionLabel->setText(resolutionToLabelString(GH.screenDimensions().x, GH.screenDimensions().y));
}
else
{
const auto & currentResolution = settings["video"]["resolution"];
if (resolutionButton)
else
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()
@ -370,6 +364,11 @@ void GeneralOptionsTab::setGameScaling(int index)
gameRes["scaling"].Float() = scaling;
widget<CLabel>("scalingLabel")->setText(scalingToLabelString(scaling));
GH.dispatchMainThread([](){
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.onScreenResize();
});
}
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/CGameStateCampaign.cpp
${MAIN_LIB_DIR}/gameState/InfoAboutArmy.cpp
${MAIN_LIB_DIR}/gameState/TavernHeroesPool.cpp
${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.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/InfoAboutArmy.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}/logging/CBasicLogConfigurator.h

View File

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

View File

@ -9,25 +9,25 @@
{
"DATA/" :
[
{"type" : "lod", "path" : "Data/H3ab_bmp.lod"},
{"type" : "lod", "path" : "Data/H3bitmap.lod"},
{"type" : "lod", "path" : "Data/h3abp_bm.lod"}, // Polish version of H3 only
{"type" : "lod", "path" : "Data/H3pbitma.lod"}, // Polish version of H3 only
{"type" : "lod", "path" : "Data/H3ab_bmp.lod"}, // Contains H3:AB data
{"type" : "lod", "path" : "Data/h3abp_bm.lod"}, // Localized versions only, contains H3:AB patch data
{"type" : "lod", "path" : "Data/H3bitmap.lod"}, // Contains H3:SoD data (overrides H3:AB data)
{"type" : "lod", "path" : "Data/H3pbitma.lod"}, // Localized versions only, contains H3:SoD patch data
{"type" : "dir", "path" : "Data"}
],
"SPRITES/":
[
{"type" : "lod", "path" : "Data/H3ab_spr.lod"},
{"type" : "lod", "path" : "Data/H3sprite.lod"},
{"type" : "lod", "path" : "Data/h3abp_sp.lod"}, // Polish version of H3 only
{"type" : "lod", "path" : "Data/H3psprit.lod"}, // Polish version of H3 only
{"type" : "lod", "path" : "Data/H3ab_spr.lod"}, // Contains H3:AB data
{"type" : "lod", "path" : "Data/H3sprite.lod"}, // Localized versions only, contains H3:AB patch data
{"type" : "lod", "path" : "Data/h3abp_sp.lod"}, // Contains H3:SoD data (overrides H3:AB data)
// {"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"}
],
"SOUNDS/":
[
{"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-cd2.snd"},
//WoG have overriden sounds with .82m extension in Data
{"type" : "dir", "path" : "Data", "depth": 0}
],

View File

@ -311,6 +311,8 @@
"accumulateWhenNeutral" : false,
// if enabled, dwellings owned by players will accumulate creatures
"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" :

View File

@ -63,12 +63,12 @@
},
"language" : {
"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"
},
"gameDataLanguage" : {
"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"
},
"lastSave" : {

View File

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

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>745</width>
<height>389</height>
<height>397</height>
</rect>
</property>
<property name="windowTitle">
@ -96,7 +96,7 @@
<item>
<widget class="QStackedWidget" name="installerTabs">
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="pageLanguageSelect">
<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>
</property>
<property name="wordWrap">
<bool>true</bool>
<bool>false</bool>
</property>
</widget>
</item>
@ -638,7 +638,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
<string>Heroes III Translation</string>
</property>
<property name="wordWrap">
<bool>true</bool>
<bool>false</bool>
</property>
</widget>
</item>
@ -699,10 +699,10 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
</font>
</property>
<property name="text">
<string>High Definition Support</string>
<string>Interface Improvements</string>
</property>
<property name="wordWrap">
<bool>true</bool>
<bool>false</bool>
</property>
</widget>
</item>
@ -724,7 +724,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
<string>In The Wake of Gods</string>
</property>
<property name="wordWrap">
<bool>true</bool>
<bool>false</bool>
</property>
</widget>
</item>
@ -769,7 +769,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
</sizepolicy>
</property>
<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 name="wordWrap">
<bool>true</bool>

View File

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

View File

@ -280,12 +280,6 @@ void CSettingsView::on_comboBoxDisplayIndex_currentIndexChanged(int 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)
{
Settings node = settings.write["server"]["friendlyAI"];
@ -500,6 +494,19 @@ void CSettingsView::on_spinBoxFramerateLimit_valueChanged(int 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)
{

View File

@ -35,7 +35,6 @@ public slots:
private slots:
void on_comboBoxResolution_currentTextChanged(const QString & arg1);
void on_comboBoxFullScreen_currentIndexChanged(int index);
void on_comboBoxPlayerAI_currentTextChanged(const QString & arg1);
void on_comboBoxFriendlyAI_currentTextChanged(const QString & arg1);
void on_comboBoxNeutralAI_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_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1);
void on_comboBoxAlliedPlayerAI_currentTextChanged(const QString &arg1);
void on_checkBoxAutoSavePrefix_stateChanged(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>
<translation>33</translation>
</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="740"/>
<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>
<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>
<translation>3800x600以上分辨率支持</translation>
<translation type="vanished">3800x600以上分辨率支持</translation>
</message>
<message>
<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>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source>
<translation></translation>
<translation type="vanished"></translation>
</message>
<message>
<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>
<translation type="unfinished"></translation>
</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="740"/>
<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>
<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>
</message>
<message>
@ -780,11 +785,6 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
<source>Heroes III Translation</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>
<source>In The Wake of Gods</source>

View File

@ -257,7 +257,7 @@
<translation>Impressions écran</translation>
</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>
<translation> %p% (%v Ko sur %m Ko)</translation>
</message>
@ -780,9 +780,8 @@ Mode exclusif plein écran - le jeu couvrira l&quot;intégralité de votre écra
</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<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>
</message>
<message>
@ -860,7 +859,7 @@ Heroes® of Might and Magic® III HD n&quot;est actuellement pas pris en charge
<message>
<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>
<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>
<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>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>High Definition Support</source>
<translation>Support de Haute Définition</translation>
<translation type="vanished">Support de Haute Définition</translation>
</message>
<message>
<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>
<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>
<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>
<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>

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>
<translation>Automatische Erkennung der Sprache fehlgeschlagen. Bitte wählen Sie die Sprache Ihrer Heroes III Kopie</translation>
</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="740"/>
<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>
<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>
<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>
<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>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<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>
<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>
<translation>Automatyczna detekcja języka nie powiodła się. Proszę wybrać język twojego Heroes III</translation>
</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="740"/>
<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>
<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>
<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>
<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>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source>
<translation>Wsparcie High Definition</translation>
<translation type="vanished">Wsparcie High Definition</translation>
</message>
<message>
<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>
<translation>Язык Героев III не был определен. Пожалуйста, выберите язык вашей копии Героев III</translation>
</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="740"/>
<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>
<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>
<translation>Установить поддержку запуска Героев III в разрешениях, отличных от 800x600</translation>
<translation type="vanished">Установить поддержку запуска Героев III в разрешениях, отличных от 800x600</translation>
</message>
<message>
<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>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source>
<translation>Поддержка высоких разрешений</translation>
<translation type="vanished">Поддержка высоких разрешений</translation>
</message>
<message>
<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>
<translation>Se ha detectado con éxito el idioma de tu Heroes III.</translation>
</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>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="127"/>
<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>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<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>
<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>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
<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>
<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>
<translation>Не вдалося визначити мову гри. Будь ласка, виберіть мову вашої копії Heroes III</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>Interface Improvements</source>
<translation>Удосконалення нтерфейсу</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
<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>
<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>
<translation>Встановити підтримку для гри в Heroes III у роздільних здатностях, більших за 800x600</translation>
<translation type="vanished">Встановити підтримку для гри в Heroes III у роздільних здатностях, більших за 800x600</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
@ -821,9 +830,8 @@ Heroes® of Might and Magic® III HD наразі не підтримуєтьс
<translation>Переклад Heroes III</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
<source>High Definition Support</source>
<translation>Підтримка високих роздільних здатностей</translation>
<translation type="vanished">Підтримка високих роздільних здатностей</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>

View File

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

View File

@ -13,6 +13,7 @@
#include "gameState/CGameState.h"
#include "gameState/InfoAboutArmy.h"
#include "gameState/SThievesGuildInfo.h"
#include "gameState/TavernHeroesPool.h"
#include "CGeneralTextHandler.h"
#include "StartInfo.h" // for StartInfo
#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
{
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);
//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);
if(townOrTavern->ID == Obj::TAVERN || (town && town->hasBuilt(BuildingID::TAVERN)))
{
range::copy(gs->players[*player].availableHeroes, std::back_inserter(ret));
vstd::erase_if(ret, [](const CGHeroInstance * h) {
return h == nullptr;
});
}
return gs->heroesPool->getHeroesFor(*player);
return ret;
}

View File

@ -108,7 +108,6 @@ public:
// 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
// virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
// const CTown *getNativeTown(PlayerColor color) const;
//from gs
// const TeamState *getTeam(TeamID teamID) const;
@ -206,7 +205,6 @@ public:
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 bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
virtual const CTown *getNativeTown(PlayerColor color) const;
//from gs
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(heroes, other.heroes);
std::swap(towns, other.towns);
std::swap(availableHeroes, other.availableHeroes);
std::swap(dwellings, other.dwellings);
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::vector<ConstTransitivePtr<CGHeroInstance> > heroes;
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<QuestInfo> quests; //store info about all received quests
@ -74,7 +73,6 @@ public:
h & status;
h & heroes;
h & towns;
h & availableHeroes;
h & dwellings;
h & quests;
h & visitedObjects;

View File

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

View File

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

View File

@ -40,6 +40,7 @@
VCMI_LIB_NAMESPACE_BEGIN
const HeroTypeID HeroTypeID::NONE = HeroTypeID(-1);
const ObjectInstanceID ObjectInstanceID::NONE = ObjectInstanceID(-1);
const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
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)
DLL_LINKAGE static const ObjectInstanceID NONE;
friend class CGameInfoCallback;
friend class CNonConstInfoCallback;
};
@ -357,9 +359,11 @@ class PlayerColor : public BaseForID<PlayerColor, ui8>
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 CANNOT_DETERMINE; //253
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::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" },
{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_TOTAL_CAP, "heroes", "perPlayerTotalCap" },
{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" },

View File

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

View File

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

View File

@ -36,7 +36,7 @@ public:
virtual void visitSetMana(SetMana & pack) {}
virtual void visitSetMovePoints(SetMovePoints & pack) {}
virtual void visitFoWChange(FoWChange & pack) {}
virtual void visitSetAvailableHeroes(SetAvailableHeroes & pack) {}
virtual void visitSetAvailableHeroes(SetAvailableHero & pack) {}
virtual void visitGiveBonus(GiveBonus & pack) {}
virtual void visitChangeObjPos(ChangeObjPos & pack) {}
virtual void visitPlayerEndsGame(PlayerEndsGame & pack) {}
@ -162,4 +162,4 @@ public:
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/CObstacleInstance.h"
#include "gameState/EVictoryLossCheckResult.h"
#include "gameState/TavernSlot.h"
#include "gameState/QuestInfo.h"
#include "mapObjects/CGHeroInstance.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)
i.clear();
army.clear();
}
void applyGs(CGameState * gs);
TavernHeroSlot slotID;
TavernSlotRole roleID;
PlayerColor player;
si32 hid[GameConstants::AVAILABLE_HEROES_PER_PLAYER]; //-1 if no hero
CSimpleArmy army[GameConstants::AVAILABLE_HEROES_PER_PLAYER];
HeroTypeID hid; //HeroTypeID::NONE if no hero
CSimpleArmy army;
virtual void visitTyped(ICPackVisitor & visitor) override;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & slotID;
h & roleID;
h & player;
h & hid;
h & army;
@ -692,7 +696,7 @@ struct DLL_LINKAGE HeroRecruited : public CPackForClient
{
void applyGs(CGameState * gs) const;
si32 hid = -1; //subID of hero
HeroTypeID hid; //subID of hero
ObjectInstanceID tid;
ObjectInstanceID boatId;
int3 tile;
@ -2437,12 +2441,12 @@ struct DLL_LINKAGE SetFormation : public CPackForServer
struct DLL_LINKAGE HireHero : public CPackForServer
{
HireHero() = default;
HireHero(si32 HID, const ObjectInstanceID & TID)
HireHero(HeroTypeID HID, const ObjectInstanceID & TID)
: hid(HID)
, tid(TID)
{
}
si32 hid = 0; //available hero serial
HeroTypeID hid; //available hero serial
ObjectInstanceID tid; //town (tavern) id
PlayerColor player;

View File

@ -20,6 +20,7 @@
#include "spells/CSpellHandler.h"
#include "CCreatureHandler.h"
#include "gameState/CGameState.h"
#include "gameState/TavernHeroesPool.h"
#include "CStack.h"
#include "battle/BattleInfo.h"
#include "CTownHandler.h"
@ -151,7 +152,7 @@ void FoWChange::visitTyped(ICPackVisitor & visitor)
visitor.visitFoWChange(*this);
}
void SetAvailableHeroes::visitTyped(ICPackVisitor & visitor)
void SetAvailableHero::visitTyped(ICPackVisitor & visitor)
{
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);
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);
}
gs->heroesPool->setHeroForPlayer(player, slotID, hid, army, roleID);
}
void GiveBonus::applyGs(CGameState *gs)
@ -1132,7 +1124,16 @@ void RemoveObject::applyGs(CGameState *gs)
PlayerState * p = gs->getPlayerState(beatenHero->tempOwner);
gs->map->heroesOnMap -= 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
vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi)
{
@ -1150,11 +1151,8 @@ void RemoveObject::applyGs(CGameState *gs)
beatenHero->inTownGarrison = false;
}
//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;
//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
{
assert(vstd::contains(gs->hpool.heroesPool, hid));
CGHeroInstance *h = gs->hpool.heroesPool[hid];
CGHeroInstance *h = gs->heroesPool->takeHeroFromPool(hid);
CGTownInstance *t = gs->getTown(tid);
PlayerState *p = gs->getPlayerState(player);
@ -1411,7 +1408,6 @@ void HeroRecruited::applyGs(CGameState * gs) const
}
}
gs->hpool.heroesPool.erase(hid);
if(h->id == ObjectInstanceID())
{
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);
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());
continue;
}
hero->setMovementPoints(h.move);
hero->mana = h.mana;
}
gs->heroesPool->onNewDay();
for(const auto & re : res)
{
assert(re.first < PlayerColor::PLAYER_LIMIT);

View File

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

View File

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

View File

@ -12,6 +12,7 @@
#include "EVictoryLossCheckResult.h"
#include "InfoAboutArmy.h"
#include "TavernHeroesPool.h"
#include "CGameStateCampaign.h"
#include "SThievesGuildInfo.h"
@ -102,81 +103,6 @@ static CGObjectInstance * createObject(const Obj & id, int subid, const int3 & p
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)
{
const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
@ -459,6 +385,7 @@ int CGameState::getDate(Date::EDateType mode) const
CGameState::CGameState()
{
gs = this;
heroesPool = std::make_unique<TavernHeroesPool>();
applier = std::make_shared<CApplier<CBaseForGSApply>>();
registerTypesClientPacks1(*applier);
registerTypesClientPacks2(*applier);
@ -469,9 +396,6 @@ CGameState::~CGameState()
{
map.dellNull();
curB.dellNull();
for(auto ptr : hpool.heroesPool) // clean hero pool
ptr.second.dellNull();
}
void CGameState::preInit(Services * services)
@ -951,8 +875,7 @@ void CGameState::initHeroes()
if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID)))
continue;
ph->initHero(getRandomGenerator());
hpool.heroesPool[ph->subID] = ph;
hpool.pavailable[ph->subID] = 0xff;
heroesPool->addHeroToPool(ph);
heroesToCreate.erase(ph->type->getId());
map->allHeroes[ph->subID] = ph;
@ -965,14 +888,11 @@ void CGameState::initHeroes()
int typeID = htype.getNum();
map->allHeroes[typeID] = vhi;
hpool.heroesPool[typeID] = vhi;
hpool.pavailable[typeID] = 0xff;
heroesPool->addHeroToPool(vhi);
}
for(auto & elem : map->disposedHeroes)
{
hpool.pavailable[elem.heroId] = elem.players;
}
heroesPool->setAvailability(elem.heroId, elem.players);
if (campaign)
campaign->initHeroes();
@ -2067,17 +1987,6 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
#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()
{
buildGlobalTeamPlayerTree();

View File

@ -29,6 +29,7 @@ struct EventCondition;
struct CampaignTravel;
class CStackInstance;
class CGameStateCampaign;
class TavernHeroesPool;
struct SThievesGuildInfo;
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
{
friend class CGameStateCampaign;
public:
struct DLL_LINKAGE 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
//we have here all heroes available on this map that are not hired
std::unique_ptr<TavernHeroesPool> heroesPool;
CGameState();
virtual ~CGameState();
@ -142,7 +128,6 @@ public:
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
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(const CGObjectInstance * obj, const std::optional<PlayerColor> & player) const override;
@ -169,7 +154,7 @@ public:
h & map;
h & players;
h & teams;
h & hpool;
h & heroesPool;
h & globalEffects;
h & rand;
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("hillFort", HillFortInstanceConstructor);
SET_HANDLER_CLASS("shipyard", ShipyardInstanceConstructor);
SET_HANDLER_CLASS("monster", CreatureInstanceConstructor);
SET_HANDLER_CLASS("resource", ResourceInstanceConstructor);
SET_HANDLER_CLASS("static", CObstacleConstructor);
SET_HANDLER_CLASS("", CObstacleConstructor);
@ -73,7 +75,6 @@ CObjectClassesHandler::CObjectClassesHandler()
SET_HANDLER("artifact", CGArtifact);
SET_HANDLER("borderGate", CGBorderGate);
SET_HANDLER("borderGuard", CGBorderGuard);
SET_HANDLER("monster", CGCreature);
SET_HANDLER("denOfThieves", CGDenOfthieves);
SET_HANDLER("event", CGEvent);
SET_HANDLER("garrison", CGGarrison);
@ -87,7 +88,6 @@ CObjectClassesHandler::CObjectClassesHandler()
SET_HANDLER("pandora", CGPandoraBox);
SET_HANDLER("prison", CGHeroInstance);
SET_HANDLER("questGuard", CGQuestGuard);
SET_HANDLER("resource", CGResource);
SET_HANDLER("scholar", CGScholar);
SET_HANDLER("seerHut", CGSeerHut);
SET_HANDLER("sign", CGSignBottle);

View File

@ -35,6 +35,26 @@ bool CObstacleConstructor::isStaticObject()
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)
{
VLC->modh->identifiers.requestIdentifier("faction", input["faction"], [&](si32 index)
@ -86,6 +106,16 @@ void CTownInstanceConstructor::randomizeObject(CGTownInstance * object, CRandomG
object->appearance = templ;
}
bool CTownInstanceConstructor::hasNameTextID() const
{
return true;
}
std::string CTownInstanceConstructor::getNameTextID() const
{
return faction->getNameTextID();
}
void CHeroInstanceConstructor::initTypeData(const JsonNode & input)
{
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)
{
layer = EPathfindingLayer::SAIL;

View File

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

View File

@ -324,6 +324,21 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
{
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);
if(!slot.validSlot()) //no available slot
{

View File

@ -56,10 +56,10 @@ struct DLL_LINKAGE DisposedHero
{
DisposedHero();
ui32 heroId;
ui32 portrait; /// The portrait id of the hero, -1 is default.
HeroTypeID heroId;
HeroTypeID portrait; /// The portrait id of the hero, -1 is default.
std::string name;
ui8 players; /// Who can hire this hero (bitfield).
PlayerColor::Mask players; /// Who can hire this hero (bitfield).
template <typename Handler>
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, ShipyardInstanceConstructor>();
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> >()
@ -239,7 +241,7 @@ void registerTypesClientPacks1(Serializer &s)
s.template registerType<CPackForClient, SetMana>();
s.template registerType<CPackForClient, SetMovePoints>();
s.template registerType<CPackForClient, FoWChange>();
s.template registerType<CPackForClient, SetAvailableHeroes>();
s.template registerType<CPackForClient, SetAvailableHero>();
s.template registerType<CPackForClient, GiveBonus>();
s.template registerType<CPackForClient, ChangeObjPos>();
s.template registerType<CPackForClient, PlayerEndsGame>();

View File

@ -14,6 +14,7 @@
#include "../StartInfo.h"
#include "../gameState/CGameState.h"
#include "../gameState/CGameStateCampaign.h"
#include "../gameState/TavernHeroesPool.h"
#include "../mapping/CMap.h"
#include "../CModHandler.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);
instance.setPosition(guardPos - object.getPosition());
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;
}

View File

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

View File

@ -250,7 +250,10 @@ void Timed::serializeJsonUnitEffect(JsonSerializeFormat & handler)
auto guard = handler.enterStruct(p.first);
const JsonNode & bonusNode = handler.getCurrent();
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 "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/FileInfo.h"
@ -35,7 +43,6 @@
#include "../lib/GameSettings.h"
#include "../lib/battle/BattleInfo.h"
#include "../lib/CondSh.h"
#include "ServerNetPackVisitors.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/mapping/CMap.h"
#include "../lib/mapping/CMapService.h"
@ -44,9 +51,6 @@
#include "../lib/ScopeGuard.h"
#include "../lib/CSoundBase.h"
#include "../lib/TerrainHandler.h"
#include "CGameHandler.h"
#include "ServerSpellCastEnvironment.h"
#include "CVCMIServer.h"
#include "../lib/CCreatureSet.h"
#include "../lib/CThreadHelper.h"
#include "../lib/GameConstants.h"
@ -294,6 +298,11 @@ events::EventBus * CGameHandler::eventBus() const
return serverEventBus.get();
}
CVCMIServer * CGameHandler::gameLobby() const
{
return lobby;
}
void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill)
{
changeSecSkill(hero, skill, 1, 0);
@ -868,24 +877,12 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
std::set<PlayerColor> playerColors = {finishingBattle->loser, finishingBattle->victor};
checkVictoryLossConditions(playerColors);
if (result.result == BattleResult::SURRENDER || result.result == BattleResult::ESCAPE) //loser has escaped or surrendered
{
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 (result.result == BattleResult::SURRENDER)
heroPool->onHeroSurrendered(finishingBattle->loser, finishingBattle->loserHero);
if (const CGHeroInstance *another = getPlayerState(finishingBattle->loser)->availableHeroes.at(0))
sah.hid[1] = another->subID;
else
sah.hid[1] = -1;
if (result.result == BattleResult::ESCAPE)
heroPool->onHeroEscaped(finishingBattle->loser, finishingBattle->loserHero);
sendAndApply(&sah);
}
if (result.winner != 2 && finishingBattle->winnerHero && finishingBattle->winnerHero->stacks.empty()
&& (!finishingBattle->winnerHero->commander || !finishingBattle->winnerHero->commander->alive))
{
@ -893,20 +890,7 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
sendAndApply(&ro);
if (VLC->settings()->getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS))
{
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);
}
heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero);
}
finishingBattle.reset();
@ -1240,7 +1224,7 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
if(playerConnection != playerConnections.second.end())
{
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)
: lobby(lobby)
, heroPool(std::make_unique<HeroPoolProcessor>(this))
, playerMessages(std::make_unique<PlayerMessageProcessor>(this))
, complainNoCreatures("No creatures to split")
, complainNotEnoughCreatures("Cannot split that stack, not enough creatures!")
, 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)
{
if (elem.first == PlayerColor::NEUTRAL)
@ -1797,29 +1762,7 @@ void CGameHandler::newTurn()
hadGold.insert(playerGold);
if (newWeek) //new heroes in tavern
{
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);
}
heroPool->onNewWeek(elem.first);
n.res[elem.first] = elem.second.resources;
@ -2709,14 +2652,6 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st
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)
{
sendAndApply(bonus);
@ -2927,10 +2862,8 @@ bool CGameHandler::isPlayerOwns(CPackForServer * pack, ObjectInstanceID id)
void CGameHandler::throwNotAllowedAction(CPackForServer * pack)
{
if(pack->c)
{
SystemMessage temp_message("You are not allowed to perform this action!");
pack->c->sendPack(&temp_message);
}
playerMessages->sendSystemMessage(pack->c, "You are not allowed to perform this action!");
logNetwork->error("Player is not allowed to perform this action!");
throw ExceptionNotAllowedAction();
}
@ -2940,11 +2873,9 @@ void CGameHandler::wrongPlayerMessage(CPackForServer * pack, PlayerColor expecte
std::ostringstream oss;
oss << "You were identified as player " << getPlayerAt(pack->c) << " while expecting " << expectedplayer;
logNetwork->error(oss.str());
if(pack->c)
{
SystemMessage temp_message(oss.str());
pack->c->sendPack(&temp_message);
}
playerMessages->sendSystemMessage(pack->c, oss.str());
}
void CGameHandler::throwOnWrongOwner(CPackForServer * pack, ObjectInstanceID id)
@ -3628,7 +3559,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
sendAndApply(&fw);
if(t->visitingHero)
objectVisited(t, t->visitingHero);
visitCastleObjects(t, t->visitingHero);
if(t->garrisonHero)
visitCastleObjects(t, t->garrisonHero);
@ -3659,13 +3590,6 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
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)
{
const CGDwelling * dw = static_cast<const CGDwelling *>(getObj(objid));
@ -4383,93 +4307,6 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, ui8 formation)
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)
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
@ -4977,140 +4814,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
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)
{
switch(ba.actionType)
@ -5473,7 +5176,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n)
bool CGameHandler::complain(const std::string &problem)
{
sendMessageToAll("Server encountered a problem: " + problem);
playerMessages->broadcastSystemMessage("Server encountered a problem: " + problem);
logGlobal->error(problem);
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)
{
BattleObstaclesChanged obsRem;
@ -7394,3 +6878,11 @@ void CGameHandler::createObject(const int3 & visitablePosition, Obj type, int32_
no.targetPos = visitablePosition;
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
class HeroPoolProcessor;
class CGameHandler;
class CVCMIServer;
class CBaseForGHApply;
class PlayerMessageProcessor;
struct PlayerStatus
{
@ -97,13 +99,18 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En
CVCMIServer * lobby;
std::shared_ptr<CApplier<CBaseForGHApply>> applier;
std::unique_ptr<boost::thread> battleThread;
public:
std::unique_ptr<HeroPoolProcessor> heroPool;
using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
//use enums as parameters, because doMove(sth, true, false, true) is not readable
enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS};
enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST};
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
PlayerStatuses states; //player color -> player state
@ -119,6 +126,7 @@ public:
const GameCb * game() const override;
vstd::CLoggerBase * logger() const override;
events::EventBus * eventBus() const override;
CVCMIServer * gameLobby() const;
bool isValidObject(const CGObjectInstance *obj) const;
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 setBattleResult(BattleResult::EResult resultType, int victoriusSide);
CGameHandler() = default;
CGameHandler(CVCMIServer * lobby);
~CGameHandler();
@ -230,7 +239,6 @@ public:
PlayerColor getPlayerAt(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();
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)
@ -240,7 +248,6 @@ public:
void removeObstacle(const CObstacleInstance &obstacle);
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 setFormation( ObjectInstanceID hid, ui8 formation );
bool tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2);
@ -283,7 +290,12 @@ public:
h & QID;
h & states;
h & finishingBattle;
h & heroPool;
h & getRandomGenerator();
h & playerMessages;
if (!h.saving)
deserializationFix();
#if SCRIPTING_ENABLED
JsonNode scriptsState;
@ -295,8 +307,6 @@ public:
#endif
}
void sendMessageToAll(const std::string &message);
void sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message);
void sendToAllClients(CPackForClient * pack);
void sendAndApply(CPackForClient * pack) override;
void applyAndSend(CPackForClient * pack);
@ -346,7 +356,11 @@ public:
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);
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();
@ -355,6 +369,8 @@ public:
scripting::Pool * getContextPool() const override;
#endif
std::list<PlayerColor> generatePlayerTurnOrder() const;
friend class CVCMIServer;
private:
std::unique_ptr<events::EventBus> serverEventBus;
@ -363,16 +379,12 @@ private:
#endif
void reinitScripting();
void deserializationFix();
std::list<PlayerColor> generatePlayerTurnOrder() const;
void makeStackDoNothing(const CStack * next);
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 complainNotEnoughCreatures;
const std::string complainInvalidSlot;

View File

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

View File

@ -39,6 +39,7 @@
#include "../lib/VCMI_Lib.h"
#include "../lib/VCMIDirs.h"
#include "CGameHandler.h"
#include "PlayerMessageProcessor.h"
#include "../lib/mapping/CMapInfo.h"
#include "../lib/GameConstants.h"
#include "../lib/logging/CBasicLogConfigurator.h"
@ -605,7 +606,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
if(gh && si && state == EServerState::GAMEPLAY)
{
gh->playerMessage(playerSettings->color, playerLeftMsgText, ObjectInstanceID{});
gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText);
gh->connections[playerSettings->color].insert(hostClient);
startAiPack.players.push_back(playerSettings->color);
}
@ -633,7 +634,7 @@ void CVCMIServer::reconnectPlayer(int connId)
continue;
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);
}
@ -822,7 +823,7 @@ void CVCMIServer::setPlayer(PlayerColor clickedColor)
void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
{
PlayerSettings & s = si->playerInfos[player];
si16 & cur = s.castle;
FactionID & cur = s.castle;
auto & allowed = getPlayerInfo(player.getNum()).allowedFactions;
const bool allowRandomTown = getPlayerInfo(player.getNum()).isFactionRandom;
@ -856,7 +857,7 @@ void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
else
{
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);
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 "CGameHandler.h"
#include "HeroPoolProcessor.h"
#include "PlayerMessageProcessor.h"
#include "../lib/IGameCallback.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/gameState/CGameState.h"
@ -246,12 +249,10 @@ void ApplyGhNetPackVisitor::visitSetFormation(SetFormation & pack)
void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack)
{
const CGObjectInstance * obj = gh.getObj(pack.tid);
const CGTownInstance * town = dynamic_ptr_cast<CGTownInstance>(obj);
if(town && PlayerRelations::ENEMIES == gh.getPlayerRelations(obj->tempOwner, gh.getPlayerAt(pack.c)))
gh.throwAndComplain(&pack, "Can't buy hero in enemy town!");
if (!gh.hasPlayerAt(pack.player, pack.c))
gh.throwAndComplain(&pack, "No such pack.player!");
result = gh.hireHero(obj, pack.hid, pack.player);
result = gh.heroPool->hireHero(pack.tid, pack.hid, pack.player);
}
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
gh.throwOnWrongPlayer(&pack, pack.player);
gh.playerMessage(pack.player, pack.text, pack.currObj);
gh.playerMessages->playerMessage(pack.player, pack.text, pack.currObj);
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;
}
};