From 763fae843080560dc47d5babca52e98cf816ec56 Mon Sep 17 00:00:00 2001 From: grantramsay Date: Sun, 1 Mar 2020 14:32:32 +1300 Subject: [PATCH 1/9] Tidy missile class composition Missile attributes/characteristics are now specified in one place, and are initialised in the missile constructor (proper composition) Missile attributes now use nested classes instead of friendship for private member access --- apps/freeablo/CMakeLists.txt | 1 + apps/freeablo/faworld/gamelevel.cpp | 2 +- apps/freeablo/faworld/missile/missile.cpp | 18 +-- apps/freeablo/faworld/missile/missile.h | 136 +++++++----------- .../missile/missileactorengagement.cpp | 25 +--- .../faworld/missile/missileattributes.cpp | 38 +++++ .../faworld/missile/missilecreation.cpp | 30 +--- .../faworld/missile/missilemovement.cpp | 27 +--- 8 files changed, 113 insertions(+), 164 deletions(-) create mode 100644 apps/freeablo/faworld/missile/missileattributes.cpp diff --git a/apps/freeablo/CMakeLists.txt b/apps/freeablo/CMakeLists.txt index d2e868aee..12f45ff0d 100644 --- a/apps/freeablo/CMakeLists.txt +++ b/apps/freeablo/CMakeLists.txt @@ -57,6 +57,7 @@ add_library(freeablo_lib # split into a library so I can link to it from tests faworld/missile/missile.cpp faworld/missile/missile.h faworld/missile/missileactorengagement.cpp + faworld/missile/missileattributes.cpp faworld/missile/missilecreation.cpp faworld/missile/missileenums.h faworld/missile/missilegraphic.cpp diff --git a/apps/freeablo/faworld/gamelevel.cpp b/apps/freeablo/faworld/gamelevel.cpp index c80ba2ec9..ea8546b8a 100644 --- a/apps/freeablo/faworld/gamelevel.cpp +++ b/apps/freeablo/faworld/gamelevel.cpp @@ -267,7 +267,7 @@ namespace FAWorld // Only display missiles for this (the currently displayed) level. if (missile->getLevel() != this) continue; - for (const auto& graphic : missile->mGraphics) + for (const auto& graphic : missile->getGraphics()) { auto tmp = graphic->getCurrentFrame(); auto spriteGroup = tmp.first; diff --git a/apps/freeablo/faworld/missile/missile.cpp b/apps/freeablo/faworld/missile/missile.cpp index 66c6cd623..3fef8f7b0 100644 --- a/apps/freeablo/faworld/missile/missile.cpp +++ b/apps/freeablo/faworld/missile/missile.cpp @@ -10,15 +10,18 @@ namespace FAWorld namespace Missile { Missile::Missile(MissileId missileId, Actor& creator, Misc::Point dest) - : mCreator(&creator), mMissileId(missileId), mLevel(creator.getLevel()), mSrcPoint(creator.getPos().current()) + : mCreator(&creator), mMissileId(missileId), mLevel(creator.getLevel()), + mSrcPoint(creator.getPos().current()), mAttr(Attributes::fromId(missileId)) { - MissileCreation::get(missileId)(*this, dest); + mAttr.mCreation(*this, dest); if (!missileData().mSoundEffect.empty()) Engine::ThreadManager::get()->playSound(missileData().mSoundEffect); } Missile::Missile(FASaveGame::GameLoader& loader) + : mMissileId(static_cast(loader.load())), + mAttr(Attributes::fromId(mMissileId)) { auto creatorId = loader.load(); auto levelIndex = loader.load(); @@ -28,7 +31,6 @@ namespace FAWorld mLevel = world->getLevel(levelIndex); }); - mMissileId = static_cast(loader.load()); mSrcPoint = Misc::Point(loader); mComplete = loader.load(); @@ -42,10 +44,10 @@ namespace FAWorld { Serial::ScopedCategorySaver cat("Missile", saver); + saver.save(static_cast(mMissileId)); saver.save(mCreator->getId()); saver.save(mLevel->getLevelIndex()); - saver.save(static_cast(mMissileId)); mSrcPoint.save(saver); saver.save(mComplete); @@ -92,14 +94,14 @@ namespace FAWorld graphic->update(); - MissileMovement::get(mMissileId)(*this, *graphic); + mAttr.mMovement(*this, *graphic); auto curPoint = graphic->mCurPos.current(); // Check if actor is hit. auto actor = mLevel->getActorAt(curPoint); if (actor) - MissileActorEngagement::get(mMissileId)(*this, *graphic, *actor); + mAttr.mActorEngagement(*this, *graphic, *actor); // Stop when walls are hit. if (!actor && !mLevel->isPassable(curPoint, mCreator)) @@ -110,11 +112,11 @@ namespace FAWorld // Stop after max range is exceeded. auto distance = (Vec2Fix(curPoint.x, curPoint.y) - Vec2Fix(mSrcPoint.x, mSrcPoint.y)).magnitude(); - if (distance > MissileMaxRange::get(mMissileId)) + if (distance > mAttr.mMaxRange) graphic->stop(); // Stop after "time to live" has expired. - if (graphic->getTicksSinceStarted() > MissileTimeToLive::get(mMissileId)) + if (graphic->getTicksSinceStarted() > mAttr.mTimeToLive) graphic->stop(); } // Set complete flag when all graphics are finished. diff --git a/apps/freeablo/faworld/missile/missile.h b/apps/freeablo/faworld/missile/missile.h index fd6254782..6fe0c6241 100644 --- a/apps/freeablo/faworld/missile/missile.h +++ b/apps/freeablo/faworld/missile/missile.h @@ -18,15 +18,8 @@ namespace FAWorld { class Missile { - friend class MissileCreation; - friend class MissileMovement; - friend class MissileActorEngagement; - friend class MissileMaxRange; - friend class MissileTimeToLive; - public: virtual ~Missile() = default; - Missile() = default; Missile(MissileId missileId, Actor& creator, Misc::Point dest); Missile(FASaveGame::GameLoader& loader); @@ -36,93 +29,70 @@ namespace FAWorld virtual bool isComplete() const { return mComplete; } MissileId getMissileId() const { return mMissileId; } const GameLevel* getLevel() const { return mLevel; } - - std::vector> mGraphics; + const std::vector>& getGraphics() const { return mGraphics; } protected: - Actor* mCreator = nullptr; - MissileId mMissileId = MissileId(0); - GameLevel* mLevel = nullptr; - Misc::Point mSrcPoint; - bool mComplete = false; + // Static inner classes for missile attribute composition. + class Creation + { + public: + Creation() = delete; + typedef void (*Method)(Missile& missile, Misc::Point dest); - const DiabloExe::MissileData& missileData() const; - const DiabloExe::MissileGraphics& missileGraphics() const; - std::string getGraphicsPath(int32_t i) const; - void playImpactSound(); - }; + static void singleFrame16Direction(Missile& missile, Misc::Point dest); + static void animated16Direction(Missile& missile, Misc::Point dest); + static void firewall(Missile& missile, Misc::Point dest); + static void basicAnimated(Missile& missile, Misc::Point dest); + }; - class MissileCreation - { - public: - typedef void (*MissileCreationMethod)(Missile& missile, Misc::Point dest); - static MissileCreationMethod get(MissileId missileId); + class Movement + { + public: + Movement() = delete; + typedef void (*Method)(Missile& missile, MissileGraphic& graphic); - protected: - static void singleFrame16Direction(Missile& missile, Misc::Point dest); - static void animated16Direction(Missile& missile, Misc::Point dest); - static void firewall(Missile& missile, Misc::Point dest); - static void basicAnimated(Missile& missile, Misc::Point dest); - }; + static void stationary(Missile& missile, MissileGraphic& graphic); + static void linear(Missile& missile, MissileGraphic& graphic); + static void hoverOverCreator(Missile& missile, MissileGraphic& graphic); + }; - class MissileMovement - { - public: - typedef void (*MissileMovementMethod)(Missile& missile, MissileGraphic& graphic); - static MissileMovementMethod get(MissileId missileId); + class ActorEngagement + { + public: + ActorEngagement() = delete; + typedef void (*Method)(Missile& missile, MissileGraphic& graphic, Actor& actor); - protected: - static void fixed(Missile& missile, MissileGraphic& graphic); - static void linear(Missile& missile, MissileGraphic& graphic); - static void hoverOverCreator(Missile& missile, MissileGraphic& graphic); - }; + static void none(Missile& missile, MissileGraphic& graphic, Actor& actor); + static void damageEnemy(Missile& missile, MissileGraphic& graphic, Actor& actor); + static void damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor); + }; - class MissileActorEngagement - { - public: - typedef void (*MissileActorEngagementMethod)(Missile& missile, MissileGraphic& graphic, Actor& actor); - static MissileActorEngagementMethod get(MissileId missileId); + // Inner class that holds reference to all missile attributes. + class Attributes + { + public: + Attributes(Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, FixedPoint maxRange, Tick timeToLive); + static Attributes fromId(MissileId missileId); - protected: - static void none(Missile& missile, MissileGraphic& graphic, Actor& actor); - static void damageEnemy(Missile& missile, MissileGraphic& graphic, Actor& actor); - static void damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor); - }; + const Missile::Creation::Method mCreation; + const Missile::Movement::Method mMovement; + const Missile::ActorEngagement::Method mActorEngagement; + const FixedPoint mMaxRange; + const Tick mTimeToLive; + }; - class MissileMaxRange - { - public: - static FixedPoint get(MissileId missileId) - { - switch (missileId) - { - case MissileId::arrow: - case MissileId::firebolt: - case MissileId::farrow: - case MissileId::larrow: - return 15; // placeholder - default: - return FixedPoint::fromRawValue(INT64_MAX); - } - } - }; + const DiabloExe::MissileData& missileData() const; + const DiabloExe::MissileGraphics& missileGraphics() const; + std::string getGraphicsPath(int32_t i) const; + void playImpactSound(); - class MissileTimeToLive - { - public: - static Tick get(MissileId missileId) - { - switch (missileId) - { - case MissileId::firewall: - case MissileId::firewalla: - case MissileId::firewallc: - case MissileId::manashield: - return World::getTicksInPeriod("8"); // placeholder - default: - return std::numeric_limits::max(); - } - } + Actor* mCreator; + MissileId mMissileId; + GameLevel* mLevel; + Misc::Point mSrcPoint; + Missile::Attributes mAttr; + std::vector> mGraphics; + bool mComplete = false; }; } } diff --git a/apps/freeablo/faworld/missile/missileactorengagement.cpp b/apps/freeablo/faworld/missile/missileactorengagement.cpp index 99b76601a..6e339bf3b 100644 --- a/apps/freeablo/faworld/missile/missileactorengagement.cpp +++ b/apps/freeablo/faworld/missile/missileactorengagement.cpp @@ -5,28 +5,9 @@ namespace FAWorld { namespace Missile { - MissileActorEngagement::MissileActorEngagementMethod MissileActorEngagement::get(MissileId missileId) - { - switch (missileId) - { - case MissileId::arrow: - case MissileId::firebolt: - case MissileId::farrow: - case MissileId::larrow: - return damageEnemyAndStop; - case MissileId::firewall: - case MissileId::firewalla: - case MissileId::firewallc: - return damageEnemy; - case MissileId::manashield: // TODO - default: - return none; - } - } - - void MissileActorEngagement::none(Missile&, MissileGraphic&, Actor&) {} + void Missile::ActorEngagement::none(Missile&, MissileGraphic&, Actor&) {} - void MissileActorEngagement::damageEnemy(Missile& missile, MissileGraphic&, Actor& actor) + void Missile::ActorEngagement::damageEnemy(Missile& missile, MissileGraphic&, Actor& actor) { const uint32_t damage = 10; // placeholder if (missile.mCreator->canIAttack(&actor)) @@ -36,7 +17,7 @@ namespace FAWorld } } - void MissileActorEngagement::damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor) + void Missile::ActorEngagement::damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor) { damageEnemy(missile, graphic, actor); // Stop on friendlies too. diff --git a/apps/freeablo/faworld/missile/missileattributes.cpp b/apps/freeablo/faworld/missile/missileattributes.cpp new file mode 100644 index 000000000..110fa7c45 --- /dev/null +++ b/apps/freeablo/faworld/missile/missileattributes.cpp @@ -0,0 +1,38 @@ +#include "missile.h" + +namespace FAWorld +{ + namespace Missile + { + Missile::Attributes::Attributes( + Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, FixedPoint maxRange, Tick timeToLive) + : mCreation(creation), mMovement(movement), mActorEngagement(actorEngagement), mMaxRange(maxRange), mTimeToLive(timeToLive) + { + } + + Missile::Attributes Missile::Attributes::fromId(MissileId missileId) + { + static FixedPoint maxRangeIgnore = FixedPoint::fromRawValue(INT64_MAX); + static Tick ttlIgnore = std::numeric_limits::max(); + + switch (missileId) + { + case MissileId::arrow: + return Attributes(Creation::singleFrame16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + case MissileId::firebolt: + return Attributes(Creation::animated16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + case MissileId::farrow: + case MissileId::larrow: + return Attributes(Creation::animated16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + case MissileId::firewall: + case MissileId::firewalla: + case MissileId::firewallc: + return Attributes(Creation::firewall, Movement::stationary, ActorEngagement::damageEnemy, maxRangeIgnore, World::getTicksInPeriod(8)); + case MissileId::manashield: + return Attributes(Creation::basicAnimated, Movement::hoverOverCreator, ActorEngagement::none, maxRangeIgnore, World::getTicksInPeriod(8)); + default: + invalid_enum(MissileId, missileId); + } + } + } +} diff --git a/apps/freeablo/faworld/missile/missilecreation.cpp b/apps/freeablo/faworld/missile/missilecreation.cpp index 65959c745..9b38b2dc1 100644 --- a/apps/freeablo/faworld/missile/missilecreation.cpp +++ b/apps/freeablo/faworld/missile/missilecreation.cpp @@ -5,29 +5,7 @@ namespace FAWorld { namespace Missile { - MissileCreation::MissileCreationMethod MissileCreation::get(MissileId missileId) - { - switch (missileId) - { - case MissileId::arrow: - return singleFrame16Direction; - case MissileId::firebolt: - case MissileId::farrow: - case MissileId::larrow: - return animated16Direction; - case MissileId::firewall: - case MissileId::firewalla: - case MissileId::firewallc: - return firewall; - case MissileId::manashield: - return basicAnimated; - default: - invalid_enum(MissileId, missileId); - // return nullptr; // MSVC generates C4702 unreachable code. - } - } - - void MissileCreation::singleFrame16Direction(Missile& missile, Misc::Point dest) + void Missile::Creation::singleFrame16Direction(Missile& missile, Misc::Point dest) { auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); auto srcPos = Position(missile.mSrcPoint, direction); @@ -35,7 +13,7 @@ namespace FAWorld missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), direction16, srcPos)); } - void MissileCreation::animated16Direction(Missile& missile, Misc::Point dest) + void Missile::Creation::animated16Direction(Missile& missile, Misc::Point dest) { auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); auto srcPos = Position(missile.mSrcPoint, direction); @@ -43,7 +21,7 @@ namespace FAWorld missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(direction16), std::nullopt, srcPos)); } - void MissileCreation::firewall(Missile& missile, Misc::Point dest) + void Missile::Creation::firewall(Missile& missile, Misc::Point dest) { // Flames are placed at -5 -> +5 perpendicular to the clicked point, and // two flames are placed at the clicked point (for double damage). @@ -62,7 +40,7 @@ namespace FAWorld } } - void MissileCreation::basicAnimated(Missile& missile, Misc::Point) + void Missile::Creation::basicAnimated(Missile& missile, Misc::Point) { missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), std::nullopt, Position(missile.mSrcPoint))); } diff --git a/apps/freeablo/faworld/missile/missilemovement.cpp b/apps/freeablo/faworld/missile/missilemovement.cpp index 045c22f4a..287949d55 100644 --- a/apps/freeablo/faworld/missile/missilemovement.cpp +++ b/apps/freeablo/faworld/missile/missilemovement.cpp @@ -5,36 +5,15 @@ namespace FAWorld { namespace Missile { - MissileMovement::MissileMovementMethod MissileMovement::get(MissileId missileId) - { - switch (missileId) - { - case MissileId::arrow: - case MissileId::firebolt: - case MissileId::farrow: - case MissileId::larrow: - return linear; - case MissileId::firewall: - case MissileId::firewalla: - case MissileId::firewallc: - return fixed; - case MissileId::manashield: - return hoverOverCreator; - default: - invalid_enum(MissileId, missileId); - // return nullptr; // MSVC generates C4702 unreachable code. - } - } - - void MissileMovement::fixed(Missile&, MissileGraphic&) {} + void Missile::Movement::stationary(Missile&, MissileGraphic&) {} - void MissileMovement::linear(Missile&, MissileGraphic& graphic) + void Missile::Movement::linear(Missile&, MissileGraphic& graphic) { graphic.mCurPos.setFreeMovement(); graphic.mCurPos.update(FixedPoint(7) / FixedPoint(World::ticksPerSecond)); } - void MissileMovement::hoverOverCreator(Missile& missile, MissileGraphic& graphic) + void Missile::Movement::hoverOverCreator(Missile& missile, MissileGraphic& graphic) { // TODO: change level with actor. graphic.mCurPos = missile.mCreator->getPos(); From f7fec519e18ba1d13d212d6c252c2d3b00d3c721 Mon Sep 17 00:00:00 2001 From: grantramsay Date: Sun, 1 Mar 2020 14:32:58 +1300 Subject: [PATCH 2/9] Tidy namespaces --- apps/freeablo/faworld/missile/missile.cpp | 187 +++++++++--------- apps/freeablo/faworld/missile/missile.h | 136 ++++++------- .../missile/missileactorengagement.cpp | 33 ++-- .../faworld/missile/missileattributes.cpp | 55 +++--- .../faworld/missile/missilecreation.cpp | 65 +++--- .../faworld/missile/missilegraphic.cpp | 105 +++++----- .../freeablo/faworld/missile/missilegraphic.h | 57 +++--- .../faworld/missile/missilemovement.cpp | 25 ++- 8 files changed, 321 insertions(+), 342 deletions(-) diff --git a/apps/freeablo/faworld/missile/missile.cpp b/apps/freeablo/faworld/missile/missile.cpp index 3fef8f7b0..7b08eb296 100644 --- a/apps/freeablo/faworld/missile/missile.cpp +++ b/apps/freeablo/faworld/missile/missile.cpp @@ -5,122 +5,119 @@ #include "fasavegame/gameloader.h" #include "faworld/actor.h" -namespace FAWorld +namespace FAWorld::Missile { - namespace Missile + Missile::Missile(MissileId missileId, Actor& creator, Misc::Point dest) + : mCreator(&creator), mMissileId(missileId), mLevel(creator.getLevel()), + mSrcPoint(creator.getPos().current()), mAttr(Attributes::fromId(missileId)) { - Missile::Missile(MissileId missileId, Actor& creator, Misc::Point dest) - : mCreator(&creator), mMissileId(missileId), mLevel(creator.getLevel()), - mSrcPoint(creator.getPos().current()), mAttr(Attributes::fromId(missileId)) - { - mAttr.mCreation(*this, dest); + mAttr.mCreation(*this, dest); - if (!missileData().mSoundEffect.empty()) - Engine::ThreadManager::get()->playSound(missileData().mSoundEffect); - } + if (!missileData().mSoundEffect.empty()) + Engine::ThreadManager::get()->playSound(missileData().mSoundEffect); + } - Missile::Missile(FASaveGame::GameLoader& loader) - : mMissileId(static_cast(loader.load())), - mAttr(Attributes::fromId(mMissileId)) - { - auto creatorId = loader.load(); - auto levelIndex = loader.load(); - auto world = loader.currentlyLoadingWorld; - loader.addFunctionToRunAtEnd([this, world, creatorId, levelIndex]() { - mCreator = world->getActorById(creatorId); - mLevel = world->getLevel(levelIndex); - }); - - mSrcPoint = Misc::Point(loader); - mComplete = loader.load(); - - auto graphicsSize = loader.load(); - mGraphics.reserve(graphicsSize); - for (uint32_t i = 0; i < graphicsSize; i++) - mGraphics.push_back(std::make_unique(loader)); - } + Missile::Missile(FASaveGame::GameLoader& loader) + : mMissileId(static_cast(loader.load())), + mAttr(Attributes::fromId(mMissileId)) + { + auto creatorId = loader.load(); + auto levelIndex = loader.load(); + auto world = loader.currentlyLoadingWorld; + loader.addFunctionToRunAtEnd([this, world, creatorId, levelIndex]() { + mCreator = world->getActorById(creatorId); + mLevel = world->getLevel(levelIndex); + }); + + mSrcPoint = Misc::Point(loader); + mComplete = loader.load(); + + auto graphicsSize = loader.load(); + mGraphics.reserve(graphicsSize); + for (uint32_t i = 0; i < graphicsSize; i++) + mGraphics.push_back(std::make_unique(loader)); + } - void Missile::save(FASaveGame::GameSaver& saver) - { - Serial::ScopedCategorySaver cat("Missile", saver); + void Missile::save(FASaveGame::GameSaver& saver) + { + Serial::ScopedCategorySaver cat("Missile", saver); - saver.save(static_cast(mMissileId)); - saver.save(mCreator->getId()); - saver.save(mLevel->getLevelIndex()); + saver.save(static_cast(mMissileId)); + saver.save(mCreator->getId()); + saver.save(mLevel->getLevelIndex()); - mSrcPoint.save(saver); - saver.save(mComplete); + mSrcPoint.save(saver); + saver.save(mComplete); - saver.save(static_cast(mGraphics.size())); - for (auto& graphic : mGraphics) - graphic->save(saver); - } + saver.save(static_cast(mGraphics.size())); + for (auto& graphic : mGraphics) + graphic->save(saver); + } - const DiabloExe::MissileData& Missile::missileData() const - { - const auto& missileDataTable = Engine::EngineMain::get()->exe().getMissileDataTable(); - return missileDataTable.at((size_t)mMissileId); - } + const DiabloExe::MissileData& Missile::missileData() const + { + const auto& missileDataTable = Engine::EngineMain::get()->exe().getMissileDataTable(); + return missileDataTable.at((size_t)mMissileId); + } - const DiabloExe::MissileGraphics& Missile::missileGraphics() const - { - const auto& missileGraphicsTable = Engine::EngineMain::get()->exe().getMissileGraphicsTable(); - return missileGraphicsTable.at((size_t)missileData().mMissileGraphicsId); - } + const DiabloExe::MissileGraphics& Missile::missileGraphics() const + { + const auto& missileGraphicsTable = Engine::EngineMain::get()->exe().getMissileGraphicsTable(); + return missileGraphicsTable.at((size_t)missileData().mMissileGraphicsId); + } - std::string Missile::getGraphicsPath(int32_t i) const - { - release_assert(i >= 0 && i < missileGraphics().mNumAnimationFiles); - std::stringstream path; - path << "missiles/" << missileGraphics().mFilename; - if (missileGraphics().mNumAnimationFiles > 1) - path << i + 1; - path << ".cl2"; - return path.str(); - } + std::string Missile::getGraphicsPath(int32_t i) const + { + release_assert(i >= 0 && i < missileGraphics().mNumAnimationFiles); + std::stringstream path; + path << "missiles/" << missileGraphics().mFilename; + if (missileGraphics().mNumAnimationFiles > 1) + path << i + 1; + path << ".cl2"; + return path.str(); + } - void Missile::playImpactSound() - { - if (!missileData().mImpactSoundEffect.empty()) - Engine::ThreadManager::get()->playSound(missileData().mImpactSoundEffect); - } + void Missile::playImpactSound() + { + if (!missileData().mImpactSoundEffect.empty()) + Engine::ThreadManager::get()->playSound(missileData().mImpactSoundEffect); + } - void Missile::update() + void Missile::update() + { + for (auto& graphic : mGraphics) { - for (auto& graphic : mGraphics) - { - if (graphic->isComplete()) - continue; + if (graphic->isComplete()) + continue; - graphic->update(); + graphic->update(); - mAttr.mMovement(*this, *graphic); + mAttr.mMovement(*this, *graphic); - auto curPoint = graphic->mCurPos.current(); + auto curPoint = graphic->mCurPos.current(); - // Check if actor is hit. - auto actor = mLevel->getActorAt(curPoint); - if (actor) - mAttr.mActorEngagement(*this, *graphic, *actor); + // Check if actor is hit. + auto actor = mLevel->getActorAt(curPoint); + if (actor) + mAttr.mActorEngagement(*this, *graphic, *actor); - // Stop when walls are hit. - if (!actor && !mLevel->isPassable(curPoint, mCreator)) - { - playImpactSound(); - graphic->stop(); - } + // Stop when walls are hit. + if (!actor && !mLevel->isPassable(curPoint, mCreator)) + { + playImpactSound(); + graphic->stop(); + } - // Stop after max range is exceeded. - auto distance = (Vec2Fix(curPoint.x, curPoint.y) - Vec2Fix(mSrcPoint.x, mSrcPoint.y)).magnitude(); - if (distance > mAttr.mMaxRange) - graphic->stop(); + // Stop after max range is exceeded. + auto distance = (Vec2Fix(curPoint.x, curPoint.y) - Vec2Fix(mSrcPoint.x, mSrcPoint.y)).magnitude(); + if (distance > mAttr.mMaxRange) + graphic->stop(); - // Stop after "time to live" has expired. - if (graphic->getTicksSinceStarted() > mAttr.mTimeToLive) - graphic->stop(); - } - // Set complete flag when all graphics are finished. - mComplete = std::all_of(mGraphics.begin(), mGraphics.end(), [](const std::unique_ptr& graphic) { return graphic->isComplete(); }); + // Stop after "time to live" has expired. + if (graphic->getTicksSinceStarted() > mAttr.mTimeToLive) + graphic->stop(); } + // Set complete flag when all graphics are finished. + mComplete = std::all_of(mGraphics.begin(), mGraphics.end(), [](const std::unique_ptr& graphic) { return graphic->isComplete(); }); } } diff --git a/apps/freeablo/faworld/missile/missile.h b/apps/freeablo/faworld/missile/missile.h index 6fe0c6241..41a7fa679 100644 --- a/apps/freeablo/faworld/missile/missile.h +++ b/apps/freeablo/faworld/missile/missile.h @@ -13,86 +13,86 @@ namespace FASaveGame namespace FAWorld { class Actor; +} - namespace Missile +namespace FAWorld::Missile +{ + class Missile { - class Missile - { - public: - virtual ~Missile() = default; + public: + virtual ~Missile() = default; - Missile(MissileId missileId, Actor& creator, Misc::Point dest); - Missile(FASaveGame::GameLoader& loader); + Missile(MissileId missileId, Actor& creator, Misc::Point dest); + Missile(FASaveGame::GameLoader& loader); - virtual void save(FASaveGame::GameSaver& saver); - virtual void update(); - virtual bool isComplete() const { return mComplete; } - MissileId getMissileId() const { return mMissileId; } - const GameLevel* getLevel() const { return mLevel; } - const std::vector>& getGraphics() const { return mGraphics; } + virtual void save(FASaveGame::GameSaver& saver); + virtual void update(); + virtual bool isComplete() const { return mComplete; } + MissileId getMissileId() const { return mMissileId; } + const GameLevel* getLevel() const { return mLevel; } + const std::vector>& getGraphics() const { return mGraphics; } - protected: - // Static inner classes for missile attribute composition. - class Creation - { - public: - Creation() = delete; - typedef void (*Method)(Missile& missile, Misc::Point dest); + protected: + // Static inner classes for missile attribute composition. + class Creation + { + public: + Creation() = delete; + typedef void (*Method)(Missile& missile, Misc::Point dest); - static void singleFrame16Direction(Missile& missile, Misc::Point dest); - static void animated16Direction(Missile& missile, Misc::Point dest); - static void firewall(Missile& missile, Misc::Point dest); - static void basicAnimated(Missile& missile, Misc::Point dest); - }; + static void singleFrame16Direction(Missile& missile, Misc::Point dest); + static void animated16Direction(Missile& missile, Misc::Point dest); + static void firewall(Missile& missile, Misc::Point dest); + static void basicAnimated(Missile& missile, Misc::Point dest); + }; - class Movement - { - public: - Movement() = delete; - typedef void (*Method)(Missile& missile, MissileGraphic& graphic); + class Movement + { + public: + Movement() = delete; + typedef void (*Method)(Missile& missile, MissileGraphic& graphic); - static void stationary(Missile& missile, MissileGraphic& graphic); - static void linear(Missile& missile, MissileGraphic& graphic); - static void hoverOverCreator(Missile& missile, MissileGraphic& graphic); - }; + static void stationary(Missile& missile, MissileGraphic& graphic); + static void linear(Missile& missile, MissileGraphic& graphic); + static void hoverOverCreator(Missile& missile, MissileGraphic& graphic); + }; - class ActorEngagement - { - public: - ActorEngagement() = delete; - typedef void (*Method)(Missile& missile, MissileGraphic& graphic, Actor& actor); + class ActorEngagement + { + public: + ActorEngagement() = delete; + typedef void (*Method)(Missile& missile, MissileGraphic& graphic, Actor& actor); - static void none(Missile& missile, MissileGraphic& graphic, Actor& actor); - static void damageEnemy(Missile& missile, MissileGraphic& graphic, Actor& actor); - static void damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor); - }; + static void none(Missile& missile, MissileGraphic& graphic, Actor& actor); + static void damageEnemy(Missile& missile, MissileGraphic& graphic, Actor& actor); + static void damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor); + }; - // Inner class that holds reference to all missile attributes. - class Attributes - { - public: - Attributes(Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, FixedPoint maxRange, Tick timeToLive); - static Attributes fromId(MissileId missileId); + // Inner class that holds reference to all missile attributes. + class Attributes + { + public: + Attributes(Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, FixedPoint maxRange, Tick timeToLive); + static Attributes fromId(MissileId missileId); - const Missile::Creation::Method mCreation; - const Missile::Movement::Method mMovement; - const Missile::ActorEngagement::Method mActorEngagement; - const FixedPoint mMaxRange; - const Tick mTimeToLive; - }; + const Missile::Creation::Method mCreation; + const Missile::Movement::Method mMovement; + const Missile::ActorEngagement::Method mActorEngagement; + const FixedPoint mMaxRange; + const Tick mTimeToLive; + }; - const DiabloExe::MissileData& missileData() const; - const DiabloExe::MissileGraphics& missileGraphics() const; - std::string getGraphicsPath(int32_t i) const; - void playImpactSound(); + const DiabloExe::MissileData& missileData() const; + const DiabloExe::MissileGraphics& missileGraphics() const; + std::string getGraphicsPath(int32_t i) const; + void playImpactSound(); - Actor* mCreator; - MissileId mMissileId; - GameLevel* mLevel; - Misc::Point mSrcPoint; - Missile::Attributes mAttr; - std::vector> mGraphics; - bool mComplete = false; - }; - } + Actor* mCreator; + MissileId mMissileId; + GameLevel* mLevel; + Misc::Point mSrcPoint; + Missile::Attributes mAttr; + std::vector> mGraphics; + bool mComplete = false; + }; } diff --git a/apps/freeablo/faworld/missile/missileactorengagement.cpp b/apps/freeablo/faworld/missile/missileactorengagement.cpp index 6e339bf3b..f6835432c 100644 --- a/apps/freeablo/faworld/missile/missileactorengagement.cpp +++ b/apps/freeablo/faworld/missile/missileactorengagement.cpp @@ -1,28 +1,25 @@ #include "faworld/actor.h" #include "missile.h" -namespace FAWorld +namespace FAWorld::Missile { - namespace Missile - { - void Missile::ActorEngagement::none(Missile&, MissileGraphic&, Actor&) {} + void Missile::ActorEngagement::none(Missile&, MissileGraphic&, Actor&) {} - void Missile::ActorEngagement::damageEnemy(Missile& missile, MissileGraphic&, Actor& actor) + void Missile::ActorEngagement::damageEnemy(Missile& missile, MissileGraphic&, Actor& actor) + { + const uint32_t damage = 10; // placeholder + if (missile.mCreator->canIAttack(&actor)) { - const uint32_t damage = 10; // placeholder - if (missile.mCreator->canIAttack(&actor)) - { - missile.mCreator->dealDamageToEnemy(&actor, damage, DamageType::Bow); - missile.playImpactSound(); - } + missile.mCreator->dealDamageToEnemy(&actor, damage, DamageType::Bow); + missile.playImpactSound(); } + } - void Missile::ActorEngagement::damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor) - { - damageEnemy(missile, graphic, actor); - // Stop on friendlies too. - if (&actor != missile.mCreator) - graphic.stop(); - } + void Missile::ActorEngagement::damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor) + { + damageEnemy(missile, graphic, actor); + // Stop on friendlies too. + if (&actor != missile.mCreator) + graphic.stop(); } } diff --git a/apps/freeablo/faworld/missile/missileattributes.cpp b/apps/freeablo/faworld/missile/missileattributes.cpp index 110fa7c45..5ca09c252 100644 --- a/apps/freeablo/faworld/missile/missileattributes.cpp +++ b/apps/freeablo/faworld/missile/missileattributes.cpp @@ -1,38 +1,35 @@ #include "missile.h" -namespace FAWorld +namespace FAWorld::Missile { - namespace Missile + Missile::Attributes::Attributes( + Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, FixedPoint maxRange, Tick timeToLive) + : mCreation(creation), mMovement(movement), mActorEngagement(actorEngagement), mMaxRange(maxRange), mTimeToLive(timeToLive) { - Missile::Attributes::Attributes( - Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, FixedPoint maxRange, Tick timeToLive) - : mCreation(creation), mMovement(movement), mActorEngagement(actorEngagement), mMaxRange(maxRange), mTimeToLive(timeToLive) - { - } + } - Missile::Attributes Missile::Attributes::fromId(MissileId missileId) - { - static FixedPoint maxRangeIgnore = FixedPoint::fromRawValue(INT64_MAX); - static Tick ttlIgnore = std::numeric_limits::max(); + Missile::Attributes Missile::Attributes::fromId(MissileId missileId) + { + static FixedPoint maxRangeIgnore = FixedPoint::fromRawValue(INT64_MAX); + static Tick ttlIgnore = std::numeric_limits::max(); - switch (missileId) - { - case MissileId::arrow: - return Attributes(Creation::singleFrame16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); - case MissileId::firebolt: - return Attributes(Creation::animated16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); - case MissileId::farrow: - case MissileId::larrow: - return Attributes(Creation::animated16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); - case MissileId::firewall: - case MissileId::firewalla: - case MissileId::firewallc: - return Attributes(Creation::firewall, Movement::stationary, ActorEngagement::damageEnemy, maxRangeIgnore, World::getTicksInPeriod(8)); - case MissileId::manashield: - return Attributes(Creation::basicAnimated, Movement::hoverOverCreator, ActorEngagement::none, maxRangeIgnore, World::getTicksInPeriod(8)); - default: - invalid_enum(MissileId, missileId); - } + switch (missileId) + { + case MissileId::arrow: + return Attributes(Creation::singleFrame16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + case MissileId::firebolt: + return Attributes(Creation::animated16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + case MissileId::farrow: + case MissileId::larrow: + return Attributes(Creation::animated16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + case MissileId::firewall: + case MissileId::firewalla: + case MissileId::firewallc: + return Attributes(Creation::firewall, Movement::stationary, ActorEngagement::damageEnemy, maxRangeIgnore, World::getTicksInPeriod(8)); + case MissileId::manashield: + return Attributes(Creation::basicAnimated, Movement::hoverOverCreator, ActorEngagement::none, maxRangeIgnore, World::getTicksInPeriod(8)); + default: + invalid_enum(MissileId, missileId); } } } diff --git a/apps/freeablo/faworld/missile/missilecreation.cpp b/apps/freeablo/faworld/missile/missilecreation.cpp index 9b38b2dc1..c3fc58da3 100644 --- a/apps/freeablo/faworld/missile/missilecreation.cpp +++ b/apps/freeablo/faworld/missile/missilecreation.cpp @@ -1,48 +1,45 @@ #include "missile.h" #include -namespace FAWorld +namespace FAWorld::Missile { - namespace Missile + void Missile::Creation::singleFrame16Direction(Missile& missile, Misc::Point dest) { - void Missile::Creation::singleFrame16Direction(Missile& missile, Misc::Point dest) - { - auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); - auto srcPos = Position(missile.mSrcPoint, direction); - int32_t direction16 = static_cast(direction.getDirection16()); - missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), direction16, srcPos)); - } + auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); + auto srcPos = Position(missile.mSrcPoint, direction); + int32_t direction16 = static_cast(direction.getDirection16()); + missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), direction16, srcPos)); + } - void Missile::Creation::animated16Direction(Missile& missile, Misc::Point dest) - { - auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); - auto srcPos = Position(missile.mSrcPoint, direction); - int32_t direction16 = static_cast(direction.getDirection16()); - missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(direction16), std::nullopt, srcPos)); - } + void Missile::Creation::animated16Direction(Missile& missile, Misc::Point dest) + { + auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); + auto srcPos = Position(missile.mSrcPoint, direction); + int32_t direction16 = static_cast(direction.getDirection16()); + missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(direction16), std::nullopt, srcPos)); + } - void Missile::Creation::firewall(Missile& missile, Misc::Point dest) + void Missile::Creation::firewall(Missile& missile, Misc::Point dest) + { + // Flames are placed at -5 -> +5 perpendicular to the clicked point, and + // two flames are placed at the clicked point (for double damage). + auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); + for (auto angleOffset : {-90, 90}) { - // Flames are placed at -5 -> +5 perpendicular to the clicked point, and - // two flames are placed at the clicked point (for double damage). - auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); - for (auto angleOffset : {-90, 90}) + Misc::Direction dir = direction; + dir.adjust(angleOffset); + auto point = dest; + for (int32_t i = 0; i < 6; i++) { - Misc::Direction dir = direction; - dir.adjust(angleOffset); - auto point = dest; - for (int32_t i = 0; i < 6; i++) - { - missile.mGraphics.push_back( - std::make_unique(missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point))); - point = Misc::getNextPosByDir(point, dir); - } + missile.mGraphics.push_back( + std::make_unique(missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point))); + point = Misc::getNextPosByDir(point, dir); } } + } - void Missile::Creation::basicAnimated(Missile& missile, Misc::Point) - { - missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), std::nullopt, Position(missile.mSrcPoint))); - } + void Missile::Creation::basicAnimated(Missile& missile, Misc::Point) + { + missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), std::nullopt, Position(missile.mSrcPoint))); } } diff --git a/apps/freeablo/faworld/missile/missilegraphic.cpp b/apps/freeablo/faworld/missile/missilegraphic.cpp index b51226a41..8c8626c5f 100644 --- a/apps/freeablo/faworld/missile/missilegraphic.cpp +++ b/apps/freeablo/faworld/missile/missilegraphic.cpp @@ -5,72 +5,69 @@ #include "fasavegame/gameloader.h" #include "faworld/actor.h" -namespace FAWorld +namespace FAWorld::Missile { - namespace Missile + MissileGraphic::MissileGraphic(std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position) + : mCurPos(position), mMainGraphicPath(mainGraphicPath), mSingleFrame(singleFrame) { - MissileGraphic::MissileGraphic(std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position) - : mCurPos(position), mMainGraphicPath(mainGraphicPath), mSingleFrame(singleFrame) - { - playAnimation(initialGraphicPath, FARender::AnimationPlayer::AnimationType::Once); - } + playAnimation(initialGraphicPath, FARender::AnimationPlayer::AnimationType::Once); + } - MissileGraphic::MissileGraphic(FASaveGame::GameLoader& loader) - { - mCurPos = Position(loader); - mMainGraphicPath = loader.load(); - mSingleFrame = (mSingleFrame = loader.load()) == -1 ? std::nullopt : mSingleFrame; - mAnimationPlayer = FARender::AnimationPlayer(loader); - mTicksSinceStarted = loader.load(); - mComplete = loader.load(); - } + MissileGraphic::MissileGraphic(FASaveGame::GameLoader& loader) + { + mCurPos = Position(loader); + mMainGraphicPath = loader.load(); + mSingleFrame = (mSingleFrame = loader.load()) == -1 ? std::nullopt : mSingleFrame; + mAnimationPlayer = FARender::AnimationPlayer(loader); + mTicksSinceStarted = loader.load(); + mComplete = loader.load(); + } - void MissileGraphic::save(FASaveGame::GameSaver& saver) - { - mCurPos.save(saver); - saver.save(mMainGraphicPath); - saver.save(static_cast(mSingleFrame == std::nullopt ? -1 : *mSingleFrame)); - mAnimationPlayer.save(saver); - saver.save(mTicksSinceStarted); - saver.save(mComplete); - } + void MissileGraphic::save(FASaveGame::GameSaver& saver) + { + mCurPos.save(saver); + saver.save(mMainGraphicPath); + saver.save(static_cast(mSingleFrame == std::nullopt ? -1 : *mSingleFrame)); + mAnimationPlayer.save(saver); + saver.save(mTicksSinceStarted); + saver.save(mComplete); + } - void MissileGraphic::update() - { - mTicksSinceStarted++; + void MissileGraphic::update() + { + mTicksSinceStarted++; - if (mComplete) - return; + if (mComplete) + return; - if (!mAnimationPlayer.isPlaying()) - playAnimation(mMainGraphicPath, FARender::AnimationPlayer::AnimationType::Looped); + if (!mAnimationPlayer.isPlaying()) + playAnimation(mMainGraphicPath, FARender::AnimationPlayer::AnimationType::Looped); - mAnimationPlayer.update(); - } + mAnimationPlayer.update(); + } - std::pair MissileGraphic::getCurrentFrame() - { - auto frame = mAnimationPlayer.getCurrentFrame(); - // Some animations just use a single offset frame. - // e.g. arrows have a single frame for each of the 16 directions. - if (mSingleFrame) - frame.second = *mSingleFrame; - return frame; - } + std::pair MissileGraphic::getCurrentFrame() + { + auto frame = mAnimationPlayer.getCurrentFrame(); + // Some animations just use a single offset frame. + // e.g. arrows have a single frame for each of the 16 directions. + if (mSingleFrame) + frame.second = *mSingleFrame; + return frame; + } - void MissileGraphic::stop() - { - mAnimationPlayer.stopAnimation(); - mComplete = true; - } + void MissileGraphic::stop() + { + mAnimationPlayer.stopAnimation(); + mComplete = true; + } - void MissileGraphic::playAnimation(std::string path, FARender::AnimationPlayer::AnimationType animationType) + void MissileGraphic::playAnimation(std::string path, FARender::AnimationPlayer::AnimationType animationType) + { + if (!path.empty()) { - if (!path.empty()) - { - auto spriteGroup = FARender::Renderer::get()->loadImage(path); - mAnimationPlayer.playAnimation(spriteGroup, World::getTicksInPeriod("0.06"), animationType); - } + auto spriteGroup = FARender::Renderer::get()->loadImage(path); + mAnimationPlayer.playAnimation(spriteGroup, World::getTicksInPeriod("0.06"), animationType); } } } diff --git a/apps/freeablo/faworld/missile/missilegraphic.h b/apps/freeablo/faworld/missile/missilegraphic.h index 18469a0ca..449cbf5ce 100644 --- a/apps/freeablo/faworld/missile/missilegraphic.h +++ b/apps/freeablo/faworld/missile/missilegraphic.h @@ -10,36 +10,33 @@ namespace FASaveGame class GameSaver; } -namespace FAWorld +namespace FAWorld::Missile { - namespace Missile + class MissileGraphic { - class MissileGraphic - { - public: - virtual ~MissileGraphic() = default; - - MissileGraphic(std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position); - MissileGraphic(FASaveGame::GameLoader& loader); - - virtual void save(FASaveGame::GameSaver& saver); - virtual void update(); - - std::pair getCurrentFrame(); - void stop(); - bool isComplete() const { return mComplete; } - Tick getTicksSinceStarted() const { return mTicksSinceStarted; } - - Position mCurPos; - - protected: - void playAnimation(std::string path, FARender::AnimationPlayer::AnimationType animationType); - - std::string mMainGraphicPath; - std::optional mSingleFrame; - FARender::AnimationPlayer mAnimationPlayer; - Tick mTicksSinceStarted = 0; - bool mComplete = false; - }; - } + public: + virtual ~MissileGraphic() = default; + + MissileGraphic(std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position); + MissileGraphic(FASaveGame::GameLoader& loader); + + virtual void save(FASaveGame::GameSaver& saver); + virtual void update(); + + std::pair getCurrentFrame(); + void stop(); + bool isComplete() const { return mComplete; } + Tick getTicksSinceStarted() const { return mTicksSinceStarted; } + + Position mCurPos; + + protected: + void playAnimation(std::string path, FARender::AnimationPlayer::AnimationType animationType); + + std::string mMainGraphicPath; + std::optional mSingleFrame; + FARender::AnimationPlayer mAnimationPlayer; + Tick mTicksSinceStarted = 0; + bool mComplete = false; + }; } diff --git a/apps/freeablo/faworld/missile/missilemovement.cpp b/apps/freeablo/faworld/missile/missilemovement.cpp index 287949d55..2aeb9fe92 100644 --- a/apps/freeablo/faworld/missile/missilemovement.cpp +++ b/apps/freeablo/faworld/missile/missilemovement.cpp @@ -1,22 +1,19 @@ #include "faworld/actor.h" #include "missile.h" -namespace FAWorld +namespace FAWorld::Missile { - namespace Missile - { - void Missile::Movement::stationary(Missile&, MissileGraphic&) {} + void Missile::Movement::stationary(Missile&, MissileGraphic&) {} - void Missile::Movement::linear(Missile&, MissileGraphic& graphic) - { - graphic.mCurPos.setFreeMovement(); - graphic.mCurPos.update(FixedPoint(7) / FixedPoint(World::ticksPerSecond)); - } + void Missile::Movement::linear(Missile&, MissileGraphic& graphic) + { + graphic.mCurPos.setFreeMovement(); + graphic.mCurPos.update(FixedPoint(7) / FixedPoint(World::ticksPerSecond)); + } - void Missile::Movement::hoverOverCreator(Missile& missile, MissileGraphic& graphic) - { - // TODO: change level with actor. - graphic.mCurPos = missile.mCreator->getPos(); - } + void Missile::Movement::hoverOverCreator(Missile& missile, MissileGraphic& graphic) + { + // TODO: change level with actor. + graphic.mCurPos = missile.mCreator->getPos(); } } From 909b60b0a02e2cf027b234f01d253f9619626f63 Mon Sep 17 00:00:00 2001 From: grantramsay Date: Sun, 1 Mar 2020 15:48:22 +1300 Subject: [PATCH 3/9] Allow setting different speeds for missiles --- apps/freeablo/faworld/missile/missile.h | 11 +++++++---- apps/freeablo/faworld/missile/missileattributes.cpp | 6 +++--- apps/freeablo/faworld/missile/missilemovement.cpp | 9 +++++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/freeablo/faworld/missile/missile.h b/apps/freeablo/faworld/missile/missile.h index 41a7fa679..fb0b25217 100644 --- a/apps/freeablo/faworld/missile/missile.h +++ b/apps/freeablo/faworld/missile/missile.h @@ -38,7 +38,7 @@ namespace FAWorld::Missile { public: Creation() = delete; - typedef void (*Method)(Missile& missile, Misc::Point dest); + typedef std::function Method; static void singleFrame16Direction(Missile& missile, Misc::Point dest); static void animated16Direction(Missile& missile, Misc::Point dest); @@ -50,18 +50,21 @@ namespace FAWorld::Missile { public: Movement() = delete; - typedef void (*Method)(Missile& missile, MissileGraphic& graphic); + typedef std::function Method; static void stationary(Missile& missile, MissileGraphic& graphic); - static void linear(Missile& missile, MissileGraphic& graphic); + static Method linear(FixedPoint speed); static void hoverOverCreator(Missile& missile, MissileGraphic& graphic); + + private: + static void linear(MissileGraphic& graphic, FixedPoint speed); }; class ActorEngagement { public: ActorEngagement() = delete; - typedef void (*Method)(Missile& missile, MissileGraphic& graphic, Actor& actor); + typedef std::function Method; static void none(Missile& missile, MissileGraphic& graphic, Actor& actor); static void damageEnemy(Missile& missile, MissileGraphic& graphic, Actor& actor); diff --git a/apps/freeablo/faworld/missile/missileattributes.cpp b/apps/freeablo/faworld/missile/missileattributes.cpp index 5ca09c252..6faa93fe7 100644 --- a/apps/freeablo/faworld/missile/missileattributes.cpp +++ b/apps/freeablo/faworld/missile/missileattributes.cpp @@ -16,12 +16,12 @@ namespace FAWorld::Missile switch (missileId) { case MissileId::arrow: - return Attributes(Creation::singleFrame16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + return Attributes(Creation::singleFrame16Direction, Movement::linear(30), ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); case MissileId::firebolt: - return Attributes(Creation::animated16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + return Attributes(Creation::animated16Direction, Movement::linear(15), ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); case MissileId::farrow: case MissileId::larrow: - return Attributes(Creation::animated16Direction, Movement::linear, ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + return Attributes(Creation::animated16Direction, Movement::linear(30), ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); case MissileId::firewall: case MissileId::firewalla: case MissileId::firewallc: diff --git a/apps/freeablo/faworld/missile/missilemovement.cpp b/apps/freeablo/faworld/missile/missilemovement.cpp index 2aeb9fe92..3dcd2716b 100644 --- a/apps/freeablo/faworld/missile/missilemovement.cpp +++ b/apps/freeablo/faworld/missile/missilemovement.cpp @@ -5,10 +5,15 @@ namespace FAWorld::Missile { void Missile::Movement::stationary(Missile&, MissileGraphic&) {} - void Missile::Movement::linear(Missile&, MissileGraphic& graphic) + Missile::Movement::Method Missile::Movement::linear(FixedPoint speed) + { + return [=](Missile&, MissileGraphic& graphic) { linear(graphic, speed); }; + } + + void Missile::Movement::linear(MissileGraphic& graphic, FixedPoint speed) { graphic.mCurPos.setFreeMovement(); - graphic.mCurPos.update(FixedPoint(7) / FixedPoint(World::ticksPerSecond)); + graphic.mCurPos.update(speed / FixedPoint(World::ticksPerSecond)); } void Missile::Movement::hoverOverCreator(Missile& missile, MissileGraphic& graphic) From fa63c7a74fb5255c2393f29e1ce44a12501d1663 Mon Sep 17 00:00:00 2001 From: grantramsay Date: Sun, 1 Mar 2020 15:58:51 +1300 Subject: [PATCH 4/9] Format --- apps/freeablo/faworld/missile/missile.cpp | 7 ++----- apps/freeablo/faworld/missile/missile.h | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/freeablo/faworld/missile/missile.cpp b/apps/freeablo/faworld/missile/missile.cpp index 7b08eb296..4d4857f2c 100644 --- a/apps/freeablo/faworld/missile/missile.cpp +++ b/apps/freeablo/faworld/missile/missile.cpp @@ -8,8 +8,7 @@ namespace FAWorld::Missile { Missile::Missile(MissileId missileId, Actor& creator, Misc::Point dest) - : mCreator(&creator), mMissileId(missileId), mLevel(creator.getLevel()), - mSrcPoint(creator.getPos().current()), mAttr(Attributes::fromId(missileId)) + : mCreator(&creator), mMissileId(missileId), mLevel(creator.getLevel()), mSrcPoint(creator.getPos().current()), mAttr(Attributes::fromId(missileId)) { mAttr.mCreation(*this, dest); @@ -17,9 +16,7 @@ namespace FAWorld::Missile Engine::ThreadManager::get()->playSound(missileData().mSoundEffect); } - Missile::Missile(FASaveGame::GameLoader& loader) - : mMissileId(static_cast(loader.load())), - mAttr(Attributes::fromId(mMissileId)) + Missile::Missile(FASaveGame::GameLoader& loader) : mMissileId(static_cast(loader.load())), mAttr(Attributes::fromId(mMissileId)) { auto creatorId = loader.load(); auto levelIndex = loader.load(); diff --git a/apps/freeablo/faworld/missile/missile.h b/apps/freeablo/faworld/missile/missile.h index fb0b25217..361d2d274 100644 --- a/apps/freeablo/faworld/missile/missile.h +++ b/apps/freeablo/faworld/missile/missile.h @@ -38,7 +38,7 @@ namespace FAWorld::Missile { public: Creation() = delete; - typedef std::function Method; + typedef std::function Method; static void singleFrame16Direction(Missile& missile, Misc::Point dest); static void animated16Direction(Missile& missile, Misc::Point dest); @@ -50,7 +50,7 @@ namespace FAWorld::Missile { public: Movement() = delete; - typedef std::function Method; + typedef std::function Method; static void stationary(Missile& missile, MissileGraphic& graphic); static Method linear(FixedPoint speed); @@ -64,7 +64,7 @@ namespace FAWorld::Missile { public: ActorEngagement() = delete; - typedef std::function Method; + typedef std::function Method; static void none(Missile& missile, MissileGraphic& graphic, Actor& actor); static void damageEnemy(Missile& missile, MissileGraphic& graphic, Actor& actor); From 15a2a35380c10f6e264e2b6f4594221e4f6238fd Mon Sep 17 00:00:00 2001 From: grantramsay Date: Sun, 1 Mar 2020 18:06:40 +1300 Subject: [PATCH 5/9] Allow missile graphics to be on different levels Add a reference to missiles graphics on each level to avoid having to check every actor in world to find missile graphics on a level --- apps/freeablo/faworld/gamelevel.cpp | 21 ++++---------- apps/freeablo/faworld/gamelevel.h | 5 ++++ apps/freeablo/faworld/missile/missile.cpp | 15 ++++------ apps/freeablo/faworld/missile/missile.h | 13 ++++----- .../faworld/missile/missilecreation.cpp | 16 +++++------ .../faworld/missile/missilegraphic.cpp | 28 +++++++++++++++++-- .../freeablo/faworld/missile/missilegraphic.h | 11 ++++++-- .../faworld/missile/missilemovement.cpp | 3 +- 8 files changed, 67 insertions(+), 45 deletions(-) diff --git a/apps/freeablo/faworld/gamelevel.cpp b/apps/freeablo/faworld/gamelevel.cpp index ea8546b8a..4ab88b455 100644 --- a/apps/freeablo/faworld/gamelevel.cpp +++ b/apps/freeablo/faworld/gamelevel.cpp @@ -260,22 +260,13 @@ namespace FAWorld } } - for (const auto& actor : mActors) + for (const auto& graphic : mMissileGraphics) { - for (const auto& missile : actor->getMissiles()) - { - // Only display missiles for this (the currently displayed) level. - if (missile->getLevel() != this) - continue; - for (const auto& graphic : missile->getGraphics()) - { - auto tmp = graphic->getCurrentFrame(); - auto spriteGroup = tmp.first; - auto frame = tmp.second; - if (spriteGroup) - state->mObjects.push_back({spriteGroup, static_cast(frame), graphic->mCurPos, std::nullopt}); - } - } + auto tmp = graphic->getCurrentFrame(); + auto spriteGroup = tmp.first; + auto frame = tmp.second; + if (spriteGroup) + state->mObjects.push_back({spriteGroup, static_cast(frame), graphic->mCurPos, std::nullopt}); } for (auto& p : mItemMap->mItems) diff --git a/apps/freeablo/faworld/gamelevel.h b/apps/freeablo/faworld/gamelevel.h index 25fae3ee7..e209d8d52 100644 --- a/apps/freeablo/faworld/gamelevel.h +++ b/apps/freeablo/faworld/gamelevel.h @@ -30,6 +30,7 @@ namespace FAWorld namespace Missile { class Missile; + class MissileGraphic; } class GameLevelImpl @@ -108,6 +109,10 @@ namespace FAWorld World* getWorld() { return &mWorld; } + // These are not saved, they are added/remove in MissileGraphic constructor/destructor + // This avoids having to check every actor in world to find missile graphics on a level + std::vector mMissileGraphics; + private: GameLevel(World& world); diff --git a/apps/freeablo/faworld/missile/missile.cpp b/apps/freeablo/faworld/missile/missile.cpp index 4d4857f2c..6901abe63 100644 --- a/apps/freeablo/faworld/missile/missile.cpp +++ b/apps/freeablo/faworld/missile/missile.cpp @@ -8,9 +8,9 @@ namespace FAWorld::Missile { Missile::Missile(MissileId missileId, Actor& creator, Misc::Point dest) - : mCreator(&creator), mMissileId(missileId), mLevel(creator.getLevel()), mSrcPoint(creator.getPos().current()), mAttr(Attributes::fromId(missileId)) + : mCreator(&creator), mMissileId(missileId), mSrcPoint(creator.getPos().current()), mAttr(Attributes::fromId(missileId)) { - mAttr.mCreation(*this, dest); + mAttr.mCreation(*this, dest, creator.getLevel()); if (!missileData().mSoundEffect.empty()) Engine::ThreadManager::get()->playSound(missileData().mSoundEffect); @@ -19,12 +19,8 @@ namespace FAWorld::Missile Missile::Missile(FASaveGame::GameLoader& loader) : mMissileId(static_cast(loader.load())), mAttr(Attributes::fromId(mMissileId)) { auto creatorId = loader.load(); - auto levelIndex = loader.load(); auto world = loader.currentlyLoadingWorld; - loader.addFunctionToRunAtEnd([this, world, creatorId, levelIndex]() { - mCreator = world->getActorById(creatorId); - mLevel = world->getLevel(levelIndex); - }); + loader.addFunctionToRunAtEnd([this, world, creatorId]() { mCreator = world->getActorById(creatorId); }); mSrcPoint = Misc::Point(loader); mComplete = loader.load(); @@ -41,7 +37,6 @@ namespace FAWorld::Missile saver.save(static_cast(mMissileId)); saver.save(mCreator->getId()); - saver.save(mLevel->getLevelIndex()); mSrcPoint.save(saver); saver.save(mComplete); @@ -94,12 +89,12 @@ namespace FAWorld::Missile auto curPoint = graphic->mCurPos.current(); // Check if actor is hit. - auto actor = mLevel->getActorAt(curPoint); + auto actor = graphic->getLevel()->getActorAt(curPoint); if (actor) mAttr.mActorEngagement(*this, *graphic, *actor); // Stop when walls are hit. - if (!actor && !mLevel->isPassable(curPoint, mCreator)) + if (!actor && !graphic->getLevel()->isPassable(curPoint, mCreator)) { playImpactSound(); graphic->stop(); diff --git a/apps/freeablo/faworld/missile/missile.h b/apps/freeablo/faworld/missile/missile.h index 361d2d274..f00f2d0a1 100644 --- a/apps/freeablo/faworld/missile/missile.h +++ b/apps/freeablo/faworld/missile/missile.h @@ -1,6 +1,7 @@ #pragma once #include "missileenums.h" #include "missilegraphic.h" +#include #include #include @@ -29,7 +30,6 @@ namespace FAWorld::Missile virtual void update(); virtual bool isComplete() const { return mComplete; } MissileId getMissileId() const { return mMissileId; } - const GameLevel* getLevel() const { return mLevel; } const std::vector>& getGraphics() const { return mGraphics; } protected: @@ -38,12 +38,12 @@ namespace FAWorld::Missile { public: Creation() = delete; - typedef std::function Method; + typedef std::function Method; - static void singleFrame16Direction(Missile& missile, Misc::Point dest); - static void animated16Direction(Missile& missile, Misc::Point dest); - static void firewall(Missile& missile, Misc::Point dest); - static void basicAnimated(Missile& missile, Misc::Point dest); + static void singleFrame16Direction(Missile& missile, Misc::Point dest, GameLevel* level); + static void animated16Direction(Missile& missile, Misc::Point dest, GameLevel* level); + static void firewall(Missile& missile, Misc::Point dest, GameLevel* level); + static void basicAnimated(Missile& missile, Misc::Point dest, GameLevel* level); }; class Movement @@ -92,7 +92,6 @@ namespace FAWorld::Missile Actor* mCreator; MissileId mMissileId; - GameLevel* mLevel; Misc::Point mSrcPoint; Missile::Attributes mAttr; std::vector> mGraphics; diff --git a/apps/freeablo/faworld/missile/missilecreation.cpp b/apps/freeablo/faworld/missile/missilecreation.cpp index c3fc58da3..687e835cc 100644 --- a/apps/freeablo/faworld/missile/missilecreation.cpp +++ b/apps/freeablo/faworld/missile/missilecreation.cpp @@ -3,23 +3,23 @@ namespace FAWorld::Missile { - void Missile::Creation::singleFrame16Direction(Missile& missile, Misc::Point dest) + void Missile::Creation::singleFrame16Direction(Missile& missile, Misc::Point dest, GameLevel* level) { auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); auto srcPos = Position(missile.mSrcPoint, direction); int32_t direction16 = static_cast(direction.getDirection16()); - missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), direction16, srcPos)); + missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), direction16, srcPos, level)); } - void Missile::Creation::animated16Direction(Missile& missile, Misc::Point dest) + void Missile::Creation::animated16Direction(Missile& missile, Misc::Point dest, GameLevel* level) { auto direction = (Vec2Fix(dest.x, dest.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).getDirection(); auto srcPos = Position(missile.mSrcPoint, direction); int32_t direction16 = static_cast(direction.getDirection16()); - missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(direction16), std::nullopt, srcPos)); + missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(direction16), std::nullopt, srcPos, level)); } - void Missile::Creation::firewall(Missile& missile, Misc::Point dest) + void Missile::Creation::firewall(Missile& missile, Misc::Point dest, GameLevel* level) { // Flames are placed at -5 -> +5 perpendicular to the clicked point, and // two flames are placed at the clicked point (for double damage). @@ -32,14 +32,14 @@ namespace FAWorld::Missile for (int32_t i = 0; i < 6; i++) { missile.mGraphics.push_back( - std::make_unique(missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point))); + std::make_unique(missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), level)); point = Misc::getNextPosByDir(point, dir); } } } - void Missile::Creation::basicAnimated(Missile& missile, Misc::Point) + void Missile::Creation::basicAnimated(Missile& missile, Misc::Point, GameLevel* level) { - missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), std::nullopt, Position(missile.mSrcPoint))); + missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), std::nullopt, Position(missile.mSrcPoint), level)); } } diff --git a/apps/freeablo/faworld/missile/missilegraphic.cpp b/apps/freeablo/faworld/missile/missilegraphic.cpp index 8c8626c5f..03b6c3509 100644 --- a/apps/freeablo/faworld/missile/missilegraphic.cpp +++ b/apps/freeablo/faworld/missile/missilegraphic.cpp @@ -7,9 +7,14 @@ namespace FAWorld::Missile { - MissileGraphic::MissileGraphic(std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position) - : mCurPos(position), mMainGraphicPath(mainGraphicPath), mSingleFrame(singleFrame) + MissileGraphic::MissileGraphic(std::string initialGraphicPath, + std::string mainGraphicPath, + std::optional singleFrame, + Position position, + GameLevel* level) + : mCurPos(position), mMainGraphicPath(mainGraphicPath), mSingleFrame(singleFrame), mLevel(level) { + level->mMissileGraphics.push_back(this); playAnimation(initialGraphicPath, FARender::AnimationPlayer::AnimationType::Once); } @@ -19,16 +24,28 @@ namespace FAWorld::Missile mMainGraphicPath = loader.load(); mSingleFrame = (mSingleFrame = loader.load()) == -1 ? std::nullopt : mSingleFrame; mAnimationPlayer = FARender::AnimationPlayer(loader); + auto levelIndex = loader.load(); + auto world = loader.currentlyLoadingWorld; + loader.addFunctionToRunAtEnd([this, world, levelIndex]() { + mLevel = world->getLevel(levelIndex); + mLevel->mMissileGraphics.push_back(this); + }); mTicksSinceStarted = loader.load(); mComplete = loader.load(); } + MissileGraphic::~MissileGraphic() + { + mLevel->mMissileGraphics.erase(std::remove(mLevel->mMissileGraphics.begin(), mLevel->mMissileGraphics.end(), this), mLevel->mMissileGraphics.end()); + } + void MissileGraphic::save(FASaveGame::GameSaver& saver) { mCurPos.save(saver); saver.save(mMainGraphicPath); saver.save(static_cast(mSingleFrame == std::nullopt ? -1 : *mSingleFrame)); mAnimationPlayer.save(saver); + saver.save(mLevel->getLevelIndex()); saver.save(mTicksSinceStarted); saver.save(mComplete); } @@ -62,6 +79,13 @@ namespace FAWorld::Missile mComplete = true; } + void MissileGraphic::setLevel(GameLevel* level) + { + mLevel->mMissileGraphics.erase(std::remove(mLevel->mMissileGraphics.begin(), mLevel->mMissileGraphics.end(), this), mLevel->mMissileGraphics.end()); + mLevel = level; + mLevel->mMissileGraphics.push_back(this); + } + void MissileGraphic::playAnimation(std::string path, FARender::AnimationPlayer::AnimationType animationType) { if (!path.empty()) diff --git a/apps/freeablo/faworld/missile/missilegraphic.h b/apps/freeablo/faworld/missile/missilegraphic.h index 449cbf5ce..49ed60676 100644 --- a/apps/freeablo/faworld/missile/missilegraphic.h +++ b/apps/freeablo/faworld/missile/missilegraphic.h @@ -15,9 +15,13 @@ namespace FAWorld::Missile class MissileGraphic { public: - virtual ~MissileGraphic() = default; + virtual ~MissileGraphic(); - MissileGraphic(std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position); + MissileGraphic(std::string initialGraphicPath, + std::string mainGraphicPath, + std::optional singleFrame, + Position position, + GameLevel* level); MissileGraphic(FASaveGame::GameLoader& loader); virtual void save(FASaveGame::GameSaver& saver); @@ -27,6 +31,8 @@ namespace FAWorld::Missile void stop(); bool isComplete() const { return mComplete; } Tick getTicksSinceStarted() const { return mTicksSinceStarted; } + GameLevel* getLevel() { return mLevel; } + void setLevel(GameLevel* level); Position mCurPos; @@ -35,6 +41,7 @@ namespace FAWorld::Missile std::string mMainGraphicPath; std::optional mSingleFrame; + GameLevel* mLevel; FARender::AnimationPlayer mAnimationPlayer; Tick mTicksSinceStarted = 0; bool mComplete = false; diff --git a/apps/freeablo/faworld/missile/missilemovement.cpp b/apps/freeablo/faworld/missile/missilemovement.cpp index 3dcd2716b..9bc51cd73 100644 --- a/apps/freeablo/faworld/missile/missilemovement.cpp +++ b/apps/freeablo/faworld/missile/missilemovement.cpp @@ -18,7 +18,8 @@ namespace FAWorld::Missile void Missile::Movement::hoverOverCreator(Missile& missile, MissileGraphic& graphic) { - // TODO: change level with actor. + if (graphic.getLevel() != missile.mCreator->getLevel()) + graphic.setLevel(missile.mCreator->getLevel()); graphic.mCurPos = missile.mCreator->getPos(); } } From 9a3de116e7b50008aba488a24f36c9fb4fc67699 Mon Sep 17 00:00:00 2001 From: grantramsay Date: Mon, 2 Mar 2020 14:48:51 +1300 Subject: [PATCH 6/9] Add town portal spell --- apps/freeablo/faworld/gamelevel.cpp | 4 +-- apps/freeablo/faworld/gamelevel.h | 4 ++- apps/freeablo/faworld/missile/missile.h | 2 ++ .../missile/missileactorengagement.cpp | 22 ++++++++++++++++ .../faworld/missile/missileattributes.cpp | 2 ++ .../faworld/missile/missilecreation.cpp | 26 +++++++++++++++++++ apps/freeablo/faworld/spells.h | 2 +- 7 files changed, 58 insertions(+), 4 deletions(-) diff --git a/apps/freeablo/faworld/gamelevel.cpp b/apps/freeablo/faworld/gamelevel.cpp index 4ab88b455..2db778f3b 100644 --- a/apps/freeablo/faworld/gamelevel.cpp +++ b/apps/freeablo/faworld/gamelevel.cpp @@ -177,7 +177,7 @@ namespace FAWorld actorMapInsert(mActors[i]); } - Misc::Point GameLevel::getFreeSpotNear(Misc::Point point, int32_t radius) const + Misc::Point GameLevel::getFreeSpotNear(Misc::Point point, int32_t radius, const std::function& additionalConstraints) const { // partially based on https://stackoverflow.com/a/398302 @@ -192,7 +192,7 @@ namespace FAWorld Misc::Point targetPoint = point + Misc::Point{xOffset, yOffset}; if (targetPoint.x >= 0 && targetPoint.x < width() && targetPoint.y >= 0 && targetPoint.y < height()) { - if (isPassable(targetPoint, nullptr)) + if (isPassable(targetPoint, nullptr) && (additionalConstraints == nullptr || additionalConstraints(targetPoint))) return targetPoint; } diff --git a/apps/freeablo/faworld/gamelevel.h b/apps/freeablo/faworld/gamelevel.h index e209d8d52..b1375e25c 100644 --- a/apps/freeablo/faworld/gamelevel.h +++ b/apps/freeablo/faworld/gamelevel.h @@ -84,7 +84,9 @@ namespace FAWorld void actorMapRefresh(); - Misc::Point getFreeSpotNear(Misc::Point point, int32_t radius = std::numeric_limits::max()) const; + Misc::Point getFreeSpotNear(Misc::Point point, + int32_t radius = std::numeric_limits::max(), + const std::function& additionalConstraints = nullptr) const; virtual bool isPassable(const Misc::Point& point, const FAWorld::Actor* forActor) const; diff --git a/apps/freeablo/faworld/missile/missile.h b/apps/freeablo/faworld/missile/missile.h index f00f2d0a1..5494e11aa 100644 --- a/apps/freeablo/faworld/missile/missile.h +++ b/apps/freeablo/faworld/missile/missile.h @@ -44,6 +44,7 @@ namespace FAWorld::Missile static void animated16Direction(Missile& missile, Misc::Point dest, GameLevel* level); static void firewall(Missile& missile, Misc::Point dest, GameLevel* level); static void basicAnimated(Missile& missile, Misc::Point dest, GameLevel* level); + static void townPortal(Missile& missile, Misc::Point dest, GameLevel* level); }; class Movement @@ -69,6 +70,7 @@ namespace FAWorld::Missile static void none(Missile& missile, MissileGraphic& graphic, Actor& actor); static void damageEnemy(Missile& missile, MissileGraphic& graphic, Actor& actor); static void damageEnemyAndStop(Missile& missile, MissileGraphic& graphic, Actor& actor); + static void townPortal(Missile& missile, MissileGraphic& graphic, Actor& actor); }; // Inner class that holds reference to all missile attributes. diff --git a/apps/freeablo/faworld/missile/missileactorengagement.cpp b/apps/freeablo/faworld/missile/missileactorengagement.cpp index f6835432c..63e36ce07 100644 --- a/apps/freeablo/faworld/missile/missileactorengagement.cpp +++ b/apps/freeablo/faworld/missile/missileactorengagement.cpp @@ -22,4 +22,26 @@ namespace FAWorld::Missile if (&actor != missile.mCreator) graphic.stop(); } + + void Missile::ActorEngagement::townPortal(Missile& missile, MissileGraphic& graphic, Actor& actor) + { + if (&actor == missile.mCreator) + { + // Teleport to other portal + auto& otherPortal = missile.mGraphics[0].get() != &graphic ? missile.mGraphics[0] : missile.mGraphics[1]; + auto noMissilesAtPoint = [&otherPortal](const Misc::Point& p) { + return std::none_of( + otherPortal->getLevel()->mMissileGraphics.begin(), otherPortal->getLevel()->mMissileGraphics.end(), + [&p](const MissileGraphic* g) { return p == g->mCurPos.current();}); + }; + auto point = otherPortal->getLevel()->getFreeSpotNear(otherPortal->mCurPos.current(), INT32_MAX, noMissilesAtPoint); + actor.teleport(otherPortal->getLevel(), Position(point)); + // Close the portal if teleporting back through the 2nd (town located) portal + if (&graphic == missile.mGraphics[1].get()) + { + graphic.stop(); + otherPortal->stop(); + } + } + } } diff --git a/apps/freeablo/faworld/missile/missileattributes.cpp b/apps/freeablo/faworld/missile/missileattributes.cpp index 6faa93fe7..21222ee45 100644 --- a/apps/freeablo/faworld/missile/missileattributes.cpp +++ b/apps/freeablo/faworld/missile/missileattributes.cpp @@ -28,6 +28,8 @@ namespace FAWorld::Missile return Attributes(Creation::firewall, Movement::stationary, ActorEngagement::damageEnemy, maxRangeIgnore, World::getTicksInPeriod(8)); case MissileId::manashield: return Attributes(Creation::basicAnimated, Movement::hoverOverCreator, ActorEngagement::none, maxRangeIgnore, World::getTicksInPeriod(8)); + case MissileId::town: + return Attributes(Creation::townPortal, Movement::stationary, ActorEngagement::townPortal, maxRangeIgnore, ttlIgnore); default: invalid_enum(MissileId, missileId); } diff --git a/apps/freeablo/faworld/missile/missilecreation.cpp b/apps/freeablo/faworld/missile/missilecreation.cpp index 687e835cc..07ec89547 100644 --- a/apps/freeablo/faworld/missile/missilecreation.cpp +++ b/apps/freeablo/faworld/missile/missilecreation.cpp @@ -1,3 +1,5 @@ +#include "engine/enginemain.h" +#include "faworld/gamelevel.h" #include "missile.h" #include @@ -42,4 +44,28 @@ namespace FAWorld::Missile { missile.mGraphics.push_back(std::make_unique("", missile.getGraphicsPath(0), std::nullopt, Position(missile.mSrcPoint), level)); } + + void Missile::Creation::townPortal(Missile& missile, Misc::Point, GameLevel* level) + { + // Add portal near player + auto noMissilesAtPoint = [&level](const Misc::Point& p) { + return std::none_of( + level->mMissileGraphics.begin(), level->mMissileGraphics.end(), + [&p](const MissileGraphic* g) { return p == g->mCurPos.current();}); + }; + auto point = level->getFreeSpotNear(missile.mSrcPoint, INT32_MAX, noMissilesAtPoint); + missile.mGraphics.push_back(std::make_unique( + missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), level)); + // Add portal in town + auto town = Engine::EngineMain::get()->mWorld->getLevel(0); + static const Misc::Point townPortalPoint = Misc::Point(60, 80); + auto noMissilesAtTownPoint = [&town](const Misc::Point& p) { + return std::none_of( + town->mMissileGraphics.begin(), town->mMissileGraphics.end(), + [&p](const MissileGraphic* g) { return p == g->mCurPos.current();}); + }; + point = town->getFreeSpotNear(townPortalPoint, INT32_MAX, noMissilesAtTownPoint); + missile.mGraphics.push_back(std::make_unique( + missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), town)); + } } diff --git a/apps/freeablo/faworld/spells.h b/apps/freeablo/faworld/spells.h index efacd2e6c..fa08ca305 100644 --- a/apps/freeablo/faworld/spells.h +++ b/apps/freeablo/faworld/spells.h @@ -62,7 +62,7 @@ namespace FAWorld // Temporary quirk to only allow implemented spells to be used. static bool isSpellImplemented(SpellId spell) { - static const SpellId implementedSpells[] = {SpellId::firebolt, SpellId::firewall, SpellId::manashield}; + static const SpellId implementedSpells[] = {SpellId::firebolt, SpellId::firewall, SpellId::manashield, SpellId::town}; for (auto sp : implementedSpells) if (spell == sp) return true; From 367c5608ee291c3bdb11df8457bc549d0144064b Mon Sep 17 00:00:00 2001 From: grantramsay Date: Mon, 2 Mar 2020 14:58:20 +1300 Subject: [PATCH 7/9] Fix build --- apps/freeablo/faworld/gamelevel.h | 1 + .../faworld/missile/missileactorengagement.cpp | 6 +++--- .../faworld/missile/missilecreation.cpp | 18 ++++++++---------- .../faworld/missile/missilegraphic.cpp | 7 ++----- apps/freeablo/faworld/missile/missilegraphic.h | 6 +----- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/apps/freeablo/faworld/gamelevel.h b/apps/freeablo/faworld/gamelevel.h index b1375e25c..482e46950 100644 --- a/apps/freeablo/faworld/gamelevel.h +++ b/apps/freeablo/faworld/gamelevel.h @@ -1,6 +1,7 @@ #pragma once #include "hoverstate.h" #include "itemmap.h" // TODO: remove, only included for the Tile type +#include #include #include #include diff --git a/apps/freeablo/faworld/missile/missileactorengagement.cpp b/apps/freeablo/faworld/missile/missileactorengagement.cpp index 63e36ce07..a90106e63 100644 --- a/apps/freeablo/faworld/missile/missileactorengagement.cpp +++ b/apps/freeablo/faworld/missile/missileactorengagement.cpp @@ -30,9 +30,9 @@ namespace FAWorld::Missile // Teleport to other portal auto& otherPortal = missile.mGraphics[0].get() != &graphic ? missile.mGraphics[0] : missile.mGraphics[1]; auto noMissilesAtPoint = [&otherPortal](const Misc::Point& p) { - return std::none_of( - otherPortal->getLevel()->mMissileGraphics.begin(), otherPortal->getLevel()->mMissileGraphics.end(), - [&p](const MissileGraphic* g) { return p == g->mCurPos.current();}); + return std::none_of(otherPortal->getLevel()->mMissileGraphics.begin(), + otherPortal->getLevel()->mMissileGraphics.end(), + [&p](const MissileGraphic* g) { return p == g->mCurPos.current(); }); }; auto point = otherPortal->getLevel()->getFreeSpotNear(otherPortal->mCurPos.current(), INT32_MAX, noMissilesAtPoint); actor.teleport(otherPortal->getLevel(), Position(point)); diff --git a/apps/freeablo/faworld/missile/missilecreation.cpp b/apps/freeablo/faworld/missile/missilecreation.cpp index 07ec89547..14dc98a45 100644 --- a/apps/freeablo/faworld/missile/missilecreation.cpp +++ b/apps/freeablo/faworld/missile/missilecreation.cpp @@ -49,23 +49,21 @@ namespace FAWorld::Missile { // Add portal near player auto noMissilesAtPoint = [&level](const Misc::Point& p) { - return std::none_of( - level->mMissileGraphics.begin(), level->mMissileGraphics.end(), - [&p](const MissileGraphic* g) { return p == g->mCurPos.current();}); + return std::none_of( + level->mMissileGraphics.begin(), level->mMissileGraphics.end(), [&p](const MissileGraphic* g) { return p == g->mCurPos.current(); }); }; auto point = level->getFreeSpotNear(missile.mSrcPoint, INT32_MAX, noMissilesAtPoint); - missile.mGraphics.push_back(std::make_unique( - missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), level)); + missile.mGraphics.push_back( + std::make_unique(missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), level)); // Add portal in town auto town = Engine::EngineMain::get()->mWorld->getLevel(0); static const Misc::Point townPortalPoint = Misc::Point(60, 80); auto noMissilesAtTownPoint = [&town](const Misc::Point& p) { - return std::none_of( - town->mMissileGraphics.begin(), town->mMissileGraphics.end(), - [&p](const MissileGraphic* g) { return p == g->mCurPos.current();}); + return std::none_of( + town->mMissileGraphics.begin(), town->mMissileGraphics.end(), [&p](const MissileGraphic* g) { return p == g->mCurPos.current(); }); }; point = town->getFreeSpotNear(townPortalPoint, INT32_MAX, noMissilesAtTownPoint); - missile.mGraphics.push_back(std::make_unique( - missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), town)); + missile.mGraphics.push_back( + std::make_unique(missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), town)); } } diff --git a/apps/freeablo/faworld/missile/missilegraphic.cpp b/apps/freeablo/faworld/missile/missilegraphic.cpp index 03b6c3509..381bb2a90 100644 --- a/apps/freeablo/faworld/missile/missilegraphic.cpp +++ b/apps/freeablo/faworld/missile/missilegraphic.cpp @@ -7,11 +7,8 @@ namespace FAWorld::Missile { - MissileGraphic::MissileGraphic(std::string initialGraphicPath, - std::string mainGraphicPath, - std::optional singleFrame, - Position position, - GameLevel* level) + MissileGraphic::MissileGraphic( + std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position, GameLevel* level) : mCurPos(position), mMainGraphicPath(mainGraphicPath), mSingleFrame(singleFrame), mLevel(level) { level->mMissileGraphics.push_back(this); diff --git a/apps/freeablo/faworld/missile/missilegraphic.h b/apps/freeablo/faworld/missile/missilegraphic.h index 49ed60676..baecbd58e 100644 --- a/apps/freeablo/faworld/missile/missilegraphic.h +++ b/apps/freeablo/faworld/missile/missilegraphic.h @@ -17,11 +17,7 @@ namespace FAWorld::Missile public: virtual ~MissileGraphic(); - MissileGraphic(std::string initialGraphicPath, - std::string mainGraphicPath, - std::optional singleFrame, - Position position, - GameLevel* level); + MissileGraphic(std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position, GameLevel* level); MissileGraphic(FASaveGame::GameLoader& loader); virtual void save(FASaveGame::GameSaver& saver); From aaa634747270f04c6fefa89d735a5b9a84fdc94b Mon Sep 17 00:00:00 2001 From: grantramsay Date: Tue, 3 Mar 2020 20:10:07 +1300 Subject: [PATCH 8/9] Put missile max range within missile movement Missiles that don't move shouldn't be concerned with range --- apps/freeablo/faworld/missile/missile.cpp | 5 ----- apps/freeablo/faworld/missile/missile.h | 7 +++---- .../faworld/missile/missileattributes.cpp | 18 ++++++++---------- .../faworld/missile/missilemovement.cpp | 12 +++++++++--- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/apps/freeablo/faworld/missile/missile.cpp b/apps/freeablo/faworld/missile/missile.cpp index 6901abe63..dac7b3ec6 100644 --- a/apps/freeablo/faworld/missile/missile.cpp +++ b/apps/freeablo/faworld/missile/missile.cpp @@ -100,11 +100,6 @@ namespace FAWorld::Missile graphic->stop(); } - // Stop after max range is exceeded. - auto distance = (Vec2Fix(curPoint.x, curPoint.y) - Vec2Fix(mSrcPoint.x, mSrcPoint.y)).magnitude(); - if (distance > mAttr.mMaxRange) - graphic->stop(); - // Stop after "time to live" has expired. if (graphic->getTicksSinceStarted() > mAttr.mTimeToLive) graphic->stop(); diff --git a/apps/freeablo/faworld/missile/missile.h b/apps/freeablo/faworld/missile/missile.h index 5494e11aa..ee64e755d 100644 --- a/apps/freeablo/faworld/missile/missile.h +++ b/apps/freeablo/faworld/missile/missile.h @@ -54,11 +54,11 @@ namespace FAWorld::Missile typedef std::function Method; static void stationary(Missile& missile, MissileGraphic& graphic); - static Method linear(FixedPoint speed); + static Method linear(FixedPoint speed, FixedPoint maxRange); static void hoverOverCreator(Missile& missile, MissileGraphic& graphic); private: - static void linear(MissileGraphic& graphic, FixedPoint speed); + static void linear(Missile& missile, MissileGraphic& graphic, FixedPoint speed, FixedPoint maxRange); }; class ActorEngagement @@ -77,13 +77,12 @@ namespace FAWorld::Missile class Attributes { public: - Attributes(Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, FixedPoint maxRange, Tick timeToLive); + Attributes(Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, Tick timeToLive); static Attributes fromId(MissileId missileId); const Missile::Creation::Method mCreation; const Missile::Movement::Method mMovement; const Missile::ActorEngagement::Method mActorEngagement; - const FixedPoint mMaxRange; const Tick mTimeToLive; }; diff --git a/apps/freeablo/faworld/missile/missileattributes.cpp b/apps/freeablo/faworld/missile/missileattributes.cpp index 21222ee45..bc349d2d3 100644 --- a/apps/freeablo/faworld/missile/missileattributes.cpp +++ b/apps/freeablo/faworld/missile/missileattributes.cpp @@ -2,34 +2,32 @@ namespace FAWorld::Missile { - Missile::Attributes::Attributes( - Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, FixedPoint maxRange, Tick timeToLive) - : mCreation(creation), mMovement(movement), mActorEngagement(actorEngagement), mMaxRange(maxRange), mTimeToLive(timeToLive) + Missile::Attributes::Attributes(Creation::Method creation, Movement::Method movement, ActorEngagement::Method actorEngagement, Tick timeToLive) + : mCreation(creation), mMovement(movement), mActorEngagement(actorEngagement), mTimeToLive(timeToLive) { } Missile::Attributes Missile::Attributes::fromId(MissileId missileId) { - static FixedPoint maxRangeIgnore = FixedPoint::fromRawValue(INT64_MAX); static Tick ttlIgnore = std::numeric_limits::max(); switch (missileId) { case MissileId::arrow: - return Attributes(Creation::singleFrame16Direction, Movement::linear(30), ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + return Attributes(Creation::singleFrame16Direction, Movement::linear(30, 15), ActorEngagement::damageEnemyAndStop, ttlIgnore); case MissileId::firebolt: - return Attributes(Creation::animated16Direction, Movement::linear(15), ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + return Attributes(Creation::animated16Direction, Movement::linear(15, 15), ActorEngagement::damageEnemyAndStop, ttlIgnore); case MissileId::farrow: case MissileId::larrow: - return Attributes(Creation::animated16Direction, Movement::linear(30), ActorEngagement::damageEnemyAndStop, 15, ttlIgnore); + return Attributes(Creation::animated16Direction, Movement::linear(30, 15), ActorEngagement::damageEnemyAndStop, ttlIgnore); case MissileId::firewall: case MissileId::firewalla: case MissileId::firewallc: - return Attributes(Creation::firewall, Movement::stationary, ActorEngagement::damageEnemy, maxRangeIgnore, World::getTicksInPeriod(8)); + return Attributes(Creation::firewall, Movement::stationary, ActorEngagement::damageEnemy, World::getTicksInPeriod(8)); case MissileId::manashield: - return Attributes(Creation::basicAnimated, Movement::hoverOverCreator, ActorEngagement::none, maxRangeIgnore, World::getTicksInPeriod(8)); + return Attributes(Creation::basicAnimated, Movement::hoverOverCreator, ActorEngagement::none, World::getTicksInPeriod(8)); case MissileId::town: - return Attributes(Creation::townPortal, Movement::stationary, ActorEngagement::townPortal, maxRangeIgnore, ttlIgnore); + return Attributes(Creation::townPortal, Movement::stationary, ActorEngagement::townPortal, ttlIgnore); default: invalid_enum(MissileId, missileId); } diff --git a/apps/freeablo/faworld/missile/missilemovement.cpp b/apps/freeablo/faworld/missile/missilemovement.cpp index 9bc51cd73..24cc14091 100644 --- a/apps/freeablo/faworld/missile/missilemovement.cpp +++ b/apps/freeablo/faworld/missile/missilemovement.cpp @@ -5,15 +5,21 @@ namespace FAWorld::Missile { void Missile::Movement::stationary(Missile&, MissileGraphic&) {} - Missile::Movement::Method Missile::Movement::linear(FixedPoint speed) + Missile::Movement::Method Missile::Movement::linear(FixedPoint speed, FixedPoint maxRange) { - return [=](Missile&, MissileGraphic& graphic) { linear(graphic, speed); }; + return [=](Missile& missile, MissileGraphic& graphic) { linear(missile, graphic, speed, maxRange); }; } - void Missile::Movement::linear(MissileGraphic& graphic, FixedPoint speed) + void Missile::Movement::linear(Missile& missile, MissileGraphic& graphic, FixedPoint speed, FixedPoint maxRange) { graphic.mCurPos.setFreeMovement(); graphic.mCurPos.update(speed / FixedPoint(World::ticksPerSecond)); + + // Stop after max range is exceeded. + auto curPoint = graphic.mCurPos.current(); + auto distance = (Vec2Fix(curPoint.x, curPoint.y) - Vec2Fix(missile.mSrcPoint.x, missile.mSrcPoint.y)).magnitude(); + if (distance > maxRange) + graphic.stop(); } void Missile::Movement::hoverOverCreator(Missile& missile, MissileGraphic& graphic) From d55b60a61ca37d0ed1a83bf08c499f86d16035af Mon Sep 17 00:00:00 2001 From: grantramsay Date: Sat, 7 Mar 2020 13:11:44 +1300 Subject: [PATCH 9/9] PR updates Update changelog Any player can use town portal Change mMissileGraphics to std::unordered_set Add comment on not using mMissileGraphics for game logic Add TODO for removing "additionalConstraints" after GameObject refactor Use std::numeric_limits::max() over INT32_MAX --- apps/freeablo/faworld/gamelevel.h | 11 ++++++++--- .../faworld/missile/missileactorengagement.cpp | 13 +++++++------ apps/freeablo/faworld/missile/missilecreation.cpp | 4 ++-- apps/freeablo/faworld/missile/missilegraphic.cpp | 13 +++++-------- changelog.md | 2 ++ 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/apps/freeablo/faworld/gamelevel.h b/apps/freeablo/faworld/gamelevel.h index 482e46950..81f1d121e 100644 --- a/apps/freeablo/faworld/gamelevel.h +++ b/apps/freeablo/faworld/gamelevel.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace FARender { @@ -85,6 +86,8 @@ namespace FAWorld void actorMapRefresh(); + // TODO: Remove the additionalConstraints parameter, it is currently only used as a bit of a hack to not + // place a player on a town portal when teleporting (see https://github.com/wheybags/freeablo/issues/478) Misc::Point getFreeSpotNear(Misc::Point point, int32_t radius = std::numeric_limits::max(), const std::function& additionalConstraints = nullptr) const; @@ -112,9 +115,11 @@ namespace FAWorld World* getWorld() { return &mWorld; } - // These are not saved, they are added/remove in MissileGraphic constructor/destructor - // This avoids having to check every actor in world to find missile graphics on a level - std::vector mMissileGraphics; + // This list avoids having to check every actor in world to find missile graphics on a level. + // It is not saved, items are added/removed in MissileGraphic constructor/destructor. + // This is currently only intended for rendering so order is unimportant, hence using std::unordered_set + // and not saving/loading. Since order is not maintained this should not use be used for game logic! + std::unordered_set mMissileGraphics; private: GameLevel(World& world); diff --git a/apps/freeablo/faworld/missile/missileactorengagement.cpp b/apps/freeablo/faworld/missile/missileactorengagement.cpp index a90106e63..ca8c9c6e4 100644 --- a/apps/freeablo/faworld/missile/missileactorengagement.cpp +++ b/apps/freeablo/faworld/missile/missileactorengagement.cpp @@ -1,4 +1,4 @@ -#include "faworld/actor.h" +#include "faworld/player.h" #include "missile.h" namespace FAWorld::Missile @@ -25,7 +25,8 @@ namespace FAWorld::Missile void Missile::ActorEngagement::townPortal(Missile& missile, MissileGraphic& graphic, Actor& actor) { - if (&actor == missile.mCreator) + // Any player can use a town portal + if (auto player = dynamic_cast(&actor)) { // Teleport to other portal auto& otherPortal = missile.mGraphics[0].get() != &graphic ? missile.mGraphics[0] : missile.mGraphics[1]; @@ -34,10 +35,10 @@ namespace FAWorld::Missile otherPortal->getLevel()->mMissileGraphics.end(), [&p](const MissileGraphic* g) { return p == g->mCurPos.current(); }); }; - auto point = otherPortal->getLevel()->getFreeSpotNear(otherPortal->mCurPos.current(), INT32_MAX, noMissilesAtPoint); - actor.teleport(otherPortal->getLevel(), Position(point)); - // Close the portal if teleporting back through the 2nd (town located) portal - if (&graphic == missile.mGraphics[1].get()) + auto point = otherPortal->getLevel()->getFreeSpotNear(otherPortal->mCurPos.current(), std::numeric_limits::max(), noMissilesAtPoint); + player->teleport(otherPortal->getLevel(), Position(point)); + // Close the portal if the creator is teleporting back through the 2nd (town located) portal + if (player == missile.mCreator && &graphic == missile.mGraphics[1].get()) { graphic.stop(); otherPortal->stop(); diff --git a/apps/freeablo/faworld/missile/missilecreation.cpp b/apps/freeablo/faworld/missile/missilecreation.cpp index 14dc98a45..6c4832454 100644 --- a/apps/freeablo/faworld/missile/missilecreation.cpp +++ b/apps/freeablo/faworld/missile/missilecreation.cpp @@ -52,7 +52,7 @@ namespace FAWorld::Missile return std::none_of( level->mMissileGraphics.begin(), level->mMissileGraphics.end(), [&p](const MissileGraphic* g) { return p == g->mCurPos.current(); }); }; - auto point = level->getFreeSpotNear(missile.mSrcPoint, INT32_MAX, noMissilesAtPoint); + auto point = level->getFreeSpotNear(missile.mSrcPoint, std::numeric_limits::max(), noMissilesAtPoint); missile.mGraphics.push_back( std::make_unique(missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), level)); // Add portal in town @@ -62,7 +62,7 @@ namespace FAWorld::Missile return std::none_of( town->mMissileGraphics.begin(), town->mMissileGraphics.end(), [&p](const MissileGraphic* g) { return p == g->mCurPos.current(); }); }; - point = town->getFreeSpotNear(townPortalPoint, INT32_MAX, noMissilesAtTownPoint); + point = town->getFreeSpotNear(townPortalPoint, std::numeric_limits::max(), noMissilesAtTownPoint); missile.mGraphics.push_back( std::make_unique(missile.getGraphicsPath(0), missile.getGraphicsPath(1), std::nullopt, Position(point), town)); } diff --git a/apps/freeablo/faworld/missile/missilegraphic.cpp b/apps/freeablo/faworld/missile/missilegraphic.cpp index 381bb2a90..d7546863c 100644 --- a/apps/freeablo/faworld/missile/missilegraphic.cpp +++ b/apps/freeablo/faworld/missile/missilegraphic.cpp @@ -11,7 +11,7 @@ namespace FAWorld::Missile std::string initialGraphicPath, std::string mainGraphicPath, std::optional singleFrame, Position position, GameLevel* level) : mCurPos(position), mMainGraphicPath(mainGraphicPath), mSingleFrame(singleFrame), mLevel(level) { - level->mMissileGraphics.push_back(this); + level->mMissileGraphics.insert(this); playAnimation(initialGraphicPath, FARender::AnimationPlayer::AnimationType::Once); } @@ -25,16 +25,13 @@ namespace FAWorld::Missile auto world = loader.currentlyLoadingWorld; loader.addFunctionToRunAtEnd([this, world, levelIndex]() { mLevel = world->getLevel(levelIndex); - mLevel->mMissileGraphics.push_back(this); + mLevel->mMissileGraphics.insert(this); }); mTicksSinceStarted = loader.load(); mComplete = loader.load(); } - MissileGraphic::~MissileGraphic() - { - mLevel->mMissileGraphics.erase(std::remove(mLevel->mMissileGraphics.begin(), mLevel->mMissileGraphics.end(), this), mLevel->mMissileGraphics.end()); - } + MissileGraphic::~MissileGraphic() { mLevel->mMissileGraphics.erase(this); } void MissileGraphic::save(FASaveGame::GameSaver& saver) { @@ -78,9 +75,9 @@ namespace FAWorld::Missile void MissileGraphic::setLevel(GameLevel* level) { - mLevel->mMissileGraphics.erase(std::remove(mLevel->mMissileGraphics.begin(), mLevel->mMissileGraphics.end(), this), mLevel->mMissileGraphics.end()); + mLevel->mMissileGraphics.erase(this); mLevel = level; - mLevel->mMissileGraphics.push_back(this); + mLevel->mMissileGraphics.insert(this); } void MissileGraphic::playAnimation(std::string path, FARender::AnimationPlayer::AnimationType animationType) diff --git a/changelog.md b/changelog.md index 5effb1094..1af6533c6 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,8 @@ ## v0.5 [?? ??? ????] +- Added town portal spell + ## v0.4 [6 Mar 2020] - Added multiplayer