diff --git a/epicinium/src/ai/ai.cpp b/epicinium/src/ai/ai.cpp index 5765a54..45d2969 100644 --- a/epicinium/src/ai/ai.cpp +++ b/epicinium/src/ai/ai.cpp @@ -131,3 +131,13 @@ const std::vector& AI::hiddenPool() }; return pool; } + +const std::vector& AI::selfHostedPool() +{ + static std::vector pool = { + "RampantRhino", + "HungryHippo", + "ChargingCheetah", + }; + return pool; +} diff --git a/epicinium/src/ai/ai.hpp b/epicinium/src/ai/ai.hpp index 2b7ff6a..1fe9989 100644 --- a/epicinium/src/ai/ai.hpp +++ b/epicinium/src/ai/ai.hpp @@ -45,6 +45,7 @@ class AI static std::string libraryDefaultFilename(const std::string& name); static const std::vector& pool(); + static const std::vector& selfHostedPool(); private: static const std::vector& hiddenPool(); diff --git a/epicinium/src/ai/aichallenge.cpp b/epicinium/src/ai/aichallenge.cpp index 699df53..366dd35 100644 --- a/epicinium/src/ai/aichallenge.cpp +++ b/epicinium/src/ai/aichallenge.cpp @@ -37,21 +37,14 @@ const char* AIChallenge::getKey(const Challenge::Id& id) switch (id) { case CUSTOM: return ""; - // OLD: "showcase_2018_07_04"; - // OLD: "elimination_2017_07_20"; - // OLD: "everythingisfree_2018_07_16"; // OLD: "trample_2017_08_01"; - // OLD: "trample_2018_11_26"; - // OLD: "highspeed_2018_09_21"; - // OLD: "investment_2019_04_15"; - // OLD: "morale_2020_05_06"; - case SHOWCASE: return "showcase_2021_05_25"; - case ELIMINATION: return "elimination_2021_05_25"; - case EVERYTHINGISFREE: return "everythingisfree_2021_05_25"; - case TRAMPLE: return "trample_2021_05_25"; - case HIGHSPEED: return "highspeed_2021_05_25"; - case INVESTMENT: return "investment_2021_05_25"; - case MORALE: return "morale_2021_05_25"; + case SHOWCASE: return "showcase_2018_07_04"; + case ELIMINATION: return "elimination_2017_07_20"; + case EVERYTHINGISFREE: return "everythingisfree_2018_07_16"; + case TRAMPLE: return "trample_2018_11_26"; + case HIGHSPEED: return "highspeed_2018_09_21"; + case INVESTMENT: return "investment_2019_04_15"; + case MORALE: return "morale_2020_05_06"; } return ""; } @@ -165,7 +158,7 @@ const char* AIChallenge::getRulesetName(const Challenge::Id& id) { switch (id) { - case CUSTOM: return ""; + case CUSTOM: return "challenge_custom"; case SHOWCASE: return ""; case ELIMINATION: return ""; case EVERYTHINGISFREE: return "challenge_everythingisfree"; diff --git a/epicinium/src/ai/aineuralnewt.cpp b/epicinium/src/ai/aineuralnewt.cpp index 8df70cb..d4c8aee 100644 --- a/epicinium/src/ai/aineuralnewt.cpp +++ b/epicinium/src/ai/aineuralnewt.cpp @@ -28,6 +28,9 @@ #include "aim.hpp" #include "bible.hpp" + // windows.h is being annoying +#undef near + std::string AINeuralNewt::ainame() const { @@ -937,7 +940,10 @@ void AINeuralNewt::moveDefense(const Descriptor& unitdesc, for (Cell at : _board) { if (_board.gas(at)) continue; - if (_board.frostbite(at)) continue; + if (_board.frostbite(at) + // In Spring, frostbite is used to indicate "Chilled" units. + && !(_bible.frostbiteGivesColdFeet() + && _bible.chaosMinFrostbite(_season) < 0)) continue; if (_board.firestorm(at)) continue; if (_board.death(at)) continue; int threatdis = _enemyBaseFloodfill.steps(at); diff --git a/epicinium/src/ai/airampantrhino.cpp b/epicinium/src/ai/airampantrhino.cpp index eb6d7e8..1839641 100644 --- a/epicinium/src/ai/airampantrhino.cpp +++ b/epicinium/src/ai/airampantrhino.cpp @@ -29,6 +29,9 @@ #include "aim.hpp" #include "bible.hpp" + // windows.h is being annoying +#undef near + std::string AIRampantRhino::ainame() const { @@ -1530,6 +1533,14 @@ bool AIRampantRhino::checkLockdown(const Descriptor& unitdesc) { Cell at = _board.cell(unitdesc.position); + bool canUseAbilities = true; + if (_bible.frostbiteGivesColdFeet() + // In Spring, frostbite is used to indicate "Chilled" units. + && _board.frostbite(at) && _bible.chaosMinFrostbite(_season) < 0) + { + canUseAbilities = false; + } + std::vector dirs = {Move::E, Move::S, Move::W, Move::N}; std::random_shuffle(dirs.begin(), dirs.end()); for (const Move& move : dirs) @@ -1540,7 +1551,7 @@ bool AIRampantRhino::checkLockdown(const Descriptor& unitdesc) if (_board.ground(target) && _board.ground(target).owner != _player) { - if (canUseCombatAbilities()) + if (canUseAbilities && canUseCombatAbilities()) { UnitType unittype = _board.ground(at).type; std::vector shapes = _bible.unitShapes( @@ -1567,6 +1578,7 @@ bool AIRampantRhino::checkLockdown(const Descriptor& unitdesc) Descriptor::cell(target.pos())); } } + return true; } } @@ -1588,6 +1600,13 @@ void AIRampantRhino::controlIdleDefense(const Descriptor& unitdesc) Cell at = _board.cell(unitdesc.position); + if (_bible.frostbiteGivesColdFeet() + // In Spring, frostbite is used to indicate "Chilled" units. + && _board.frostbite(at) && _bible.chaosMinFrostbite(_season) < 0) + { + return; + } + std::vector dirs = {Move::E, Move::S, Move::W, Move::N}; std::random_shuffle(dirs.begin(), dirs.end()); for (const Move& move : dirs) @@ -1613,6 +1632,13 @@ void AIRampantRhino::controlCaptor(const Descriptor& unitdesc) { Cell at = _board.cell(unitdesc.position); + if (_bible.frostbiteGivesColdFeet() + // In Spring, frostbite is used to indicate "Chilled" units. + && _board.frostbite(at) && _bible.chaosMinFrostbite(_season) < 0) + { + return; + } + if (_board.tile(at) && _board.tile(at).owner != _player && _bible.tileOwnable(_board.tile(at).type)) @@ -1643,7 +1669,10 @@ void AIRampantRhino::controlBlocker(const Descriptor& unitdesc) if (at == from) continue; if (_board.ground(at)) continue; if (_board.gas(at)) continue; - if (_board.frostbite(at)) continue; + if (_board.frostbite(at) + // In Spring, frostbite is used to indicate "Chilled" units. + && !(_bible.frostbiteGivesColdFeet() + && _bible.chaosMinFrostbite(_season) < 0)) continue; if (_board.firestorm(at)) continue; if (_board.death(at)) continue; if (isTarget(at.pos())) continue; @@ -1758,7 +1787,10 @@ void AIRampantRhino::controlDefense(const Descriptor& unitdesc) if (at != from && _board.ground(at) && _board.ground(at).owner == _player) continue; if (_board.gas(at)) continue; - if (_board.frostbite(at)) continue; + if (_board.frostbite(at) + // In Spring, frostbite is used to indicate "Chilled" units. + && !(_bible.frostbiteGivesColdFeet() + && _bible.chaosMinFrostbite(_season) < 0)) continue; if (_board.firestorm(at)) continue; if (_board.death(at)) continue; if (isTarget(at.pos())) continue; @@ -1911,7 +1943,10 @@ void AIRampantRhino::declareOffense(const Descriptor& unitdesc) if (at != from && _board.ground(at) && _board.ground(at).owner == _player) continue; if (_board.gas(at)) continue; - if (_board.frostbite(at)) continue; + if (_board.frostbite(at) + // In Spring, frostbite is used to indicate "Chilled" units. + && !(_bible.frostbiteGivesColdFeet() + && _bible.chaosMinFrostbite(_season) < 0)) continue; if (_board.firestorm(at)) continue; if (_board.death(at)) continue; if (isTarget(at.pos())) continue; @@ -2055,6 +2090,13 @@ void AIRampantRhino::controlBombarder(const Descriptor& unitdesc) Cell from = _board.cell(unitdesc.position); const UnitToken& unit = _board.ground(from); + if (_bible.frostbiteGivesColdFeet() + // In Spring, frostbite is used to indicate "Chilled" units. + && _board.frostbite(from) && _bible.chaosMinFrostbite(_season) < 0) + { + return; + } + UnitType unittype = unit.type; int rangeMin = _bible.unitRangeMin(unittype); int rangeMax = _bible.unitRangeMax(unittype); diff --git a/epicinium/src/common/header.hpp b/epicinium/src/common/header.hpp index f38fe62..bda70ff 100644 --- a/epicinium/src/common/header.hpp +++ b/epicinium/src/common/header.hpp @@ -149,3 +149,7 @@ struct stringref; #ifndef EDITOR_DEPRECATED_ENABLED #define EDITOR_DEPRECATED_ENABLED false #endif + +#ifndef NEURALNEWT_20x13_ENABLED +#define NEURALNEWT_20x13_ENABLED false +#endif diff --git a/epicinium/src/common/locator.cpp b/epicinium/src/common/locator.cpp index 3366e29..eb5edac 100644 --- a/epicinium/src/common/locator.cpp +++ b/epicinium/src/common/locator.cpp @@ -30,6 +30,7 @@ std::string Locator::_resourceroot = ""; std::string Locator::_cacheroot = ""; std::string Locator::_authoredroot = ""; +std::vector Locator::_externalfolders = {}; void Locator::setResourceRoot(const std::string& root) { @@ -119,6 +120,36 @@ std::string Locator::picture(const std::string& picturename) std::string Locator::pictureFilename(const std::string& picturename) { + if (picturename.find_first_of('@') != std::string::npos) + { + size_t seppos = picturename.find_first_of('/'); + std::string shortname; + // Use x.rfind(y, 0) == 0 as x.starts_with(y). + if (picturename.rfind("panels/", 0) == 0) + { + shortname = "panel"; + } + + if (!shortname.empty() && seppos != std::string::npos) + { + std::string tag = picturename.substr(seppos + 1); + for (const auto& folder : _externalfolders) + { + if (folder.uniqueTag == tag) + { + return folder.sourcePath + shortname + ".png"; + } + } + LOGW << "Failed to find external tag '" << tag << "'" + " for picture '" << picturename << "'"; + } + else + { + LOGW << "Failed to determine shortname" + " for picture '" << picturename << "'"; + } + } + return _cacheroot + "pictures/" + picturename + ".png"; } @@ -137,6 +168,20 @@ std::string Locator::pictureName(const std::string& fullfilename) std::string Locator::rulesetFilename(const std::string& rulesetname) { + if (rulesetname.find_first_of('@') != std::string::npos) + { + std::string tag = rulesetname; + for (const auto& folder : _externalfolders) + { + if (folder.uniqueTag == tag) + { + return folder.sourcePath + "ruleset.json"; + } + } + size_t seppos = rulesetname.find_first_of('@'); + std::string subpath = rulesetname.substr(seppos + 1); + return _cacheroot + "rulesets/external/" + subpath + ".json"; + } return _cacheroot + "rulesets/" + rulesetname + ".json"; } @@ -180,3 +225,58 @@ std::string Locator::fzmodelName(const std::string& fullfilename) } return ""; } + +void Locator::useExternalFolder(ExternalFolder&& newFolder) +{ + LOGD << "Using '" << newFolder.uniqueTag << "'" + ": " << newFolder.sourcePath; + + for (auto& folder : _externalfolders) + { + if (folder.uniqueTag == newFolder.uniqueTag) + { + folder = newFolder; + return; + } + } + + _externalfolders.emplace_back(newFolder); +} + +void Locator::forgetExternalFolder(const std::string& uniqueTag) +{ + LOGD << "Forgetting '" << uniqueTag << "'"; + _externalfolders.erase( + std::remove_if( + _externalfolders.begin(), + _externalfolders.end(), + [&](const ExternalFolder& folder) { + return folder.uniqueTag == uniqueTag; + }), + _externalfolders.end()); +} + +std::vector Locator::externalRulesets() +{ + std::vector rulesets; + for (const auto& folder : _externalfolders) + { + if (System::isFile(folder.sourcePath + "ruleset.json")) + { + rulesets.emplace_back(folder.uniqueTag); + } + } + return rulesets; +} + +const std::vector& Locator::externalFolders() +{ + return _externalfolders; +} + +std::vector Locator::listAuthoredRulesets() +{ + auto list = System::listDirectory(_authoredroot + "rulesets/", ".json"); + std::sort(list.begin(), list.end()); + return list; +} diff --git a/epicinium/src/common/locator.hpp b/epicinium/src/common/locator.hpp index 299058e..df24146 100644 --- a/epicinium/src/common/locator.hpp +++ b/epicinium/src/common/locator.hpp @@ -28,6 +28,12 @@ class Locator { public: + struct ExternalFolder + { + std::string uniqueTag; + std::string sourcePath; + }; + static std::string picture(const std::string& picturename); static std::string pictureFilename(const std::string& picturename); @@ -45,9 +51,18 @@ class Locator static std::string _resourceroot; static std::string _cacheroot; static std::string _authoredroot; + static std::vector _externalfolders; public: static void setResourceRoot(const std::string& root); static void setCacheRoot(const std::string& root); static void setAuthoredRoot(const std::string& root); + + static void useExternalFolder(ExternalFolder&& folder); + static void forgetExternalFolder(const std::string& uniqueTag); + + static std::vector externalRulesets(); + static const std::vector& externalFolders(); + + static std::vector listAuthoredRulesets(); }; diff --git a/epicinium/src/common/system.cpp b/epicinium/src/common/system.cpp index 980c5bc..cb0ea29 100644 --- a/epicinium/src/common/system.cpp +++ b/epicinium/src/common/system.cpp @@ -52,6 +52,10 @@ std::vector System::listDirectory(const std::string& dirname, } std::vector filenames; + if (!System::isDirectory(dirname)) + { + return filenames; + } for(const auto& p : fs::directory_iterator(dirname)) { if (p.path().extension() == extensionPattern) diff --git a/epicinium/src/common/writer.cpp b/epicinium/src/common/writer.cpp index 6243eb7..5f72c4b 100644 --- a/epicinium/src/common/writer.cpp +++ b/epicinium/src/common/writer.cpp @@ -34,7 +34,7 @@ static std::map _installed; -static std::mutex _mutex; +static std::mutex _writerMutex; std::string Writer::write(const Json::Value& json) { @@ -62,7 +62,7 @@ Writer::Writer() void Writer::install() { - std::lock_guard lock(_mutex); + std::lock_guard lock(_writerMutex); _installed[std::this_thread::get_id()] = this; } diff --git a/epicinium/src/logic/automaton.cpp b/epicinium/src/logic/automaton.cpp index 8904a87..5952994 100644 --- a/epicinium/src/logic/automaton.cpp +++ b/epicinium/src/logic/automaton.cpp @@ -1298,6 +1298,10 @@ void Automaton::processMove(const Player& player, Order& order) bool air = _bible.unitAir(movingunit.type); bool gliding = false; + // When a unit has Cold Feet, they cannot attack until after they have + // moved. We check whether they currently have Cold Feet. + bool coldfeet = hasColdFeet(current); + // Before actually moving, we will determine how far we walk and if we will attack an enemy. int moves = 0; int attack = 0; @@ -1418,6 +1422,7 @@ void Automaton::processMove(const Player& player, Order& order) // Only some units can attack. if (!_bible.unitCanAttack(movingunit.type)) break; + if (coldfeet) break; // We can only attack if we can stop, or if we do a "bypass attack"; // in the latter case we can only have bypassed a single unit. @@ -1628,6 +1633,13 @@ void Automaton::processGuard(const Player& player, Order& order) return discarded(player, order, cset, Notice::ORDERINVALID); } + // Does the unit get cold feet? + if (hasColdFeet(from)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + // If the cell is empty, we cannot attack. // If the unit is friendly, we cannot attack. if (!otherunit || otherunit.owner == guardingunit.owner) @@ -1716,6 +1728,13 @@ void Automaton::processFocus(const Player& player, Order& order) return discarded(player, order, cset, Notice::ORDERINVALID); } + // Does the unit get cold feet? + if (hasColdFeet(from)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + // If the cell is empty, we cannot attack. // If the unit is friendly, we cannot attack. if (!otherunit || otherunit.owner == focussingunit.owner) @@ -1811,9 +1830,6 @@ void Automaton::processLockdown(const Player& player, Order& order) return discarded(player, order, cset, Notice::ORDERINVALID); } - // This unit is now in lockdown mode. - _lockdowns.emplace_back(activeunit.id(), from, to); - // Before attacking, we look for a target. const UnitToken& otherunit = _board.unit(to, desctype); const TileToken& tile = _board.tile(to); @@ -1826,6 +1842,16 @@ void Automaton::processLockdown(const Player& player, Order& order) return discarded(player, order, cset, Notice::ORDERINVALID); } + // Does the unit get cold feet? + if (hasColdFeet(from)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + + // This unit is now in lockdown mode. + _lockdowns.emplace_back(activeunit.id(), from, to); + // If the cell is empty, we cannot attack. // If the unit is friendly, we cannot attack. if (!otherunit || otherunit.owner == activeunit.owner) @@ -1920,6 +1946,13 @@ void Automaton::processShell(const Player& player, Order& order) return discarded(player, order, cset, Notice::ORDERINVALID); } + // Does the unit get cold feet? + if (hasColdFeet(from)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + // Deal the damage. for (int volley = 0; volley < _bible.unitAbilityVolleys(unittype); volley++) { @@ -1995,6 +2028,13 @@ void Automaton::processBombard(const Player& player, Order& order) return discarded(player, order, cset, Notice::ORDERINVALID); } + // Does the unit get cold feet? + if (hasColdFeet(from)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + // Deal the damage. for (int volley = 0; volley < _bible.unitAbilityVolleys(unittype); volley++) { @@ -2060,6 +2100,13 @@ void Automaton::processBomb(const Player& player, Order& order) return discarded(player, order, cset, Notice::ORDERINVALID); } + // Does the unit get cold feet? + if (hasColdFeet(at)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + // Deal the damage. for (int volley = 0; volley < _bible.unitAbilityVolleys(unittype); volley++) { @@ -2125,6 +2172,13 @@ void Automaton::processCapture(const Player& player, Order& order) return discarded(player, order, cset, Notice::ORDERINVALID); } + // Does the unit get cold feet? + if (hasColdFeet(at)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + // We can't capture a tile that is already ours. if (tile.owner == conqueror.owner) { @@ -2242,6 +2296,13 @@ void Automaton::processShape(const Player& player, Order& order) return discarded(player, order, cset, Notice::UNBUILDABLE); } + // Does the unit get cold feet? + if (hasColdFeet(at)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + // If the player cannot afford the unit cost, the order is postponed. if (_money[player] < build.cost) { @@ -2365,6 +2426,13 @@ void Automaton::processSettle(const Player& player, Order& order) return discarded(player, order, cset, Notice::UNBUILDABLE); } + // Does the unit get cold feet? + if (hasColdFeet(at)) + { + LOGV << "unit has cold feet, order discarded"; + return discarded(player, order, cset, Notice::COLDFEET); + } + // If the player cannot afford the unit cost, the order is postponed. if (_money[player] < build.cost) { @@ -2410,6 +2478,7 @@ void Automaton::processSettle(const Player& player, Order& order) checkTileBuildMoraleGain(at, cset); // Check for cleanses. + checkColdFeetCleanse(at, cset); checkTileBuildCleanse(at, cset); // Check for auto-cultivate triggers. @@ -3468,6 +3537,9 @@ void Automaton::doMove(Cell from, Cell to, changes.push(exitchange, unsees); } + // After we move, cold feet wears off. + checkColdFeetCleanse(from, changes); + // After we move, we check for trample damage. // If there is any trample damage, it takes place in the same changeset. checkTrample(to, todesc, changes); @@ -3539,7 +3611,7 @@ void Automaton::doActiveAttack(Cell from, Cell to, // If the defending unit was not killed during the attack and can retaliate, it will. const UnitToken& takingunit = _board.unit(to, taker.type); - if (takingunit && _bible.unitCanAttack(takingunit.type)) + if (takingunit && _bible.unitCanAttack(takingunit.type) && !hasColdFeet(to)) { doRetaliationAttack(to, from, taker, attacker, changes); } @@ -3637,7 +3709,8 @@ void Automaton::doFocussedAttack(Cell from, Cell to, // Get the ground unit of that tile (only ground units can attack). const UnitToken& unit = _board.ground(at); - if (!unit || !_bible.unitCanAttack(unit.type)) continue; + if (!unit || !_bible.unitCanAttack(unit.type) + || hasColdFeet(at)) continue; // Only units belonging to the player that gave the order respond to it. if (unit.owner != focussingunit.owner) continue; @@ -3686,7 +3759,7 @@ void Automaton::doFocussedAttack(Cell from, Cell to, // If the defending unit was not killed during the attack and can retaliate, it will. const UnitToken& takingunit = _board.unit(to, taker.type); - if (takingunit && _bible.unitCanAttack(takingunit.type)) + if (takingunit && _bible.unitCanAttack(takingunit.type) && !hasColdFeet(to)) { doRetaliationAttack(to, from, taker, attacker, changes); } @@ -3715,7 +3788,8 @@ void Automaton::checkAttackOfOpportunity(Cell at, { // Get the ground unit of that tile (only ground units can attack). const UnitToken& unit = _board.ground(from); - if (!unit || !_bible.unitCanAttack(unit.type)) continue; + if (!unit || !_bible.unitCanAttack(unit.type) + || hasColdFeet(from)) continue; if (unit.owner == movingunit.owner) continue; // The attacker aims. @@ -3800,7 +3874,7 @@ bool Automaton::checkLockdown(Cell at, lockdown = true; // The lockdown unit may or may not also be able to attack. - if (!_bible.unitCanAttack(unit.type)) continue; + if (!_bible.unitCanAttack(unit.type) || hasColdFeet(from)) continue; // The attacker aims. changes.push(Change(Change::Type::AIMS, @@ -4210,6 +4284,9 @@ void Automaton::doUnitDeath(Cell at, const UnitToken& unit, _board.enact(deathchange); changes.push(deathchange, _board.vision(at)); + // Check cleanses. + checkColdFeetCleanse(at, changes); + // Some units release gas or radation on destruction. checkUnitDeathLeak(at, oldtype, changes); @@ -4534,8 +4611,27 @@ void Automaton::doDeathEffect(Cell at) void Automaton::doFrostbiteEffect(Cell at) { + if (_bible.frostbiteGivesColdFeet() + && _board.frostbite(at) && _board.ground(at) + // Frostbite's Cold Feet is applied in Winter, not in Spring. + && _bible.chaosMinFrostbite(_season) >= 0) + { + ChangeSet cset; + for (int figure = 0; figure < _board.ground(at).stacks; figure++) + { + // The FROSTBITTEN change tells a player that a unit was bitten + // by frostbite. + Change change(Change::Type::FROSTBITTEN, + Descriptor::ground(at.pos()), + figure, /*killing=*/false, /*depowering=*/false); + _board.enact(change); + cset.push(change, _board.vision(at)); + } + _changesets.push(cset); + } + // Is there enough frostbite to do damage? - if (_board.frostbite(at) < _bible.frostbiteThresholdDamage()) return; + if ((_board.frostbite(at) ? 1 : 0) < _bible.frostbiteThresholdDamage()) return; // Frostbite no longer affects tiles or air. bool hittiles = !_bible.frostbiteOnlyTargetsGroundUnits(); @@ -4798,6 +4894,30 @@ void Automaton::doRadiationEffect(Cell at) _changesets.push(cset); } +bool Automaton::hasColdFeet(Cell at) +{ + return (_bible.frostbiteGivesColdFeet() + && _board.frostbite(at) && _board.ground(at) + // Frostbite's Cold Feet effect applies in Spring, not in Winter. + && _bible.chaosMinFrostbite(_season) < 0); +} + +void Automaton::checkColdFeetCleanse(Cell at, ChangeSet& changes) +{ + if (_bible.frostbiteGivesColdFeet() + // Once the unit moves away or is killed, Cold Feet wears off. + && _board.frostbite(at) && !_board.ground(at) + // But only in Spring, not in Winter. + && _bible.chaosMinFrostbite(_season) < 0) + { + Change change(Change::Type::FROSTBITE, + Descriptor::cell(at.pos())); + change.xFrostbite(false); + _board.enact(change); + changes.push(change, _board.vision(at)); + } +} + void Automaton::decay() { { diff --git a/epicinium/src/logic/automaton.hpp b/epicinium/src/logic/automaton.hpp index 127b932..0cb9023 100644 --- a/epicinium/src/logic/automaton.hpp +++ b/epicinium/src/logic/automaton.hpp @@ -186,6 +186,8 @@ class Automaton : private PlayerInfo, private RoundInfo void doFirestormEffect(Cell at); void doGasEffect(Cell at); void doRadiationEffect(Cell at); + bool hasColdFeet(Cell at); + void checkColdFeetCleanse(Cell at, ChangeSet& changes); void checkRegularDefeat(); void checkChallengeDefeat(); diff --git a/epicinium/src/logic/bible.cpp b/epicinium/src/logic/bible.cpp index e219f4f..99368e4 100644 --- a/epicinium/src/logic/bible.cpp +++ b/epicinium/src/logic/bible.cpp @@ -292,6 +292,7 @@ Bible::Bible(const std::string& name, const Version& version) : _moraleGatheredInSummer(false), _moraleGatheredWhenBuilt(false), _snowCoversNiceness(false), + _frostbiteGivesColdFeet(false), _snowSlowAmount(0), _snowSlowMaximum(0), _startingMoney(0), @@ -393,7 +394,7 @@ void Bible::initialize() _tileNatural[i] = true; _tilePlane[i] = true; _tileFlammable[i] = true; - //_tileFirestormResistance[i] = 10; + _tileFirestormResistance[i] = 10; _tileRegrowOnlyInSpring[i] = true; _tileRegrowthProbabilityDivisor[i] = 4; _tileRegrowthAmount[i] = 1; @@ -466,7 +467,7 @@ void Bible::initialize() _tileGrassy[i] = true; _tileNatural[i] = true; _tileFlammable[i] = true; - //_tileFirestormResistance[i] = 10; + _tileFirestormResistance[i] = 10; _tileChaosProtection[i] = true; _tileRegrowOnlyInSpring[i] = true; _tileStacksBuilt[i] = 1; @@ -498,8 +499,8 @@ void Bible::initialize() _tileHitpoints[i] = 2; _tileIncome[i] = 1; _tileEmission[i] = 2; - //_tileFlammable[i] = true; - //_tileFirestormResistance[i] = 70; + _tileFlammable[i] = true; + _tileFirestormResistance[i] = 70; _tileProduces[i] = {{(UnitType) MILITIA, 10}, (UnitType) SETTLER}; _tileExpands[i] = {(TileType) INDUSTRY, (TileType) BARRACKS}; _tileCost[i] = 50; @@ -524,8 +525,8 @@ void Bible::initialize() _tileHitpoints[i] = 2; _tileIncome[i] = 1; _tileEmission[i] = 1; - //_tileFlammable[i] = true; - //_tileFirestormResistance[i] = 70; + _tileFlammable[i] = true; + _tileFirestormResistance[i] = 70; _tileProduces[i] = {(UnitType) SETTLER}; _tileUpgrades[i] = {{TileType::NONE, 4}, {(TileType) CITY, 40}}; _tileCost[i] = 10; @@ -550,8 +551,8 @@ void Bible::initialize() _tileHitpoints[i] = 2; _tileIncome[i] = 0; _tileEmission[i] = 1; - //_tileFlammable[i] = true; - //_tileFirestormResistance[i] = 70; + _tileFlammable[i] = true; + _tileFirestormResistance[i] = 70; _tileProduces[i] = {(UnitType) SETTLER, (UnitType) MILITIA}; _tileUpgrades[i] = {}; _tileCost[i] = 0; @@ -579,8 +580,8 @@ void Bible::initialize() _tileEmission[i] = 5; _tilePollutionAmount[i] = 1; _tilePollutionRadius[i] = 2; - //_tileFlammable[i] = true; - //_tileFirestormResistance[i] = 70; + _tileFlammable[i] = true; + _tileFirestormResistance[i] = 70; _tileProduces[i] = {(UnitType) TANK}; _tileExpands[i] = {(TileType) AIRFIELD}; _tileUpgrades[i] = {{TileType::NONE, 50}}; @@ -604,8 +605,8 @@ void Bible::initialize() _tileVision[i] = 2; _tileHitpoints[i] = 3; _tileEmission[i] = 1; - //_tileFlammable[i] = true; - //_tileFirestormResistance[i] = 70; + _tileFlammable[i] = true; + _tileFirestormResistance[i] = 70; _tileProduces[i] = {(UnitType) RIFLEMAN, (UnitType) GUNNER, (UnitType) SAPPER}; _tileUpgrades[i] = {{TileType::NONE, 25}}; _tileCost[i] = 5; @@ -629,8 +630,8 @@ void Bible::initialize() _tileHitpoints[i] = 3; _tileLeakGas[i] = 1; _tileEmission[i] = 1; - //_tileFlammable[i] = true; - //_tileFirestormResistance[i] = 70; + _tileFlammable[i] = true; + _tileFirestormResistance[i] = 70; _tileProduces[i] = {(UnitType) ZEPPELIN}; _tileCost[i] = 5; _tileDestroyed[i] = (TileType) RUBBLE; @@ -654,8 +655,8 @@ void Bible::initialize() _tileHitpoints[i] = 2; _tileIncome[i] = 0; _tileEmission[i] = 1; - //_tileFlammable[i] = true; - //_tileFirestormResistance[i] = 70; + _tileFlammable[i] = true; + _tileFirestormResistance[i] = 70; _tileProduces[i] = {(UnitType) SETTLER, (UnitType) MILITIA}; _tileCultivates[i] = {(TileType) SOIL}; _tileCost[i] = 5; @@ -673,7 +674,7 @@ void Bible::initialize() _tileNatural[i] = false; _tilePlane[i] = true; _tileFlammable[i] = true; - //_tileFirestormResistance[i] = 10; + _tileFirestormResistance[i] = 10; _tileRegrowOnlyInSpring[i] = false; _tileVision[i] = 0; _tileIncome[i] = 0; @@ -695,7 +696,7 @@ void Bible::initialize() _tileNatural[i] = true; _tilePlane[i] = true; _tileFlammable[i] = true; - //_tileFirestormResistance[i] = 10; + _tileFirestormResistance[i] = 10; _tileVision[i] = 0; _tileIncome[i] = 1; _tileDestroyed[i] = (TileType) DIRT; @@ -713,7 +714,7 @@ void Bible::initialize() _tileControllable[i] = false; _tilePlane[i] = false; _tileFlammable[i] = true; - //_tileFirestormResistance[i] = 10; + _tileFirestormResistance[i] = 10; _tileTrenches[i] = true; _tileForceOccupy[i] = true; _tileCost[i] = 0; @@ -872,7 +873,6 @@ void Bible::initialize() _aridificationRange = 2; _aridificationCount = 4; - _firestormCount = 5; _deathCount = 1; _humidityMax = 4; @@ -906,7 +906,7 @@ void Bible::initialize() _humidityMaxDegradation[i] = 0; _humidityMaxDesertification[i] = 0; _humidityMinSnow[i] = 4; - _humidityMaxFirestorm[i] = 4; + _humidityMaxFirestorm[i] = 3; _humidityMaxBonedrought[i] = 0; _humidityMaxStonedrought[i] = 0; _chaosMinDegradation[i] = 0; @@ -954,15 +954,13 @@ void Bible::initialize() _chaosMinStonedrought[i] = 4 * _chaosThreshold; _chaosMinDeath[i] = -1; - _frostbiteShots = 2; - _frostbiteDamage = 1; - _frostbiteThresholdDamage = 1; + _frostbiteThresholdDamage = 100; _frostbiteThresholdVulnerability = 100; _firestormShots = 3; _firestormDamage = 2; - //_firestormBasePercentage = 30; - //_firestormDroughtPercentage = 20; + _firestormBasePercentage = 30; + _firestormDroughtPercentage = 20; _gasShots = 3; _gasDamage = 1; @@ -1009,9 +1007,8 @@ void Bible::initialize() _emptyBasedFrostbite = false; _planeBasedFrostbite = true; _planeBasedAridification = true; - _flammableBasedFirestorm = true; - _percentageBasedFirestorm = false; - _randomizedFirestorm = true; + _percentageBasedFirestorm = true; + _randomizedFirestorm = false; _randomizedAridification = true; _cumulativeDeath = true; _vulnerabilitiesStack = true; @@ -1023,6 +1020,7 @@ void Bible::initialize() _moraleGatheredInSummer = false; _moraleGatheredWhenBuilt = false; _snowCoversNiceness = true; + _frostbiteGivesColdFeet = true; _snowSlowAmount = 1; _snowSlowMaximum = 1; @@ -1034,12 +1032,29 @@ void Bible::initialize() void Bible::customize() { - const size_t CITY = (size_t) tiletype("city"); + //const size_t CITY = (size_t) tiletype("city"); + const size_t TOWN = (size_t) tiletype("town"); + const size_t FARM = (size_t) tiletype("farm"); const size_t BARRACKS = (size_t) tiletype("barracks"); - const size_t RIFLEMAN = (size_t) unittype("rifleman"); - - _tileExpands[CITY] = {(TileType) BARRACKS}; - _tileProduces[BARRACKS] = {(UnitType) RIFLEMAN}; + const size_t INDUSTRY = (size_t) tiletype("industry"); + //const size_t RIFLEMAN = (size_t) unittype("rifleman"); + const size_t GUNNER = (size_t) unittype("gunner"); + //const size_t MILITIA = (size_t) unittype("militia"); + const size_t SETTLER = (size_t) unittype("settler"); + const size_t SAPPER = (size_t) unittype("sapper"); + + //_tileProduces[CITY] = {(UnitType) SETTLER, {((UnitType) RIFLEMAN), 25}}; + _tileUpgrades[TOWN] = {}; + _tileGrowthMax[TOWN] = 5; + _tileProduces[BARRACKS] = {(UnitType) SAPPER, (UnitType) GUNNER}; + _unitCost[SAPPER] = 20; + _unitCost[GUNNER] = 25; + _tileUpgrades[BARRACKS] = {}; + _tileUpgrades[INDUSTRY] = {}; + _tileExpands[INDUSTRY] = {}; + _unitSettles[SETTLER] = {(TileType) TOWN, (TileType) FARM}; + _unitCanLockdown[GUNNER] = false; + //_unitShapes[RIFLEMAN] = {}; } void Bible::finalize() @@ -2263,6 +2278,7 @@ Bible::Bible(const std::string& biblename, const Json::Value& json) : } } AUTO(PARSEBOOL, snowCoversNiceness) + AUTO(PARSEBOOL, frostbiteGivesColdFeet) AUTO(PARSEINT, snowSlowAmount) else @@ -2278,14 +2294,14 @@ Bible::Bible(const std::string& biblename, const Json::Value& json) : } // Backwards compatiblity: trenches had their slow amount hardcoded. { - bool trenchesSlowAmount; + int trenchesSlowAmount; PARSEINT(trenchesSlowAmount, "trenchesSlowAmount") else { // Backwards compatibility. trenchesSlowAmount = 10; } - if (trenchesSlowAmount) + if (trenchesSlowAmount > 0) { TileType trenchestype = tiletype("trenches"); if (trenchestype != TileType::NONE) @@ -2296,14 +2312,14 @@ Bible::Bible(const std::string& biblename, const Json::Value& json) : } // Backwards compatiblity: trenches had their slow maximum hardcoded. { - bool trenchesSlowMaximum; + int trenchesSlowMaximum; PARSEINT(trenchesSlowMaximum, "trenchesSlowMaximum") else { // Backwards compatibility. trenchesSlowMaximum = 10; } - if (trenchesSlowMaximum) + if (trenchesSlowMaximum > 0) { TileType trenchestype = tiletype("trenches"); if (trenchestype != TileType::NONE) @@ -2897,6 +2913,7 @@ Json::Value Bible::toJson() const AUTO(PUTBOOL, moraleGatheredInSummer) AUTO(PUTBOOL, moraleGatheredWhenBuilt) AUTO(PUTBOOL, snowCoversNiceness) + AUTO(PUTBOOL, frostbiteGivesColdFeet) AUTO(FORCEPUTINT, snowSlowAmount) // Backwards compatibility. AUTO(FORCEPUTINT, snowSlowMaximum) // Backwards compatibility. @@ -3275,6 +3292,7 @@ bool Bible::operator==(const Bible& other) const AUTO(CHECK, moraleGatheredInSummer) AUTO(CHECK, moraleGatheredWhenBuilt) AUTO(CHECK, snowCoversNiceness) + AUTO(CHECK, frostbiteGivesColdFeet) AUTO(CHECK, snowSlowAmount) AUTO(CHECK, snowSlowMaximum) diff --git a/epicinium/src/logic/bible.hpp b/epicinium/src/logic/bible.hpp index 2449251..bcbf55c 100644 --- a/epicinium/src/logic/bible.hpp +++ b/epicinium/src/logic/bible.hpp @@ -359,6 +359,7 @@ class Bible : public TypeNamer bool _moraleGatheredInSummer; bool _moraleGatheredWhenBuilt; bool _snowCoversNiceness; + bool _frostbiteGivesColdFeet; int8_t _snowSlowAmount; int8_t _snowSlowMaximum; @@ -663,6 +664,7 @@ class Bible : public TypeNamer bool moraleGatheredInSummer() const { return _moraleGatheredInSummer; } bool moraleGatheredWhenBuilt() const { return _moraleGatheredWhenBuilt; } bool snowCoversNiceness() const { return _snowCoversNiceness; } + bool frostbiteGivesColdFeet() const { return _frostbiteGivesColdFeet; } int8_t snowSlowAmount() const { return _snowSlowAmount; } int8_t snowSlowMaximum() const { return _snowSlowMaximum; } diff --git a/epicinium/src/logic/board.cpp b/epicinium/src/logic/board.cpp index a88cd1a..e7984f0 100644 --- a/epicinium/src/logic/board.cpp +++ b/epicinium/src/logic/board.cpp @@ -78,9 +78,30 @@ void Board::load(const std::string& mapname) std::string line; std::string fname = Map::readOnlyFilename(mapname); + if (!System::isFile(fname)) + { + LOGF << "Failed to load map '" << mapname << "' (" << fname << "): " + << "file does not exist"; + throw std::runtime_error("failed to load map " + mapname + ": \t" + + "file does not exist"); + } + std::ifstream file = System::ifstream(fname); - if (!std::getline(file, line) || !reader.parse(line, metadata) - || !metadata.isObject()) + if (!file) + { + LOGF << "Failed to load map '" << mapname << "' (" << fname << "): " + << "failed to open file"; + throw std::runtime_error("failed to load map " + mapname + ": \t" + + "failed to open file"); + } + else if (!std::getline(file, line) || line.empty()) + { + LOGF << "Failed to load map '" << mapname << "' (" << fname << "): " + << "failed to read line"; + throw std::runtime_error("failed to load map " + mapname + ": \t" + + "failed to read line"); + } + else if (!reader.parse(line, metadata) || !metadata.isObject()) { // Try old style. file.close(); diff --git a/epicinium/src/logic/damage.cpp b/epicinium/src/logic/damage.cpp index 190db18..574c9c1 100644 --- a/epicinium/src/logic/damage.cpp +++ b/epicinium/src/logic/damage.cpp @@ -286,7 +286,7 @@ int Damage::hitpoints(Cell index, const TileToken& tile) int loss = 0; // Frostbite only affects ground units. - if (_board.frostbite(index) >= _bible.frostbiteThresholdVulnerability() + if ((_board.frostbite(index) ? 1 : 0) >= _bible.frostbiteThresholdVulnerability() && !_bible.frostbiteOnlyTargetsGroundUnits()) { loss++; @@ -333,7 +333,7 @@ int Damage::hitpoints(Cell index, const UnitToken& unit) int loss = 0; // Frostbite affects everything. - if (_board.frostbite(index) >= _bible.frostbiteThresholdVulnerability() + if ((_board.frostbite(index) ? 1 : 0) >= _bible.frostbiteThresholdVulnerability() && (!_bible.frostbiteOnlyTargetsGroundUnits() || !_bible.unitAir(unit.type))) { diff --git a/epicinium/src/logic/gameowner.hpp b/epicinium/src/logic/gameowner.hpp index 0c73d7f..340f9ed 100644 --- a/epicinium/src/logic/gameowner.hpp +++ b/epicinium/src/logic/gameowner.hpp @@ -48,7 +48,8 @@ class GameOwner virtual ~GameOwner() = default; virtual std::weak_ptr startGame(imploding_ptr game) = 0; - virtual std::weak_ptr startChallenge(const Challenge& challenge) = 0; + virtual std::weak_ptr startChallenge(const Challenge& challenge, + const std::string& name = "") = 0; virtual std::weak_ptr startGame( const Player& player, const std::string& rulesetname, uint32_t planningTime) = 0; @@ -69,4 +70,6 @@ class GameOwner const std::string& mapname, const std::string& rulesetname) = 0; virtual void stopGame() = 0; + + virtual void reportAwardedStars(int /*amount*/) {}; }; diff --git a/epicinium/src/logic/map.cpp b/epicinium/src/logic/map.cpp index 4225030..dc0a892 100644 --- a/epicinium/src/logic/map.cpp +++ b/epicinium/src/logic/map.cpp @@ -129,6 +129,14 @@ std::string Map::authoredFilename(const std::string& name) Json::Value Map::loadMetadata(const std::string& name) { + for (const auto& item : _cachedexternalitems) + { + if (item.uniqueTag == name) + { + return item.metadata; + } + } + try { std::ifstream file = System::ifstream(readOnlyFilename(name)); diff --git a/epicinium/src/logic/map.hpp b/epicinium/src/logic/map.hpp index 9833769..326190e 100644 --- a/epicinium/src/logic/map.hpp +++ b/epicinium/src/logic/map.hpp @@ -47,6 +47,7 @@ class Map std::string uniqueTag; std::string quotedName; std::string sourceFilename; + Json::Value metadata; }; private: diff --git a/epicinium/src/logic/markertransition.cpp b/epicinium/src/logic/markertransition.cpp index 65c5d01..db02a2d 100644 --- a/epicinium/src/logic/markertransition.cpp +++ b/epicinium/src/logic/markertransition.cpp @@ -39,6 +39,7 @@ namespace MarkerFlag FIRESTORM = 0x04, BONEDROUGHT = 0x08, DEATH = 0x10, + COLDFEET = 0x20, }; } @@ -152,10 +153,16 @@ void MarkerTransition::execute() } } +constexpr Season previousSeason(const Season& season) +{ + return (Season) ((((size_t) season) + SEASON_SIZE - 1) % SEASON_SIZE); +} + void MarkerTransition::map(Cell index) { bool snow; bool frostbite; + bool coldfeet; bool firestorm; bool bonedrought; bool death; @@ -184,6 +191,9 @@ void MarkerTransition::map(Cell index) : (_bible.stackBasedFrostbite()) ? ((stacks + 1) * _bible.chaosMinFrostbite(_season) <= chaos) : true)); + coldfeet = (_bible.frostbiteGivesColdFeet() + && _board.frostbite(index) && _board.ground(index) + && _bible.chaosMinFrostbite(previousSeason(_season)) >= 0); firestorm = (_bible.chaosMinFirestorm(_season) >= 0 && !_bible.randomizedFirestorm() && !_bible.percentageBasedFirestorm() @@ -245,6 +255,7 @@ void MarkerTransition::map(Cell index) && temp <= _bible.temperatureMaxSnow(_season); // Frostbite occurs if the temperature is critically low. frostbite = temp <= _bible.temperatureMaxFrostbite(_season); + coldfeet = false; // Firestorm occurs if the temperature is critically high. firestorm = temp >= _bible.temperatureMinFirestorm(_season); // Bonedrought occurs if the humidity is critically low. @@ -259,6 +270,7 @@ void MarkerTransition::map(Cell index) uint8_t result = 0; if (snow) result |= MarkerFlag::SNOW; if (frostbite) result |= MarkerFlag::FROSTBITE; + if (coldfeet) result |= MarkerFlag::COLDFEET; if (firestorm) result |= MarkerFlag::FIRESTORM; if (bonedrought) result |= MarkerFlag::BONEDROUGHT; if (death) result |= MarkerFlag::DEATH; @@ -283,11 +295,17 @@ void MarkerTransition::reduce(Cell index) { bool frostbite = (result & MarkerFlag::FROSTBITE); - if (frostbite != _board.frostbite(index)) + bool coldfeet = (result & MarkerFlag::COLDFEET); + // Frostbite freezes ground units in Winter. This is implemented by + // having it stick around until Spring or until the unit moves. + // We issue a second positive FROSTBITE update when this happens, to + // help with animations. + if (frostbite != _board.frostbite(index) + || (frostbite || coldfeet) != _board.frostbite(index)) { Change change(Change::Type::FROSTBITE, Descriptor::cell(index.pos())); - change.xFrostbite(frostbite); + change.xFrostbite(frostbite || coldfeet); _board.enact(change); _changeset.push(change, _board.vision(index)); } diff --git a/epicinium/src/logic/notice.cpp b/epicinium/src/logic/notice.cpp index 3d41126..9be3321 100644 --- a/epicinium/src/logic/notice.cpp +++ b/epicinium/src/logic/notice.cpp @@ -43,6 +43,7 @@ Notice parseNotice(const std::string& str) else if (str == "lackingstacks") return Notice::LACKINGSTACKS; else if (str == "lackingpower") return Notice::LACKINGPOWER; else if (str == "lackingmoney") return Notice::LACKINGMONEY; + else if (str == "coldfeet") return Notice::COLDFEET; else if (str == "occupiedbyenemy") return Notice::OCCUPIEDBYENEMY; else if (str == "activeattack") return Notice::ACTIVEATTACK; else if (str == "retaliationattack") return Notice::RETALIATIONATTACK; @@ -73,6 +74,7 @@ const char* stringify(const Notice& notice) case Notice::LACKINGSTACKS: return "lackingstacks"; case Notice::LACKINGPOWER: return "lackingpower"; case Notice::LACKINGMONEY: return "lackingmoney"; + case Notice::COLDFEET: return "coldfeet"; case Notice::OCCUPIEDBYENEMY: return "occupiedbyenemy"; case Notice::ACTIVEATTACK: return "activeattack"; case Notice::RETALIATIONATTACK: return "retaliationattack"; diff --git a/epicinium/src/logic/notice.hpp b/epicinium/src/logic/notice.hpp index d7061ed..497b67d 100644 --- a/epicinium/src/logic/notice.hpp +++ b/epicinium/src/logic/notice.hpp @@ -41,6 +41,7 @@ enum class Notice : uint8_t LACKINGSTACKS, LACKINGPOWER, LACKINGMONEY, + COLDFEET, OCCUPIEDBYENEMY, ACTIVEATTACK, RETALIATIONATTACK, diff --git a/epicinium/src/logic/position.hpp b/epicinium/src/logic/position.hpp index 9f79936..05c56f1 100644 --- a/epicinium/src/logic/position.hpp +++ b/epicinium/src/logic/position.hpp @@ -30,8 +30,13 @@ struct Position // The board size must be strictly smaller than 128x128 because we use // int8_t to store _rows in Cell, so it can have values 1 to 127 inclusive. // We use a much smaller maximum value to help train neural networks. +#if NEURALNEWT_20x13_ENABLED + static constexpr int MAX_COLS = 20; + static constexpr int MAX_ROWS = 13; +#else static constexpr int MAX_COLS = 32; static constexpr int MAX_ROWS = MAX_COLS; +#endif int8_t row; int8_t col; diff --git a/epicinium/src/logic/tiletype.hpp b/epicinium/src/logic/tiletype.hpp index 30b88ab..aa6c8f7 100644 --- a/epicinium/src/logic/tiletype.hpp +++ b/epicinium/src/logic/tiletype.hpp @@ -32,7 +32,11 @@ enum class TileType : uint8_t NONE = 0, }; +#if NEURALNEWT_20x13_ENABLED +static constexpr size_t TILETYPE_SIZE = 19; +#else static constexpr size_t TILETYPE_SIZE = 32; +#endif TileType parseTileType(const TypeNamer& namer, const std::string& str); diff --git a/epicinium/src/logic/unittype.hpp b/epicinium/src/logic/unittype.hpp index b5b4170..1c6b8d7 100644 --- a/epicinium/src/logic/unittype.hpp +++ b/epicinium/src/logic/unittype.hpp @@ -32,7 +32,11 @@ enum class UnitType : uint8_t NONE = 0, }; +#if NEURALNEWT_20x13_ENABLED +static constexpr size_t UNITTYPE_SIZE = 8; +#else static constexpr size_t UNITTYPE_SIZE = 32; +#endif UnitType parseUnitType(const TypeNamer& namer, const std::string& str); diff --git a/epicinium/src/logic/visiontransition.cpp b/epicinium/src/logic/visiontransition.cpp index 14f0cd2..f2ab84a 100644 --- a/epicinium/src/logic/visiontransition.cpp +++ b/epicinium/src/logic/visiontransition.cpp @@ -32,6 +32,9 @@ #include "tiletoken.hpp" #include "unittoken.hpp" + // windows.h is being annoying +#undef near + template VisionTMRI::VisionTMRI(const Bible& bible, Board& board,