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:
commit
5882cbae78
@ -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 )
|
||||
|
123
ChangeLog.md
123
ChangeLog.md
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,6 +305,7 @@ CKeyShortcut::CKeyShortcut()
|
||||
|
||||
CKeyShortcut::CKeyShortcut(EShortcut key)
|
||||
: assignedKey(key)
|
||||
, shortcutPressed(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 );
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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 ]
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -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}
|
||||
],
|
||||
|
@ -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" :
|
||||
|
@ -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" : {
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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>自动检测英雄无敌3语言失败。请手动选择英雄无敌3语言</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>安装英雄无敌3的800x600以上分辨率支持</translation>
|
||||
<translation type="vanished">安装英雄无敌3的800x600以上分辨率支持</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"/>
|
||||
|
@ -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>
|
||||
|
@ -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"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"est actuellement pas pris en charge
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/>
|
||||
<source>If you don't have a copy of Heroes III installed, you can use our automatic installation tool 'vcmibuilder', which only requires the GoG.com Heroes III installer. Please visit our wiki for detailed instructions.</source>
|
||||
<translation>Si vous n"avez pas installé de copie de Heroes III, vous pouvez utiliser notre outil d"installation automatique "vcmibuilder", qui ne nécessite que le programme d"installation de GoG.com Heroes III. Veuillez visiter notre wiki pour des instructions détaillées.</translation>
|
||||
<translation>Si vous n"avez pas installé de copie de Heroes III, vous pouvez utiliser notre outil d"installation automatique "vcmibuilder", qui ne nécessite que le programme d"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"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"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"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 "Horn of the Abyss", a fan-made Heroes III expansion ported by the VCMI team</source>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -20,6 +20,11 @@ CRandomGenerator::CRandomGenerator()
|
||||
resetSeed();
|
||||
}
|
||||
|
||||
CRandomGenerator::CRandomGenerator(int seed)
|
||||
{
|
||||
setSeed(seed);
|
||||
}
|
||||
|
||||
void CRandomGenerator::setSeed(int seed)
|
||||
{
|
||||
rand.seed(seed);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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" },
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -160,7 +160,7 @@ public:
|
||||
h & x;
|
||||
h & y;
|
||||
h & w;
|
||||
h & h;
|
||||
h & this->h;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 -
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
142
lib/gameState/TavernHeroesPool.cpp
Normal file
142
lib/gameState/TavernHeroesPool.cpp
Normal 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
|
87
lib/gameState/TavernHeroesPool.h
Normal file
87
lib/gameState/TavernHeroesPool.h
Normal 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
|
35
lib/gameState/TavernSlot.h
Normal file
35
lib/gameState/TavernSlot.h
Normal 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
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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>();
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
397
server/HeroPoolProcessor.cpp
Normal file
397
server/HeroPoolProcessor.cpp
Normal 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);
|
||||
}
|
66
server/HeroPoolProcessor.h
Normal file
66
server/HeroPoolProcessor.h
Normal 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;
|
||||
}
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
523
server/PlayerMessageProcessor.cpp
Normal file
523
server/PlayerMessageProcessor.cpp
Normal 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);
|
||||
}
|
65
server/PlayerMessageProcessor.h
Normal file
65
server/PlayerMessageProcessor.h
Normal 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;
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user