mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-03 14:52:11 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
f4c0bee003
8
.github/workflows/github.yml
vendored
8
.github/workflows/github.yml
vendored
@ -146,7 +146,13 @@ jobs:
|
||||
HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
|
||||
if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
|
||||
run: |
|
||||
wget --progress=dot:giga https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip
|
||||
if [[ ${{github.repository_owner}} == vcmi ]]
|
||||
then
|
||||
data_url="https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip"
|
||||
else
|
||||
data_url="https://github.com/${{github.repository_owner}}/vcmi-test-data/releases/download/v1.0/h3_assets.zip"
|
||||
fi
|
||||
wget --progress=dot:giga "$data_url" -O h3_assets.zip
|
||||
7za x h3_assets.zip -p$HEROES_3_DATA_PASSWORD
|
||||
mkdir -p ~/.local/share/vcmi/
|
||||
mv h3_assets/* ~/.local/share/vcmi/
|
||||
|
@ -316,7 +316,7 @@ void BuildAnalyzer::updateDailyIncome()
|
||||
|
||||
if(mine)
|
||||
{
|
||||
dailyIncome[mine->producedResource.getNum()] += mine->producedQuantity;
|
||||
dailyIncome[mine->producedResource.getNum()] += mine->getProducedQuantity();
|
||||
}
|
||||
}
|
||||
|
||||
|
35
ChangeLog.md
35
ChangeLog.md
@ -1,11 +1,11 @@
|
||||
# 1.5.5 -> 1.6.0
|
||||
# 1.5.5 -> 1.6.0 (in development)
|
||||
|
||||
# General
|
||||
### General
|
||||
* Saved game size reduced by approximately 3 times, especially for large maps or games with a large number of mods.
|
||||
* Added option to start vcmi server on randomly selected TCP port
|
||||
* Fixed potential desynchronization between server and clients on randomization of map objects if client and server run on different operating systems
|
||||
|
||||
# Interface
|
||||
### Interface
|
||||
* Implemented spell quick selection panel in combat
|
||||
* The number of units resurrected using the Life Drain ability is now written to the combat log.
|
||||
* Fixed playback of audio stream with different formats from video files in some Heroes 3 versions
|
||||
@ -31,6 +31,35 @@
|
||||
* Added support for multiple music tracks for terrains on adventure map
|
||||
* Fixed several cases where vcmi will report errors in json without specifying filename of invalid file
|
||||
|
||||
# 1.5.5 -> 1.5.6
|
||||
|
||||
### Stability
|
||||
* Fixed possible crash on transferring hero to next campaign scenario if hero has combined artifact some components of which can be transferred
|
||||
* Fixed possible crash on transferring hero to next campaign scenario that has creature with faction limiter in his army
|
||||
* Fixed possible crash on application shutdown due to incorrect destruction order of UI entities
|
||||
|
||||
### Multiplayer
|
||||
* Mod compatibility issues when joining a lobby room now use color coding to make them less easy to miss.
|
||||
* Incompatible mods are now placed before compatible mods when joining lobby room.
|
||||
* Fixed text overflow in online lobby interface
|
||||
* Fixed jittering simultaneous turns slider after moving it twice over short period
|
||||
* Fixed non-functioning slider in invite to game room dialog
|
||||
|
||||
### Interface
|
||||
* Fixed some shortcuts that were not active during the enemy's turn, such as Thieves' Guild.
|
||||
* Game now correctly uses melee damage calculation when forcing a melee attack with a shooter.
|
||||
* Game will now close all open dialogs on start of our turn, to avoid bugs like locked right-click popups
|
||||
|
||||
### Map Objects
|
||||
* Spells the hero can't learn are no longer hidden when received from a rewardable object, such as the Pandora Box
|
||||
* Spells that cannot be learned are now displayed with gray text in the name of the spell.
|
||||
* Configurable objects with scouted state such as Witch Hut in HotA now correctly show their reward on right click after vising them but refusing to accept reward
|
||||
* Right-click tooltip on map dwelling now always shows produced creatures. Player that owns the dwelling can also see number of creatures available for recruit
|
||||
|
||||
### Modding
|
||||
* Fixed possible crash on invalid SPELL_LIKE_ATTACK bonus
|
||||
* Added compatibility check when loading maps with old names for boats
|
||||
|
||||
# 1.5.4 -> 1.5.5
|
||||
|
||||
* Fixed crash when advancing to the next scenario in campaigns when the hero not transferring has a combination artefact that can be transferred to the next scenario.
|
||||
|
@ -73,7 +73,11 @@
|
||||
"vcmi.lobby.sortDate" : "Sorts maps by change date",
|
||||
"vcmi.lobby.backToLobby" : "Return to lobby",
|
||||
"vcmi.lobby.author" : "Author",
|
||||
|
||||
"vcmi.lobby.handicap" : "Handicap",
|
||||
"vcmi.lobby.handicap.resource" : "Gives players appropriate resources to start with in addition to the normal starting resources. Negative values are allowed, but are limited to 0 in total (the player never starts with negative resources).",
|
||||
"vcmi.lobby.handicap.income" : "Changes the player's various incomes by the percentage. Is rounded up.",
|
||||
"vcmi.lobby.handicap.growth" : "Changes the growth rate of creatures in the towns owned by the player. Is rounded up.",
|
||||
|
||||
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
||||
"vcmi.lobby.login.username" : "Username:",
|
||||
"vcmi.lobby.login.connecting" : "Connecting...",
|
||||
|
@ -73,6 +73,10 @@
|
||||
"vcmi.lobby.sortDate" : "Ordnet Karten nach Änderungsdatum",
|
||||
"vcmi.lobby.backToLobby" : "Zur Lobby zurückkehren",
|
||||
"vcmi.lobby.author" : "Author",
|
||||
"vcmi.lobby.handicap" : "Handicap",
|
||||
"vcmi.lobby.handicap.resource" : "Gibt den Spielern entsprechende Ressourcen zum Start zusätzlich zu den normalen Startressourcen. Negative Werte sind erlaubt, werden aber insgesamt auf 0 begrenzt (der Spieler beginnt nie mit negativen Ressourcen).",
|
||||
"vcmi.lobby.handicap.income" : "Verändert die verschiedenen Einkommen des Spielers um den Prozentsatz. Wird aufgerundet.",
|
||||
"vcmi.lobby.handicap.growth" : "Verändert die Wachstumsrate der Kreaturen in den Städten, die der Spieler besitzt. Wird aufgerundet.",
|
||||
|
||||
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
||||
"vcmi.lobby.login.username" : "Benutzername:",
|
||||
|
@ -63,7 +63,7 @@
|
||||
"vcmi.mainMenu.serverClosing" : "Fechando...",
|
||||
"vcmi.mainMenu.hostTCP" : "Hospedar jogo TCP/IP",
|
||||
"vcmi.mainMenu.joinTCP" : "Entrar em jogo TCP/IP",
|
||||
|
||||
|
||||
"vcmi.lobby.filepath" : "Caminho do arquivo",
|
||||
"vcmi.lobby.creationDate" : "Data de criação",
|
||||
"vcmi.lobby.scenarioName" : "Nome do cenário",
|
||||
@ -237,6 +237,8 @@
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Pula a Música de Introdução}\n\nPermite ações durante a música de introdução que toca no início de cada batalha.",
|
||||
"vcmi.battleOptions.endWithAutocombat.hover": "Terminar a batalha",
|
||||
"vcmi.battleOptions.endWithAutocombat.help": "{Termina a batalha}\n\nO Combate Automático reproduz a batalha até o final instantâneo.",
|
||||
"vcmi.battleOptions.showQuickSpell.hover": "Mostrar Painel de Feitiço Rápido",
|
||||
"vcmi.battleOptions.showQuickSpell.help": "{Mostrar Painel de Feitiço Rápido}\n\nMostra um painel para seleção rápida de feitiços",
|
||||
|
||||
"vcmi.adventureMap.revisitObject.hover" : "Revisitar Objeto",
|
||||
"vcmi.adventureMap.revisitObject.help" : "{Revisitar Objeto}\n\nSe um herói estiver atualmente em um Objeto do Mapa, ele pode revisitar o local.",
|
||||
|
@ -171,40 +171,39 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
|
||||
adventureInt.reset(new AdventureMapInterface());
|
||||
}
|
||||
|
||||
void CPlayerInterface::closeAllDialogs()
|
||||
{
|
||||
// remove all active dialogs that do not expect query answer
|
||||
for (;;)
|
||||
{
|
||||
auto adventureWindow = GH.windows().topWindow<AdventureMapInterface>();
|
||||
auto infoWindow = GH.windows().topWindow<CInfoWindow>();
|
||||
|
||||
if(adventureWindow != nullptr)
|
||||
break;
|
||||
|
||||
if(infoWindow && infoWindow->ID != QueryID::NONE)
|
||||
break;
|
||||
|
||||
if (infoWindow)
|
||||
infoWindow->close();
|
||||
else
|
||||
GH.windows().popWindows(1);
|
||||
}
|
||||
|
||||
if(castleInt)
|
||||
castleInt->close();
|
||||
|
||||
castleInt = nullptr;
|
||||
}
|
||||
|
||||
void CPlayerInterface::playerEndsTurn(PlayerColor player)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
if (player == playerID)
|
||||
{
|
||||
makingTurn = false;
|
||||
|
||||
// remove all active dialogs that do not expect query answer
|
||||
for (;;)
|
||||
{
|
||||
auto adventureWindow = GH.windows().topWindow<AdventureMapInterface>();
|
||||
auto infoWindow = GH.windows().topWindow<CInfoWindow>();
|
||||
|
||||
if(adventureWindow != nullptr)
|
||||
break;
|
||||
|
||||
if(infoWindow && infoWindow->ID != QueryID::NONE)
|
||||
break;
|
||||
|
||||
if (infoWindow)
|
||||
infoWindow->close();
|
||||
else
|
||||
GH.windows().popWindows(1);
|
||||
}
|
||||
|
||||
if(castleInt)
|
||||
castleInt->close();
|
||||
|
||||
castleInt = nullptr;
|
||||
|
||||
// remove all pending dialogs that do not expect query answer
|
||||
vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){
|
||||
return window->ID == QueryID::NONE;
|
||||
});
|
||||
closeAllDialogs();
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,6 +285,7 @@ void CPlayerInterface::gamePause(bool pause)
|
||||
|
||||
void CPlayerInterface::yourTurn(QueryID queryID)
|
||||
{
|
||||
closeAllDialogs();
|
||||
CTutorialWindow::openWindowFirstTime(TutorialMode::TOUCH_ADVENTUREMAP);
|
||||
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
@ -1477,7 +1477,7 @@ void CPlayerInterface::update()
|
||||
return;
|
||||
|
||||
//if there are any waiting dialogs, show them
|
||||
if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->isBusy())
|
||||
if (makingTurn && !dialogs.empty() && !showingDialog->isBusy())
|
||||
{
|
||||
showingDialog->setBusy();
|
||||
GH.windows().pushWindow(dialogs.front());
|
||||
|
@ -197,6 +197,7 @@ public: // public interface for use by client via LOCPLINT access
|
||||
void performAutosave();
|
||||
void gamePause(bool pause);
|
||||
void endNetwork();
|
||||
void closeAllDialogs();
|
||||
|
||||
///returns true if all events are processed internally
|
||||
bool capturedAllEvents();
|
||||
|
@ -161,11 +161,6 @@ CServerHandler::CServerHandler()
|
||||
registerTypesLobbyPacks(*applier);
|
||||
}
|
||||
|
||||
void CServerHandler::setHighScoreCalc(const std::shared_ptr<HighScoreCalculation> &newHighScoreCalc)
|
||||
{
|
||||
campaignScoreCalculator = newHighScoreCalc;
|
||||
}
|
||||
|
||||
void CServerHandler::threadRunNetwork()
|
||||
{
|
||||
logGlobal->info("Starting network thread");
|
||||
@ -497,6 +492,14 @@ void CServerHandler::setPlayerName(PlayerColor color, const std::string & name)
|
||||
sendLobbyPack(lspn);
|
||||
}
|
||||
|
||||
void CServerHandler::setPlayerHandicap(PlayerColor color, Handicap handicap) const
|
||||
{
|
||||
LobbySetPlayerHandicap lsph;
|
||||
lsph.color = color;
|
||||
lsph.handicap = handicap;
|
||||
sendLobbyPack(lsph);
|
||||
}
|
||||
|
||||
void CServerHandler::setPlayerOption(ui8 what, int32_t value, PlayerColor player) const
|
||||
{
|
||||
LobbyChangePlayerOption lcpo;
|
||||
@ -655,7 +658,7 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
|
||||
break;
|
||||
case EStartMode::CAMPAIGN:
|
||||
if(si->campState->conqueredScenarios().empty())
|
||||
campaignScoreCalculator.reset();
|
||||
si->campState->highscoreParameters.clear();
|
||||
client->newGame(gameState);
|
||||
break;
|
||||
case EStartMode::LOAD_GAME:
|
||||
@ -686,12 +689,12 @@ HighScoreParameter CServerHandler::prepareHighScores(PlayerColor player, bool vi
|
||||
for(const CGTownInstance * t : playerState->towns)
|
||||
if(t->builtBuildings.count(BuildingID::GRAIL))
|
||||
param.hasGrail = true;
|
||||
param.allDefeated = true;
|
||||
param.allEnemiesDefeated = true;
|
||||
for (PlayerColor otherPlayer(0); otherPlayer < PlayerColor::PLAYER_LIMIT; ++otherPlayer)
|
||||
{
|
||||
auto ps = gs->getPlayerState(otherPlayer, false);
|
||||
if(ps && otherPlayer != player && !ps->checkVanquished())
|
||||
param.allDefeated = false;
|
||||
param.allEnemiesDefeated = false;
|
||||
}
|
||||
param.scenarioName = gs->getMapHeader()->name.toString();
|
||||
param.playerName = gs->getStartInfo()->playerInfos.find(player)->second.name;
|
||||
@ -756,19 +759,16 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
|
||||
if (!cs)
|
||||
ourCampaign = si->campState;
|
||||
|
||||
if(campaignScoreCalculator == nullptr)
|
||||
{
|
||||
campaignScoreCalculator = std::make_shared<HighScoreCalculation>();
|
||||
campaignScoreCalculator->isCampaign = true;
|
||||
campaignScoreCalculator->parameters.clear();
|
||||
}
|
||||
param.campaignName = cs->getNameTranslated();
|
||||
campaignScoreCalculator->parameters.push_back(param);
|
||||
cs->highscoreParameters.push_back(param);
|
||||
auto campaignScoreCalculator = std::make_shared<HighScoreCalculation>();
|
||||
campaignScoreCalculator->isCampaign = true;
|
||||
campaignScoreCalculator->parameters = cs->highscoreParameters;
|
||||
|
||||
endGameplay();
|
||||
|
||||
auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
|
||||
auto finisher = [this, ourCampaign]()
|
||||
auto finisher = [ourCampaign, campaignScoreCalculator]()
|
||||
{
|
||||
if(ourCampaign->campaignSet != "" && ourCampaign->isCampaignFinished())
|
||||
{
|
||||
|
@ -28,6 +28,8 @@ struct CPack;
|
||||
struct CPackForLobby;
|
||||
struct CPackForClient;
|
||||
|
||||
class HighScoreParameter;
|
||||
|
||||
template<typename T> class CApplier;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
@ -39,7 +41,6 @@ class GameChatHandler;
|
||||
class IServerRunner;
|
||||
|
||||
class HighScoreCalculation;
|
||||
class HighScoreParameter;
|
||||
|
||||
enum class ESelectionScreen : ui8;
|
||||
enum class ELoadMode : ui8;
|
||||
@ -81,6 +82,7 @@ public:
|
||||
virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
|
||||
virtual void setPlayer(PlayerColor color) const = 0;
|
||||
virtual void setPlayerName(PlayerColor color, const std::string & name) const = 0;
|
||||
virtual void setPlayerHandicap(PlayerColor color, Handicap handicap) const = 0;
|
||||
virtual void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const = 0;
|
||||
virtual void setDifficulty(int to) const = 0;
|
||||
virtual void setTurnTimerInfo(const TurnTimerInfo &) const = 0;
|
||||
@ -105,7 +107,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
|
||||
std::unique_ptr<IServerRunner> serverRunner;
|
||||
std::shared_ptr<CMapInfo> mapToStart;
|
||||
std::vector<std::string> localPlayerNames;
|
||||
std::shared_ptr<HighScoreCalculation> campaignScoreCalculator;
|
||||
|
||||
boost::thread threadNetwork;
|
||||
|
||||
@ -191,6 +192,7 @@ public:
|
||||
void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
|
||||
void setPlayer(PlayerColor color) const override;
|
||||
void setPlayerName(PlayerColor color, const std::string & name) const override;
|
||||
void setPlayerHandicap(PlayerColor color, Handicap handicap) const override;
|
||||
void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const override;
|
||||
void setDifficulty(int to) const override;
|
||||
void setTurnTimerInfo(const TurnTimerInfo &) const override;
|
||||
@ -219,7 +221,6 @@ public:
|
||||
|
||||
void visitForLobby(CPackForLobby & lobbyPack);
|
||||
void visitForClient(CPackForClient & clientPack);
|
||||
void setHighScoreCalc(const std::shared_ptr<HighScoreCalculation> &newHighScoreCalc);
|
||||
};
|
||||
|
||||
extern CServerHandler * CSH;
|
||||
|
@ -375,7 +375,7 @@ void AdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID, bool isHuma
|
||||
mapAudio->onEnemyTurnStarted();
|
||||
widget->getMinimap()->setAIRadar(!isHuman);
|
||||
widget->getInfoBar()->startEnemyTurn(playerID);
|
||||
setState(isHuman ? EAdventureState::OTHER_HUMAN_PLAYER_TURN : EAdventureState::AI_PLAYER_TURN);
|
||||
setState(isHuman ? EAdventureState::MAKING_TURN : EAdventureState::AI_PLAYER_TURN);
|
||||
}
|
||||
|
||||
void AdventureMapInterface::setState(EAdventureState state)
|
||||
|
@ -532,7 +532,7 @@ bool AdventureMapShortcuts::optionCanVisitObject()
|
||||
auto * hero = LOCPLINT->localState->getCurrentHero();
|
||||
auto objects = LOCPLINT->cb->getVisitableObjs(hero->visitablePos());
|
||||
|
||||
assert(vstd::contains(objects,hero));
|
||||
//assert(vstd::contains(objects,hero));
|
||||
return objects.size() > 1; // there is object other than our hero
|
||||
}
|
||||
|
||||
@ -577,16 +577,15 @@ bool AdventureMapShortcuts::optionInWorldView()
|
||||
|
||||
bool AdventureMapShortcuts::optionSidePanelActive()
|
||||
{
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionMapScrollingActive()
|
||||
{
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionMapViewActive()
|
||||
{
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL
|
||||
|| state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL;
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ enum class EAdventureState
|
||||
HOTSEAT_WAIT,
|
||||
MAKING_TURN,
|
||||
AI_PLAYER_TURN,
|
||||
OTHER_HUMAN_PLAYER_TURN,
|
||||
CASTING_SPELL,
|
||||
WORLD_VIEW
|
||||
};
|
||||
|
@ -499,9 +499,12 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
const auto * attacker = owner.stacksController->getActiveStack();
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||
int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex] : 0;
|
||||
DamageEstimation retaliation;
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex, &retaliation);
|
||||
BattleAttackInfo attackInfo(attacker, targetStack, distance, false );
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
|
||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||
bool enemyMayBeKilled = estimation.kills.max == targetStack->getCount();
|
||||
@ -514,7 +517,8 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
const auto * shooter = owner.stacksController->getActiveStack();
|
||||
|
||||
DamageEstimation retaliation;
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(shooter, targetStack, shooter->getPosition(), &retaliation);
|
||||
BattleAttackInfo attackInfo(shooter, targetStack, 0, true );
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
|
||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||
return formatRangedAttack(estimation, targetStack->getName(), shooter->shots.available());
|
||||
|
@ -218,7 +218,7 @@ void BattleWindow::showStickyQuickSpellWindow()
|
||||
Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
|
||||
showStickyQuickSpellWindow->Bool() = true;
|
||||
|
||||
if(GH.screenDimensions().x >= 1050)
|
||||
if(GH.screenDimensions().x >= 1050 && owner.getBattle()->battleGetMyHero()->hasSpellbook())
|
||||
{
|
||||
quickSpellWindow->enable();
|
||||
quickSpellWindow->isEnabled = true;
|
||||
|
@ -94,7 +94,7 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow()
|
||||
};
|
||||
|
||||
listBackground = std::make_shared<TransparentFilledRectangle>(Rect(8, 48, 220, 324), ColorRGBA(0, 0, 0, 64), ColorRGBA(64, 80, 128, 255), 1);
|
||||
accountList = std::make_shared<CListBox>(createAccountCardCallback, Point(10, 50), Point(0, 40), 8, 0, 0, 1 | 4, Rect(200, 0, 320, 320));
|
||||
accountList = std::make_shared<CListBox>(createAccountCardCallback, Point(10, 50), Point(0, 40), 8, CSH->getGlobalLobby().getActiveAccounts().size(), 0, 1 | 4, Rect(200, 0, 320, 320));
|
||||
|
||||
buttonClose = std::make_shared<CButton>(Point(86, 384), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this]() { close(); }, EShortcut::GLOBAL_RETURN );
|
||||
|
||||
|
@ -43,7 +43,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
||||
labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.login.title"));
|
||||
labelUsernameTitle = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username"));
|
||||
labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString());
|
||||
labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString(), 265);
|
||||
backgroundUsername = std::make_shared<TransparentFilledRectangle>(Rect(10, 90, 264, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
|
||||
inputUsername = std::make_shared<CTextInput>(Rect(15, 93, 260, 16), FONT_SMALL, ETextAlignment::CENTERLEFT, true);
|
||||
buttonLogin = std::make_shared<CButton>(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); }, EShortcut::GLOBAL_ACCEPT);
|
||||
|
@ -37,7 +37,7 @@ GlobalLobbyRoomAccountCard::GlobalLobbyRoomAccountCard(const GlobalLobbyAccount
|
||||
pos.w = 130;
|
||||
pos.h = 40;
|
||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName, 120);
|
||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status);
|
||||
}
|
||||
|
||||
@ -56,9 +56,14 @@ GlobalLobbyRoomModCard::GlobalLobbyRoomModCard(const GlobalLobbyRoomModInfo & mo
|
||||
pos.h = 40;
|
||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
||||
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName, 190);
|
||||
labelVersion = std::make_shared<CLabel>(195, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, modInfo.version);
|
||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status)));
|
||||
auto statusColor = Colors::RED;
|
||||
if(modInfo.status == ModVerificationStatus::FULL_MATCH)
|
||||
statusColor = ColorRGBA(128, 128, 128);
|
||||
else if(modInfo.status == ModVerificationStatus::VERSION_MISMATCH)
|
||||
statusColor = Colors::YELLOW;
|
||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, statusColor, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status)));
|
||||
}
|
||||
|
||||
static std::string getJoinRoomErrorMessage(const GlobalLobbyRoom & roomDescription, const std::vector<GlobalLobbyRoomModInfo> & modVerificationList)
|
||||
@ -134,6 +139,13 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s
|
||||
|
||||
modVerificationList.push_back(modInfo);
|
||||
}
|
||||
std::sort(modVerificationList.begin(), modVerificationList.end(), [](const GlobalLobbyRoomModInfo &a, const GlobalLobbyRoomModInfo &b)
|
||||
{
|
||||
if(a.status == b.status)
|
||||
return a.modName < b.modName;
|
||||
|
||||
return a.status < b.status;
|
||||
});
|
||||
|
||||
MetaString subtitleText;
|
||||
subtitleText.appendTextID("vcmi.lobby.preview.subtitle");
|
||||
@ -142,7 +154,7 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s
|
||||
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
||||
labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.title").toString());
|
||||
labelSubtitle = std::make_shared<CLabel>( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, subtitleText.toString());
|
||||
labelSubtitle = std::make_shared<CLabel>( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, subtitleText.toString(), 400);
|
||||
|
||||
labelVersionTitle = std::make_shared<CLabel>( 10, 60, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.version").toString());
|
||||
labelVersionValue = std::make_shared<CLabel>( 10, 80, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.gameVersion);
|
||||
|
@ -207,7 +207,7 @@ GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const
|
||||
: GlobalLobbyChannelCardBase(window, Point(130, 40), "player", accountDescription.accountID, accountDescription.displayName)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName, 120);
|
||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status);
|
||||
}
|
||||
|
||||
@ -238,8 +238,8 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa
|
||||
else
|
||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
||||
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName);
|
||||
labelDescription = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName, 180);
|
||||
labelDescription = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description, 160);
|
||||
labelRoomSize = std::make_shared<CLabel>(212, 10, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomSizeText.toString());
|
||||
labelRoomStatus = std::make_shared<CLabel>(225, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomStatusText.toString());
|
||||
iconRoomSize = std::make_shared<CPicture>(ImagePath::builtin("lobby/iconPlayer"), Point(214, 5));
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "../eventsSDL/InputHandler.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../adventureMap/AdventureMapInterface.h"
|
||||
#include "../render/Colors.h"
|
||||
#include "../render/Graphics.h"
|
||||
#include "../render/IFont.h"
|
||||
@ -145,7 +146,13 @@ CGuiHandler::CGuiHandler()
|
||||
{
|
||||
}
|
||||
|
||||
CGuiHandler::~CGuiHandler() = default;
|
||||
CGuiHandler::~CGuiHandler()
|
||||
{
|
||||
// enforce deletion order on shutdown
|
||||
// all UI elements including adventure map must be destroyed before Gui Handler
|
||||
// proper solution would be removal of adventureInt global
|
||||
adventureInt.reset();
|
||||
}
|
||||
|
||||
ShortcutHandler & CGuiHandler::shortcuts()
|
||||
{
|
||||
|
@ -345,7 +345,8 @@ std::shared_ptr<CLabel> InterfaceObjectConfigurable::buildLabel(const JsonNode &
|
||||
auto color = readColor(config["color"]);
|
||||
auto text = readText(config["text"]);
|
||||
auto position = readPosition(config["position"]);
|
||||
return std::make_shared<CLabel>(position.x, position.y, font, alignment, color, text);
|
||||
auto maxWidth = config["maxWidth"].Integer();
|
||||
return std::make_shared<CLabel>(position.x, position.y, font, alignment, color, text, maxWidth);
|
||||
}
|
||||
|
||||
std::shared_ptr<CMultiLineLabel> InterfaceObjectConfigurable::buildMultiLineLabel(const JsonNode & config) const
|
||||
|
@ -91,6 +91,7 @@ enum class EShortcut
|
||||
LOBBY_FLIP_COIN,
|
||||
LOBBY_RANDOM_TOWN,
|
||||
LOBBY_RANDOM_TOWN_VS,
|
||||
LOBBY_HANDICAP,
|
||||
|
||||
MAPS_SIZE_S,
|
||||
MAPS_SIZE_M,
|
||||
|
@ -288,6 +288,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
|
||||
{"lobbyFlipCoin", EShortcut::LOBBY_FLIP_COIN },
|
||||
{"lobbyRandomTown", EShortcut::LOBBY_RANDOM_TOWN },
|
||||
{"lobbyRandomTownVs", EShortcut::LOBBY_RANDOM_TOWN_VS },
|
||||
{"lobbyHandicap", EShortcut::LOBBY_HANDICAP },
|
||||
{"mapsSizeS", EShortcut::MAPS_SIZE_S },
|
||||
{"mapsSizeM", EShortcut::MAPS_SIZE_M },
|
||||
{"mapsSizeL", EShortcut::MAPS_SIZE_L },
|
||||
|
@ -437,6 +437,13 @@ PvPBox::PvPBox(const Rect & rect)
|
||||
CSH->sendLobbyPack(lpa);
|
||||
}, EShortcut::LOBBY_RANDOM_TOWN_VS);
|
||||
buttonRandomTownVs->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.randomTownVs.hover"), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
|
||||
buttonHandicap = std::make_shared<CButton>(Point(190, 81), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.handicap")), [](){
|
||||
if(!CSH->isHost())
|
||||
return;
|
||||
GH.windows().createAndPushWindow<OptionsTab::HandicapWindow>();
|
||||
}, EShortcut::LOBBY_HANDICAP);
|
||||
buttonHandicap->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.handicap"), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
}
|
||||
|
||||
TownSelector::TownSelector(const Point & loc)
|
||||
|
@ -153,6 +153,7 @@ class PvPBox : public CIntObject
|
||||
std::shared_ptr<CButton> buttonFlipCoin;
|
||||
std::shared_ptr<CButton> buttonRandomTown;
|
||||
std::shared_ptr<CButton> buttonRandomTownVs;
|
||||
std::shared_ptr<CButton> buttonHandicap;
|
||||
public:
|
||||
PvPBox(const Rect & rect);
|
||||
};
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "../widgets/ObjectLists.h"
|
||||
#include "../widgets/Slider.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../windows/GUIClasses.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../windows/CHeroOverview.h"
|
||||
@ -793,6 +794,119 @@ void OptionsTab::SelectionWindow::showPopupWindow(const Point & cursorPosition)
|
||||
setElement(elem, false);
|
||||
}
|
||||
|
||||
OptionsTab::HandicapWindow::HandicapWindow()
|
||||
: CWindowObject(BORDERED)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
|
||||
addUsedEvents(LCLICK);
|
||||
|
||||
pos = Rect(0, 0, 660, 100 + SEL->getStartInfo()->playerInfos.size() * 30);
|
||||
|
||||
backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), pos);
|
||||
backgroundTexture->setPlayerColor(PlayerColor(1));
|
||||
|
||||
labels.push_back(std::make_shared<CLabel>(pos.w / 2 + 8, 15, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.handicap")));
|
||||
|
||||
enum Columns : int32_t
|
||||
{
|
||||
INCOME = 1000,
|
||||
GROWTH = 2000,
|
||||
};
|
||||
auto columns = std::vector<EGameResID>{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS, Columns::INCOME, Columns::GROWTH};
|
||||
|
||||
int i = 0;
|
||||
for(auto & pInfo : SEL->getStartInfo()->playerInfos)
|
||||
{
|
||||
PlayerColor player = pInfo.first;
|
||||
anim.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("ITGFLAGS"), player.getNum(), 0, 7, 57 + i * 30));
|
||||
for(int j = 0; j < columns.size(); j++)
|
||||
{
|
||||
bool isIncome = int(columns[j]) == Columns::INCOME;
|
||||
bool isGrowth = int(columns[j]) == Columns::GROWTH;
|
||||
EGameResID resource = columns[j];
|
||||
|
||||
const PlayerSettings &ps = SEL->getStartInfo()->getIthPlayersSettings(player);
|
||||
|
||||
int xPos = 30 + j * 70;
|
||||
xPos += j > 0 ? 10 : 0; // Gold field is larger
|
||||
|
||||
if(i == 0)
|
||||
{
|
||||
if(isIncome)
|
||||
labels.push_back(std::make_shared<CLabel>(xPos, 35, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.jktext.32")));
|
||||
else if(isGrowth)
|
||||
labels.push_back(std::make_shared<CLabel>(xPos, 35, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.194")));
|
||||
else
|
||||
anim.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SMALRES"), GameResID(resource), 0, 15 + xPos + (j == 0 ? 10 : 0), 35));
|
||||
}
|
||||
|
||||
auto area = Rect(xPos, 60 + i * 30, j == 0 ? 60 : 50, 16);
|
||||
textinputbackgrounds.push_back(std::make_shared<TransparentFilledRectangle>(area.resize(3), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)));
|
||||
textinputs[player][resource] = std::make_shared<CTextInput>(area, FONT_SMALL, ETextAlignment::CENTERLEFT, true);
|
||||
textinputs[player][resource]->setText(std::to_string(isIncome ? ps.handicap.percentIncome : (isGrowth ? ps.handicap.percentGrowth : ps.handicap.startBonus[resource])));
|
||||
textinputs[player][resource]->setCallback([this, player, resource, isIncome, isGrowth](const std::string & s){
|
||||
// text input processing: add/remove sign when pressing "-"; remove non digits; cut length; fill empty field with 0
|
||||
std::string tmp = s;
|
||||
bool negative = std::count_if( s.begin(), s.end(), []( char c ){ return c == '-'; }) == 1 && !isIncome && !isGrowth;
|
||||
tmp.erase(std::remove_if(tmp.begin(), tmp.end(), [](char c) { return !isdigit(c); }), tmp.end());
|
||||
int maxLength = isIncome || isGrowth ? 3 : (resource == EGameResID::GOLD ? 6 : 5);
|
||||
tmp = tmp.substr(0, maxLength);
|
||||
textinputs[player][resource]->setText(tmp.length() == 0 ? "0" : (negative ? "-" : "") + std::to_string(stoi(tmp)));
|
||||
});
|
||||
textinputs[player][resource]->setPopupCallback([isIncome, isGrowth](){
|
||||
// Help for the textinputs
|
||||
if(isIncome)
|
||||
CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.lobby.handicap.income"));
|
||||
else if(isGrowth)
|
||||
CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.lobby.handicap.growth"));
|
||||
else
|
||||
CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.lobby.handicap.resource"));
|
||||
});
|
||||
if(isIncome || isGrowth)
|
||||
labels.push_back(std::make_shared<CLabel>(area.topRight().x, area.center().y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, "%"));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
buttons.push_back(std::make_shared<CButton>(Point(pos.w / 2 - 32, 60 + SEL->getStartInfo()->playerInfos.size() * 30), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){
|
||||
for (const auto& player : textinputs)
|
||||
{
|
||||
TResources resources = TResources();
|
||||
int income = 100;
|
||||
int growth = 100;
|
||||
for (const auto& resource : player.second)
|
||||
{
|
||||
bool isIncome = int(resource.first) == Columns::INCOME;
|
||||
bool isGrowth = int(resource.first) == Columns::GROWTH;
|
||||
if(isIncome)
|
||||
income = std::stoi(resource.second->getText());
|
||||
else if(isGrowth)
|
||||
growth = std::stoi(resource.second->getText());
|
||||
else
|
||||
resources[resource.first] = std::stoi(resource.second->getText());
|
||||
}
|
||||
CSH->setPlayerHandicap(player.first, Handicap{resources, income, growth});
|
||||
}
|
||||
|
||||
close();
|
||||
}, EShortcut::GLOBAL_RETURN));
|
||||
|
||||
updateShadow();
|
||||
center();
|
||||
}
|
||||
|
||||
bool OptionsTab::HandicapWindow::receiveEvent(const Point & position, int eventType) const
|
||||
{
|
||||
return true; // capture click also outside of window
|
||||
}
|
||||
|
||||
void OptionsTab::HandicapWindow::clickReleased(const Point & cursorPosition)
|
||||
{
|
||||
if(!pos.isInside(cursorPosition)) // make it possible to close window by touching/clicking outside of window
|
||||
close();
|
||||
}
|
||||
|
||||
OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & playerSettings, SelType type)
|
||||
: Scrollable(LCLICK | SHOW_POPUP, position, Orientation::HORIZONTAL)
|
||||
, CPlayerSettingsHelper(playerSettings, type)
|
||||
@ -923,6 +1037,47 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
|
||||
}
|
||||
labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
|
||||
|
||||
auto hasHandicap = [this](){ return s->handicap.startBonus.empty() && s->handicap.percentIncome == 100 && s->handicap.percentGrowth == 100; };
|
||||
std::string labelHandicapText = hasHandicap() ? CGI->generaltexth->arraytxt[210] : MetaString::createFromTextID("vcmi.lobby.handicap").toString();
|
||||
labelHandicap = std::make_shared<CMultiLineLabel>(Rect(57, 24, 47, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, labelHandicapText);
|
||||
handicap = std::make_shared<LRClickableArea>(Rect(56, 24, 49, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), [](){
|
||||
if(!CSH->isHost())
|
||||
return;
|
||||
|
||||
GH.windows().createAndPushWindow<HandicapWindow>();
|
||||
}, [this, hasHandicap](){
|
||||
if(hasHandicap())
|
||||
CRClickPopup::createAndPush(MetaString::createFromTextID("core.help.124.help").toString());
|
||||
else
|
||||
{
|
||||
auto str = MetaString::createFromTextID("vcmi.lobby.handicap");
|
||||
str.appendRawString(":\n");
|
||||
for(auto & res : EGameResID::ALL_RESOURCES())
|
||||
if(s->handicap.startBonus[res] != 0)
|
||||
{
|
||||
str.appendRawString("\n");
|
||||
str.appendName(res);
|
||||
str.appendRawString(": ");
|
||||
str.appendRawString(std::to_string(s->handicap.startBonus[res]));
|
||||
}
|
||||
if(s->handicap.percentIncome != 100)
|
||||
{
|
||||
str.appendRawString("\n");
|
||||
str.appendTextID("core.jktext.32");
|
||||
str.appendRawString(": ");
|
||||
str.appendRawString(std::to_string(s->handicap.percentIncome) + "%");
|
||||
}
|
||||
if(s->handicap.percentGrowth != 100)
|
||||
{
|
||||
str.appendRawString("\n");
|
||||
str.appendTextID("core.genrltxt.194");
|
||||
str.appendRawString(": ");
|
||||
str.appendRawString(std::to_string(s->handicap.percentGrowth) + "%");
|
||||
}
|
||||
CRClickPopup::createAndPush(str.toString());
|
||||
}
|
||||
});
|
||||
|
||||
if(SEL->screenType == ESelectionScreen::newGame)
|
||||
{
|
||||
buttonTownLeft = std::make_shared<CButton>(Point(107, 5), AnimationPath::builtin("ADOPLFA.DEF"), CGI->generaltexth->zelp[132], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, -1, s->color));
|
||||
|
@ -27,8 +27,10 @@ class CComponentBox;
|
||||
class CTextBox;
|
||||
class CButton;
|
||||
class CSlider;
|
||||
class LRClickableArea;
|
||||
|
||||
class FilledTexturePlayerColored;
|
||||
class TransparentFilledRectangle;
|
||||
|
||||
/// The options tab which is shown at the map selection phase.
|
||||
class OptionsTab : public OptionsTabBase
|
||||
@ -51,6 +53,22 @@ public:
|
||||
BONUS
|
||||
};
|
||||
|
||||
class HandicapWindow : public CWindowObject
|
||||
{
|
||||
std::shared_ptr<FilledTexturePlayerColored> backgroundTexture;
|
||||
|
||||
std::vector<std::shared_ptr<CLabel>> labels;
|
||||
std::vector<std::shared_ptr<CAnimImage>> anim;
|
||||
std::vector<std::shared_ptr<TransparentFilledRectangle>> textinputbackgrounds;
|
||||
std::map<PlayerColor, std::map<EGameResID, std::shared_ptr<CTextInput>>> textinputs;
|
||||
std::vector<std::shared_ptr<CButton>> buttons;
|
||||
|
||||
bool receiveEvent(const Point & position, int eventType) const override;
|
||||
void clickReleased(const Point & cursorPosition) override;
|
||||
public:
|
||||
HandicapWindow();
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
struct CPlayerSettingsHelper
|
||||
@ -192,6 +210,8 @@ private:
|
||||
std::shared_ptr<SelectedBox> town;
|
||||
std::shared_ptr<SelectedBox> hero;
|
||||
std::shared_ptr<SelectedBox> bonus;
|
||||
std::shared_ptr<LRClickableArea> handicap;
|
||||
std::shared_ptr<CMultiLineLabel> labelHandicap;
|
||||
enum {HUMAN_OR_CPU, HUMAN, CPU} whoCanPlay;
|
||||
|
||||
PlayerOptionsEntry(const PlayerSettings & S, const OptionsTab & parentTab);
|
||||
|
@ -340,10 +340,10 @@ void OptionsTabBase::recreate(bool campaign)
|
||||
|
||||
//Simultaneous turns
|
||||
if(auto turnSlider = widget<CSlider>("simturnsDurationMin"))
|
||||
turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.requiredTurns);
|
||||
turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.requiredTurns, false);
|
||||
|
||||
if(auto turnSlider = widget<CSlider>("simturnsDurationMax"))
|
||||
turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.optionalTurns);
|
||||
turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.optionalTurns, false);
|
||||
|
||||
if(auto w = widget<CLabel>("labelSimturnsDurationValueMin"))
|
||||
w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.requiredTurns));
|
||||
@ -388,7 +388,7 @@ void OptionsTabBase::recreate(bool campaign)
|
||||
auto & tpreset = variables["timerPresets"].Vector()[idx];
|
||||
if(tpreset.Vector().at(1).Integer() == turnTimerRemote.turnTimer / 1000)
|
||||
{
|
||||
turnSlider->scrollTo(idx);
|
||||
turnSlider->scrollTo(idx, false);
|
||||
if(auto w = widget<CLabel>("labelTurnDurationValue"))
|
||||
w->setText(CGI->generaltexth->turnDurations[idx]);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/constants/EntityIdentifiers.h"
|
||||
#include "../../lib/gameState/HighScore.h"
|
||||
|
||||
auto HighScoreCalculation::calculate()
|
||||
{
|
||||
@ -48,7 +49,7 @@ auto HighScoreCalculation::calculate()
|
||||
const std::array<double, 5> difficultyMultipliers{0.8, 1.0, 1.3, 1.6, 2.0};
|
||||
for(auto & param : parameters)
|
||||
{
|
||||
double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0);
|
||||
double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allEnemiesDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0);
|
||||
firstResult = Result{static_cast<int>(tmp), static_cast<int>(tmp * difficultyMultipliers.at(param.difficulty)), param.day, param.usedCheat};
|
||||
summary.basic += firstResult.basic * 5.0 / parameters.size();
|
||||
summary.total += firstResult.total * 5.0 / parameters.size();
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
#include "../windows/CWindowObject.h"
|
||||
#include "../../lib/gameState/HighScore.h"
|
||||
|
||||
class CButton;
|
||||
class CLabel;
|
||||
@ -20,24 +21,10 @@ class CFilledTexture;
|
||||
|
||||
class TransparentFilledRectangle;
|
||||
|
||||
class HighScoreParameter
|
||||
{
|
||||
public:
|
||||
int difficulty;
|
||||
int day;
|
||||
int townAmount;
|
||||
bool usedCheat;
|
||||
bool hasGrail;
|
||||
bool allDefeated;
|
||||
std::string campaignName;
|
||||
std::string scenarioName;
|
||||
std::string playerName;
|
||||
};
|
||||
|
||||
class HighScoreCalculation
|
||||
{
|
||||
public:
|
||||
std::vector<HighScoreParameter> parameters = std::vector<HighScoreParameter>();
|
||||
std::vector<HighScoreParameter> parameters;
|
||||
bool isCampaign = false;
|
||||
|
||||
auto calculate();
|
||||
|
@ -16,6 +16,7 @@ class Point;
|
||||
class CGObjectInstance;
|
||||
class ObjectInstanceID;
|
||||
struct TerrainTile;
|
||||
class ColorRGBA;
|
||||
struct CGPath;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
@ -67,6 +68,12 @@ public:
|
||||
/// returns index of image for overlay on specific tile, or numeric_limits::max if none
|
||||
virtual size_t overlayImageIndex(const int3 & coordinates) const = 0;
|
||||
|
||||
/// returns text that should be used as overlay for current tile
|
||||
virtual std::string overlayText(const int3 & coordinates) const = 0;
|
||||
|
||||
/// returns text that should be used as overlay for current tile
|
||||
virtual ColorRGBA overlayTextColor(const int3 & coordinates) const = 0;
|
||||
|
||||
/// returns animation frame for terrain
|
||||
virtual size_t terrainImageIndex(size_t groupSize) const = 0;
|
||||
|
||||
@ -80,7 +87,10 @@ public:
|
||||
virtual bool showBorder() const = 0;
|
||||
|
||||
/// if true, world view overlay will be shown
|
||||
virtual bool showOverlay() const = 0;
|
||||
virtual bool showImageOverlay() const = 0;
|
||||
|
||||
// if true, new text overlay will be shown
|
||||
virtual bool showTextOverlay() const = 0;
|
||||
|
||||
/// if true, map grid should be visible on map
|
||||
virtual bool showGrid() const = 0;
|
||||
|
@ -113,7 +113,7 @@ const CGPath * MapRendererBaseContext::currentPath() const
|
||||
|
||||
size_t MapRendererBaseContext::objectGroupIndex(ObjectInstanceID objectID) const
|
||||
{
|
||||
static const std::vector<size_t> idleGroups = {0, 13, 0, 1, 2, 3, 4, 15, 14};
|
||||
static const std::array<size_t, 9> idleGroups = {0, 13, 0, 1, 2, 3, 4, 15, 14};
|
||||
return idleGroups[getObjectRotation(objectID)];
|
||||
}
|
||||
|
||||
@ -156,6 +156,16 @@ size_t MapRendererBaseContext::overlayImageIndex(const int3 & coordinates) const
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
std::string MapRendererBaseContext::overlayText(const int3 & coordinates) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
ColorRGBA MapRendererBaseContext::overlayTextColor(const int3 & coordinates) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
double MapRendererBaseContext::viewTransitionProgress() const
|
||||
{
|
||||
return 0;
|
||||
@ -181,7 +191,12 @@ bool MapRendererBaseContext::showBorder() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MapRendererBaseContext::showOverlay() const
|
||||
bool MapRendererBaseContext::showImageOverlay() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MapRendererBaseContext::showTextOverlay() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -253,6 +268,59 @@ size_t MapRendererAdventureContext::terrainImageIndex(size_t groupSize) const
|
||||
return frameIndex;
|
||||
}
|
||||
|
||||
std::string MapRendererAdventureContext::overlayText(const int3 & coordinates) const
|
||||
{
|
||||
if(!isVisible(coordinates))
|
||||
return {};
|
||||
|
||||
const auto & tile = getMapTile(coordinates);
|
||||
|
||||
if (!tile.visitable)
|
||||
return {};
|
||||
|
||||
return tile.visitableObjects.back()->getObjectName();
|
||||
}
|
||||
|
||||
ColorRGBA MapRendererAdventureContext::overlayTextColor(const int3 & coordinates) const
|
||||
{
|
||||
if(!isVisible(coordinates))
|
||||
return {};
|
||||
|
||||
const auto & tile = getMapTile(coordinates);
|
||||
|
||||
if (!tile.visitable)
|
||||
return {};
|
||||
|
||||
auto * object = tile.visitableObjects.back();
|
||||
|
||||
if (object->getOwner() == LOCPLINT->playerID)
|
||||
return { 0, 192, 0};
|
||||
|
||||
if (LOCPLINT->cb->getPlayerRelations(object->getOwner(), LOCPLINT->playerID) == PlayerRelations::ALLIES)
|
||||
return { 0, 128, 255};
|
||||
|
||||
if (object->getOwner().isValidPlayer())
|
||||
return { 255, 0, 0};
|
||||
|
||||
if (object->ID == MapObjectID::MONSTER)
|
||||
return { 255, 0, 0};
|
||||
|
||||
auto hero = LOCPLINT->localState->getCurrentHero();
|
||||
|
||||
if (hero)
|
||||
{
|
||||
if (object->wasVisited(hero))
|
||||
return { 160, 160, 160 };
|
||||
}
|
||||
else
|
||||
{
|
||||
if (object->wasVisited(LOCPLINT->playerID))
|
||||
return { 160, 160, 160 };
|
||||
}
|
||||
|
||||
return { 255, 192, 0 };
|
||||
}
|
||||
|
||||
bool MapRendererAdventureContext::showBorder() const
|
||||
{
|
||||
return true;
|
||||
@ -273,6 +341,11 @@ bool MapRendererAdventureContext::showBlocked() const
|
||||
return settingShowBlocked;
|
||||
}
|
||||
|
||||
bool MapRendererAdventureContext::showTextOverlay() const
|
||||
{
|
||||
return settingTextOverlay;
|
||||
}
|
||||
|
||||
bool MapRendererAdventureContext::showSpellRange(const int3 & position) const
|
||||
{
|
||||
if (!settingSpellRange)
|
||||
@ -330,7 +403,7 @@ size_t MapRendererAdventureMovingContext::objectGroupIndex(ObjectInstanceID obje
|
||||
{
|
||||
if(target == objectID)
|
||||
{
|
||||
static const std::vector<size_t> moveGroups = {0, 10, 5, 6, 7, 8, 9, 12, 11};
|
||||
static const std::array<size_t, 9> moveGroups = {0, 10, 5, 6, 7, 8, 9, 12, 11};
|
||||
return moveGroups[getObjectRotation(objectID)];
|
||||
}
|
||||
return MapRendererAdventureContext::objectGroupIndex(objectID);
|
||||
@ -411,7 +484,7 @@ MapRendererWorldViewContext::MapRendererWorldViewContext(const MapRendererContex
|
||||
{
|
||||
}
|
||||
|
||||
bool MapRendererWorldViewContext::showOverlay() const
|
||||
bool MapRendererWorldViewContext::showImageOverlay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -48,13 +48,16 @@ public:
|
||||
size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
|
||||
size_t terrainImageIndex(size_t groupSize) const override;
|
||||
size_t overlayImageIndex(const int3 & coordinates) const override;
|
||||
std::string overlayText(const int3 & coordinates) const override;
|
||||
ColorRGBA overlayTextColor(const int3 & coordinates) const override;
|
||||
|
||||
double viewTransitionProgress() const override;
|
||||
bool filterGrayscale() const override;
|
||||
bool showRoads() const override;
|
||||
bool showRivers() const override;
|
||||
bool showBorder() const override;
|
||||
bool showOverlay() const override;
|
||||
bool showImageOverlay() const override;
|
||||
bool showTextOverlay() const override;
|
||||
bool showGrid() const override;
|
||||
bool showVisitable() const override;
|
||||
bool showBlocked() const override;
|
||||
@ -69,6 +72,7 @@ public:
|
||||
bool settingShowVisitable = false;
|
||||
bool settingShowBlocked = false;
|
||||
bool settingSpellRange= false;
|
||||
bool settingTextOverlay = false;
|
||||
bool settingsAdventureObjectAnimation = true;
|
||||
bool settingsAdventureTerrainAnimation = true;
|
||||
|
||||
@ -77,11 +81,14 @@ public:
|
||||
const CGPath * currentPath() const override;
|
||||
size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
|
||||
size_t terrainImageIndex(size_t groupSize) const override;
|
||||
std::string overlayText(const int3 & coordinates) const override;
|
||||
ColorRGBA overlayTextColor(const int3 & coordinates) const override;
|
||||
|
||||
bool showBorder() const override;
|
||||
bool showGrid() const override;
|
||||
bool showVisitable() const override;
|
||||
bool showBlocked() const override;
|
||||
bool showTextOverlay() const override;
|
||||
|
||||
bool showSpellRange(const int3 & position) const override;
|
||||
};
|
||||
@ -133,7 +140,7 @@ public:
|
||||
explicit MapRendererWorldViewContext(const MapRendererContextState & viewState);
|
||||
|
||||
size_t overlayImageIndex(const int3 & coordinates) const override;
|
||||
bool showOverlay() const override;
|
||||
bool showImageOverlay() const override;
|
||||
};
|
||||
|
||||
class MapRendererSpellViewContext : public MapRendererWorldViewContext
|
||||
|
@ -18,9 +18,12 @@
|
||||
#include "../render/CAnimation.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IImage.h"
|
||||
#include "../render/IFont.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
#include "../render/Graphics.h"
|
||||
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
|
||||
#include "../../lib/mapObjects/CObjectHandler.h"
|
||||
#include "../../lib/int3.h"
|
||||
@ -30,6 +33,7 @@ MapViewCache::~MapViewCache() = default;
|
||||
MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model)
|
||||
: model(model)
|
||||
, cachedLevel(0)
|
||||
, overlayWasVisible(false)
|
||||
, mapRenderer(new MapRenderer())
|
||||
, iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol"), EImageBlitMode::COLORKEY))
|
||||
, intermediate(new Canvas(Point(32, 32)))
|
||||
@ -137,7 +141,9 @@ void MapViewCache::update(const std::shared_ptr<IMapRendererContext> & context)
|
||||
void MapViewCache::render(const std::shared_ptr<IMapRendererContext> & context, Canvas & target, bool fullRedraw)
|
||||
{
|
||||
bool mapMoved = (cachedPosition != model->getMapViewCenter());
|
||||
bool lazyUpdate = !mapMoved && !fullRedraw && vstd::isAlmostZero(context->viewTransitionProgress());
|
||||
bool overlayVisible = context->showImageOverlay() || context->showTextOverlay();
|
||||
bool overlayVisibilityChanged = overlayVisible != overlayWasVisible;
|
||||
bool lazyUpdate = !overlayVisibilityChanged && !mapMoved && !fullRedraw && vstd::isAlmostZero(context->viewTransitionProgress());
|
||||
|
||||
Rect dimensions = model->getTilesTotalRect();
|
||||
|
||||
@ -161,18 +167,18 @@ void MapViewCache::render(const std::shared_ptr<IMapRendererContext> & context,
|
||||
}
|
||||
}
|
||||
|
||||
if(context->showOverlay())
|
||||
if(context->showImageOverlay())
|
||||
{
|
||||
for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
|
||||
{
|
||||
for(int x = dimensions.left(); x < dimensions.right(); ++x)
|
||||
{
|
||||
int3 tile(x, y, model->getLevel());
|
||||
Rect targetRect = model->getTargetTileArea(tile);
|
||||
auto overlay = getOverlayImageForTile(context, tile);
|
||||
|
||||
if(overlay)
|
||||
{
|
||||
Rect targetRect = model->getTargetTileArea(tile);
|
||||
Point position = targetRect.center() - overlay->dimensions() / 2;
|
||||
target.draw(overlay, position);
|
||||
}
|
||||
@ -180,10 +186,42 @@ void MapViewCache::render(const std::shared_ptr<IMapRendererContext> & context,
|
||||
}
|
||||
}
|
||||
|
||||
if(context->showTextOverlay())
|
||||
{
|
||||
for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
|
||||
{
|
||||
for(int x = dimensions.left(); x < dimensions.right(); ++x)
|
||||
{
|
||||
int3 tile(x, y, model->getLevel());
|
||||
auto overlay = context->overlayText(tile);
|
||||
|
||||
if(!overlay.empty())
|
||||
{
|
||||
Rect targetRect = model->getTargetTileArea(tile);
|
||||
Point position = targetRect.center();
|
||||
if (x % 2 == 0)
|
||||
position.y += targetRect.h / 4;
|
||||
else
|
||||
position.y -= targetRect.h / 4;
|
||||
|
||||
const auto font = graphics->fonts[EFonts::FONT_TINY];
|
||||
|
||||
Point dimensions(font->getStringWidth(overlay), font->getLineHeight());
|
||||
Rect textRect = Rect(position - dimensions / 2, dimensions).resize(2);
|
||||
|
||||
target.drawColor(textRect, context->overlayTextColor(tile));
|
||||
target.drawBorder(textRect, Colors::BRIGHT_YELLOW);
|
||||
target.drawText(position, EFonts::FONT_TINY, Colors::BLACK, ETextAlignment::CENTER, overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!vstd::isAlmostZero(context->viewTransitionProgress()))
|
||||
target.drawTransparent(*terrainTransition, Point(0, 0), 1.0 - context->viewTransitionProgress());
|
||||
|
||||
cachedPosition = model->getMapViewCenter();
|
||||
overlayWasVisible = overlayVisible;
|
||||
}
|
||||
|
||||
void MapViewCache::createTransitionSnapshot(const std::shared_ptr<IMapRendererContext> & context)
|
||||
|
@ -44,6 +44,7 @@ class MapViewCache
|
||||
Point cachedSize;
|
||||
Point cachedPosition;
|
||||
int cachedLevel;
|
||||
bool overlayWasVisible;
|
||||
|
||||
std::shared_ptr<MapViewModel> model;
|
||||
|
||||
|
@ -224,6 +224,7 @@ void MapViewController::updateState()
|
||||
adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool();
|
||||
adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool();
|
||||
adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool();
|
||||
adventureContext->settingTextOverlay = GH.isKeyboardAltDown();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,7 +293,10 @@ std::string CComponent::getSubtitle() const
|
||||
return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getNameTranslated();
|
||||
case ComponentType::SPELL_SCROLL:
|
||||
case ComponentType::SPELL:
|
||||
return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();
|
||||
if (data.value < 0)
|
||||
return "{#A9A9A9|" + CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated() + "}";
|
||||
else
|
||||
return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();
|
||||
case ComponentType::NONE:
|
||||
case ComponentType::MORALE:
|
||||
case ComponentType::LUCK:
|
||||
|
@ -30,7 +30,7 @@ CTextInput::CTextInput(const Rect & Pos)
|
||||
pos.h = Pos.h;
|
||||
pos.w = Pos.w;
|
||||
|
||||
addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT);
|
||||
addUsedEvents(LCLICK | SHOW_POPUP | KEYBOARD | TEXTINPUT);
|
||||
}
|
||||
|
||||
void CTextInput::createLabel(bool giveFocusToInput)
|
||||
@ -106,6 +106,11 @@ void CTextInput::setCallback(const TextEditedCallback & cb)
|
||||
onTextEdited = cb;
|
||||
}
|
||||
|
||||
void CTextInput::setPopupCallback(const std::function<void()> & cb)
|
||||
{
|
||||
callbackPopup = cb;
|
||||
}
|
||||
|
||||
void CTextInput::setFilterFilename()
|
||||
{
|
||||
assert(!onTextFiltering);
|
||||
@ -122,6 +127,12 @@ std::string CTextInput::getVisibleText() const
|
||||
return hasFocus() ? currentText + composedText + "_" : currentText;
|
||||
}
|
||||
|
||||
void CTextInput::showPopupWindow(const Point & cursorPosition)
|
||||
{
|
||||
if(callbackPopup)
|
||||
callbackPopup();
|
||||
}
|
||||
|
||||
void CTextInput::clickPressed(const Point & cursorPosition)
|
||||
{
|
||||
// attempt to give focus unconditionally, even if we already have it
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "../render/EFont.h"
|
||||
|
||||
#include "../../lib/filesystem/ResourcePath.h"
|
||||
#include "../../lib/FunctionList.h"
|
||||
|
||||
class CLabel;
|
||||
class IImage;
|
||||
@ -58,6 +59,7 @@ class CTextInput final : public CFocusable
|
||||
|
||||
TextEditedCallback onTextEdited;
|
||||
TextFilterCallback onTextFiltering;
|
||||
CFunctionList<void()> callbackPopup;
|
||||
|
||||
//Filter that will block all characters not allowed in filenames
|
||||
static void filenameFilter(std::string & text, const std::string & oldText);
|
||||
@ -74,6 +76,7 @@ class CTextInput final : public CFocusable
|
||||
void textEdited(const std::string & enteredText) final;
|
||||
void onFocusGot() final;
|
||||
void onFocusLost() final;
|
||||
void showPopupWindow(const Point & cursorPosition) final;
|
||||
|
||||
CTextInput(const Rect & Pos);
|
||||
public:
|
||||
@ -89,6 +92,9 @@ public:
|
||||
/// Set callback that will be called whenever player enters new text
|
||||
void setCallback(const TextEditedCallback & cb);
|
||||
|
||||
/// Set callback when player want to open popup
|
||||
void setPopupCallback(const std::function<void()> & cb);
|
||||
|
||||
/// Enables filtering entered text that ensures that text is valid filename (existing or not)
|
||||
void setFilterFilename();
|
||||
/// Enable filtering entered text that ensures that text is valid number in provided range [min, max]
|
||||
|
@ -70,7 +70,7 @@ int CSlider::getValue() const
|
||||
return value;
|
||||
}
|
||||
|
||||
void CSlider::setValue(int to)
|
||||
void CSlider::setValue(int to, bool callCallbacks)
|
||||
{
|
||||
scrollTo(value);
|
||||
}
|
||||
@ -113,7 +113,7 @@ void CSlider::updateSliderPos()
|
||||
}
|
||||
}
|
||||
|
||||
void CSlider::scrollTo(int to)
|
||||
void CSlider::scrollTo(int to, bool callCallbacks)
|
||||
{
|
||||
vstd::amax(to, 0);
|
||||
vstd::amin(to, positions);
|
||||
@ -125,7 +125,8 @@ void CSlider::scrollTo(int to)
|
||||
|
||||
updateSliderPos();
|
||||
|
||||
moved(getValue());
|
||||
if (callCallbacks)
|
||||
moved(getValue());
|
||||
}
|
||||
|
||||
void CSlider::clickPressed(const Point & cursorPosition)
|
||||
@ -321,7 +322,7 @@ int SliderNonlinear::getValue() const
|
||||
return scaledValues.at(CSlider::getValue());
|
||||
}
|
||||
|
||||
void SliderNonlinear::setValue(int to)
|
||||
void SliderNonlinear::setValue(int to, bool callCallbacks)
|
||||
{
|
||||
size_t nearest = 0;
|
||||
|
||||
@ -334,5 +335,5 @@ void SliderNonlinear::setValue(int to)
|
||||
nearest = i;
|
||||
}
|
||||
|
||||
scrollTo(nearest);
|
||||
scrollTo(nearest, callCallbacks);
|
||||
}
|
||||
|
@ -52,14 +52,14 @@ public:
|
||||
void clearScrollBounds();
|
||||
|
||||
/// Value modifiers
|
||||
void scrollTo(int value);
|
||||
void scrollTo(int value, bool callCallbacks = true);
|
||||
void scrollBy(int amount) override;
|
||||
void scrollToMin();
|
||||
void scrollToMax();
|
||||
|
||||
/// Amount modifier
|
||||
void setAmount(int to);
|
||||
virtual void setValue(int to);
|
||||
virtual void setValue(int to, bool callCallbacks = true);
|
||||
|
||||
/// Accessors
|
||||
int getAmount() const;
|
||||
@ -95,7 +95,7 @@ class SliderNonlinear : public CSlider
|
||||
|
||||
using CSlider::setAmount; // make private
|
||||
public:
|
||||
void setValue(int to) override;
|
||||
void setValue(int to, bool callCallbacks) override;
|
||||
int getValue() const override;
|
||||
|
||||
SliderNonlinear(Point position, int length, const std::function<void(int)> & Moved, const std::vector<int> & values, int Value, Orientation orientation, EStyle style);
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/CSkillHandler.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjects/MiscObjects.h"
|
||||
@ -586,15 +587,16 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
|
||||
minesCount[mine->producedResource]++;
|
||||
|
||||
if (mine->producedResource == EGameResID::GOLD)
|
||||
totalIncome += mine->producedQuantity;
|
||||
totalIncome += mine->getProducedQuantity();
|
||||
}
|
||||
}
|
||||
|
||||
//Heroes can produce gold as well - skill, specialty or arts
|
||||
std::vector<const CGHeroInstance*> heroes = LOCPLINT->cb->getHeroesInfo(true);
|
||||
auto * playerSettings = LOCPLINT->cb->getPlayerSettings(LOCPLINT->playerID);
|
||||
for(auto & hero : heroes)
|
||||
{
|
||||
totalIncome += hero->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(EGameResID::GOLD))));
|
||||
totalIncome += hero->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(EGameResID::GOLD)))) * playerSettings->handicap.percentIncome / 100;
|
||||
}
|
||||
|
||||
//Add town income of all towns
|
||||
@ -605,8 +607,8 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
|
||||
}
|
||||
|
||||
//if player has some modded boosts we want to show that as well
|
||||
totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_CONSTANT_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD)));
|
||||
totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * towns.size();
|
||||
totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_CONSTANT_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * playerSettings->handicap.percentIncome / 100;
|
||||
totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * towns.size() * playerSettings->handicap.percentIncome / 100;
|
||||
|
||||
for(int i=0; i<7; i++)
|
||||
{
|
||||
|
@ -155,18 +155,21 @@
|
||||
"types" : {
|
||||
"boatNecropolis" : {
|
||||
"index" : 0,
|
||||
"compatibilityIdentifiers" : [ "evil" ],
|
||||
"actualAnimation" : "AB01_.def",
|
||||
"overlayAnimation" : "ABM01_.def",
|
||||
"flagAnimations" : ["ABF01L", "ABF01G", "ABF01R", "ABF01D", "ABF01B", "ABF01P", "ABF01W", "ABF01K"]
|
||||
},
|
||||
"boatCastle" : {
|
||||
"index" : 1,
|
||||
"compatibilityIdentifiers" : [ "good" ],
|
||||
"actualAnimation" : "AB02_.def",
|
||||
"overlayAnimation" : "ABM02_.def",
|
||||
"flagAnimations" : ["ABF02L", "ABF02G", "ABF02R", "ABF02D", "ABF02B", "ABF02P", "ABF02W", "ABF02K"]
|
||||
},
|
||||
"boatFortress" : {
|
||||
"index" : 2,
|
||||
"compatibilityIdentifiers" : [ "neutral" ],
|
||||
"actualAnimation" : "AB03_.def",
|
||||
"overlayAnimation" : "ABM03_.def",
|
||||
"flagAnimations" : ["ABF03L", "ABF03G", "ABF03R", "ABF03D", "ABF03B", "ABF03P", "ABF03W", "ABF03K"]
|
||||
|
@ -150,10 +150,11 @@
|
||||
"lobbyRandomMap": "R",
|
||||
"lobbyRandomTown": "T",
|
||||
"lobbyRandomTownVs": "V",
|
||||
"lobbyHandicap": "H",
|
||||
"lobbyReplayVideo": "R",
|
||||
"lobbySaveGame": [ "S", "Return", "Keypad Enter"],
|
||||
"lobbySelectScenario": "S",
|
||||
"lobbyToggleChat": "H",
|
||||
"lobbyToggleChat": "C",
|
||||
"lobbyTurnOptions": "T",
|
||||
"mainMenuBack": [ "B", "Escape" ],
|
||||
"mainMenuCampaign": "C",
|
||||
|
@ -43,7 +43,8 @@
|
||||
{
|
||||
"name" : "accountNameLabel",
|
||||
"type": "labelTitleMain",
|
||||
"position": {"x": 15, "y": 10}
|
||||
"position": {"x": 15, "y": 10},
|
||||
"maxWidth": 230
|
||||
},
|
||||
|
||||
{
|
||||
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -4,6 +4,12 @@ vcmi (1.6.0) jammy; urgency=medium
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 30 Aug 2024 12:00:00 +0200
|
||||
|
||||
vcmi (1.5.6) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Sun, 4 Aug 2024 12:00:00 +0200
|
||||
|
||||
vcmi (1.5.5) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
@ -1,7 +1,7 @@
|
||||
[](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.4)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.5)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.6)
|
||||
[](https://github.com/vcmi/vcmi/releases)
|
||||
|
||||
# VCMI Project
|
||||
|
@ -45,6 +45,27 @@ It can be found at https://aur.archlinux.org/packages/vcmi-git/
|
||||
|
||||
Information about building packages from the Arch User Repository (AUR) can be found at the Arch wiki.
|
||||
|
||||
### On NixOS or Nix
|
||||
|
||||
On NixOS or any system with nix available, [it is recommended](https://nixos.wiki/wiki/C) to use nix-shell. Create a shell.nix file with the following content:
|
||||
|
||||
```nix
|
||||
with import <nixpkgs> {};
|
||||
stdenv.mkDerivation {
|
||||
name = "build";
|
||||
nativeBuildInputs = [ cmake ];
|
||||
buildInputs = [
|
||||
cmake clang clang-tools llvm ccache ninja
|
||||
boost zlib minizip xz
|
||||
SDL2 SDL2_ttf SDL2_net SDL2_image SDL2_sound SDL2_mixer SDL2_gfx
|
||||
ffmpeg tbb vulkan-headers libxkbcommon
|
||||
qt6.full luajit
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
And put it into build directory. Then run `nix-shell` before running any build commands.
|
||||
|
||||
## Getting the sources
|
||||
|
||||
We recommend the following directory structure:
|
||||
|
@ -463,7 +463,9 @@ Configurable object has following structure:
|
||||
|
||||
`"text"`: [text](#text),
|
||||
|
||||
`"position"`: [position](#position)
|
||||
`"position"`: [position](#position),
|
||||
|
||||
`"maxWidth"`: int` optional, trim longer text
|
||||
|
||||
#### [VCMI-1.4] Multi-line label
|
||||
|
||||
|
@ -91,6 +91,7 @@
|
||||
<launchable type="desktop-id">vcmilauncher.desktop</launchable>
|
||||
<releases>
|
||||
<release version="1.6.0" date="2024-08-30" type="development"/>
|
||||
<release version="1.5.6" date="2024-08-04" type="stable"/>
|
||||
<release version="1.5.5" date="2024-07-17" type="stable"/>
|
||||
<release version="1.5.4" date="2024-07-12" type="stable"/>
|
||||
<release version="1.5.3" date="2024-06-21" type="stable"/>
|
||||
|
@ -463,6 +463,7 @@ set(lib_MAIN_HEADERS
|
||||
gameState/CGameState.h
|
||||
gameState/CGameStateCampaign.h
|
||||
gameState/EVictoryLossCheckResult.h
|
||||
gameState/HighScore.h
|
||||
gameState/InfoAboutArmy.h
|
||||
gameState/RumorState.h
|
||||
gameState/SThievesGuildInfo.h
|
||||
|
@ -25,7 +25,7 @@
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
PlayerSettings::PlayerSettings()
|
||||
: bonus(PlayerStartingBonus::RANDOM), color(0), handicap(NO_HANDICAP), compOnly(false)
|
||||
: bonus(PlayerStartingBonus::RANDOM), color(0), compOnly(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "ExtraOptionsInfo.h"
|
||||
#include "campaign/CampaignConstants.h"
|
||||
#include "serializer/Serializeable.h"
|
||||
#include "ResourceSet.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -65,6 +66,20 @@ enum class PlayerStartingBonus : int8_t
|
||||
RESOURCE = 2
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE Handicap {
|
||||
TResources startBonus = TResources();
|
||||
int percentIncome = 100;
|
||||
int percentGrowth = 100;
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler &h)
|
||||
{
|
||||
h & startBonus;
|
||||
h & percentIncome;
|
||||
h & percentGrowth;
|
||||
}
|
||||
};
|
||||
|
||||
/// Struct which describes the name, the color, the starting bonus of a player
|
||||
struct DLL_LINKAGE PlayerSettings
|
||||
{
|
||||
@ -77,8 +92,8 @@ struct DLL_LINKAGE PlayerSettings
|
||||
|
||||
std::string heroNameTextId;
|
||||
PlayerColor color; //from 0 -
|
||||
enum EHandicap {NO_HANDICAP, MILD, SEVERE};
|
||||
EHandicap handicap;//0-no, 1-mild, 2-severe
|
||||
|
||||
Handicap handicap;
|
||||
|
||||
std::string name;
|
||||
std::set<ui8> connectedPlayerIDs; //Empty - AI, or connectrd player ids
|
||||
@ -92,7 +107,14 @@ struct DLL_LINKAGE PlayerSettings
|
||||
h & heroNameTextId;
|
||||
h & bonus;
|
||||
h & color;
|
||||
h & handicap;
|
||||
if (h.version >= Handler::Version::PLAYER_HANDICAP)
|
||||
h & handicap;
|
||||
else
|
||||
{
|
||||
enum EHandicap {NO_HANDICAP, MILD, SEVERE};
|
||||
EHandicap handicapLegacy = NO_HANDICAP;
|
||||
h & handicapLegacy;
|
||||
}
|
||||
h & name;
|
||||
h & connectedPlayerIDs;
|
||||
h & compOnly;
|
||||
|
@ -50,6 +50,12 @@ static bool sameSideOfWall(BattleHex pos1, BattleHex pos2)
|
||||
return stackLeft == destLeft;
|
||||
}
|
||||
|
||||
static bool isInsideWalls(BattleHex pos)
|
||||
{
|
||||
const int wallInStackLine = lineToWallHex(pos.getY());
|
||||
return wallInStackLine < pos;
|
||||
}
|
||||
|
||||
// parts of wall
|
||||
static const std::pair<int, EWallPart> wallParts[] =
|
||||
{
|
||||
@ -128,6 +134,8 @@ ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const spells::Caster *
|
||||
return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
|
||||
if(hero->hasBonusOfType(BonusType::BLOCK_ALL_MAGIC))
|
||||
return ESpellCastProblem::MAGIC_IS_BLOCKED;
|
||||
if(!hero->hasSpellbook())
|
||||
return ESpellCastProblem::NO_SPELLBOOK;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -158,6 +166,11 @@ std::pair< std::vector<BattleHex>, int > CBattleInfoCallback::getPath(BattleHex
|
||||
return std::make_pair(path, reachability.distances[dest]);
|
||||
}
|
||||
|
||||
bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const
|
||||
{
|
||||
return isInsideWalls(from);
|
||||
}
|
||||
|
||||
bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const
|
||||
{
|
||||
auto isTileBlocked = [&](BattleHex tile)
|
||||
@ -742,15 +755,15 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit *
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE({});
|
||||
auto reachability = battleGetDistances(attacker, attacker->getPosition());
|
||||
int getMovementRange = attackerPosition.isValid() ? reachability[attackerPosition] : 0;
|
||||
return battleEstimateDamage(attacker, defender, getMovementRange, retaliationDmg);
|
||||
int movementRange = attackerPosition.isValid() ? reachability[attackerPosition] : 0;
|
||||
return battleEstimateDamage(attacker, defender, movementRange, retaliationDmg);
|
||||
}
|
||||
|
||||
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg) const
|
||||
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementRange, DamageEstimation * retaliationDmg) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE({});
|
||||
const bool shooting = battleCanShoot(attacker, defender->getPosition());
|
||||
const BattleAttackInfo bai(attacker, defender, getMovementRange, shooting);
|
||||
const BattleAttackInfo bai(attacker, defender, movementRange, shooting);
|
||||
return battleEstimateDamage(bai, retaliationDmg);
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,7 @@ public:
|
||||
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const;
|
||||
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg = nullptr) const;
|
||||
|
||||
bool battleIsInsideWalls(BattleHex from) const;
|
||||
bool battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const;
|
||||
bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
|
||||
bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
|
||||
|
@ -53,7 +53,7 @@ public:
|
||||
JsonNode toJsonNode() const;
|
||||
};
|
||||
|
||||
#define BONUS_TREE_DESERIALIZATION_FIX if(!h.saving && h.smartPointerSerialization) deserializationFix();
|
||||
#define BONUS_TREE_DESERIALIZATION_FIX if(!h.saving && h.loadingGamestate) deserializationFix();
|
||||
|
||||
/// Struct for handling bonuses of several types. Can be transferred to any hero
|
||||
struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>, public Serializeable
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "../texts/TextLocalizationContainer.h"
|
||||
#include "CampaignConstants.h"
|
||||
#include "CampaignScenarioPrologEpilog.h"
|
||||
#include "../gameState/HighScore.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -318,6 +319,8 @@ public:
|
||||
|
||||
std::string campaignSet;
|
||||
|
||||
std::vector<HighScoreParameter> highscoreParameters;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<Campaign&>(*this);
|
||||
@ -330,6 +333,8 @@ public:
|
||||
h & campaignSet;
|
||||
if (h.version >= Handler::Version::CAMPAIGN_MAP_TRANSLATIONS)
|
||||
h & mapTranslations;
|
||||
if (h.version >= Handler::Version::HIGHSCORE_PARAMETERS)
|
||||
h & highscoreParameters;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -32,18 +32,14 @@ public:
|
||||
int32_t getNum() const
|
||||
{
|
||||
int32_t result;
|
||||
|
||||
std::visit([&result] (const auto& v) { result = v.getNum(); }, value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string toString() const
|
||||
{
|
||||
std::string result;
|
||||
|
||||
std::visit([&result] (const auto& v) { result = v.encode(v.getNum()); }, value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -58,6 +54,13 @@ public:
|
||||
return IdentifierType();
|
||||
}
|
||||
|
||||
bool hasValue() const
|
||||
{
|
||||
bool result = false;
|
||||
std::visit([&result] (const auto& v) { result = v.hasValue(); }, value);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & value;
|
||||
|
@ -387,10 +387,14 @@ void CGameState::initDifficulty()
|
||||
const JsonNode & difficultyAI(config["ai"][GameConstants::DIFFICULTY_NAMES[scenarioOps->difficulty]]);
|
||||
const JsonNode & difficultyHuman(config["human"][GameConstants::DIFFICULTY_NAMES[scenarioOps->difficulty]]);
|
||||
|
||||
auto setDifficulty = [](PlayerState & state, const JsonNode & json)
|
||||
auto setDifficulty = [this](PlayerState & state, const JsonNode & json)
|
||||
{
|
||||
//set starting resources
|
||||
state.resources = TResources(json["resources"]);
|
||||
|
||||
//handicap
|
||||
const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(state.color);
|
||||
state.resources += ps.handicap.startBonus;
|
||||
|
||||
//set global bonuses
|
||||
for(auto & jsonBonus : json["globalBonuses"].Vector())
|
||||
@ -1614,7 +1618,7 @@ struct statsHLP
|
||||
}
|
||||
|
||||
// get total gold income
|
||||
static int getIncome(const PlayerState * ps)
|
||||
static int getIncome(const PlayerState * ps, int percentIncome)
|
||||
{
|
||||
int totalIncome = 0;
|
||||
const CGObjectInstance * heroOrTown = nullptr;
|
||||
@ -1622,7 +1626,7 @@ struct statsHLP
|
||||
//Heroes can produce gold as well - skill, specialty or arts
|
||||
for(const auto & h : ps->heroes)
|
||||
{
|
||||
totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD))));
|
||||
totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD)))) * percentIncome / 100;
|
||||
|
||||
if(!heroOrTown)
|
||||
heroOrTown = h;
|
||||
@ -1657,7 +1661,7 @@ struct statsHLP
|
||||
assert(mine);
|
||||
|
||||
if (mine->producedResource == EGameResID::GOLD)
|
||||
totalIncome += mine->producedQuantity;
|
||||
totalIncome += mine->getProducedQuantity();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1747,7 +1751,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
|
||||
}
|
||||
if(level >= 5) //income
|
||||
{
|
||||
FILL_FIELD(income, statsHLP::getIncome(&g->second))
|
||||
FILL_FIELD(income, statsHLP::getIncome(&g->second, scenarioOps->getIthPlayersSettings(g->second.color).handicap.percentIncome))
|
||||
}
|
||||
if(level >= 2) //best hero's stats
|
||||
{
|
||||
|
@ -133,13 +133,15 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr
|
||||
if(!art)
|
||||
return false;
|
||||
|
||||
bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
|
||||
ArtifactLocation al(hero.hero->id, artifactPosition);
|
||||
|
||||
if (takeable)
|
||||
bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
|
||||
bool locked = hero.hero->getSlot(al.slot)->locked;
|
||||
|
||||
if (!locked && takeable)
|
||||
hero.transferrableArtifacts.push_back(artifactPosition);
|
||||
|
||||
ArtifactLocation al(hero.hero->id, artifactPosition);
|
||||
if(!takeable && !hero.hero->getSlot(al.slot)->locked) //don't try removing locked artifacts -> it crashes #1719
|
||||
if (!locked && !takeable)
|
||||
{
|
||||
hero.hero->getArt(al.slot)->removeFrom(*hero.hero, al.slot);
|
||||
return true;
|
||||
|
41
lib/gameState/HighScore.h
Normal file
41
lib/gameState/HighScore.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* HighScore.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
|
||||
|
||||
class DLL_LINKAGE HighScoreParameter
|
||||
{
|
||||
public:
|
||||
int difficulty;
|
||||
int day;
|
||||
int townAmount;
|
||||
bool usedCheat;
|
||||
bool hasGrail;
|
||||
bool allEnemiesDefeated;
|
||||
std::string campaignName;
|
||||
std::string scenarioName;
|
||||
std::string playerName;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & difficulty;
|
||||
h & day;
|
||||
h & townAmount;
|
||||
h & usedCheat;
|
||||
h & hasGrail;
|
||||
h & allEnemiesDefeated;
|
||||
h & campaignName;
|
||||
h & scenarioName;
|
||||
h & playerName;
|
||||
}
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -348,15 +348,19 @@ void CGDwelling::newTurn(vstd::RNG & rand) const
|
||||
|
||||
std::vector<Component> CGDwelling::getPopupComponents(PlayerColor player) const
|
||||
{
|
||||
if (getOwner() != player)
|
||||
return {};
|
||||
bool visitedByOwner = getOwner() == player;
|
||||
|
||||
std::vector<Component> result;
|
||||
|
||||
if (ID == Obj::CREATURE_GENERATOR1 && !creatures.empty())
|
||||
{
|
||||
for (auto const & creature : creatures.front().second)
|
||||
result.emplace_back(ComponentType::CREATURE, creature, creatures.front().first);
|
||||
{
|
||||
if (visitedByOwner)
|
||||
result.emplace_back(ComponentType::CREATURE, creature, creatures.front().first);
|
||||
else
|
||||
result.emplace_back(ComponentType::CREATURE, creature);
|
||||
}
|
||||
}
|
||||
|
||||
if (ID == Obj::CREATURE_GENERATOR4)
|
||||
@ -364,7 +368,12 @@ std::vector<Component> CGDwelling::getPopupComponents(PlayerColor player) const
|
||||
for (auto const & creatureLevel : creatures)
|
||||
{
|
||||
if (!creatureLevel.second.empty())
|
||||
result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back(), creatureLevel.first);
|
||||
{
|
||||
if (visitedByOwner)
|
||||
result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back(), creatureLevel.first);
|
||||
else
|
||||
result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -426,7 +435,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
|
||||
if(count) //there are available creatures
|
||||
{
|
||||
|
||||
if (VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED))
|
||||
if (VLC->settings()->getBoolean(EGameSettings::DWELLINGS_MERGE_ON_RECRUIT))
|
||||
{
|
||||
SlotID testSlot = h->getSlotFor(crid);
|
||||
if(!testSlot.validSlot()) //no available slot - try merging army of visiting hero
|
||||
|
@ -1717,6 +1717,16 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
setHeroTypeName(typeName);
|
||||
}
|
||||
|
||||
if(!handler.saving)
|
||||
{
|
||||
if(!appearance)
|
||||
{
|
||||
// crossoverDeserialize
|
||||
type = getHeroType().toHeroType();
|
||||
appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
|
||||
}
|
||||
}
|
||||
|
||||
CArmedInstance::serializeJsonOptions(handler);
|
||||
|
||||
{
|
||||
@ -1732,13 +1742,6 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
|
||||
if(!handler.saving)
|
||||
{
|
||||
if(!appearance)
|
||||
{
|
||||
// crossoverDeserialize
|
||||
type = getHeroType().toHeroType();
|
||||
appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
|
||||
}
|
||||
|
||||
patrol.patrolling = (rawPatrolRadius > NO_PATROLING);
|
||||
patrol.initialPos = visitablePos();
|
||||
patrol.patrolRadius = (rawPatrolRadius > NO_PATROLING) ? rawPatrolRadius : 0;
|
||||
|
@ -137,6 +137,14 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
|
||||
const int base = creature->getGrowth();
|
||||
int castleBonus = 0;
|
||||
|
||||
if(tempOwner.isValidPlayer())
|
||||
{
|
||||
auto * playerSettings = cb->getPlayerSettings(tempOwner);
|
||||
ret.handicapPercentage = playerSettings->handicap.percentGrowth;
|
||||
}
|
||||
else
|
||||
ret.handicapPercentage = 100;
|
||||
|
||||
ret.entries.emplace_back(VLC->generaltexth->allTexts[590], base); // \n\nBasic growth %d"
|
||||
|
||||
if (hasBuilt(BuildingID::CASTLE))
|
||||
@ -215,6 +223,11 @@ TResources CGTownInstance::dailyIncome() const
|
||||
ret += p.second->produce;
|
||||
}
|
||||
}
|
||||
|
||||
auto playerSettings = cb->gameState()->scenarioOps->getIthPlayersSettings(getOwner());
|
||||
for(TResources::nziterator it(ret); it.valid(); it++)
|
||||
// always round up income - we don't want to always produce zero if handicap in use
|
||||
ret[it->resType] = (ret[it->resType] * playerSettings.handicap.percentIncome + 99) / 100;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1257,7 +1270,8 @@ int GrowthInfo::totalGrowth() const
|
||||
for(const Entry &entry : entries)
|
||||
ret += entry.count;
|
||||
|
||||
return ret;
|
||||
// always round up income - we don't want buildings to always produce zero if handicap in use
|
||||
return (ret * handicapPercentage + 99) / 100;
|
||||
}
|
||||
|
||||
void CGTownInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const
|
||||
|
@ -41,6 +41,7 @@ struct DLL_LINKAGE GrowthInfo
|
||||
|
||||
std::vector<Entry> entries;
|
||||
int totalGrowth() const;
|
||||
int handicapPercentage;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket, public INativeTerrainProvider, public ICreatureUpgrader
|
||||
|
@ -184,7 +184,26 @@ void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const
|
||||
void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
|
||||
{
|
||||
if(answer == 0)
|
||||
{
|
||||
switch (configuration.visitMode)
|
||||
{
|
||||
case Rewardable::VISIT_UNLIMITED:
|
||||
case Rewardable::VISIT_BONUS:
|
||||
case Rewardable::VISIT_HERO:
|
||||
case Rewardable::VISIT_LIMITER:
|
||||
{
|
||||
// workaround for object with refusable reward not getting marked as visited
|
||||
// TODO: better solution that would also work for player-visitable objects
|
||||
if (!wasScouted(hero->getOwner()))
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_TEAM, id, hero->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return; // player refused
|
||||
}
|
||||
|
||||
if(answer > 0 && answer-1 < configuration.info.size())
|
||||
{
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "../gameState/CGameState.h"
|
||||
#include "../mapping/CMap.h"
|
||||
#include "../CPlayerState.h"
|
||||
#include "../StartInfo.h"
|
||||
#include "../serializer/JsonSerializeFormat.h"
|
||||
#include "../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
@ -103,7 +104,7 @@ void CGMine::newTurn(vstd::RNG & rand) const
|
||||
if (tempOwner == PlayerColor::NEUTRAL)
|
||||
return;
|
||||
|
||||
cb->giveResource(tempOwner, producedResource, producedQuantity);
|
||||
cb->giveResource(tempOwner, producedResource, getProducedQuantity());
|
||||
}
|
||||
|
||||
void CGMine::initObj(vstd::RNG & rand)
|
||||
@ -177,7 +178,7 @@ void CGMine::flagMine(const PlayerColor & player) const
|
||||
iw.type = EInfoWindowMode::AUTO;
|
||||
iw.text.appendTextID(TextIdentifier("core.mineevnt", producedResource.getNum()).get()); //not use subID, abandoned mines uses default mine texts
|
||||
iw.player = player;
|
||||
iw.components.emplace_back(ComponentType::RESOURCE_PER_DAY, producedResource, producedQuantity);
|
||||
iw.components.emplace_back(ComponentType::RESOURCE_PER_DAY, producedResource, getProducedQuantity());
|
||||
cb->showInfoDialog(&iw);
|
||||
}
|
||||
|
||||
@ -195,6 +196,13 @@ ui32 CGMine::defaultResProduction() const
|
||||
}
|
||||
}
|
||||
|
||||
ui32 CGMine::getProducedQuantity() const
|
||||
{
|
||||
auto * playerSettings = cb->getPlayerSettings(getOwner());
|
||||
// always round up income - we don't want mines to always produce zero if handicap in use
|
||||
return (producedQuantity * playerSettings->handicap.percentIncome + 99) / 100;
|
||||
}
|
||||
|
||||
void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
|
||||
{
|
||||
if(result.winner == 0) //attacker won
|
||||
|
@ -181,6 +181,7 @@ public:
|
||||
h & abandonedMineResources;
|
||||
}
|
||||
ui32 defaultResProduction() const;
|
||||
ui32 getProducedQuantity() const;
|
||||
|
||||
protected:
|
||||
void serializeJsonOptions(JsonSerializeFormat & handler) override;
|
||||
|
@ -168,6 +168,7 @@ public:
|
||||
virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) {}
|
||||
virtual void visitLobbySetPlayer(LobbySetPlayer & pack) {}
|
||||
virtual void visitLobbySetPlayerName(LobbySetPlayerName & pack) {}
|
||||
virtual void visitLobbySetPlayerHandicap(LobbySetPlayerHandicap & pack) {}
|
||||
virtual void visitLobbySetSimturns(LobbySetSimturns & pack) {}
|
||||
virtual void visitLobbySetTurnTime(LobbySetTurnTime & pack) {}
|
||||
virtual void visitLobbySetExtraOptions(LobbySetExtraOptions & pack) {}
|
||||
|
@ -790,6 +790,11 @@ void LobbySetPlayerName::visitTyped(ICPackVisitor & visitor)
|
||||
visitor.visitLobbySetPlayerName(*this);
|
||||
}
|
||||
|
||||
void LobbySetPlayerHandicap::visitTyped(ICPackVisitor & visitor)
|
||||
{
|
||||
visitor.visitLobbySetPlayerHandicap(*this);
|
||||
}
|
||||
|
||||
void LobbySetSimturns::visitTyped(ICPackVisitor & visitor)
|
||||
{
|
||||
visitor.visitLobbySetSimturns(*this);
|
||||
|
@ -155,9 +155,13 @@ struct DLL_LINKAGE LobbyStartGame : public CLobbyPackToPropagate
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
if (!h.saving)
|
||||
h.loadingGamestate = true;
|
||||
h & clientId;
|
||||
h & initializedStartInfo;
|
||||
h & initializedGameState;
|
||||
if (!h.saving)
|
||||
h.loadingGamestate = false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -281,6 +285,20 @@ struct DLL_LINKAGE LobbySetPlayerName : public CLobbyPackToServer
|
||||
}
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE LobbySetPlayerHandicap : public CLobbyPackToServer
|
||||
{
|
||||
PlayerColor color = PlayerColor::CANNOT_DETERMINE;
|
||||
Handicap handicap = Handicap();
|
||||
|
||||
void visitTyped(ICPackVisitor & visitor) override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & color;
|
||||
h & handicap;
|
||||
}
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE LobbySetSimturns : public CLobbyPackToServer
|
||||
{
|
||||
SimturnsInfo simturnsInfo;
|
||||
|
@ -55,6 +55,7 @@ void registerTypesLobbyPacks(Serializer &s)
|
||||
s.template registerType<CLobbyPackToServer, LobbySetCampaignBonus>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetPlayer>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetPlayerName>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetPlayerHandicap>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetTurnTime>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetSimturns>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetDifficulty>();
|
||||
|
@ -115,8 +115,10 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps, const CG
|
||||
comps.emplace_back(ComponentType::ARTIFACT, entry);
|
||||
|
||||
for(const auto & entry : spells)
|
||||
if (!h || h->canLearnSpell(entry.toEntity(VLC), true))
|
||||
comps.emplace_back(ComponentType::SPELL, entry);
|
||||
{
|
||||
bool learnable = !h || h->canLearnSpell(entry.toEntity(VLC), true);
|
||||
comps.emplace_back(ComponentType::SPELL, entry, learnable ? 0 : -1);
|
||||
}
|
||||
|
||||
for(const auto & entry : creatures)
|
||||
comps.emplace_back(ComponentType::CREATURE, entry.type->getId(), entry.count);
|
||||
|
@ -15,9 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
BinaryDeserializer::BinaryDeserializer(IBinaryReader * r): CLoaderBase(r)
|
||||
{
|
||||
saving = false;
|
||||
version = Version::NONE;
|
||||
smartPointerSerialization = true;
|
||||
reverseEndianness = false;
|
||||
|
||||
registerTypes(*this);
|
||||
|
@ -165,8 +165,9 @@ public:
|
||||
std::map<uint32_t, Serializeable*> loadedPointers;
|
||||
std::map<const Serializeable*, std::shared_ptr<Serializeable>> loadedSharedPointers;
|
||||
IGameCallback * cb = nullptr;
|
||||
bool smartPointerSerialization;
|
||||
bool saving;
|
||||
static constexpr bool trackSerializedPointers = true;
|
||||
static constexpr bool saving = false;
|
||||
bool loadingGamestate = false;
|
||||
|
||||
bool hasFeature(Version what) const
|
||||
{
|
||||
@ -342,7 +343,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t pid = 0xffffffff; //pointer id (or maybe rather pointee id)
|
||||
if(smartPointerSerialization)
|
||||
if(trackSerializedPointers)
|
||||
{
|
||||
load( pid ); //get the id
|
||||
auto i = loadedPointers.find(pid); //lookup
|
||||
@ -383,7 +384,7 @@ public:
|
||||
template <typename T>
|
||||
void ptrAllocated(T *ptr, uint32_t pid)
|
||||
{
|
||||
if(smartPointerSerialization && pid != 0xffffffff)
|
||||
if(trackSerializedPointers && pid != 0xffffffff)
|
||||
loadedPointers[pid] = const_cast<Serializeable*>(dynamic_cast<const Serializeable*>(ptr)); //add loaded pointer to our lookup map; cast is to avoid errors with const T* pt
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,6 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
BinarySerializer::BinarySerializer(IBinaryWriter * w): CSaverBase(w)
|
||||
{
|
||||
saving=true;
|
||||
smartPointerSerialization = true;
|
||||
registerTypes(*this);
|
||||
}
|
||||
|
||||
|
@ -118,8 +118,9 @@ public:
|
||||
std::map<const Serializeable*, uint32_t> savedPointers;
|
||||
|
||||
Version version = Version::CURRENT;
|
||||
bool smartPointerSerialization;
|
||||
bool saving;
|
||||
static constexpr bool trackSerializedPointers = true;
|
||||
static constexpr bool saving = true;
|
||||
bool loadingGamestate = false;
|
||||
|
||||
bool hasFeature(Version what) const
|
||||
{
|
||||
@ -257,7 +258,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if(smartPointerSerialization)
|
||||
if(trackSerializedPointers)
|
||||
{
|
||||
// We might have an object that has multiple inheritance and store it via the non-first base pointer.
|
||||
// Therefore, all pointers need to be normalized to the actual object address.
|
||||
|
@ -29,6 +29,7 @@ int CLoadFile::read(std::byte * data, unsigned size)
|
||||
|
||||
void CLoadFile::openNextFile(const boost::filesystem::path & fname, ESerializationVersion minimalVersion)
|
||||
{
|
||||
serializer.loadingGamestate = true;
|
||||
assert(!serializer.reverseEndianness);
|
||||
assert(minimalVersion <= ESerializationVersion::CURRENT);
|
||||
|
||||
|
@ -151,18 +151,6 @@ void CConnection::enterGameplayConnectionMode(CGameState * gs)
|
||||
enableSmartVectorMemberSerializatoin(gs);
|
||||
}
|
||||
|
||||
void CConnection::disableSmartPointerSerialization()
|
||||
{
|
||||
deserializer->smartPointerSerialization = false;
|
||||
serializer->smartPointerSerialization = false;
|
||||
}
|
||||
|
||||
void CConnection::enableSmartPointerSerialization()
|
||||
{
|
||||
deserializer->smartPointerSerialization = true;
|
||||
serializer->smartPointerSerialization = true;
|
||||
}
|
||||
|
||||
void CConnection::disableSmartVectorMemberSerialization()
|
||||
{
|
||||
packReader->smartVectorMembersSerialization = false;
|
||||
|
@ -38,8 +38,6 @@ class DLL_LINKAGE CConnection : boost::noncopyable
|
||||
|
||||
void disableStackSendingByID();
|
||||
void enableStackSendingByID();
|
||||
void disableSmartPointerSerialization();
|
||||
void enableSmartPointerSerialization();
|
||||
void disableSmartVectorMemberSerialization();
|
||||
void enableSmartVectorMemberSerializatoin(CGameState * gs);
|
||||
|
||||
|
@ -57,6 +57,8 @@ enum class ESerializationVersion : int32_t
|
||||
SIMPLE_TEXT_CONTAINER_SERIALIZATION, // 847 - text container is serialized using common routine instead of custom approach
|
||||
MAP_FORMAT_ADDITIONAL_INFOS, // 848 - serialize new infos in map format
|
||||
REMOVE_LIB_RNG, // 849 - removed random number generators from library classes
|
||||
HIGHSCORE_PARAMETERS, // 850 - saves parameter for campaign
|
||||
PLAYER_HANDICAP, // 851 - player handicap selection at game start
|
||||
|
||||
CURRENT = REMOVE_LIB_RNG
|
||||
CURRENT = PLAYER_HANDICAP
|
||||
};
|
||||
|
@ -367,6 +367,11 @@ void MetaString::appendName(const CreatureID & id, TQuantity count)
|
||||
appendNamePlural(id);
|
||||
}
|
||||
|
||||
void MetaString::appendName(const GameResID& id)
|
||||
{
|
||||
appendTextID(TextIdentifier("core.restypes", id.getNum()).get());
|
||||
}
|
||||
|
||||
void MetaString::appendNameSingular(const CreatureID & id)
|
||||
{
|
||||
appendTextID(id.toEntity(VLC)->getNameSingularTextID());
|
||||
|
@ -80,6 +80,7 @@ public:
|
||||
void appendName(const SpellID& id);
|
||||
void appendName(const PlayerColor& id);
|
||||
void appendName(const CreatureID & id, TQuantity count);
|
||||
void appendName(const GameResID& id);
|
||||
void appendNameSingular(const CreatureID & id);
|
||||
void appendNamePlural(const CreatureID & id);
|
||||
void appendEOL();
|
||||
|
@ -694,6 +694,15 @@ void CGameHandler::onNewTurn()
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & player : gs->players)
|
||||
{
|
||||
if (player.second.status != EPlayerStatus::INGAME)
|
||||
continue;
|
||||
|
||||
if (player.second.heroes.empty() && player.second.towns.empty())
|
||||
throw std::runtime_error("Invalid player in player state! Player " + std::to_string(player.first.getNum()) + ", map name: " + gs->map->name.toString() + ", map description: " + gs->map->description.toString());
|
||||
}
|
||||
|
||||
if (newWeek && !firstTurn)
|
||||
{
|
||||
n.specialWeek = NewTurn::NORMAL;
|
||||
@ -760,6 +769,8 @@ void CGameHandler::onNewTurn()
|
||||
continue;
|
||||
|
||||
assert(elem.first.isValidPlayer());//illegal player number!
|
||||
|
||||
auto playerSettings = gameState()->scenarioOps->getIthPlayersSettings(elem.first);
|
||||
|
||||
std::pair<PlayerColor, si32> playerGold(elem.first, elem.second.resources[EGameResID::GOLD]);
|
||||
hadGold.insert(playerGold);
|
||||
@ -773,8 +784,8 @@ void CGameHandler::onNewTurn()
|
||||
{
|
||||
for (GameResID k = GameResID::WOOD; k < GameResID::COUNT; k++)
|
||||
{
|
||||
n.res[elem.first][k] += elem.second.valOfBonuses(BonusType::RESOURCES_CONSTANT_BOOST, BonusSubtypeID(k));
|
||||
n.res[elem.first][k] += elem.second.valOfBonuses(BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST, BonusSubtypeID(k)) * elem.second.towns.size();
|
||||
n.res[elem.first][k] += elem.second.valOfBonuses(BonusType::RESOURCES_CONSTANT_BOOST, BonusSubtypeID(k)) * playerSettings.handicap.percentIncome / 100;
|
||||
n.res[elem.first][k] += elem.second.valOfBonuses(BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST, BonusSubtypeID(k)) * elem.second.towns.size() * playerSettings.handicap.percentIncome / 100;
|
||||
}
|
||||
|
||||
if(newWeek) //weekly crystal generation if 1 or more crystal dragons in any hero army or town garrison
|
||||
@ -806,7 +817,7 @@ void CGameHandler::onNewTurn()
|
||||
}
|
||||
}
|
||||
if(hasCrystalGenCreature)
|
||||
n.res[elem.first][EGameResID::CRYSTAL] += 3;
|
||||
n.res[elem.first][EGameResID::CRYSTAL] += 3 * playerSettings.handicap.percentIncome / 100;
|
||||
}
|
||||
}
|
||||
|
||||
@ -828,7 +839,7 @@ void CGameHandler::onNewTurn()
|
||||
{
|
||||
for (GameResID k = GameResID::WOOD; k < GameResID::COUNT; k++)
|
||||
{
|
||||
n.res[elem.first][k] += h->valOfBonuses(BonusType::GENERATE_RESOURCE, BonusSubtypeID(k));
|
||||
n.res[elem.first][k] += h->valOfBonuses(BonusType::GENERATE_RESOURCE, BonusSubtypeID(k)) * playerSettings.handicap.percentIncome / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -624,8 +624,6 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
|
||||
pset.heroNameTextId = pinfo.mainCustomHeroNameTextId;
|
||||
pset.heroPortrait = pinfo.mainCustomHeroPortrait;
|
||||
}
|
||||
|
||||
pset.handicap = PlayerSettings::NO_HANDICAP;
|
||||
}
|
||||
|
||||
if(mi->isRandomMap && mapGenOpts)
|
||||
@ -765,6 +763,60 @@ void CVCMIServer::setPlayerName(PlayerColor color, std::string name)
|
||||
setPlayerConnectedId(player, nameID);
|
||||
}
|
||||
|
||||
void CVCMIServer::setPlayerHandicap(PlayerColor color, Handicap handicap)
|
||||
{
|
||||
if(color == PlayerColor::CANNOT_DETERMINE)
|
||||
return;
|
||||
|
||||
si->playerInfos[color].handicap = handicap;
|
||||
|
||||
int humanPlayer = 0;
|
||||
for (const auto & pi : si->playerInfos)
|
||||
if(pi.second.isControlledByHuman())
|
||||
humanPlayer++;
|
||||
|
||||
if(humanPlayer < 2) // Singleplayer
|
||||
return;
|
||||
|
||||
MetaString str;
|
||||
str.appendTextID("vcmi.lobby.handicap");
|
||||
str.appendRawString(" ");
|
||||
str.appendName(color);
|
||||
str.appendRawString(":");
|
||||
|
||||
if(handicap.startBonus.empty() && handicap.percentIncome == 100 && handicap.percentGrowth == 100)
|
||||
{
|
||||
str.appendRawString(" ");
|
||||
str.appendTextID("core.genrltxt.523");
|
||||
announceTxt(str);
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto & res : EGameResID::ALL_RESOURCES())
|
||||
if(handicap.startBonus[res] != 0)
|
||||
{
|
||||
str.appendRawString(" ");
|
||||
str.appendName(res);
|
||||
str.appendRawString(":");
|
||||
str.appendRawString(std::to_string(handicap.startBonus[res]));
|
||||
}
|
||||
if(handicap.percentIncome != 100)
|
||||
{
|
||||
str.appendRawString(" ");
|
||||
str.appendTextID("core.jktext.32");
|
||||
str.appendRawString(":");
|
||||
str.appendRawString(std::to_string(handicap.percentIncome) + "%");
|
||||
}
|
||||
if(handicap.percentGrowth != 100)
|
||||
{
|
||||
str.appendRawString(" ");
|
||||
str.appendTextID("core.genrltxt.194");
|
||||
str.appendRawString(":");
|
||||
str.appendRawString(std::to_string(handicap.percentGrowth) + "%");
|
||||
}
|
||||
announceTxt(str);
|
||||
}
|
||||
|
||||
void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
|
||||
{
|
||||
PlayerSettings & s = si->playerInfos[player];
|
||||
@ -1011,6 +1063,39 @@ void CVCMIServer::multiplayerWelcomeMessage()
|
||||
|
||||
gh->playerMessages->broadcastSystemMessage("Use '!help' to list available commands");
|
||||
|
||||
for (const auto & pi : si->playerInfos)
|
||||
if(!pi.second.handicap.startBonus.empty() || pi.second.handicap.percentIncome != 100 || pi.second.handicap.percentGrowth != 100)
|
||||
{
|
||||
MetaString str;
|
||||
str.appendTextID("vcmi.lobby.handicap");
|
||||
str.appendRawString(" ");
|
||||
str.appendName(pi.first);
|
||||
str.appendRawString(":");
|
||||
for(auto & res : EGameResID::ALL_RESOURCES())
|
||||
if(pi.second.handicap.startBonus[res] != 0)
|
||||
{
|
||||
str.appendRawString(" ");
|
||||
str.appendName(res);
|
||||
str.appendRawString(":");
|
||||
str.appendRawString(std::to_string(pi.second.handicap.startBonus[res]));
|
||||
}
|
||||
if(pi.second.handicap.percentIncome != 100)
|
||||
{
|
||||
str.appendRawString(" ");
|
||||
str.appendTextID("core.jktext.32");
|
||||
str.appendRawString(":");
|
||||
str.appendRawString(std::to_string(pi.second.handicap.percentIncome) + "%");
|
||||
}
|
||||
if(pi.second.handicap.percentGrowth != 100)
|
||||
{
|
||||
str.appendRawString(" ");
|
||||
str.appendTextID("core.genrltxt.194");
|
||||
str.appendRawString(":");
|
||||
str.appendRawString(std::to_string(pi.second.handicap.percentGrowth) + "%");
|
||||
}
|
||||
gh->playerMessages->broadcastSystemMessage(str);
|
||||
}
|
||||
|
||||
std::vector<std::string> optionIds;
|
||||
if(si->extraOptionsInfo.cheatsAllowed)
|
||||
optionIds.emplace_back("vcmi.optionsTab.cheatAllowed.hover");
|
||||
|
@ -117,6 +117,7 @@ public:
|
||||
// Work with LobbyInfo
|
||||
void setPlayer(PlayerColor clickedColor);
|
||||
void setPlayerName(PlayerColor player, std::string name);
|
||||
void setPlayerHandicap(PlayerColor player, Handicap handicap);
|
||||
void optionNextHero(PlayerColor player, int dir); //dir == -1 or +1
|
||||
void optionSetHero(PlayerColor player, HeroTypeID id);
|
||||
HeroTypeID nextAllowedHero(PlayerColor player, HeroTypeID id, int direction);
|
||||
|
@ -89,6 +89,7 @@ public:
|
||||
void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override;
|
||||
void visitLobbySetPlayer(LobbySetPlayer & pack) override;
|
||||
void visitLobbySetPlayerName(LobbySetPlayerName & pack) override;
|
||||
void visitLobbySetPlayerHandicap(LobbySetPlayerHandicap & pack) override;
|
||||
void visitLobbySetTurnTime(LobbySetTurnTime & pack) override;
|
||||
void visitLobbySetExtraOptions(LobbySetExtraOptions & pack) override;
|
||||
void visitLobbySetSimturns(LobbySetSimturns & pack) override;
|
||||
|
@ -346,6 +346,12 @@ void ApplyOnServerNetPackVisitor::visitLobbySetPlayerName(LobbySetPlayerName & p
|
||||
result = true;
|
||||
}
|
||||
|
||||
void ApplyOnServerNetPackVisitor::visitLobbySetPlayerHandicap(LobbySetPlayerHandicap & pack)
|
||||
{
|
||||
srv.setPlayerHandicap(pack.color, pack.handicap);
|
||||
result = true;
|
||||
}
|
||||
|
||||
void ApplyOnServerNetPackVisitor::visitLobbySetSimturns(LobbySetSimturns & pack)
|
||||
{
|
||||
srv.si->simturnsInfo = pack.simturnsInfo;
|
||||
|
@ -496,7 +496,7 @@ bool BattleActionProcessor::doHealAction(const CBattleInfoCallback & battle, con
|
||||
else
|
||||
destStack = battle.battleGetUnitByPos(target.at(0).hexValue);
|
||||
|
||||
if(stack == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype == BonusSubtypeID())
|
||||
if(stack == nullptr || destStack == nullptr || !healerAbility || !healerAbility->subtype.hasValue())
|
||||
{
|
||||
gameHandler->complain("There is either no healer, no destination, or healer cannot heal :P");
|
||||
}
|
||||
@ -973,7 +973,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
|
||||
}
|
||||
|
||||
std::shared_ptr<const Bonus> bonus = attacker->getFirstBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
|
||||
if(bonus && ranged) //TODO: make it work in melee?
|
||||
if(bonus && ranged && bonus->subtype.hasValue()) //TODO: make it work in melee?
|
||||
{
|
||||
//this is need for displaying hit animation
|
||||
bat.flags |= BattleAttack::SPELL_LIKE;
|
||||
|
@ -389,20 +389,47 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat
|
||||
attack.side = next->unitSide();
|
||||
attack.stackNumber = next->unitId();
|
||||
|
||||
//TODO: select target by priority
|
||||
// TODO: unify logic with AI?
|
||||
// Find best target using logic similar to H3 AI
|
||||
|
||||
const auto & isBetterTarget = [&battle](const battle::Unit * candidate, const battle::Unit * current)
|
||||
{
|
||||
bool candidateInsideWalls = battle.battleIsInsideWalls(candidate->getPosition());
|
||||
bool currentInsideWalls = battle.battleIsInsideWalls(current->getPosition());
|
||||
|
||||
if (candidateInsideWalls != currentInsideWalls)
|
||||
return candidateInsideWalls > currentInsideWalls;
|
||||
|
||||
// also check for war machines - shooters are more dangerous than war machines, ballista or catapult
|
||||
bool candidateCanShoot = candidate->canShoot() && candidate->unitType()->warMachine == ArtifactID::NONE;
|
||||
bool currentCanShoot = current->canShoot() && current->unitType()->warMachine == ArtifactID::NONE;
|
||||
|
||||
if (candidateCanShoot != currentCanShoot)
|
||||
return candidateCanShoot > currentCanShoot;
|
||||
|
||||
int64_t candidateTargetValue = static_cast<int64_t>(candidate->unitType()->getAIValue() * candidate->getCount());
|
||||
int64_t currentTargetValue = static_cast<int64_t>(current->unitType()->getAIValue() * current->getCount());
|
||||
|
||||
return candidateTargetValue > currentTargetValue;
|
||||
};
|
||||
|
||||
const battle::Unit * target = nullptr;
|
||||
|
||||
for(auto & elem : battle.battleGetAllStacks(true))
|
||||
{
|
||||
if(elem->unitType()->getId() != CreatureID::CATAPULT
|
||||
&& elem->unitOwner() != next->unitOwner()
|
||||
&& elem->isValidTarget()
|
||||
&& battle.battleCanShoot(next, elem->getPosition()))
|
||||
{
|
||||
target = elem;
|
||||
break;
|
||||
}
|
||||
if (elem->unitOwner() == next->unitOwner())
|
||||
continue;
|
||||
|
||||
if (!elem->isValidTarget())
|
||||
continue;
|
||||
|
||||
if (!battle.battleCanShoot(next, elem->getPosition()))
|
||||
continue;
|
||||
|
||||
if (target && !isBetterTarget(elem, target))
|
||||
continue;
|
||||
|
||||
target = elem;
|
||||
}
|
||||
|
||||
if(target == nullptr)
|
||||
|
@ -175,8 +175,6 @@ public:
|
||||
pset.heroNameTextId = pinfo.mainCustomHeroNameTextId;
|
||||
pset.heroPortrait = HeroTypeID(pinfo.mainCustomHeroPortrait);
|
||||
}
|
||||
|
||||
pset.handicap = PlayerSettings::NO_HANDICAP;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user