diff --git a/CHANGELOG.md b/CHANGELOG.md index b892d08202..6d2a67349b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Bug #6168: Weather particles flicker for a frame at start of storms Bug #6172: Some creatures can't open doors Bug #6174: Spellmaking and Enchanting sliders differences from vanilla + Bug #6177: Followers of player follower stop following after waiting for a day Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6253: Multiple instances of Reflect stack additively @@ -81,6 +82,7 @@ Bug #6416: Morphs are applied to the wrong target Bug #6429: Wyrmhaven: Can't add AI packages to player Feature #890: OpenMW-CS: Column filtering + Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 1de3288ad4..171f64eabe 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -30,7 +30,7 @@ void printAIPackage(const ESM::AIPackage& p) { std::cout << " Travel Coordinates: (" << p.mTravel.mX << "," << p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl; - std::cout << " Travel Unknown: " << p.mTravel.mUnk << std::endl; + std::cout << " Should repeat: " << p.mTravel.mShouldRepeat << std::endl; } else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort) { @@ -38,12 +38,12 @@ void printAIPackage(const ESM::AIPackage& p) << p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl; std::cout << " Duration: " << p.mTarget.mDuration << std::endl; std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl; - std::cout << " Unknown: " << p.mTarget.mUnk << std::endl; + std::cout << " Should repeat: " << p.mTarget.mShouldRepeat << std::endl; } else if (p.mType == ESM::AI_Activate) { std::cout << " Name: " << p.mActivate.mName.toString() << std::endl; - std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl; + std::cout << " Should repeat: " << p.mActivate.mShouldRepeat << std::endl; } else { std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl; diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 6eefdb6e21..8e53b3b270 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -255,7 +255,7 @@ namespace CSMWorld { ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiWanderToD, "Wander ToD" }, - { ColumnId_AiWanderRepeat, "Wander Repeat" }, + { ColumnId_AiWanderRepeat, "Ai Repeat" }, { ColumnId_AiActivateName, "Activate" }, { ColumnId_AiTargetId, "Target ID" }, { ColumnId_AiTargetCell, "Target Cell" }, diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index c35d3c5a7c..153d1bde91 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1678,7 +1678,7 @@ namespace CSMWorld newRow.mWander.mTimeOfDay = 0; for (int i = 0; i < 8; ++i) newRow.mWander.mIdle[i] = 0; - newRow.mWander.mShouldRepeat = 0; + newRow.mWander.mShouldRepeat = 1; newRow.mCellName = ""; if (position >= (int)list.size()) @@ -1784,9 +1784,15 @@ namespace CSMWorld return static_cast(content.mWander.mIdle[subColIndex-4]); else return QVariant(); - case 12: // wander repeat + case 12: // repeat if (content.mType == ESM::AI_Wander) return content.mWander.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Travel) + return content.mTravel.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + return content.mTarget.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Activate) + return content.mActivate.mShouldRepeat != 0; else return QVariant(); case 13: // activate name @@ -1895,6 +1901,12 @@ namespace CSMWorld case 12: if (content.mType == ESM::AI_Wander) content.mWander.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Travel) + content.mTravel.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Activate) + content.mActivate.mShouldRepeat = static_cast(value.toInt()); else return; // return without saving diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index b4ddf0c030..06aef0cdb0 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -13,8 +13,8 @@ namespace MWMechanics { - AiActivate::AiActivate(const std::string &objectId) - : mObjectId(objectId) + AiActivate::AiActivate(const std::string &objectId, bool repeat) + : TypedAiPackage(repeat), mObjectId(objectId) { } @@ -48,6 +48,7 @@ namespace MWMechanics { std::unique_ptr activate(new ESM::AiSequence::AiActivate()); activate->mTargetId = mObjectId; + activate->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Activate; @@ -56,7 +57,7 @@ namespace MWMechanics } AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) - : mObjectId(activate->mTargetId) + : AiActivate(activate->mTargetId, activate->mRepeat) { } } diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index dc7e0bb26b..ad4d4e6064 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -24,7 +24,7 @@ namespace MWMechanics public: /// Constructor /** \param objectId Reference to object to activate **/ - explicit AiActivate(const std::string &objectId); + explicit AiActivate(const std::string &objectId, bool repeat); explicit AiActivate(const ESM::AiSequence::AiActivate* activate); diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 75c0461105..0184c6e66f 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -20,16 +20,16 @@ namespace MWMechanics { - AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) + AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z, bool repeat) + : TypedAiPackage(repeat), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mTargetActorRefId = actorId; } - AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z) - : mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) + AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z, bool repeat) + : TypedAiPackage(repeat), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { @@ -37,11 +37,8 @@ namespace MWMechanics } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) - : mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) - // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. - // The exact value of mDuration only matters for repeating packages. - // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. - , mDuration(escort->mRemainingDuration > 0) + : TypedAiPackage(escort->mRepeat), mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) + , mDuration(escort->mData.mDuration) , mRemainingDuration(escort->mRemainingDuration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) @@ -103,10 +100,12 @@ namespace MWMechanics escort->mData.mX = mX; escort->mData.mY = mY; escort->mData.mZ = mZ; + escort->mData.mDuration = mDuration; escort->mTargetId = mTargetActorRefId; escort->mTargetActorId = mTargetActorId; escort->mRemainingDuration = mRemainingDuration; escort->mCellId = mCellId; + escort->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Escort; diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 27a177893d..c49e227e09 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -22,11 +22,11 @@ namespace MWMechanics /// Implementation of AiEscort /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time \implement AiEscort **/ - AiEscort(const std::string &actorId, int duration, float x, float y, float z); + AiEscort(const std::string &actorId, int duration, float x, float y, float z, bool repeat); /// Implementation of AiEscortCell /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time \implement AiEscortCell **/ - AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z); + AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z, bool repeat); AiEscort(const ESM::AiSequence::AiEscort* escort); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index ec23679977..43a0f25c62 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -28,36 +28,20 @@ namespace MWMechanics { int AiFollow::mFollowIndexCounter = 0; -AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z, bool repeat) +: TypedAiPackage(repeat), mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actorId; } -AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z, bool repeat) +: TypedAiPackage(repeat), mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actorId; } -AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actor.getCellRef().getRefId(); - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); -} - -AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actor.getCellRef().getRefId(); - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); -} - AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!commanded)) , mAlwaysFollow(true), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) @@ -68,12 +52,9 @@ AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) } AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) - : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded)) + : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat)) , mAlwaysFollow(follow->mAlwaysFollow) - // mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration. - // The exact value of mDuration only matters for repeating packages. - // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. - , mDuration(follow->mRemainingDuration) + , mDuration(follow->mData.mDuration) , mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) @@ -160,12 +141,15 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte if (actor.getCell()->isExterior()) //Outside? { if (mCellId == "") //No cell to travel to + { + mRemainingDuration = mDuration; return true; + } } - else + else if (mCellId == actor.getCell()->getCell()->mName) //Cell to travel to { - if (mCellId == actor.getCell()->getCell()->mName) //Cell to travel to - return true; + mRemainingDuration = mDuration; + return true; } } } @@ -221,6 +205,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const follow->mData.mX = mX; follow->mData.mY = mY; follow->mData.mZ = mZ; + follow->mData.mDuration = mDuration; follow->mTargetId = mTargetActorRefId; follow->mTargetActorId = mTargetActorId; follow->mRemainingDuration = mRemainingDuration; @@ -228,6 +213,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const follow->mAlwaysFollow = mAlwaysFollow; follow->mCommanded = isCommanded(); follow->mActive = mActive; + follow->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index c4ac5eb3f2..b83e464fa1 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -40,12 +40,10 @@ namespace MWMechanics class AiFollow final : public TypedAiPackage { public: - AiFollow(const std::string &actorId, float duration, float x, float y, float z); - AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z); /// Follow Actor for duration or until you arrive at a world position - AiFollow(const MWWorld::Ptr& actor, float duration, float X, float Y, float Z); + AiFollow(const std::string &actorId, float duration, float x, float y, float z, bool repeat); /// Follow Actor for duration or until you arrive at a position in a cell - AiFollow(const MWWorld::Ptr& actor, const std::string &CellId, float duration, float X, float Y, float Z); + AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z, bool repeat); /// Follow Actor indefinitively AiFollow(const MWWorld::Ptr& actor, bool commanded=false); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 5270b74166..498c6e3050 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -108,7 +108,7 @@ namespace MWMechanics /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? bool shouldCancelPreviousAi() const { return mOptions.mShouldCancelPreviousAi; } - /// Return true if this package should repeat. Currently only used for Wander packages. + /// Return true if this package should repeat. bool getRepeat() const { return mOptions.mRepeat; } virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index bf4cf28deb..b1827dd8ef 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -31,14 +31,13 @@ void AiSequence::copy (const AiSequence& sequence) sequence.mAiState.copy(mAiState); } -AiSequence::AiSequence() : mDone (false), mRepeat(false), mLastAiPackage(AiPackageTypeId::None) {} +AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {} AiSequence::AiSequence (const AiSequence& sequence) { copy (sequence); mDone = sequence.mDone; mLastAiPackage = sequence.mLastAiPackage; - mRepeat = sequence.mRepeat; } AiSequence& AiSequence::operator= (const AiSequence& sequence) @@ -281,7 +280,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac if (package->execute(actor, characterController, mAiState, duration)) { // Put repeating noncombat AI packages on the end of the stack so they can be used again - if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) + if (isActualAiPackage(packageTypeId) && package->getRepeat()) { package->reset(); mPackages.push_back(package->clone()); @@ -355,7 +354,6 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo else ++it; } - mRepeat=false; } // insert new package in correct place depending on priority @@ -401,10 +399,6 @@ const AiPackage& MWMechanics::AiSequence::getActivePackage() void AiSequence::fill(const ESM::AIPackageList &list) { - // If there is more than one package in the list, enable repeating - if (list.mList.size() >= 2) - mRepeat = true; - for (const auto& esmPackage : list.mList) { std::unique_ptr package; @@ -420,22 +414,22 @@ void AiSequence::fill(const ESM::AIPackageList &list) else if (esmPackage.mType == ESM::AI_Escort) { ESM::AITarget data = esmPackage.mTarget; - package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); + package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Travel) { ESM::AITravel data = esmPackage.mTravel; - package = std::make_unique(data.mX, data.mY, data.mZ); + package = std::make_unique(data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Activate) { ESM::AIActivate data = esmPackage.mActivate; - package = std::make_unique(data.mName.toString()); + package = std::make_unique(data.mName.toString(), data.mShouldRepeat != 0); } else //if (esmPackage.mType == ESM::AI_Follow) { ESM::AITarget data = esmPackage.mTarget; - package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); + package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } mPackages.push_back(std::move(package)); } @@ -454,24 +448,6 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) if (!sequence.mPackages.empty()) clear(); - // If there is more than one non-combat, non-pursue package in the list, enable repeating. - int count = 0; - for (auto& container : sequence.mPackages) - { - switch (container.mType) - { - case ESM::AiSequence::Ai_Wander: - case ESM::AiSequence::Ai_Travel: - case ESM::AiSequence::Ai_Escort: - case ESM::AiSequence::Ai_Follow: - case ESM::AiSequence::Ai_Activate: - ++count; - } - } - - if (count > 1) - mRepeat = true; - // Load packages for (auto& container : sequence.mPackages) { diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 645524d381..90bd999c91 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -43,9 +43,6 @@ namespace MWMechanics ///Finished with top AIPackage, set for one frame bool mDone; - ///Does this AI sequence repeat (repeating of Wander packages handled separately) - bool mRepeat; - ///Copy AiSequence void copy (const AiSequence& sequence); diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 2594adcb34..0b4a1411eb 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -34,8 +34,8 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) namespace MWMechanics { - AiTravel::AiTravel(float x, float y, float z, AiTravel*) - : mX(x), mY(y), mZ(z), mHidden(false) + AiTravel::AiTravel(float x, float y, float z, bool repeat, AiTravel*) + : TypedAiPackage(repeat), mX(x), mY(y), mZ(z), mHidden(false) { } @@ -44,13 +44,13 @@ namespace MWMechanics { } - AiTravel::AiTravel(float x, float y, float z) - : AiTravel(x, y, z, this) + AiTravel::AiTravel(float x, float y, float z, bool repeat) + : AiTravel(x, y, z, repeat, this) { } AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) - : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) + : TypedAiPackage(travel->mRepeat), mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) { // Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type assert(!travel->mHidden); @@ -125,6 +125,7 @@ namespace MWMechanics travel->mData.mY = mY; travel->mData.mZ = mZ; travel->mHidden = mHidden; + travel->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Travel; diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index ee4ff9ad4d..303df7b105 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -19,11 +19,11 @@ namespace MWMechanics class AiTravel : public TypedAiPackage { public: - AiTravel(float x, float y, float z, AiTravel* derived); + AiTravel(float x, float y, float z, bool repeat, AiTravel* derived); AiTravel(float x, float y, float z, AiInternalTravel* derived); - AiTravel(float x, float y, float z); + AiTravel(float x, float y, float z, bool repeat); explicit AiTravel(const ESM::AiSequence::AiTravel* travel); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 53f050c0dd..664ae105f3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -105,7 +105,7 @@ namespace MWMechanics } AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): - TypedAiPackage(makeDefaultOptions().withRepeat(repeat)), + TypedAiPackage(repeat), mDistance(std::max(0, distance)), mDuration(std::max(0, duration)), mRemainingDuration(duration), mTimeOfDay(timeOfDay), diff --git a/apps/openmw/mwmechanics/typedaipackage.hpp b/apps/openmw/mwmechanics/typedaipackage.hpp index d2d424326c..0ea276999f 100644 --- a/apps/openmw/mwmechanics/typedaipackage.hpp +++ b/apps/openmw/mwmechanics/typedaipackage.hpp @@ -11,6 +11,9 @@ namespace MWMechanics TypedAiPackage() : AiPackage(T::getTypeId(), T::makeDefaultOptions()) {} + TypedAiPackage(bool repeat) : + AiPackage(T::getTypeId(), T::makeDefaultOptions().withRepeat(repeat)) {} + TypedAiPackage(const Options& options) : AiPackage(T::getTypeId(), options) {} diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index d1adffc9fa..5ebc0bc529 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -48,13 +48,14 @@ namespace MWScript std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - // discard additional arguments (reset), because we have no idea what they mean. + // The value of the reset argument doesn't actually matter + bool repeat = arg0; for (unsigned int i=0; i(duration), x, y, z); + MWMechanics::AiEscort escortPackage(actorID, static_cast(duration), x, y, z, repeat); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; @@ -155,7 +158,8 @@ namespace MWScript Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); - // discard additional arguments (reset), because we have no idea what they mean. + // The value of the reset argument doesn't actually matter + bool repeat = arg0; for (unsigned int i=0; igetStore().get().search(cellID)) return; - MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z); + MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z, repeat); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; @@ -237,7 +241,7 @@ namespace MWScript --arg0; } - // discard additional arguments (reset), because we have no idea what they mean. + // discard additional arguments, because we have no idea what they mean. for (unsigned int i=0; i #include namespace ESM @@ -34,12 +35,16 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); esm.getHNOT (mHidden, "HIDD"); + mRepeat = false; + esm.getHNOT(mRepeat, "REPT"); } void AiTravel::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); esm.writeHNT ("HIDD", mHidden); + if(mRepeat) + esm.writeHNT("REPT", mRepeat); } void AiEscort::load(ESMReader &esm) @@ -50,6 +55,15 @@ namespace AiSequence esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); + mRepeat = false; + esm.getHNOT(mRepeat, "REPT"); + if(esm.getFormat() < 18) + { + // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. + // The exact value of mDuration only matters for repeating packages. + // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. + mData.mDuration = std::max(mRemainingDuration > 0, mRemainingDuration); + } } void AiEscort::save(ESMWriter &esm) const @@ -60,6 +74,8 @@ namespace AiSequence esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); + if(mRepeat) + esm.writeHNT("REPT", mRepeat); } void AiFollow::load(ESMReader &esm) @@ -75,6 +91,15 @@ namespace AiSequence esm.getHNOT (mCommanded, "CMND"); mActive = false; esm.getHNOT (mActive, "ACTV"); + mRepeat = false; + esm.getHNOT(mRepeat, "REPT"); + if(esm.getFormat() < 18) + { + // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. + // The exact value of mDuration only matters for repeating packages. + // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. + mData.mDuration = std::max(mRemainingDuration > 0, mRemainingDuration); + } } void AiFollow::save(ESMWriter &esm) const @@ -89,16 +114,22 @@ namespace AiSequence esm.writeHNT ("CMND", mCommanded); if (mActive) esm.writeHNT("ACTV", mActive); + if(mRepeat) + esm.writeHNT("REPT", mRepeat); } void AiActivate::load(ESMReader &esm) { mTargetId = esm.getHNString("TARG"); + mRepeat = false; + esm.getHNOT(mRepeat, "REPT"); } void AiActivate::save(ESMWriter &esm) const { esm.writeHNString("TARG", mTargetId); + if(mRepeat) + esm.writeHNT("REPT", mRepeat); } void AiCombat::load(ESMReader &esm) @@ -166,6 +197,7 @@ namespace AiSequence void AiSequence::load(ESMReader &esm) { + int count = 0; while (esm.isNextSub("AIPK")) { int type; @@ -181,6 +213,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Travel: @@ -188,6 +221,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Escort: @@ -195,6 +229,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Follow: @@ -202,6 +237,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Activate: @@ -209,6 +245,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Combat: @@ -231,6 +268,23 @@ namespace AiSequence } esm.getHNOT (mLastAiPackage, "LAST"); + + if(count > 1 && esm.getFormat() < 18) + { + for(auto& pkg : mPackages) + { + if(pkg.mType == Ai_Wander) + static_cast(pkg.mPackage)->mData.mShouldRepeat = true; + else if(pkg.mType == Ai_Travel) + static_cast(pkg.mPackage)->mRepeat = true; + else if(pkg.mType == Ai_Escort) + static_cast(pkg.mPackage)->mRepeat = true; + else if(pkg.mType == Ai_Follow) + static_cast(pkg.mPackage)->mRepeat = true; + else if(pkg.mType == Ai_Activate) + static_cast(pkg.mPackage)->mRepeat = true; + } + } } } } diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index d8c20185f8..00c1316d9c 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -81,6 +81,7 @@ namespace ESM { AiTravelData mData; bool mHidden; + bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -94,6 +95,7 @@ namespace ESM std::string mTargetId; std::string mCellId; float mRemainingDuration; + bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -112,6 +114,7 @@ namespace ESM bool mCommanded; bool mActive; + bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -120,6 +123,7 @@ namespace ESM struct AiActivate : AiPackage { std::string mTargetId; + bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 8a98a63419..4ce0876bb8 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 17; +int ESM::SavedGame::sCurrentFormat = 18; void ESM::SavedGame::load (ESMReader &esm) {