mirror of
https://github.com/vcmi/vcmi.git
synced 2025-05-31 22:59:54 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
a7240041ec
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,7 +1,7 @@
|
||||
[submodule "test/googletest"]
|
||||
path = test/googletest
|
||||
url = https://github.com/google/googletest
|
||||
branch = v1.13.x
|
||||
branch = v1.15.x
|
||||
[submodule "AI/FuzzyLite"]
|
||||
path = AI/FuzzyLite
|
||||
url = https://github.com/fuzzylite/fuzzylite.git
|
||||
|
33
Global.h
33
Global.h
@ -102,6 +102,12 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
# define STRONG_INLINE inline
|
||||
#endif
|
||||
|
||||
// Required for building boost::stacktrace on macOS.
|
||||
// See https://github.com/boostorg/stacktrace/issues/88
|
||||
#if defined(VCMI_APPLE)
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include <algorithm>
|
||||
@ -700,6 +706,33 @@ namespace vstd
|
||||
return a + (b - a) * f;
|
||||
}
|
||||
|
||||
/// Divides dividend by divisor and rounds result up
|
||||
/// For use with integer-only arithmetic
|
||||
template<typename Integer1, typename Integer2>
|
||||
Integer1 divideAndCeil(const Integer1 & dividend, const Integer2 & divisor)
|
||||
{
|
||||
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
|
||||
return (dividend + divisor - 1) / divisor;
|
||||
}
|
||||
|
||||
/// Divides dividend by divisor and rounds result to nearest
|
||||
/// For use with integer-only arithmetic
|
||||
template<typename Integer1, typename Integer2>
|
||||
Integer1 divideAndRound(const Integer1 & dividend, const Integer2 & divisor)
|
||||
{
|
||||
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
|
||||
return (dividend + divisor / 2 - 1) / divisor;
|
||||
}
|
||||
|
||||
/// Divides dividend by divisor and rounds result down
|
||||
/// For use with integer-only arithmetic
|
||||
template<typename Integer1, typename Integer2>
|
||||
Integer1 divideAndFloor(const Integer1 & dividend, const Integer2 & divisor)
|
||||
{
|
||||
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
|
||||
return dividend / divisor;
|
||||
}
|
||||
|
||||
template<typename Floating>
|
||||
bool isAlmostZero(const Floating & value)
|
||||
{
|
||||
|
@ -72,6 +72,11 @@
|
||||
"vcmi.lobby.noUnderground" : "sem subterrâneo",
|
||||
"vcmi.lobby.sortDate" : "Classifica mapas por data de alteração",
|
||||
"vcmi.lobby.backToLobby" : "Voltar para a sala de espera",
|
||||
"vcmi.lobby.author" : "Autor",
|
||||
"vcmi.lobby.handicap" : "Desvant.",
|
||||
"vcmi.lobby.handicap.resource" : "Fornece aos jogadores recursos apropriados para começar, além dos recursos iniciais normais. Valores negativos são permitidos, mas são limitados a 0 no total (o jogador nunca começa com recursos negativos).",
|
||||
"vcmi.lobby.handicap.income" : "Altera as várias rendas do jogador em porcentagem. Arredondado para cima.",
|
||||
"vcmi.lobby.handicap.growth" : "Altera a taxa de produção das criaturas nas cidades possuídas pelo jogador. Arredondado para cima.",
|
||||
|
||||
"vcmi.lobby.login.title" : "Sala de Espera Online do VCMI",
|
||||
"vcmi.lobby.login.username" : "Nome de usuário:",
|
||||
|
@ -433,6 +433,50 @@ QuickSpellPanel::QuickSpellPanel(BattleInterface & owner)
|
||||
create();
|
||||
}
|
||||
|
||||
std::vector<std::tuple<SpellID, bool>> QuickSpellPanel::getSpells()
|
||||
{
|
||||
std::vector<SpellID> spellIds;
|
||||
std::vector<bool> spellIdsFromSetting;
|
||||
for(int i = 0; i < QUICKSPELL_SLOTS; i++)
|
||||
{
|
||||
std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(i)].String();
|
||||
SpellID id;
|
||||
try
|
||||
{
|
||||
id = SpellID::decode(spellIdentifier);
|
||||
}
|
||||
catch(const IdentifierResolutionException& e)
|
||||
{
|
||||
id = SpellID::NONE;
|
||||
}
|
||||
spellIds.push_back(id);
|
||||
spellIdsFromSetting.push_back(id != SpellID::NONE);
|
||||
}
|
||||
|
||||
// autofill empty slots with spells if possible
|
||||
auto hero = owner.getBattle()->battleGetMyHero();
|
||||
for(int i = 0; i < QUICKSPELL_SLOTS; i++)
|
||||
{
|
||||
if(spellIds[i] != SpellID::NONE)
|
||||
continue;
|
||||
|
||||
for(const auto & availableSpellID : CGI->spellh->getDefaultAllowed())
|
||||
{
|
||||
const auto * availableSpell = availableSpellID.toSpell();
|
||||
if(!availableSpell->isAdventure() && !availableSpell->isCreatureAbility() && hero->canCastThisSpell(availableSpell) && !vstd::contains(spellIds, availableSpell->getId()))
|
||||
{
|
||||
spellIds[i] = availableSpell->getId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<SpellID, bool>> ret;
|
||||
for(int i = 0; i < QUICKSPELL_SLOTS; i++)
|
||||
ret.push_back(std::make_tuple(spellIds[i], spellIdsFromSetting[i]));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void QuickSpellPanel::create()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
@ -447,18 +491,11 @@ void QuickSpellPanel::create()
|
||||
if(!hero)
|
||||
return;
|
||||
|
||||
for(int i = 0; i < 12; i++) {
|
||||
std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(i)].String();
|
||||
|
||||
auto spells = getSpells();
|
||||
for(int i = 0; i < QUICKSPELL_SLOTS; i++) {
|
||||
SpellID id;
|
||||
try
|
||||
{
|
||||
id = SpellID::decode(spellIdentifier);
|
||||
}
|
||||
catch(const IdentifierResolutionException& e)
|
||||
{
|
||||
id = SpellID::NONE;
|
||||
}
|
||||
bool fromSettings;
|
||||
std::tie(id, fromSettings) = spells[i];
|
||||
|
||||
auto button = std::make_shared<CButton>(Point(2, 7 + 50 * i), AnimationPath::builtin("spellint"), CButton::tooltip(), [this, id, hero](){
|
||||
if(id.hasValue() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero))
|
||||
@ -466,16 +503,19 @@ void QuickSpellPanel::create()
|
||||
owner.castThisSpell(id);
|
||||
}
|
||||
});
|
||||
button->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("spellint"), !spellIdentifier.empty() ? id.num + 1 : 0));
|
||||
button->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("spellint"), id != SpellID::NONE ? id.num + 1 : 0));
|
||||
button->addPopupCallback([this, i, hero](){
|
||||
GH.input().hapticFeedback();
|
||||
GH.windows().createAndPushWindow<CSpellWindow>(hero, owner.curInt.get(), true, [this, i](SpellID spell){
|
||||
Settings configID = persistentStorage.write["quickSpell"][std::to_string(i)];
|
||||
configID->String() = spell.toSpell()->identifier;
|
||||
configID->String() = spell == SpellID::NONE ? "" : spell.toSpell()->identifier;
|
||||
create();
|
||||
});
|
||||
});
|
||||
|
||||
if(fromSettings)
|
||||
buttonsIsAutoGenerated.push_back(std::make_shared<TransparentFilledRectangle>(Rect(45, 37 + 50 * i, 5, 5), Colors::ORANGE));
|
||||
|
||||
if(!id.hasValue() || !id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero))
|
||||
{
|
||||
buttonsDisabled.push_back(std::make_shared<TransparentFilledRectangle>(Rect(2, 7 + 50 * i, 48, 36), ColorRGBA(0, 0, 0, 172)));
|
||||
|
@ -155,17 +155,22 @@ private:
|
||||
std::shared_ptr<CFilledTexture> background;
|
||||
std::shared_ptr<TransparentFilledRectangle> rect;
|
||||
std::vector<std::shared_ptr<CButton>> buttons;
|
||||
std::vector<std::shared_ptr<TransparentFilledRectangle>> buttonsIsAutoGenerated;
|
||||
std::vector<std::shared_ptr<TransparentFilledRectangle>> buttonsDisabled;
|
||||
std::vector<std::shared_ptr<CLabel>> labels;
|
||||
|
||||
BattleInterface & owner;
|
||||
public:
|
||||
int QUICKSPELL_SLOTS = 12;
|
||||
|
||||
bool isEnabled; // isActive() is not working on multiple conditions, because of this we need a seperate flag
|
||||
|
||||
QuickSpellPanel(BattleInterface & owner);
|
||||
|
||||
void create();
|
||||
|
||||
std::vector<std::tuple<SpellID, bool>> getSpells();
|
||||
|
||||
void show(Canvas & to) override;
|
||||
void inputModeChanged(InputMode modi) override;
|
||||
};
|
||||
|
@ -218,7 +218,9 @@ void BattleWindow::showStickyQuickSpellWindow()
|
||||
Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
|
||||
showStickyQuickSpellWindow->Bool() = true;
|
||||
|
||||
if(GH.screenDimensions().x >= 1050 && owner.getBattle()->battleGetMyHero()->hasSpellbook())
|
||||
auto hero = owner.getBattle()->battleGetMyHero();
|
||||
|
||||
if(GH.screenDimensions().x >= 1050 && hero != nullptr && hero->hasSpellbook())
|
||||
{
|
||||
quickSpellWindow->enable();
|
||||
quickSpellWindow->isEnabled = true;
|
||||
@ -273,16 +275,13 @@ std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode &
|
||||
|
||||
void BattleWindow::useSpellIfPossible(int slot)
|
||||
{
|
||||
std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(slot)].String();
|
||||
SpellID id;
|
||||
try
|
||||
{
|
||||
id = SpellID::decode(spellIdentifier);
|
||||
}
|
||||
catch(const IdentifierResolutionException& e)
|
||||
{
|
||||
bool fromSettings;
|
||||
std::tie(id, fromSettings) = quickSpellWindow->getSpells()[slot];
|
||||
|
||||
if(id == SpellID::NONE)
|
||||
return;
|
||||
}
|
||||
|
||||
if(id.hasValue() && owner.getBattle()->battleGetMyHero() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, owner.getBattle()->battleGetMyHero()))
|
||||
{
|
||||
owner.castThisSpell(id);
|
||||
|
@ -258,6 +258,15 @@ void CIntObject::redraw()
|
||||
}
|
||||
}
|
||||
|
||||
void CIntObject::moveChildForeground(const CIntObject * childToMove)
|
||||
{
|
||||
for(auto child = children.begin(); child != children.end(); child++)
|
||||
if(*child == childToMove && child != children.end())
|
||||
{
|
||||
std::rotate(child, child + 1, children.end());
|
||||
}
|
||||
}
|
||||
|
||||
bool CIntObject::receiveEvent(const Point & position, int eventType) const
|
||||
{
|
||||
return pos.isInside(position);
|
||||
|
@ -102,6 +102,8 @@ public:
|
||||
void showAll(Canvas & to) override;
|
||||
//request complete redraw of this object
|
||||
void redraw() override;
|
||||
// Move child object to foreground
|
||||
void moveChildForeground(const CIntObject * childToMove);
|
||||
|
||||
/// returns true if this element is a popup window
|
||||
/// called only for windows
|
||||
|
@ -90,6 +90,7 @@ CArtPlace::CArtPlace(Point position, const CArtifactInstance * art)
|
||||
|
||||
image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), imageIndex);
|
||||
image->disable();
|
||||
moveSelectionForeground();
|
||||
}
|
||||
|
||||
const CArtifactInstance * CArtPlace::getArt() const
|
||||
|
@ -714,3 +714,8 @@ void SelectableSlot::setSelectionWidth(int width)
|
||||
selection = std::make_shared<TransparentFilledRectangle>( selection->pos - pos.topLeft(), Colors::TRANSPARENCY, Colors::YELLOW, width);
|
||||
selectSlot(selected);
|
||||
}
|
||||
|
||||
void SelectableSlot::moveSelectionForeground()
|
||||
{
|
||||
moveChildForeground(selection.get());
|
||||
}
|
||||
|
@ -261,4 +261,5 @@ public:
|
||||
void selectSlot(bool on);
|
||||
bool isSelected() const;
|
||||
void setSelectionWidth(int width);
|
||||
void moveSelectionForeground();
|
||||
};
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../adventureMap/AdventureMapInterface.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
#include "../render/IImage.h"
|
||||
@ -130,9 +131,9 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
|
||||
|
||||
pos = background->center(Point(pos.w/2 + pos.x, pos.h/2 + pos.y));
|
||||
|
||||
Rect r(90, isBigSpellbook ? 480 : 420, isBigSpellbook ? 160 : 110, 16);
|
||||
if(settings["general"]["enableUiEnhancements"].Bool())
|
||||
{
|
||||
Rect r(90, isBigSpellbook ? 480 : 420, isBigSpellbook ? 160 : 110, 16);
|
||||
const ColorRGBA rectangleColor = ColorRGBA(0, 0, 0, 75);
|
||||
const ColorRGBA borderColor = ColorRGBA(128, 100, 75);
|
||||
const ColorRGBA grayedColor = ColorRGBA(158, 130, 105);
|
||||
@ -143,6 +144,13 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
|
||||
searchBox->setCallback(std::bind(&CSpellWindow::searchInput, this));
|
||||
}
|
||||
|
||||
if(onSpellSelect)
|
||||
{
|
||||
Point boxPos = r.bottomLeft() + Point(-2, 5);
|
||||
showAllSpells = std::make_shared<CToggleButton>(boxPos, AnimationPath::builtin("sysopchk.def"), CButton::tooltip(CGI->generaltexth->translate("core.help.458.hover"), CGI->generaltexth->translate("core.help.458.hover")), [this](bool state){ searchInput(); });
|
||||
showAllSpellsDescription = std::make_shared<CLabel>(boxPos.x + 40, boxPos.y + 12, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, CGI->generaltexth->translate("core.help.458.hover"));
|
||||
}
|
||||
|
||||
processSpells();
|
||||
|
||||
//numbers of spell pages computed
|
||||
@ -288,7 +296,7 @@ void CSpellWindow::processSpells()
|
||||
|
||||
if(onSpellSelect)
|
||||
{
|
||||
if(spell->isCombat() == openOnBattleSpells && !spell->isSpecial() && !spell->isCreatureAbility() && searchTextFound)
|
||||
if(spell->isCombat() == openOnBattleSpells && !spell->isSpecial() && !spell->isCreatureAbility() && searchTextFound && (showAllSpells->isSelected() || myHero->canCastThisSpell(spell.get())))
|
||||
mySpells.push_back(spell.get());
|
||||
continue;
|
||||
}
|
||||
@ -359,6 +367,9 @@ void CSpellWindow::fexitb()
|
||||
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap) = selectedTab;
|
||||
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap) = currentPage;
|
||||
|
||||
if(onSpellSelect)
|
||||
onSpellSelect(SpellID::NONE);
|
||||
|
||||
close();
|
||||
}
|
||||
|
||||
@ -605,7 +616,7 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
|
||||
if(owner->onSpellSelect)
|
||||
{
|
||||
owner->onSpellSelect(mySpell->id);
|
||||
owner->fexitb();
|
||||
owner->close();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ class CPlayerInterface;
|
||||
class CSpellWindow;
|
||||
class CTextInput;
|
||||
class TransparentFilledRectangle;
|
||||
class CToggleButton;
|
||||
|
||||
/// The spell window
|
||||
class CSpellWindow : public CWindowObject
|
||||
@ -82,6 +83,9 @@ class CSpellWindow : public CWindowObject
|
||||
std::shared_ptr<TransparentFilledRectangle> searchBoxRectangle;
|
||||
std::shared_ptr<CLabel> searchBoxDescription;
|
||||
|
||||
std::shared_ptr<CToggleButton> showAllSpells;
|
||||
std::shared_ptr<CLabel> showAllSpellsDescription;
|
||||
|
||||
bool isBigSpellbook;
|
||||
int spellsPerPage;
|
||||
int offL;
|
||||
|
@ -99,7 +99,7 @@
|
||||
"type":
|
||||
{
|
||||
"type" : "string",
|
||||
"enum" : ["wide", "fictive", "repulsive"]
|
||||
"enum" : ["wide", "fictive", "repulsive", "forcePortal"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -38,7 +38,7 @@
|
||||
{ "a" : "zoneA", "b" : "zoneB", "guard" : 5000, "road" : "false" },
|
||||
{ "a" : "zoneA", "b" : "zoneC", "guard" : 5000, "road" : "random" },
|
||||
{ "a" : "zoneB", "b" : "zoneC", "type" : "wide" }
|
||||
//"type" can be "guarded" (default), "wide", "fictive" or "repulsive"
|
||||
//"type" can be "guarded" (default), "wide", "fictive", "repulsive" or "forcePortal"
|
||||
//"wide" connections have no border, or guard. "fictive" and "repulsive" connections are virtual -
|
||||
//they do not create actual path, but only attract or repulse zones, respectively
|
||||
]
|
||||
|
@ -13,6 +13,8 @@
|
||||
|
||||
#include "CThreadHelper.h"
|
||||
|
||||
#include <boost/stacktrace.hpp>
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
std::mutex CConsoleHandler::smx;
|
||||
@ -142,6 +144,30 @@ static void createMemoryDump(MINIDUMP_EXCEPTION_INFORMATION * meinfo)
|
||||
MessageBoxA(0, "VCMI has crashed. We are sorry. File with information about encountered problem has been created.", "VCMI Crashhandler", MB_OK | MB_ICONERROR);
|
||||
}
|
||||
|
||||
LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception)
|
||||
{
|
||||
logGlobal->error("Disaster happened.");
|
||||
|
||||
PEXCEPTION_RECORD einfo = exception->ExceptionRecord;
|
||||
logGlobal->error("Reason: 0x%x - %s at %04x:%x", einfo->ExceptionCode, exceptionName(einfo->ExceptionCode), exception->ContextRecord->SegCs, (void*)einfo->ExceptionAddress);
|
||||
|
||||
if (einfo->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
|
||||
{
|
||||
logGlobal->error("Attempt to %s 0x%8x", (einfo->ExceptionInformation[0] == 1 ? "write to" : "read from"), (void*)einfo->ExceptionInformation[1]);
|
||||
}
|
||||
const DWORD threadId = ::GetCurrentThreadId();
|
||||
logGlobal->error("Thread ID: %d", threadId);
|
||||
|
||||
//exception info to be placed in the dump
|
||||
MINIDUMP_EXCEPTION_INFORMATION meinfo = {threadId, exception, TRUE};
|
||||
|
||||
createMemoryDump(&meinfo);
|
||||
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[[noreturn]] static void onTerminate()
|
||||
{
|
||||
logGlobal->error("Disaster happened.");
|
||||
@ -166,37 +192,20 @@ static void createMemoryDump(MINIDUMP_EXCEPTION_INFORMATION * meinfo)
|
||||
logGlobal->error("Reason: unknown exception!");
|
||||
}
|
||||
|
||||
logGlobal->error("Call stack information:");
|
||||
std::stringstream stream;
|
||||
stream << boost::stacktrace::stacktrace();
|
||||
logGlobal->error("%s", stream.str());
|
||||
|
||||
#ifdef VCMI_WINDOWS
|
||||
const DWORD threadId = ::GetCurrentThreadId();
|
||||
logGlobal->error("Thread ID: %d", threadId);
|
||||
|
||||
createMemoryDump(nullptr);
|
||||
#endif
|
||||
std::abort();
|
||||
}
|
||||
|
||||
LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception)
|
||||
{
|
||||
logGlobal->error("Disaster happened.");
|
||||
|
||||
PEXCEPTION_RECORD einfo = exception->ExceptionRecord;
|
||||
logGlobal->error("Reason: 0x%x - %s at %04x:%x", einfo->ExceptionCode, exceptionName(einfo->ExceptionCode), exception->ContextRecord->SegCs, (void*)einfo->ExceptionAddress);
|
||||
|
||||
if (einfo->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
|
||||
{
|
||||
logGlobal->error("Attempt to %s 0x%8x", (einfo->ExceptionInformation[0] == 1 ? "write to" : "read from"), (void*)einfo->ExceptionInformation[1]);
|
||||
}
|
||||
const DWORD threadId = ::GetCurrentThreadId();
|
||||
logGlobal->error("Thread ID: %d", threadId);
|
||||
|
||||
//exception info to be placed in the dump
|
||||
MINIDUMP_EXCEPTION_INFORMATION meinfo = {threadId, exception, TRUE};
|
||||
|
||||
createMemoryDump(&meinfo);
|
||||
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void CConsoleHandler::setColor(EConsoleTextColor::EConsoleTextColor color)
|
||||
{
|
||||
TColor colorCode;
|
||||
@ -289,11 +298,14 @@ CConsoleHandler::CConsoleHandler():
|
||||
defErrColor = csbi.wAttributes;
|
||||
#ifndef _DEBUG
|
||||
SetUnhandledExceptionFilter(onUnhandledException);
|
||||
std::set_terminate(onTerminate);
|
||||
#endif
|
||||
#else
|
||||
defColor = "\x1b[0m";
|
||||
#endif
|
||||
|
||||
#ifndef _DEBUG
|
||||
std::set_terminate(onTerminate);
|
||||
#endif
|
||||
}
|
||||
CConsoleHandler::~CConsoleHandler()
|
||||
{
|
||||
|
@ -145,7 +145,8 @@ int DamageCalculator::getActorAttackIgnored() const
|
||||
|
||||
if(multAttackReductionPercent > 0)
|
||||
{
|
||||
int reduction = (getActorAttackBase() * multAttackReductionPercent + 49) / 100; //using ints so 1.5 for 5 attack is rounded down as in HotA / h3assist etc. (keep in mind h3assist 1.2 shows wrong value for 15 attack points and unupg. nix)
|
||||
//using ints so 1.5 for 5 attack is rounded down as in HotA / h3assist etc. (keep in mind h3assist 1.2 shows wrong value for 15 attack points and unupg. nix)
|
||||
int reduction = vstd::divideAndRound( getActorAttackBase() * multAttackReductionPercent, 100);
|
||||
return -std::min(reduction, getActorAttackBase());
|
||||
}
|
||||
return 0;
|
||||
|
@ -227,7 +227,7 @@ TResources CGTownInstance::dailyIncome() const
|
||||
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;
|
||||
ret[it->resType] = vstd::divideAndCeil(ret[it->resType] * playerSettings.handicap.percentIncome, 100);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1271,7 +1271,7 @@ int GrowthInfo::totalGrowth() const
|
||||
ret += entry.count;
|
||||
|
||||
// always round up income - we don't want buildings to always produce zero if handicap in use
|
||||
return (ret * handicapPercentage + 99) / 100;
|
||||
return vstd::divideAndCeil(ret * handicapPercentage, 100);
|
||||
}
|
||||
|
||||
void CGTownInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const
|
||||
|
@ -200,7 +200,7 @@ 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;
|
||||
return vstd::divideAndCeil(producedQuantity * playerSettings->handicap.percentIncome, 100);
|
||||
}
|
||||
|
||||
void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
|
||||
|
@ -463,7 +463,8 @@ void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
|
||||
"guarded",
|
||||
"fictive",
|
||||
"repulsive",
|
||||
"wide"
|
||||
"wide",
|
||||
"forcePortal"
|
||||
};
|
||||
|
||||
static const std::vector<std::string> roadOptions =
|
||||
|
@ -75,7 +75,8 @@ enum class EConnectionType
|
||||
GUARDED = 0, //default
|
||||
FICTIVE,
|
||||
REPULSIVE,
|
||||
WIDE
|
||||
WIDE,
|
||||
FORCE_PORTAL
|
||||
};
|
||||
|
||||
enum class ERoadOption
|
||||
|
@ -80,12 +80,21 @@ void CZonePlacer::findPathsBetweenZones()
|
||||
|
||||
for (auto & connection : connectedZoneIds)
|
||||
{
|
||||
if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
|
||||
switch (connection.getConnectionType())
|
||||
{
|
||||
//Do not consider virtual connections for graph distance
|
||||
continue;
|
||||
case rmg::EConnectionType::REPULSIVE:
|
||||
case rmg::EConnectionType::FORCE_PORTAL:
|
||||
continue;
|
||||
}
|
||||
auto neighbor = connection.getOtherZoneId(current);
|
||||
|
||||
if (current == neighbor)
|
||||
{
|
||||
//Do not consider self-connections
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!visited[neighbor])
|
||||
{
|
||||
visited[neighbor] = true;
|
||||
@ -552,8 +561,16 @@ void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces,
|
||||
|
||||
for (const auto & connection : zone.second->getConnections())
|
||||
{
|
||||
if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
|
||||
switch (connection.getConnectionType())
|
||||
{
|
||||
//Do not consider virtual connections for graph distance
|
||||
case rmg::EConnectionType::REPULSIVE:
|
||||
case rmg::EConnectionType::FORCE_PORTAL:
|
||||
continue;
|
||||
}
|
||||
if (connection.getZoneA() == connection.getZoneB())
|
||||
{
|
||||
//Do not consider self-connections
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -710,11 +727,19 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
|
||||
std::set<TRmgTemplateZoneId> connectedZones;
|
||||
for (const auto& connection : firstZone->getConnections())
|
||||
{
|
||||
//FIXME: Should we also exclude fictive connections?
|
||||
if (connection.getConnectionType() != rmg::EConnectionType::REPULSIVE)
|
||||
switch (connection.getConnectionType())
|
||||
{
|
||||
connectedZones.insert(connection.getOtherZoneId(firstZone->getId()));
|
||||
//Do not consider virtual connections for graph distance
|
||||
case rmg::EConnectionType::REPULSIVE:
|
||||
case rmg::EConnectionType::FORCE_PORTAL:
|
||||
continue;
|
||||
}
|
||||
if (connection.getZoneA() == connection.getZoneB())
|
||||
{
|
||||
//Do not consider self-connections
|
||||
continue;
|
||||
}
|
||||
connectedZones.insert(connection.getOtherZoneId(firstZone->getId()));
|
||||
}
|
||||
|
||||
auto level = firstZone->getCenter().z;
|
||||
|
@ -55,6 +55,17 @@ void ConnectionsPlacer::process()
|
||||
{
|
||||
for (auto& c : dConnections)
|
||||
{
|
||||
if (c.getZoneA() == c.getZoneB())
|
||||
{
|
||||
// Zone can always be connected to itself, but only by monolith pair
|
||||
RecursiveLock lock(externalAccessMutex);
|
||||
if (!vstd::contains(dCompleted, c))
|
||||
{
|
||||
placeMonolithConnection(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto otherZone = map.getZones().at(c.getZoneB());
|
||||
auto* cp = otherZone->getModificator<ConnectionsPlacer>();
|
||||
|
||||
@ -74,6 +85,11 @@ void ConnectionsPlacer::process()
|
||||
}
|
||||
};
|
||||
|
||||
diningPhilosophers([this](const rmg::ZoneConnection& c)
|
||||
{
|
||||
forcePortalConnection(c);
|
||||
});
|
||||
|
||||
diningPhilosophers([this](const rmg::ZoneConnection& c)
|
||||
{
|
||||
selfSideDirectConnection(c);
|
||||
@ -115,6 +131,15 @@ void ConnectionsPlacer::otherSideConnection(const rmg::ZoneConnection & connecti
|
||||
dCompleted.push_back(connection);
|
||||
}
|
||||
|
||||
void ConnectionsPlacer::forcePortalConnection(const rmg::ZoneConnection & connection)
|
||||
{
|
||||
// This should always succeed
|
||||
if (connection.getConnectionType() == rmg::EConnectionType::FORCE_PORTAL)
|
||||
{
|
||||
placeMonolithConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & connection)
|
||||
{
|
||||
bool success = false;
|
||||
@ -410,23 +435,30 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
|
||||
//4. place monoliths/portals
|
||||
if(!success)
|
||||
{
|
||||
auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, generator.getNextMonlithIndex());
|
||||
auto * teleport1 = factory->create(map.mapInstance->cb, nullptr);
|
||||
auto * teleport2 = factory->create(map.mapInstance->cb, nullptr);
|
||||
|
||||
RequiredObjectInfo obj1(teleport1, connection.getGuardStrength(), allowRoad);
|
||||
RequiredObjectInfo obj2(teleport2, connection.getGuardStrength(), allowRoad);
|
||||
zone.getModificator<ObjectManager>()->addRequiredObject(obj1);
|
||||
otherZone->getModificator<ObjectManager>()->addRequiredObject(obj2);
|
||||
|
||||
assert(otherZone->getModificator<ConnectionsPlacer>());
|
||||
otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
|
||||
|
||||
success = true;
|
||||
placeMonolithConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionsPlacer::placeMonolithConnection(const rmg::ZoneConnection & connection)
|
||||
{
|
||||
auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
|
||||
auto & otherZone = map.getZones().at(otherZoneId);
|
||||
|
||||
bool allowRoad = shouldGenerateRoad(connection);
|
||||
|
||||
auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, generator.getNextMonlithIndex());
|
||||
auto * teleport1 = factory->create(map.mapInstance->cb, nullptr);
|
||||
auto * teleport2 = factory->create(map.mapInstance->cb, nullptr);
|
||||
|
||||
RequiredObjectInfo obj1(teleport1, connection.getGuardStrength(), allowRoad);
|
||||
RequiredObjectInfo obj2(teleport2, connection.getGuardStrength(), allowRoad);
|
||||
zone.getModificator<ObjectManager>()->addRequiredObject(obj1);
|
||||
otherZone->getModificator<ObjectManager>()->addRequiredObject(obj2);
|
||||
|
||||
dCompleted.push_back(connection);
|
||||
|
||||
if(success)
|
||||
dCompleted.push_back(connection);
|
||||
assert(otherZone->getModificator<ConnectionsPlacer>());
|
||||
otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
|
||||
}
|
||||
|
||||
void ConnectionsPlacer::collectNeighbourZones()
|
||||
|
@ -23,7 +23,8 @@ public:
|
||||
void init() override;
|
||||
|
||||
void addConnection(const rmg::ZoneConnection& connection);
|
||||
|
||||
void placeMonolithConnection(const rmg::ZoneConnection& connection);
|
||||
void forcePortalConnection(const rmg::ZoneConnection & connection);
|
||||
void selfSideDirectConnection(const rmg::ZoneConnection & connection);
|
||||
void selfSideIndirectConnection(const rmg::ZoneConnection & connection);
|
||||
void otherSideConnection(const rmg::ZoneConnection & connection);
|
||||
|
@ -1267,7 +1267,7 @@ void BattleActionProcessor::handleDeathStare(const CBattleInfoCallback & battle,
|
||||
vstd::amin(chanceToKill, 1); //cap at 100%
|
||||
int killedCreatures = gameHandler->getRandomGenerator().nextBinomialInt(attacker->getCount(), chanceToKill);
|
||||
|
||||
int maxToKill = (attacker->getCount() * singleCreatureKillChancePercent + 99) / 100;
|
||||
int maxToKill = vstd::divideAndCeil(attacker->getCount() * singleCreatureKillChancePercent, 100);
|
||||
vstd::amin(killedCreatures, maxToKill);
|
||||
|
||||
killedCreatures += (attacker->level() * attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareCommander)) / defender->level();
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1
|
||||
Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59
|
Loading…
x
Reference in New Issue
Block a user