diff --git a/.travis.yml b/.travis.yml index 3e98b1f96..a669700c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ os: - linux - osx -osx_image: xcode7.3 +osx_image: xcode8.2 language: cpp sudo: required dist: trusty diff --git a/AUTHORS.md b/AUTHORS.md index 185b5ee66..190a2f345 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -71,6 +71,7 @@ Programmers John Blomberg (fstp) Jordan Ayers Jordan Milne + Jules Blok (Armada651) Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) Kevin Poitra (PuppyKevin) @@ -80,6 +81,7 @@ Programmers lazydev Leon Krieg (lkrieg) Leon Saunders (emoose) + logzero lohikaarme Lukasz Gromanowski (lgro) Manuel Edelmann (vorenon) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0add2e02e..87d826753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,78 @@ +0.41.0 +------ + + Bug #1138: Casting water walking doesn't move the player out of the water + Bug #1931: Rocks from blocked passage in Bamz-Amschend, Radacs Forge can reset and cant be removed again. + Bug #2048: Almvisi and Divine Intervention display wrong spell effect + Bug #2054: Show effect-indicator for "instant effect" spells and potions + Bug #2150: Clockwork City door animation problem + Bug #2288: Playback of weapon idle animation not correct + Bug #2410: Stat-review window doesn't display starting spells, powers, or abilities + Bug #2493: Repairing occasionally very slow + Bug #2716: [OSG] Water surface is too transparent from some angles + Bug #2859: [MAC OS X] Cannot exit fullscreen once enabled + Bug #3091: Editor: will not save addon if global variable value type is null + Bug #3277: Editor: Non-functional nested tables in subviews need to be hidden instead of being disabled + Bug #3348: Disabled map markers show on minimap + Bug #3350: Extending selection to instances with same object results in duplicates. + Bug #3353: [Mod] Romance version 3.7 script failed + Bug #3376: [Mod] Vampire Embrace script fails to execute + Bug #3385: Banners don't animate in stormy weather as they do in the original game + Bug #3393: Akulakhan re-enabled after main quest + Bug #3427: Editor: OpenMW-CS instances won´t get deleted + Bug #3451: Feril Salmyn corpse isn't where it is supposed to be + Bug #3497: Zero-weight armor is displayed as "heavy" in inventory tooltip + Bug #3499: Idle animations don't always loop + Bug #3500: Spark showers at Sotha Sil do not appear until you look at the ceiling + Bug #3515: Editor: Moved objects in interior cells are teleported to exterior cells. + Bug #3520: Editor: OpenMW-CS cannot find project file when launching the game + Bug #3521: Armed NPCs don't use correct melee attacks + Bug #3535: Changing cell immediately after dying causes character to freeze. + Bug #3542: Unable to rest if unalerted slaughterfish are in the cell with you + Bug #3549: Blood effects occur even when a hit is resisted + Bug #3551: NPC Todwendy in german version can't interact + Bug #3552: Opening the journal when fonts are missing results in a crash + Bug #3555: SetInvisible command should not apply graphic effect + Bug #3561: Editor: changes from omwaddon are not loaded in [New Addon] mode + Bug #3562: Non-hostile NPCs can be disarmed by stealing their weapons via sneaking + Bug #3564: Editor: openmw-cs verification results + Bug #3568: Items that should be invisible are shown in the inventory + Bug #3574: Alchemy: Alembics and retorts are used in reverse + Bug #3575: Diaglog choices don't work in mw 0.40 + Bug #3576: Minor differences in AI reaction to hostile spell effects + Bug #3577: not local nolore dialog test + Bug #3578: Animation Replacer hangs after one cicle/step + Bug #3579: Bound Armor skillups and sounds + Bug #3583: Targetted GetCurrentAiPackage returns 0 + Bug #3584: Persuasion bug + Bug #3590: Vendor, Ilen Faveran, auto equips items from stock + Bug #3594: Weather doesn't seem to update correctly in Mournhold + Bug #3598: Saving doesn't save status of objects + Bug #3600: Screen goes black when trying to travel to Sadrith Mora + Bug #3608: Water ripples aren't created when walking on water + Bug #3626: Argonian NPCs swim like khajiits + Bug #3627: Cannot delete "Blessed touch" spell from spellbook + Bug #3634: An enchanted throwing weapon consumes charges from the stack in your inventory. (0.40.0) + Bug #3635: Levelled items in merchants are "re-rolled" (not bug 2952, see inside) + Feature #1118: AI combat: flee + Feature #1596: Editor: Render water + Feature #2042: Adding a non-portable Light to the inventory should cause the player to glow + Feature #3166: Editor: Instance editing mode - rotate sub mode + Feature #3167: Editor: Instance editing mode - scale sub mode + Feature #3420: ess-Importer: player control flags + Feature #3489: You shouldn't be be able to re-cast a bound equipment spell + Feature #3496: Zero-weight boots should play light boot footsteps + Feature #3516: Water Walking should give a "can't cast" message and fail when you are too deep + Feature #3519: Play audio and visual effects for all effects in a spell + Feature #3527: Double spell explosion scaling + Feature #3534: Play particle textures for spell effects + Feature #3539: Make NPCs use opponent's weapon range to decide whether to dodge + Feature #3540: Allow dodging for creatures with "biped" flag + Feature #3545: Drop shadow for items in menu + Feature #3558: Implement same spell range for "on touch" spells as original engine + Feature #3560: Allow using telekinesis with touch spells on objects + Task #3585: Some objects added by Morrowind Rebirth do not display properly their texture + 0.40.0 ------ diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index a2ea720e5..49af86a3e 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -7,5 +7,5 @@ brew rm pkgconfig || true brew rm qt5 || true brew install cmake pkgconfig $macos_qt_formula -curl http://downloads.openmw.org/osx/dependencies/openmw-deps-263d4a8.zip -o ~/openmw-deps.zip +curl https://downloads.openmw.org/osx/dependencies/openmw-deps-0ecece4.zip -o ~/openmw-deps.zip unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 6955825a4..f3d0f716b 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -12,7 +12,7 @@ cd build cmake \ -D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.8" \ --D CMAKE_OSX_SYSROOT="macosx10.11" \ +-D CMAKE_OSX_SYSROOT="macosx10.12" \ -D CMAKE_BUILD_TYPE=Debug \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ -D DESIRED_QT_VERSION=5 \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 960259ff6..d19745ff7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ endif() message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 40) +set(OPENMW_VERSION_MINOR 41) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") diff --git a/README.md b/README.md index 3a6c4a75a..d38dfaeb2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ OpenMW is a recreation of the engine for the popular role-playing game Morrowind OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. -* Version: 0.40.0 +* Version: 0.41.0 * License: GPLv3 (see [docs/license/GPL3.txt](https://github.com/OpenMW/openmw/blob/master/docs/license/GPL3.txt) for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net diff --git a/apps/essimporter/convertplayer.cpp b/apps/essimporter/convertplayer.cpp index c363185ee..4a4a9a573 100644 --- a/apps/essimporter/convertplayer.cpp +++ b/apps/essimporter/convertplayer.cpp @@ -75,6 +75,14 @@ namespace ESSImport out.mMarkedPosition.rot[0] = out.mMarkedPosition.rot[1] = 0.0f; out.mMarkedPosition.rot[2] = mark.mRotZ; } + + if (pcdt.mHasENAM) + { + const int cellSize = 8192; + out.mLastKnownExteriorPosition[0] = (pcdt.mENAM.mCellX + 0.5f) * cellSize; + out.mLastKnownExteriorPosition[1] = (pcdt.mENAM.mCellY + 0.5f) * cellSize; + out.mLastKnownExteriorPosition[2] = 0.0f; + } } } diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 2288b149c..6921cce92 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -62,7 +62,10 @@ namespace ESSImport playerCellId.mPaged = true; playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0; mPlayer.mCellId = playerCellId; - //mPlayer.mLastKnownExteriorPosition + mPlayer.mLastKnownExteriorPosition[0] + = mPlayer.mLastKnownExteriorPosition[1] + = mPlayer.mLastKnownExteriorPosition[2] + = 0.0f; mPlayer.mHasMark = 0; mPlayer.mCurrentCrimeId = 0; // TODO mPlayer.mObject.blank(); diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index 85a3c3fd5..8c275a286 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -37,6 +37,14 @@ namespace ESSImport if (esm.isNextSub("NAM9")) esm.skipHSub(); + // Rest state. You shouldn't even be able to save during rest, but skip just in case. + if (esm.isNextSub("RNAM")) + /* + int hoursLeft; + float x, y, z; // resting position + */ + esm.skipHSub(); // 16 bytes + mBounty = 0; esm.getHNOT(mBounty, "CNAM"); @@ -70,12 +78,19 @@ namespace ESSImport mFactions.push_back(fnam); } - if (esm.isNextSub("AADT")) - esm.skipHSub(); // 44 bytes, no clue + mHasAADT = false; + if (esm.isNextSub("AADT")) // Attack animation data? + { + mHasAADT = true; + esm.getHT(mAADT); + } if (esm.isNextSub("KNAM")) esm.skipHSub(); // assigned Quick Keys, I think + if (esm.isNextSub("ANIS")) + esm.skipHSub(); // 16 bytes + if (esm.isNextSub("WERE")) { // some werewolf data, 152 bytes @@ -83,10 +98,6 @@ namespace ESSImport esm.getSubHeader(); esm.skip(152); } - - // unsure if before or after WERE - if (esm.isNextSub("ANIS")) - esm.skipHSub(); } } diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp index 775994444..924522383 100644 --- a/apps/essimporter/importplayer.hpp +++ b/apps/essimporter/importplayer.hpp @@ -98,6 +98,12 @@ struct PCDT int mCellX; int mCellY; }; + + struct AADT // 44 bytes + { + int animGroupIndex; // See convertANIS() for the mapping. + unsigned char mUnknown5[40]; + }; #pragma pack(pop) std::vector mFactions; @@ -109,6 +115,9 @@ struct PCDT bool mHasENAM; ENAM mENAM; // last exterior cell + bool mHasAADT; + AADT mAADT; + void load(ESM::ESMReader& esm); }; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 0f414972f..bd0d2ea4a 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace osg @@ -200,6 +201,10 @@ namespace MWBase virtual std::list getEnemiesNearby(const MWWorld::Ptr& actor) = 0; + /// Recursive versions of above methods + virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) = 0; + virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) = 0; + virtual void playerLoaded() = 0; virtual int countSavedGameRecords() const = 0; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 3102354a6..830c2bcac 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -448,14 +448,14 @@ namespace MWDialogue { MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); - // Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate) - float curDisp = static_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)); - if (curDisp + mPermanentDispositionChange < 0) - mPermanentDispositionChange = -curDisp; - // Apply disposition change to NPC's base disposition if (mActor.getClass().isNpc()) { + // Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate) + float curDisp = static_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)); + if (curDisp + mPermanentDispositionChange < 0) + mPermanentDispositionChange = -curDisp; + MWMechanics::NpcStats& npcStats = mActor.getClass().getNpcStats(mActor); npcStats.setBaseDisposition(static_cast(npcStats.getBaseDisposition() + mPermanentDispositionChange)); } diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 37a84b287..d51918152 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -107,11 +107,11 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const { const MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::NpcStats& stats = player.getClass().getNpcStats (player); - // check player faction + // check player faction and rank if (!info.mPcFaction.empty()) { - MWMechanics::NpcStats& stats = player.getClass().getNpcStats (player); std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (info.mPcFaction)); if(iter==stats.getFactionRanks().end()) @@ -121,6 +121,18 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const if (iter->second < info.mData.mPCrank) return false; } + else if (info.mData.mPCrank != -1) + { + // required PC faction is not specified but PC rank is; use speaker's faction + std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (mActor.getClass().getPrimaryFaction(mActor))); + + if(iter==stats.getFactionRanks().end()) + return false; + + // check rank + if (iter->second < info.mData.mPCrank) + return false; + } // check cell if (!info.mCell.empty()) diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index b7e24a7ee..72e1c09f3 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -428,7 +428,9 @@ namespace MWGui { // split lines const int lineHeight = currentFontHeight(); - unsigned int lastLine = (mPaginator.getStartTop() + mPaginator.getPageHeight() - mPaginator.getCurrentTop()) / lineHeight; + unsigned int lastLine = (mPaginator.getStartTop() + mPaginator.getPageHeight() - mPaginator.getCurrentTop()); + if (lineHeight > 0) + lastLine /= lineHeight; int ret = mPaginator.getCurrentTop() + lastLine * lineHeight; // first empty lines that would go to the next page should be ignored diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 8c55c3732..3063b3268 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -55,7 +55,7 @@ namespace MWGui void TravelWindow::addDestination(const std::string& name,ESM::Position pos,bool interior) { - int price = 0; + int price; const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); @@ -70,14 +70,21 @@ namespace MWGui else { ESM::Position PlayerPos = player.getRefData().getPosition(); - float d = sqrt( pow(pos.pos[0] - PlayerPos.pos[0],2) + pow(pos.pos[1] - PlayerPos.pos[1],2) + pow(pos.pos[2] - PlayerPos.pos[2],2) ); + float d = sqrt(pow(pos.pos[0] - PlayerPos.pos[0], 2) + pow(pos.pos[1] - PlayerPos.pos[1], 2) + pow(pos.pos[2] - PlayerPos.pos[2], 2)); price = static_cast(d / gmst.find("fTravelMult")->getFloat()); } - price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); + price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); + + // Add price for the travelling followers + std::set followers; + MWWorld::ActionTeleport::getFollowersToTeleport(player, followers); + + // Apply followers cost, in vanilla one follower travels for free + price *= std::max(1, static_cast(followers.size())); MyGUI::Button* toAdd = mDestinationsView->createWidget("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default); - toAdd->setEnabled(price<=playerGold); + toAdd->setEnabled(price <= playerGold); mCurrentY += sLineHeight; if(interior) toAdd->setUserString("interior","y"); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 23a6f497f..cd8f18777 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -29,6 +29,7 @@ #include "movement.hpp" #include "character.hpp" #include "aicombat.hpp" +#include "aicombataction.hpp" #include "aifollow.hpp" #include "aipursue.hpp" #include "actor.hpp" @@ -286,10 +287,12 @@ namespace MWMechanics void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer) { - CreatureStats& creatureStats = actor1.getClass().getCreatureStats(actor1); + const CreatureStats& creatureStats1 = actor1.getClass().getCreatureStats(actor1); + if (creatureStats1.getAiSequence().isInCombat(actor2)) + return; - if (actor2.getClass().getCreatureStats(actor2).isDead() - || actor1.getClass().getCreatureStats(actor1).isDead()) + const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2); + if (creatureStats1.isDead() || creatureStats2.isDead()) return; const ESM::Position& actor1Pos = actor1.getRefData().getPosition(); @@ -298,55 +301,26 @@ namespace MWMechanics if (sqrDist > sqrAiProcessingDistance) return; - // pure water creatures won't try to fight with the target on the ground - // except that creature is already hostile - if ((againstPlayer || !creatureStats.getAiSequence().isInCombat()) - && !MWMechanics::isEnvironmentCompatible(actor1, actor2)) // creature can't swim to target - { - return; - } - - // no combat for totally static creatures (they have no movement or attack animations anyway) + // No combat for totally static creatures if (!actor1.getClass().isMobile(actor1)) return; - bool aggressive; - - if (againstPlayer) - { - // followers with high fight should not engage in combat with the player (e.g. bm_bear_black_summon) - const std::list& followers = getActorsSidingWith(actor2); - if (std::find(followers.begin(), followers.end(), actor1) != followers.end()) - return; - - aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); - } - else + // Start combat if target actor is in combat with one of our followers or escorters + const std::list& followersAndEscorters = getActorsSidingWith(actor1); + for (std::list::const_iterator it = followersAndEscorters.begin(); it != followersAndEscorters.end(); ++it) { - aggressive = false; - - // Make guards fight aggressive creatures - if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard")) - { - if (creatureStats.getAiSequence().isInCombat() && MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2)) - aggressive = true; - } - } - - // start combat if target actor is in combat with one of our followers - const std::list& followers = getActorsSidingWith(actor1); - const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2); - for (std::list::const_iterator it = followers.begin(); it != followers.end(); ++it) - { - // need to check both ways since player doesn't use AI packages + // Need to check both ways since player doesn't use AI packages if ((creatureStats2.getAiSequence().isInCombat(*it) || it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2)) - && !creatureStats.getAiSequence().isInCombat(*it)) - aggressive = true; + && !creatureStats1.getAiSequence().isInCombat(*it)) + { + MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + return; + } } - // start combat if target actor is in combat with someone we are following - for (std::list::const_iterator it = creatureStats.getAiSequence().begin(); it != creatureStats.getAiSequence().end(); ++it) + // Start combat if target actor is in combat with someone we are following through a follow package + for (std::list::const_iterator it = creatureStats1.getAiSequence().begin(); it != creatureStats1.getAiSequence().end(); ++it) { if (!(*it)->sideWithTarget()) continue; @@ -356,20 +330,63 @@ namespace MWMechanics if (followTarget.isEmpty()) continue; - if (creatureStats.getAiSequence().isInCombat(followTarget)) + if (creatureStats1.getAiSequence().isInCombat(followTarget)) continue; - // need to check both ways since player doesn't use AI packages + // Need to check both ways since player doesn't use AI packages if (creatureStats2.getAiSequence().isInCombat(followTarget) || followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2)) - aggressive = true; + { + MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + return; + } + } + + // Start combat with the player if we are already in combat with a player follower or escorter + const std::list& playerFollowersAndEscorters = getActorsSidingWith(getPlayer()); + if (againstPlayer) + { + for (std::list::const_iterator it = playerFollowersAndEscorters.begin(); it != playerFollowersAndEscorters.end(); ++it) + { + if (creatureStats1.getAiSequence().isInCombat(*it)) + { + MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + return; + } + } + } + + // Otherwise, don't initiate combat with an unreachable target + if (!MWMechanics::canFight(actor1,actor2)) + return; + + bool aggressive = false; + + if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end()) + { + // Player followers and escorters with high fight should not initiate combat here with the player or with + // other player followers or escorters + if (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) != playerFollowersAndEscorters.end()) + return; + + aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); + } + else + { + // Make guards fight aggressive creatures + if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard")) + { + if (creatureStats1.getAiSequence().isInCombat() && MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2)) + aggressive = true; + } } - if(aggressive) + if (aggressive) { bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); - if (againstPlayer) LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); + if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end()) + LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); if (LOS) { @@ -1482,6 +1499,20 @@ namespace MWMechanics return list; } + void Actors::getActorsFollowing(const MWWorld::Ptr &actor, std::set& out) { + std::list followers = getActorsFollowing(actor); + for(std::list::iterator it = followers.begin();it != followers.end();++it) + if (out.insert(*it).second) + getActorsFollowing(*it, out); + } + + void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out) { + std::list followers = getActorsSidingWith(actor); + for(std::list::iterator it = followers.begin();it != followers.end();++it) + if (out.insert(*it).second) + getActorsSidingWith(*it, out); + } + std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) { std::list list; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 163995f6f..20aef4c17 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -123,6 +123,11 @@ namespace MWMechanics std::list getActorsSidingWith(const MWWorld::Ptr& actor); std::list getActorsFollowing(const MWWorld::Ptr& actor); + /// Recursive version of getActorsFollowing + void getActorsFollowing(const MWWorld::Ptr &actor, std::set& out); + /// Recursive version of getActorsSidingWith + void getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out); + /// Get the list of AiFollow::mFollowIndex for all actors following this target std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index d8dae8b79..fbd3819e2 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -19,6 +19,7 @@ #include "aicombataction.hpp" #include "combat.hpp" #include "coordinateconverter.hpp" +#include "actorutil.hpp" namespace { @@ -210,13 +211,14 @@ namespace MWMechanics else { timerReact = 0; - attack(actor, target, storage, characterController); + if (attack(actor, target, storage, characterController)) + return true; } return false; } - void AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) + bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) { const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); @@ -231,7 +233,10 @@ namespace MWMechanics storage.stopAttack(); characterController.setAttackingOrSpell(false); storage.mActionCooldown = 0.f; - forceFlee = true; + if (target == MWMechanics::getPlayer()) + forceFlee = true; + else + return true; } const MWWorld::Class& actorClass = actor.getClass(); @@ -243,7 +248,7 @@ namespace MWMechanics if (!forceFlee) { if (actionCooldown > 0) - return; + return false; if (characterController.readyToPrepareAttack()) { @@ -258,7 +263,7 @@ namespace MWMechanics } if (!currentAction) - return; + return false; if (storage.isFleeing() != currentAction->isFleeing()) { @@ -266,7 +271,7 @@ namespace MWMechanics { storage.startFleeing(); MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); - return; + return false; } else storage.stopFleeing(); @@ -311,6 +316,7 @@ namespace MWMechanics storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } } + return false; } void MWMechanics::AiCombat::updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index a2e995cb3..fbe864ca0 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -59,7 +59,8 @@ namespace MWMechanics int mTargetActorId; - void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); + /// Returns true if combat should end + bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); void updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 97fa98b3b..e552f4683 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -978,18 +978,6 @@ namespace MWMechanics } - void getFollowers (const MWWorld::Ptr& actor, std::set& out) - { - std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(actor); - for(std::list::iterator it = followers.begin();it != followers.end();++it) - { - if (out.insert(*it).second) - { - getFollowers(*it, out); - } - } - } - bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg, bool victimAware) { // NOTE: victim may be empty @@ -1013,7 +1001,7 @@ namespace MWMechanics // get the player's followers / allies (works recursively) that will not report crimes std::set playerFollowers; - getFollowers(player, playerFollowers); + getActorsSidingWith(player, playerFollowers); // Did anyone see it? bool crimeSeen = false; @@ -1437,6 +1425,14 @@ namespace MWMechanics return mActors.getEnemiesNearby(actor); } + void MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) { + mActors.getActorsFollowing(actor, out); + } + + void MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) { + mActors.getActorsSidingWith(actor, out); + } + int MechanicsManager::countSavedGameRecords() const { return 1 // Death counter diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 04c67fcb6..ed06f58c5 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -165,6 +165,11 @@ namespace MWMechanics virtual std::list getActorsFighting(const MWWorld::Ptr& actor); virtual std::list getEnemiesNearby(const MWWorld::Ptr& actor); + /// Recursive version of getActorsFollowing + virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out); + /// Recursive version of getActorsSidingWith + virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out); + virtual bool toggleAI(); virtual bool isAIActive(); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 43d77b99d..489ab83ec 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -613,6 +613,11 @@ namespace MWMechanics return true; } } + else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel) + { + target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude); + return true; + } else if (target.getClass().isActor() && target == getPlayer()) { MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster); @@ -1140,9 +1145,6 @@ namespace MWMechanics case ESM::MagicEffect::CureCorprusDisease: actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease(); break; - case ESM::MagicEffect::Dispel: - actor.getClass().getCreatureStats(actor).getActiveSpells().purgeAll(magnitude); - break; case ESM::MagicEffect::RemoveCurse: actor.getClass().getCreatureStats(actor).getSpells().purgeCurses(); break; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 544fb0199..2d0964f25 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -55,29 +55,46 @@ namespace MWPhysics static const float sMaxSlope = 49.0f; static const float sStepSizeUp = 34.0f; static const float sStepSizeDown = 62.0f; + static const float sMinStep = 10.f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static const int sMaxIterations = 8; - // FIXME: move to a separate file - class MovementSolver + static bool isActor(const btCollisionObject *obj) + { + assert(obj); + return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; + } + + template + static bool isWalkableSlope(const Vec3 &normal) + { + static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); + return (normal.z() > sMaxSlopeCos); + } + + static bool canStepDown(const ActorTracer &stepper) + { + return stepper.mHitObject && isWalkableSlope(stepper.mPlaneNormal) && !isActor(stepper.mHitObject); + } + + class Stepper { private: - static float getSlope(osg::Vec3f normal) - { - normal.normalize(); - return osg::RadiansToDegrees(std::acos(normal * osg::Vec3f(0.f, 0.f, 1.f))); - } + const btCollisionWorld *mColWorld; + const btCollisionObject *mColObj; - enum StepMoveResult - { - Result_Blocked, // unable to move over obstacle - Result_MaxSlope, // unable to end movement on this slope - Result_Success - }; + ActorTracer mTracer, mUpStepper, mDownStepper; + bool mHaveMoved; - static StepMoveResult stepMove(const btCollisionObject *colobj, osg::Vec3f &position, - const osg::Vec3f &toMove, float &remainingTime, const btCollisionWorld* collisionWorld) + public: + Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj) + : mColWorld(colWorld) + , mColObj(colObj) + , mHaveMoved(true) + {} + + bool step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime) { /* * Slide up an incline or set of stairs. Should be called only after a @@ -123,12 +140,14 @@ namespace MWPhysics * +--+ +-------- * ============================================== */ - ActorTracer tracer, stepper; - - stepper.doTrace(colobj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), collisionWorld); - if(stepper.mFraction < std::numeric_limits::epsilon()) - return Result_Blocked; // didn't even move the smallest representable amount - // (TODO: shouldn't this be larger? Why bother with such a small amount?) + if (mHaveMoved) + { + mHaveMoved = false; + mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld); + if(mUpStepper.mFraction < std::numeric_limits::epsilon()) + return false; // didn't even move the smallest representable amount + // (TODO: shouldn't this be larger? Why bother with such a small amount?) + } /* * Try moving from the elevated position using tracer. @@ -143,9 +162,10 @@ namespace MWPhysics * +--+ * ============================================== */ - tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, collisionWorld); - if(tracer.mFraction < std::numeric_limits::epsilon()) - return Result_Blocked; // didn't even move the smallest representable amount + osg::Vec3f tracerPos = mUpStepper.mEndPos; + mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld); + if(mTracer.mFraction < std::numeric_limits::epsilon()) + return false; // didn't even move the smallest representable amount /* * Try moving back down sStepSizeDown using stepper. @@ -162,26 +182,40 @@ namespace MWPhysics * +--+ +--+ * ============================================== */ - stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), collisionWorld); - if (getSlope(stepper.mPlaneNormal) > sMaxSlope) - return Result_MaxSlope; - if(stepper.mFraction < 1.0f) + mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld); + if (!canStepDown(mDownStepper)) + { + // Try again with increased step length + if (mTracer.mFraction < 1.0f || toMove.length2() > sMinStep*sMinStep) + return false; + + osg::Vec3f direction = toMove; + direction.normalize(); + mTracer.doTrace(mColObj, tracerPos, tracerPos + direction*sMinStep, mColWorld); + if (mTracer.mFraction < 0.001f) + return false; + + mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld); + if (!canStepDown(mDownStepper)) + return false; + } + if (mDownStepper.mFraction < 1.0f) { - // don't allow stepping up other actors - if (stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) - return Result_Blocked; // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing // NOTE: caller's variables 'position' & 'remainingTime' are modified here - position = stepper.mEndPos; - remainingTime *= (1.0f-tracer.mFraction); // remaining time is proportional to remaining distance - return Result_Success; + position = mDownStepper.mEndPos; + remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance + mHaveMoved = true; + return true; } - - return Result_Blocked; + return false; } + }; - + class MovementSolver + { + private: ///Project a vector u on another vector v static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) { @@ -229,14 +263,14 @@ namespace MWPhysics collisionWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit() && - ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length() > 35 - || getSlope(tracer.mPlaneNormal) > sMaxSlope)) + ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length2() > 35*35 + || !isWalkableSlope(tracer.mPlaneNormal))) { - actor->setOnGround(getSlope(toOsg(resultCallback1.m_hitNormalWorld)) <= sMaxSlope); + actor->setOnGround(isWalkableSlope(resultCallback1.m_hitNormalWorld)); return toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, 1.f); } - actor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope); + actor->setOnGround(isWalkableSlope(tracer.mPlaneNormal)); return tracer.mEndPos; } @@ -312,8 +346,8 @@ namespace MWPhysics velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); } + Stepper stepper(collisionWorld, colobj); osg::Vec3f origVelocity = velocity; - osg::Vec3f newPosition = position; /* * A loop to find newPosition using tracer, if successful different from the starting position. @@ -332,10 +366,7 @@ namespace MWPhysics newPosition.z() <= swimlevel) { const osg::Vec3f down(0,0,-1); - float movelen = velocity.normalize(); - osg::Vec3f reflectdir = reflect(velocity, down); - reflectdir.normalize(); - velocity = slide(reflectdir, down)*movelen; + velocity = slide(velocity, down); // NOTE: remainingTime is unchanged before the loop continues continue; // velocity updated, calculate nextpos again } @@ -364,19 +395,25 @@ namespace MWPhysics break; } + // We are touching something. + if (tracer.mFraction < 1E-9f) + { + // Try to separate by backing off slighly to unstuck the solver + const osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-3f; + newPosition += backOff; + } + // We hit something. Check if we can step up. + float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); osg::Vec3f oldPosition = newPosition; - // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) - // NOTE: stepMove modifies newPosition if successful - const float minStep = 10.f; - StepMoveResult result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, collisionWorld); - if (result == Result_MaxSlope && (velocity*remainingTime).length() < minStep) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent + bool result = false; + if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject)) { - osg::Vec3f normalizedVelocity = velocity; - normalizedVelocity.normalize(); - result = stepMove(colobj, newPosition, normalizedVelocity*minStep, remainingTime, collisionWorld); + // Try to step up onto it. + // NOTE: stepMove does not allow stepping over, modifies newPosition if successful + result = stepper.step(newPosition, velocity*remainingTime, remainingTime); } - if(result == Result_Success) + if (result) { // don't let pure water creatures move out of water after stepMove if (ptr.getClass().isPureWaterCreature(ptr) @@ -386,23 +423,19 @@ namespace MWPhysics else { // Can't move this way, try to find another spot along the plane - osg::Vec3f direction = velocity; - float movelen = direction.normalize(); - osg::Vec3f reflectdir = reflect(velocity, tracer.mPlaneNormal); - reflectdir.normalize(); + osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal); + + // Do not allow sliding upward if there is gravity. + // Stepping will have taken care of that. + if(!(newPosition.z() < swimlevel || isFlying)) + newVelocity.z() = std::min(newVelocity.z(), 0.0f); - osg::Vec3f newVelocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; if ((newVelocity-velocity).length2() < 0.01) break; - if ((velocity * origVelocity) <= 0.f) + if ((newVelocity * origVelocity) <= 0.f) break; // ^ dot product velocity = newVelocity; - - // Do not allow sliding upward if there is gravity. Stepping will have taken - // care of that. - if(!(newPosition.z() < swimlevel || isFlying)) - velocity.z() = std::min(velocity.z(), 0.0f); } } @@ -413,7 +446,7 @@ namespace MWPhysics osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown+2.f) : osg::Vec3f(0,0,2.f)); tracer.doTrace(colobj, from, to, collisionWorld); - if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope + if(tracer.mFraction < 1.0f && isWalkableSlope(tracer.mPlaneNormal) && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor) { const btCollisionObject* standingOn = tracer.mHitObject; @@ -996,6 +1029,8 @@ namespace MWPhysics bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { const Actor* physicActor = getActor(actor); + if (!physicActor) + return false; const float halfZ = physicActor->getHalfExtents().z(); const osg::Vec3f actorPosition = physicActor->getPosition(); const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 420ca1a9e..feda68ca5 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -78,6 +78,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star mFraction = newTraceCallback.m_closestHitFraction; mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mEndPos = (end-start)*mFraction + start; + mHitPoint = toOsg(newTraceCallback.m_hitPointWorld); mHitObject = newTraceCallback.m_hitCollisionObject; } else @@ -85,6 +86,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star mEndPos = end; mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; + mHitPoint = end; mHitObject = NULL; } } diff --git a/apps/openmw/mwphysics/trace.h b/apps/openmw/mwphysics/trace.h index 7b7d0391e..0297c9e07 100644 --- a/apps/openmw/mwphysics/trace.h +++ b/apps/openmw/mwphysics/trace.h @@ -15,6 +15,7 @@ namespace MWPhysics { osg::Vec3f mEndPos; osg::Vec3f mPlaneNormal; + osg::Vec3f mHitPoint; const btCollisionObject* mHitObject; float mFraction; diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 6663b8b29..5cf1ecd36 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -144,6 +145,10 @@ namespace MWRender image->allocateImage(mWidth, mHeight, 1, GL_RGB, GL_UNSIGNED_BYTE); unsigned char* data = image->data(); + osg::ref_ptr alphaImage = new osg::Image; + alphaImage->allocateImage(mWidth, mHeight, 1, GL_ALPHA, GL_UNSIGNED_BYTE); + unsigned char* alphaData = alphaImage->data(); + for (int x = mMinX; x <= mMaxX; ++x) { for (int y = mMinY; y <= mMaxY; ++y) @@ -208,6 +213,8 @@ namespace MWRender data[texelY * mWidth * 3 + texelX * 3] = r; data[texelY * mWidth * 3 + texelX * 3+1] = g; data[texelY * mWidth * 3 + texelX * 3+2] = b; + + alphaData[texelY * mWidth+ texelX] = (y2 < 0) ? static_cast(0) : static_cast(255); } } loadingListener->increaseProgress(); @@ -224,6 +231,14 @@ namespace MWRender mBaseTexture->setImage(image); mBaseTexture->setResizeNonPowerOfTwoHint(false); + mAlphaTexture = new osg::Texture2D; + mAlphaTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mAlphaTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mAlphaTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mAlphaTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mAlphaTexture->setImage(alphaImage); + mAlphaTexture->setResizeNonPowerOfTwoHint(false); + clear(); loadingListener->loadingOff(); @@ -299,6 +314,28 @@ namespace MWRender stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + if (mAlphaTexture) + { + osg::ref_ptr texcoords = new osg::Vec2Array; + + float x1 = x / static_cast(mWidth); + float x2 = (x + width) / static_cast(mWidth); + float y1 = y / static_cast(mHeight); + float y2 = (y + height) / static_cast(mHeight); + texcoords->push_back(osg::Vec2f(x1, y1)); + texcoords->push_back(osg::Vec2f(x1, y2)); + texcoords->push_back(osg::Vec2f(x2, y2)); + texcoords->push_back(osg::Vec2f(x2, y1)); + geom->setTexCoordArray(1, texcoords, osg::Array::BIND_PER_VERTEX); + + stateset->setTextureAttributeAndModes(1, mAlphaTexture, osg::StateAttribute::ON); + osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; + texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); + texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + stateset->setTextureAttributeAndModes(1, texEnvCombine); + } + camera->addChild(geom); } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index df8aa9962..1c44439fd 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -107,6 +107,7 @@ namespace MWRender std::vector< std::pair > mExploredCells; osg::ref_ptr mBaseTexture; + osg::ref_ptr mAlphaTexture; // GPU copy of overlay // Note, uploads are pushed through a Camera, instead of through mOverlayImage diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index f94cf9b43..fb259ff5f 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -437,7 +437,9 @@ bool OpenAL_SoundStream::process() alGetSourcei(mSource, AL_SOURCE_STATE, &state); if(state != AL_PLAYING && state != AL_PAUSED) { + // Ensure all processed buffers are removed so we don't replay them. refillQueue(); + alSourcePlay(mSource); } } @@ -906,7 +908,10 @@ void OpenAL_Output::finishSound(MWBase::SoundPtr sound) ALuint source = GET_PTRID(sound->mHandle); sound->mHandle = 0; - alSourceStop(source); + // Rewind the stream instead of stopping it, this puts the source into an AL_INITIAL state, + // which works around a bug in the MacOS OpenAL implementation which would otherwise think + // the initial queue already played when it hasn't. + alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); mFreeSources.push_back(source); @@ -1006,7 +1011,10 @@ void OpenAL_Output::finishStream(MWBase::SoundStreamPtr sound) sound->mHandle = 0; mStreamThread->remove(stream); - alSourceStop(source); + // Rewind the stream instead of stopping it, this puts the source into an AL_INITIAL state, + // which works around a bug in the MacOS OpenAL implementation which would otherwise think + // the initial queue already played when it hasn't. + alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); mFreeSources.push_back(source); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index c9315283d..5162cac66 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -8,23 +8,6 @@ #include "player.hpp" -namespace -{ - - void getFollowers (const MWWorld::Ptr& actor, std::set& out) - { - std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor); - for(std::list::iterator it = followers.begin();it != followers.end();++it) - { - if (out.insert(*it).second) - { - getFollowers(*it, out); - } - } - } - -} - namespace MWWorld { ActionTeleport::ActionTeleport (const std::string& cellName, @@ -37,21 +20,12 @@ namespace MWWorld { if (mTeleportFollowers) { - //find any NPC that is following the actor and teleport him too + // Find any NPCs that are following the actor and teleport them with him std::set followers; - getFollowers(actor, followers); - for(std::set::iterator it = followers.begin();it != followers.end();++it) - { - MWWorld::Ptr follower = *it; - - std::string script = follower.getClass().getScript(follower); - if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) - continue; + getFollowersToTeleport(actor, followers); - if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() - <= 800*800) - teleport(*it); - } + for (std::set::iterator it = followers.begin(); it != followers.end(); ++it) + teleport(*it); } teleport(actor); @@ -82,4 +56,21 @@ namespace MWWorld world->moveObject(actor,world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); } } + + void ActionTeleport::getFollowersToTeleport(const MWWorld::Ptr& actor, std::set& out) { + std::set followers; + MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers); + + for(std::set::iterator it = followers.begin();it != followers.end();++it) + { + MWWorld::Ptr follower = *it; + + std::string script = follower.getClass().getScript(follower); + if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) + continue; + + if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() <= 800*800) + out.insert(follower); + } + } } diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp index 6191ee9f6..c58218750 100644 --- a/apps/openmw/mwworld/actionteleport.hpp +++ b/apps/openmw/mwworld/actionteleport.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWWORLD_ACTIONTELEPORT_H #define GAME_MWWORLD_ACTIONTELEPORT_H +#include #include #include @@ -23,9 +24,12 @@ namespace MWWorld public: - ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers); - ///< If cellName is empty, an exterior cell is assumed. + /// If cellName is empty, an exterior cell is assumed. /// @param teleportFollowers Whether to teleport any following actors of the target actor as well. + ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers); + + /// Outputs every actor follower who is in teleport range and wasn't ordered to not enter interiors + static void getFollowersToTeleport(const MWWorld::Ptr& actor, std::set& out); }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 5e65bad7c..ff3427e12 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -137,6 +137,9 @@ namespace iter->load (state); return; } + + std::cerr << "Dropping reference to " << state.mRef.mRefID << " (invalid content file link)" << std::endl; + return; } // new reference diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 9f8bae280..d1b71bbd5 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -402,8 +402,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) std::pair, bool> itemsSlots = weapon->getClass().getEquipmentSlots (*weapon); - for (std::vector::const_iterator slot (itemsSlots.first.begin()); - slot!=itemsSlots.first.end(); ++slot) + if (!itemsSlots.first.empty()) { if (!itemsSlots.second) { @@ -413,8 +412,8 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) } } - slots_[*slot] = weapon; - break; + int slot = itemsSlots.first.front(); + slots_[slot] = weapon; } break; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 4d342336d..150d70c93 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -280,7 +280,9 @@ namespace MWWorld MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (size_t it = 0; it != state.mSoundIds.size(); it++) { - state.mSounds.push_back(sndMgr->playSound3D(pos, state.mSoundIds.at(it), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); + MWBase::SoundPtr sound = sndMgr->playSound3D(pos, state.mSoundIds.at(it), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + if (sound) + state.mSounds.push_back(sound); } mMagicBolts.push_back(state); @@ -571,8 +573,10 @@ namespace MWWorld for (size_t soundIter = 0; soundIter != state.mSoundIds.size(); soundIter++) { - state.mSounds.push_back(sndMgr->playSound3D(esm.mPosition, state.mSoundIds.at(soundIter), 1.0f, 1.0f, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); + MWBase::SoundPtr sound = sndMgr->playSound3D(esm.mPosition, state.mSoundIds.at(soundIter), 1.0f, 1.0f, + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + if (sound) + state.mSounds.push_back(sound); } mMagicBolts.push_back(state);