1
0
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:
Xilmi 2024-08-07 16:15:28 +02:00
commit a7240041ec
26 changed files with 271 additions and 83 deletions

2
.gitmodules vendored
View File

@ -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

View File

@ -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)
{

View File

@ -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:",

View File

@ -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)));

View File

@ -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;
};

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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());
}

View File

@ -261,4 +261,5 @@ public:
void selectSlot(bool on);
bool isSelected() const;
void setSelectionWidth(int width);
void moveSelectionForeground();
};

View File

@ -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;
}

View File

@ -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;

View File

@ -99,7 +99,7 @@
"type":
{
"type" : "string",
"enum" : ["wide", "fictive", "repulsive"]
"enum" : ["wide", "fictive", "repulsive", "forcePortal"]
}
}
},

View File

@ -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
]

View File

@ -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()
{

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -463,7 +463,8 @@ void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
"guarded",
"fictive",
"repulsive",
"wide"
"wide",
"forcePortal"
};
static const std::vector<std::string> roadOptions =

View File

@ -75,7 +75,8 @@ enum class EConnectionType
GUARDED = 0, //default
FICTIVE,
REPULSIVE,
WIDE
WIDE,
FORCE_PORTAL
};
enum class ERoadOption

View File

@ -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;

View File

@ -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()

View File

@ -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);

View File

@ -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