1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-28 12:40:06 +00:00
This commit is contained in:
Ryan Tucker 2017-01-07 23:50:20 -08:00
commit 777c4b9aad
34 changed files with 471 additions and 206 deletions

View File

@ -1,7 +1,7 @@
os: os:
- linux - linux
- osx - osx
osx_image: xcode7.3 osx_image: xcode8.2
language: cpp language: cpp
sudo: required sudo: required
dist: trusty dist: trusty

View File

@ -71,6 +71,7 @@ Programmers
John Blomberg (fstp) John Blomberg (fstp)
Jordan Ayers Jordan Ayers
Jordan Milne Jordan Milne
Jules Blok (Armada651)
Julien Voisin (jvoisin/ap0) Julien Voisin (jvoisin/ap0)
Karl-Felix Glatzer (k1ll) Karl-Felix Glatzer (k1ll)
Kevin Poitra (PuppyKevin) Kevin Poitra (PuppyKevin)
@ -80,6 +81,7 @@ Programmers
lazydev lazydev
Leon Krieg (lkrieg) Leon Krieg (lkrieg)
Leon Saunders (emoose) Leon Saunders (emoose)
logzero
lohikaarme lohikaarme
Lukasz Gromanowski (lgro) Lukasz Gromanowski (lgro)
Manuel Edelmann (vorenon) Manuel Edelmann (vorenon)

View File

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

View File

@ -7,5 +7,5 @@ brew rm pkgconfig || true
brew rm qt5 || true brew rm qt5 || true
brew install cmake pkgconfig $macos_qt_formula 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 unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

View File

@ -12,7 +12,7 @@ cd build
cmake \ cmake \
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ -D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.8" \ -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 CMAKE_BUILD_TYPE=Debug \
-D OPENMW_OSX_DEPLOYMENT=TRUE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \
-D DESIRED_QT_VERSION=5 \ -D DESIRED_QT_VERSION=5 \

View File

@ -25,7 +25,7 @@ endif()
message(STATUS "Configuring OpenMW...") message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 40) set(OPENMW_VERSION_MINOR 41)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")

View File

@ -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. 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) * 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 * Website: http://www.openmw.org
* IRC: #openmw on irc.freenode.net * IRC: #openmw on irc.freenode.net

View File

@ -75,6 +75,14 @@ namespace ESSImport
out.mMarkedPosition.rot[0] = out.mMarkedPosition.rot[1] = 0.0f; out.mMarkedPosition.rot[0] = out.mMarkedPosition.rot[1] = 0.0f;
out.mMarkedPosition.rot[2] = mark.mRotZ; 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;
}
} }
} }

View File

@ -62,7 +62,10 @@ namespace ESSImport
playerCellId.mPaged = true; playerCellId.mPaged = true;
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0; playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
mPlayer.mCellId = playerCellId; mPlayer.mCellId = playerCellId;
//mPlayer.mLastKnownExteriorPosition mPlayer.mLastKnownExteriorPosition[0]
= mPlayer.mLastKnownExteriorPosition[1]
= mPlayer.mLastKnownExteriorPosition[2]
= 0.0f;
mPlayer.mHasMark = 0; mPlayer.mHasMark = 0;
mPlayer.mCurrentCrimeId = 0; // TODO mPlayer.mCurrentCrimeId = 0; // TODO
mPlayer.mObject.blank(); mPlayer.mObject.blank();

View File

@ -37,6 +37,14 @@ namespace ESSImport
if (esm.isNextSub("NAM9")) if (esm.isNextSub("NAM9"))
esm.skipHSub(); 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; mBounty = 0;
esm.getHNOT(mBounty, "CNAM"); esm.getHNOT(mBounty, "CNAM");
@ -70,12 +78,19 @@ namespace ESSImport
mFactions.push_back(fnam); mFactions.push_back(fnam);
} }
if (esm.isNextSub("AADT")) mHasAADT = false;
esm.skipHSub(); // 44 bytes, no clue if (esm.isNextSub("AADT")) // Attack animation data?
{
mHasAADT = true;
esm.getHT(mAADT);
}
if (esm.isNextSub("KNAM")) if (esm.isNextSub("KNAM"))
esm.skipHSub(); // assigned Quick Keys, I think esm.skipHSub(); // assigned Quick Keys, I think
if (esm.isNextSub("ANIS"))
esm.skipHSub(); // 16 bytes
if (esm.isNextSub("WERE")) if (esm.isNextSub("WERE"))
{ {
// some werewolf data, 152 bytes // some werewolf data, 152 bytes
@ -83,10 +98,6 @@ namespace ESSImport
esm.getSubHeader(); esm.getSubHeader();
esm.skip(152); esm.skip(152);
} }
// unsure if before or after WERE
if (esm.isNextSub("ANIS"))
esm.skipHSub();
} }
} }

View File

@ -98,6 +98,12 @@ struct PCDT
int mCellX; int mCellX;
int mCellY; int mCellY;
}; };
struct AADT // 44 bytes
{
int animGroupIndex; // See convertANIS() for the mapping.
unsigned char mUnknown5[40];
};
#pragma pack(pop) #pragma pack(pop)
std::vector<FNAM> mFactions; std::vector<FNAM> mFactions;
@ -109,6 +115,9 @@ struct PCDT
bool mHasENAM; bool mHasENAM;
ENAM mENAM; // last exterior cell ENAM mENAM; // last exterior cell
bool mHasAADT;
AADT mAADT;
void load(ESM::ESMReader& esm); void load(ESM::ESMReader& esm);
}; };

View File

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <list> #include <list>
#include <set>
#include <stdint.h> #include <stdint.h>
namespace osg namespace osg
@ -200,6 +201,10 @@ namespace MWBase
virtual std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) = 0; virtual std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) = 0;
/// Recursive versions of above methods
virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) = 0;
virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) = 0;
virtual void playerLoaded() = 0; virtual void playerLoaded() = 0;
virtual int countSavedGameRecords() const = 0; virtual int countSavedGameRecords() const = 0;

View File

@ -448,14 +448,14 @@ namespace MWDialogue
{ {
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
// 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) // Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate)
float curDisp = static_cast<float>(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)); float curDisp = static_cast<float>(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false));
if (curDisp + mPermanentDispositionChange < 0) if (curDisp + mPermanentDispositionChange < 0)
mPermanentDispositionChange = -curDisp; mPermanentDispositionChange = -curDisp;
// Apply disposition change to NPC's base disposition
if (mActor.getClass().isNpc())
{
MWMechanics::NpcStats& npcStats = mActor.getClass().getNpcStats(mActor); MWMechanics::NpcStats& npcStats = mActor.getClass().getNpcStats(mActor);
npcStats.setBaseDisposition(static_cast<int>(npcStats.getBaseDisposition() + mPermanentDispositionChange)); npcStats.setBaseDisposition(static_cast<int>(npcStats.getBaseDisposition() + mPermanentDispositionChange));
} }

View File

@ -107,11 +107,11 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const
bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const
{ {
const MWWorld::Ptr player = MWMechanics::getPlayer(); 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()) if (!info.mPcFaction.empty())
{ {
MWMechanics::NpcStats& stats = player.getClass().getNpcStats (player);
std::map<std::string,int>::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (info.mPcFaction)); std::map<std::string,int>::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (info.mPcFaction));
if(iter==stats.getFactionRanks().end()) if(iter==stats.getFactionRanks().end())
@ -121,6 +121,18 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const
if (iter->second < info.mData.mPCrank) if (iter->second < info.mData.mPCrank)
return false; return false;
} }
else if (info.mData.mPCrank != -1)
{
// required PC faction is not specified but PC rank is; use speaker's faction
std::map<std::string,int>::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 // check cell
if (!info.mCell.empty()) if (!info.mCell.empty())

View File

@ -428,7 +428,9 @@ namespace MWGui
{ {
// split lines // split lines
const int lineHeight = currentFontHeight(); 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; int ret = mPaginator.getCurrentTop() + lastLine * lineHeight;
// first empty lines that would go to the next page should be ignored // first empty lines that would go to the next page should be ignored

View File

@ -55,7 +55,7 @@ namespace MWGui
void TravelWindow::addDestination(const std::string& name,ESM::Position pos,bool interior) void TravelWindow::addDestination(const std::string& name,ESM::Position pos,bool interior)
{ {
int price = 0; int price;
const MWWorld::Store<ESM::GameSetting> &gmst = const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
@ -70,14 +70,21 @@ namespace MWGui
else else
{ {
ESM::Position PlayerPos = player.getRefData().getPosition(); 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<int>(d / gmst.find("fTravelMult")->getFloat()); price = static_cast<int>(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<MWWorld::Ptr> followers;
MWWorld::ActionTeleport::getFollowersToTeleport(player, followers);
// Apply followers cost, in vanilla one follower travels for free
price *= std::max(1, static_cast<int>(followers.size()));
MyGUI::Button* toAdd = mDestinationsView->createWidget<MyGUI::Button>("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default); MyGUI::Button* toAdd = mDestinationsView->createWidget<MyGUI::Button>("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default);
toAdd->setEnabled(price<=playerGold); toAdd->setEnabled(price <= playerGold);
mCurrentY += sLineHeight; mCurrentY += sLineHeight;
if(interior) if(interior)
toAdd->setUserString("interior","y"); toAdd->setUserString("interior","y");

View File

@ -29,6 +29,7 @@
#include "movement.hpp" #include "movement.hpp"
#include "character.hpp" #include "character.hpp"
#include "aicombat.hpp" #include "aicombat.hpp"
#include "aicombataction.hpp"
#include "aifollow.hpp" #include "aifollow.hpp"
#include "aipursue.hpp" #include "aipursue.hpp"
#include "actor.hpp" #include "actor.hpp"
@ -286,10 +287,12 @@ namespace MWMechanics
void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer) 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() const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2);
|| actor1.getClass().getCreatureStats(actor1).isDead()) if (creatureStats1.isDead() || creatureStats2.isDead())
return; return;
const ESM::Position& actor1Pos = actor1.getRefData().getPosition(); const ESM::Position& actor1Pos = actor1.getRefData().getPosition();
@ -298,55 +301,26 @@ namespace MWMechanics
if (sqrDist > sqrAiProcessingDistance) if (sqrDist > sqrAiProcessingDistance)
return; return;
// pure water creatures won't try to fight with the target on the ground // No combat for totally static creatures
// 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)
if (!actor1.getClass().isMobile(actor1)) if (!actor1.getClass().isMobile(actor1))
return; return;
bool aggressive; // Start combat if target actor is in combat with one of our followers or escorters
const std::list<MWWorld::Ptr>& followersAndEscorters = getActorsSidingWith(actor1);
if (againstPlayer) for (std::list<MWWorld::Ptr>::const_iterator it = followersAndEscorters.begin(); it != followersAndEscorters.end(); ++it)
{ {
// followers with high fight should not engage in combat with the player (e.g. bm_bear_black_summon) // Need to check both ways since player doesn't use AI packages
const std::list<MWWorld::Ptr>& followers = getActorsSidingWith(actor2);
if (std::find(followers.begin(), followers.end(), actor1) != followers.end())
return;
aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2);
}
else
{
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<MWWorld::Ptr>& followers = getActorsSidingWith(actor1);
const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2);
for (std::list<MWWorld::Ptr>::const_iterator it = followers.begin(); it != followers.end(); ++it)
{
// need to check both ways since player doesn't use AI packages
if ((creatureStats2.getAiSequence().isInCombat(*it) if ((creatureStats2.getAiSequence().isInCombat(*it)
|| it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2)) || it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2))
&& !creatureStats.getAiSequence().isInCombat(*it)) && !creatureStats1.getAiSequence().isInCombat(*it))
aggressive = true; {
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
return;
}
} }
// start combat if target actor is in combat with someone we are following // Start combat if target actor is in combat with someone we are following through a follow package
for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats.getAiSequence().begin(); it != creatureStats.getAiSequence().end(); ++it) for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats1.getAiSequence().begin(); it != creatureStats1.getAiSequence().end(); ++it)
{ {
if (!(*it)->sideWithTarget()) if (!(*it)->sideWithTarget())
continue; continue;
@ -356,20 +330,63 @@ namespace MWMechanics
if (followTarget.isEmpty()) if (followTarget.isEmpty())
continue; continue;
if (creatureStats.getAiSequence().isInCombat(followTarget)) if (creatureStats1.getAiSequence().isInCombat(followTarget))
continue; 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) if (creatureStats2.getAiSequence().isInCombat(followTarget)
|| followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2)) || followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2))
aggressive = true; {
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
return;
}
} }
if(aggressive) // Start combat with the player if we are already in combat with a player follower or escorter
const std::list<MWWorld::Ptr>& playerFollowersAndEscorters = getActorsSidingWith(getPlayer());
if (againstPlayer)
{
for (std::list<MWWorld::Ptr>::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)
{ {
bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); 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) if (LOS)
{ {
@ -1482,6 +1499,20 @@ namespace MWMechanics
return list; return list;
} }
void Actors::getActorsFollowing(const MWWorld::Ptr &actor, std::set<MWWorld::Ptr>& out) {
std::list<MWWorld::Ptr> followers = getActorsFollowing(actor);
for(std::list<MWWorld::Ptr>::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<MWWorld::Ptr>& out) {
std::list<MWWorld::Ptr> followers = getActorsSidingWith(actor);
for(std::list<MWWorld::Ptr>::iterator it = followers.begin();it != followers.end();++it)
if (out.insert(*it).second)
getActorsSidingWith(*it, out);
}
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
{ {
std::list<int> list; std::list<int> list;

View File

@ -123,6 +123,11 @@ namespace MWMechanics
std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor); std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor);
std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor); std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor);
/// Recursive version of getActorsFollowing
void getActorsFollowing(const MWWorld::Ptr &actor, std::set<MWWorld::Ptr>& out);
/// Recursive version of getActorsSidingWith
void getActorsSidingWith(const MWWorld::Ptr &actor, std::set<MWWorld::Ptr>& out);
/// Get the list of AiFollow::mFollowIndex for all actors following this target /// Get the list of AiFollow::mFollowIndex for all actors following this target
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor); std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);

View File

@ -19,6 +19,7 @@
#include "aicombataction.hpp" #include "aicombataction.hpp"
#include "combat.hpp" #include "combat.hpp"
#include "coordinateconverter.hpp" #include "coordinateconverter.hpp"
#include "actorutil.hpp"
namespace namespace
{ {
@ -210,13 +211,14 @@ namespace MWMechanics
else else
{ {
timerReact = 0; timerReact = 0;
attack(actor, target, storage, characterController); if (attack(actor, target, storage, characterController))
return true;
} }
return false; 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; const MWWorld::CellStore*& currentCell = storage.mCell;
bool cellChange = currentCell && (actor.getCell() != currentCell); bool cellChange = currentCell && (actor.getCell() != currentCell);
@ -231,7 +233,10 @@ namespace MWMechanics
storage.stopAttack(); storage.stopAttack();
characterController.setAttackingOrSpell(false); characterController.setAttackingOrSpell(false);
storage.mActionCooldown = 0.f; storage.mActionCooldown = 0.f;
if (target == MWMechanics::getPlayer())
forceFlee = true; forceFlee = true;
else
return true;
} }
const MWWorld::Class& actorClass = actor.getClass(); const MWWorld::Class& actorClass = actor.getClass();
@ -243,7 +248,7 @@ namespace MWMechanics
if (!forceFlee) if (!forceFlee)
{ {
if (actionCooldown > 0) if (actionCooldown > 0)
return; return false;
if (characterController.readyToPrepareAttack()) if (characterController.readyToPrepareAttack())
{ {
@ -258,7 +263,7 @@ namespace MWMechanics
} }
if (!currentAction) if (!currentAction)
return; return false;
if (storage.isFleeing() != currentAction->isFleeing()) if (storage.isFleeing() != currentAction->isFleeing())
{ {
@ -266,7 +271,7 @@ namespace MWMechanics
{ {
storage.startFleeing(); storage.startFleeing();
MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); MWBase::Environment::get().getDialogueManager()->say(actor, "flee");
return; return false;
} }
else else
storage.stopFleeing(); 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 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) void MWMechanics::AiCombat::updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage)

View File

@ -59,7 +59,8 @@ namespace MWMechanics
int mTargetActorId; 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); void updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage);

View File

@ -978,18 +978,6 @@ namespace MWMechanics
} }
void getFollowers (const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out)
{
std::list<MWWorld::Ptr> followers = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(actor);
for(std::list<MWWorld::Ptr>::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) bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg, bool victimAware)
{ {
// NOTE: victim may be empty // NOTE: victim may be empty
@ -1013,7 +1001,7 @@ namespace MWMechanics
// get the player's followers / allies (works recursively) that will not report crimes // get the player's followers / allies (works recursively) that will not report crimes
std::set<MWWorld::Ptr> playerFollowers; std::set<MWWorld::Ptr> playerFollowers;
getFollowers(player, playerFollowers); getActorsSidingWith(player, playerFollowers);
// Did anyone see it? // Did anyone see it?
bool crimeSeen = false; bool crimeSeen = false;
@ -1437,6 +1425,14 @@ namespace MWMechanics
return mActors.getEnemiesNearby(actor); return mActors.getEnemiesNearby(actor);
} }
void MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) {
mActors.getActorsFollowing(actor, out);
}
void MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) {
mActors.getActorsSidingWith(actor, out);
}
int MechanicsManager::countSavedGameRecords() const int MechanicsManager::countSavedGameRecords() const
{ {
return 1 // Death counter return 1 // Death counter

View File

@ -165,6 +165,11 @@ namespace MWMechanics
virtual std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor); virtual std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor);
virtual std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor); virtual std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor);
/// Recursive version of getActorsFollowing
virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out);
/// Recursive version of getActorsSidingWith
virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out);
virtual bool toggleAI(); virtual bool toggleAI();
virtual bool isAIActive(); virtual bool isAIActive();

View File

@ -613,6 +613,11 @@ namespace MWMechanics
return true; 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()) else if (target.getClass().isActor() && target == getPlayer())
{ {
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster); MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
@ -1140,9 +1145,6 @@ namespace MWMechanics
case ESM::MagicEffect::CureCorprusDisease: case ESM::MagicEffect::CureCorprusDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease(); actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease();
break; break;
case ESM::MagicEffect::Dispel:
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeAll(magnitude);
break;
case ESM::MagicEffect::RemoveCurse: case ESM::MagicEffect::RemoveCurse:
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses(); actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
break; break;

View File

@ -55,29 +55,46 @@ namespace MWPhysics
static const float sMaxSlope = 49.0f; static const float sMaxSlope = 49.0f;
static const float sStepSizeUp = 34.0f; static const float sStepSizeUp = 34.0f;
static const float sStepSizeDown = 62.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. // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
static const int sMaxIterations = 8; static const int sMaxIterations = 8;
// FIXME: move to a separate file static bool isActor(const btCollisionObject *obj)
class MovementSolver
{ {
private: assert(obj);
static float getSlope(osg::Vec3f normal) return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
{
normal.normalize();
return osg::RadiansToDegrees(std::acos(normal * osg::Vec3f(0.f, 0.f, 1.f)));
} }
enum StepMoveResult template <class Vec3>
static bool isWalkableSlope(const Vec3 &normal)
{ {
Result_Blocked, // unable to move over obstacle static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
Result_MaxSlope, // unable to end movement on this slope return (normal.z() > sMaxSlopeCos);
Result_Success }
};
static StepMoveResult stepMove(const btCollisionObject *colobj, osg::Vec3f &position, static bool canStepDown(const ActorTracer &stepper)
const osg::Vec3f &toMove, float &remainingTime, const btCollisionWorld* collisionWorld) {
return stepper.mHitObject && isWalkableSlope(stepper.mPlaneNormal) && !isActor(stepper.mHitObject);
}
class Stepper
{
private:
const btCollisionWorld *mColWorld;
const btCollisionObject *mColObj;
ActorTracer mTracer, mUpStepper, mDownStepper;
bool mHaveMoved;
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 * Slide up an incline or set of stairs. Should be called only after a
@ -123,12 +140,14 @@ namespace MWPhysics
* +--+ +-------- * +--+ +--------
* ============================================== * ==============================================
*/ */
ActorTracer tracer, stepper; if (mHaveMoved)
{
stepper.doTrace(colobj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), collisionWorld); mHaveMoved = false;
if(stepper.mFraction < std::numeric_limits<float>::epsilon()) mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld);
return Result_Blocked; // didn't even move the smallest representable amount if(mUpStepper.mFraction < std::numeric_limits<float>::epsilon())
return false; // didn't even move the smallest representable amount
// (TODO: shouldn't this be larger? Why bother with such a small amount?) // (TODO: shouldn't this be larger? Why bother with such a small amount?)
}
/* /*
* Try moving from the elevated position using tracer. * Try moving from the elevated position using tracer.
@ -143,9 +162,10 @@ namespace MWPhysics
* +--+ * +--+
* ============================================== * ==============================================
*/ */
tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, collisionWorld); osg::Vec3f tracerPos = mUpStepper.mEndPos;
if(tracer.mFraction < std::numeric_limits<float>::epsilon()) mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld);
return Result_Blocked; // didn't even move the smallest representable amount if(mTracer.mFraction < std::numeric_limits<float>::epsilon())
return false; // didn't even move the smallest representable amount
/* /*
* Try moving back down sStepSizeDown using stepper. * 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); mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
if (getSlope(stepper.mPlaneNormal) > sMaxSlope) if (!canStepDown(mDownStepper))
return Result_MaxSlope; {
if(stepper.mFraction < 1.0f) // 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. // 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 // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing
// NOTE: caller's variables 'position' & 'remainingTime' are modified here // NOTE: caller's variables 'position' & 'remainingTime' are modified here
position = stepper.mEndPos; position = mDownStepper.mEndPos;
remainingTime *= (1.0f-tracer.mFraction); // remaining time is proportional to remaining distance remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance
return Result_Success; mHaveMoved = true;
return true;
} }
return false;
return Result_Blocked;
} }
};
class MovementSolver
{
private:
///Project a vector u on another vector v ///Project a vector u on another vector v
static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &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); collisionWorld->rayTest(from, to, resultCallback1);
if (resultCallback1.hasHit() && if (resultCallback1.hasHit() &&
( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length() > 35 ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length2() > 35*35
|| getSlope(tracer.mPlaneNormal) > sMaxSlope)) || !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); 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; return tracer.mEndPos;
} }
@ -312,8 +346,8 @@ namespace MWPhysics
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
} }
Stepper stepper(collisionWorld, colobj);
osg::Vec3f origVelocity = velocity; osg::Vec3f origVelocity = velocity;
osg::Vec3f newPosition = position; osg::Vec3f newPosition = position;
/* /*
* A loop to find newPosition using tracer, if successful different from the starting position. * A loop to find newPosition using tracer, if successful different from the starting position.
@ -332,10 +366,7 @@ namespace MWPhysics
newPosition.z() <= swimlevel) newPosition.z() <= swimlevel)
{ {
const osg::Vec3f down(0,0,-1); const osg::Vec3f down(0,0,-1);
float movelen = velocity.normalize(); velocity = slide(velocity, down);
osg::Vec3f reflectdir = reflect(velocity, down);
reflectdir.normalize();
velocity = slide(reflectdir, down)*movelen;
// NOTE: remainingTime is unchanged before the loop continues // NOTE: remainingTime is unchanged before the loop continues
continue; // velocity updated, calculate nextpos again continue; // velocity updated, calculate nextpos again
} }
@ -364,19 +395,25 @@ namespace MWPhysics
break; break;
} }
// We are touching something.
osg::Vec3f oldPosition = newPosition; if (tracer.mFraction < 1E-9f)
// 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
{ {
osg::Vec3f normalizedVelocity = velocity; // Try to separate by backing off slighly to unstuck the solver
normalizedVelocity.normalize(); const osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-3f;
result = stepMove(colobj, newPosition, normalizedVelocity*minStep, remainingTime, collisionWorld); newPosition += backOff;
} }
if(result == Result_Success)
// We hit something. Check if we can step up.
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
osg::Vec3f oldPosition = newPosition;
bool result = false;
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
{
// 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)
{ {
// don't let pure water creatures move out of water after stepMove // don't let pure water creatures move out of water after stepMove
if (ptr.getClass().isPureWaterCreature(ptr) if (ptr.getClass().isPureWaterCreature(ptr)
@ -386,23 +423,19 @@ namespace MWPhysics
else else
{ {
// Can't move this way, try to find another spot along the plane // Can't move this way, try to find another spot along the plane
osg::Vec3f direction = velocity; osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal);
float movelen = direction.normalize();
osg::Vec3f reflectdir = reflect(velocity, tracer.mPlaneNormal); // Do not allow sliding upward if there is gravity.
reflectdir.normalize(); // 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) if ((newVelocity-velocity).length2() < 0.01)
break; break;
if ((velocity * origVelocity) <= 0.f) if ((newVelocity * origVelocity) <= 0.f)
break; // ^ dot product break; // ^ dot product
velocity = newVelocity; 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 to = newPosition - (physicActor->getOnGround() ?
osg::Vec3f(0,0,sStepSizeDown+2.f) : osg::Vec3f(0,0,2.f)); osg::Vec3f(0,0,sStepSizeDown+2.f) : osg::Vec3f(0,0,2.f));
tracer.doTrace(colobj, from, to, collisionWorld); 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) && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor)
{ {
const btCollisionObject* standingOn = tracer.mHitObject; const btCollisionObject* standingOn = tracer.mHitObject;
@ -996,6 +1029,8 @@ namespace MWPhysics
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
{ {
const Actor* physicActor = getActor(actor); const Actor* physicActor = getActor(actor);
if (!physicActor)
return false;
const float halfZ = physicActor->getHalfExtents().z(); const float halfZ = physicActor->getHalfExtents().z();
const osg::Vec3f actorPosition = physicActor->getPosition(); const osg::Vec3f actorPosition = physicActor->getPosition();
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);

View File

@ -78,6 +78,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
mFraction = newTraceCallback.m_closestHitFraction; mFraction = newTraceCallback.m_closestHitFraction;
mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z());
mEndPos = (end-start)*mFraction + start; mEndPos = (end-start)*mFraction + start;
mHitPoint = toOsg(newTraceCallback.m_hitPointWorld);
mHitObject = newTraceCallback.m_hitCollisionObject; mHitObject = newTraceCallback.m_hitCollisionObject;
} }
else else
@ -85,6 +86,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
mEndPos = end; mEndPos = end;
mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f);
mFraction = 1.0f; mFraction = 1.0f;
mHitPoint = end;
mHitObject = NULL; mHitObject = NULL;
} }
} }

View File

@ -15,6 +15,7 @@ namespace MWPhysics
{ {
osg::Vec3f mEndPos; osg::Vec3f mEndPos;
osg::Vec3f mPlaneNormal; osg::Vec3f mPlaneNormal;
osg::Vec3f mHitPoint;
const btCollisionObject* mHitObject; const btCollisionObject* mHitObject;
float mFraction; float mFraction;

View File

@ -7,6 +7,7 @@
#include <osg/Group> #include <osg/Group>
#include <osg/Geometry> #include <osg/Geometry>
#include <osg/Depth> #include <osg/Depth>
#include <osg/TexEnvCombine>
#include <osgDB/WriteFile> #include <osgDB/WriteFile>
@ -144,6 +145,10 @@ namespace MWRender
image->allocateImage(mWidth, mHeight, 1, GL_RGB, GL_UNSIGNED_BYTE); image->allocateImage(mWidth, mHeight, 1, GL_RGB, GL_UNSIGNED_BYTE);
unsigned char* data = image->data(); unsigned char* data = image->data();
osg::ref_ptr<osg::Image> 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 x = mMinX; x <= mMaxX; ++x)
{ {
for (int y = mMinY; y <= mMaxY; ++y) 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] = r;
data[texelY * mWidth * 3 + texelX * 3+1] = g; data[texelY * mWidth * 3 + texelX * 3+1] = g;
data[texelY * mWidth * 3 + texelX * 3+2] = b; data[texelY * mWidth * 3 + texelX * 3+2] = b;
alphaData[texelY * mWidth+ texelX] = (y2 < 0) ? static_cast<unsigned char>(0) : static_cast<unsigned char>(255);
} }
} }
loadingListener->increaseProgress(); loadingListener->increaseProgress();
@ -224,6 +231,14 @@ namespace MWRender
mBaseTexture->setImage(image); mBaseTexture->setImage(image);
mBaseTexture->setResizeNonPowerOfTwoHint(false); 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(); clear();
loadingListener->loadingOff(); loadingListener->loadingOff();
@ -299,6 +314,28 @@ namespace MWRender
stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
if (mAlphaTexture)
{
osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
float x1 = x / static_cast<float>(mWidth);
float x2 = (x + width) / static_cast<float>(mWidth);
float y1 = y / static_cast<float>(mHeight);
float y2 = (y + height) / static_cast<float>(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<osg::TexEnvCombine> texEnvCombine = new osg::TexEnvCombine;
texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE);
texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
stateset->setTextureAttributeAndModes(1, texEnvCombine);
}
camera->addChild(geom); camera->addChild(geom);
} }

View File

@ -107,6 +107,7 @@ namespace MWRender
std::vector< std::pair<int,int> > mExploredCells; std::vector< std::pair<int,int> > mExploredCells;
osg::ref_ptr<osg::Texture2D> mBaseTexture; osg::ref_ptr<osg::Texture2D> mBaseTexture;
osg::ref_ptr<osg::Texture2D> mAlphaTexture;
// GPU copy of overlay // GPU copy of overlay
// Note, uploads are pushed through a Camera, instead of through mOverlayImage // Note, uploads are pushed through a Camera, instead of through mOverlayImage

View File

@ -437,7 +437,9 @@ bool OpenAL_SoundStream::process()
alGetSourcei(mSource, AL_SOURCE_STATE, &state); alGetSourcei(mSource, AL_SOURCE_STATE, &state);
if(state != AL_PLAYING && state != AL_PAUSED) if(state != AL_PLAYING && state != AL_PAUSED)
{ {
// Ensure all processed buffers are removed so we don't replay them.
refillQueue(); refillQueue();
alSourcePlay(mSource); alSourcePlay(mSource);
} }
} }
@ -906,7 +908,10 @@ void OpenAL_Output::finishSound(MWBase::SoundPtr sound)
ALuint source = GET_PTRID(sound->mHandle); ALuint source = GET_PTRID(sound->mHandle);
sound->mHandle = 0; 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); alSourcei(source, AL_BUFFER, 0);
mFreeSources.push_back(source); mFreeSources.push_back(source);
@ -1006,7 +1011,10 @@ void OpenAL_Output::finishStream(MWBase::SoundStreamPtr sound)
sound->mHandle = 0; sound->mHandle = 0;
mStreamThread->remove(stream); 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); alSourcei(source, AL_BUFFER, 0);
mFreeSources.push_back(source); mFreeSources.push_back(source);

View File

@ -8,23 +8,6 @@
#include "player.hpp" #include "player.hpp"
namespace
{
void getFollowers (const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out)
{
std::list<MWWorld::Ptr> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor);
for(std::list<MWWorld::Ptr>::iterator it = followers.begin();it != followers.end();++it)
{
if (out.insert(*it).second)
{
getFollowers(*it, out);
}
}
}
}
namespace MWWorld namespace MWWorld
{ {
ActionTeleport::ActionTeleport (const std::string& cellName, ActionTeleport::ActionTeleport (const std::string& cellName,
@ -37,22 +20,13 @@ namespace MWWorld
{ {
if (mTeleportFollowers) 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<MWWorld::Ptr> followers; std::set<MWWorld::Ptr> followers;
getFollowers(actor, followers); getFollowersToTeleport(actor, followers);
for(std::set<MWWorld::Ptr>::iterator it = followers.begin();it != followers.end();++it)
{
MWWorld::Ptr follower = *it;
std::string script = follower.getClass().getScript(follower); for (std::set<MWWorld::Ptr>::iterator it = followers.begin(); it != followers.end(); ++it)
if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1)
continue;
if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2()
<= 800*800)
teleport(*it); teleport(*it);
} }
}
teleport(actor); teleport(actor);
} }
@ -82,4 +56,21 @@ namespace MWWorld
world->moveObject(actor,world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); world->moveObject(actor,world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]);
} }
} }
void ActionTeleport::getFollowersToTeleport(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) {
std::set<MWWorld::Ptr> followers;
MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers);
for(std::set<MWWorld::Ptr>::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);
}
}
} }

View File

@ -1,6 +1,7 @@
#ifndef GAME_MWWORLD_ACTIONTELEPORT_H #ifndef GAME_MWWORLD_ACTIONTELEPORT_H
#define GAME_MWWORLD_ACTIONTELEPORT_H #define GAME_MWWORLD_ACTIONTELEPORT_H
#include <set>
#include <string> #include <string>
#include <components/esm/defs.hpp> #include <components/esm/defs.hpp>
@ -23,9 +24,12 @@ namespace MWWorld
public: 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. /// @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<MWWorld::Ptr>& out);
}; };
} }

View File

@ -137,6 +137,9 @@ namespace
iter->load (state); iter->load (state);
return; return;
} }
std::cerr << "Dropping reference to " << state.mRef.mRefID << " (invalid content file link)" << std::endl;
return;
} }
// new reference // new reference

View File

@ -402,8 +402,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
std::pair<std::vector<int>, bool> itemsSlots = std::pair<std::vector<int>, bool> itemsSlots =
weapon->getClass().getEquipmentSlots (*weapon); weapon->getClass().getEquipmentSlots (*weapon);
for (std::vector<int>::const_iterator slot (itemsSlots.first.begin()); if (!itemsSlots.first.empty())
slot!=itemsSlots.first.end(); ++slot)
{ {
if (!itemsSlots.second) if (!itemsSlots.second)
{ {
@ -413,8 +412,8 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
} }
} }
slots_[*slot] = weapon; int slot = itemsSlots.first.front();
break; slots_[slot] = weapon;
} }
break; break;

View File

@ -280,7 +280,9 @@ namespace MWWorld
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (size_t it = 0; it != state.mSoundIds.size(); it++) 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); mMagicBolts.push_back(state);
@ -571,8 +573,10 @@ namespace MWWorld
for (size_t soundIter = 0; soundIter != state.mSoundIds.size(); soundIter++) 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::SoundPtr sound = sndMgr->playSound3D(esm.mPosition, state.mSoundIds.at(soundIter), 1.0f, 1.0f,
MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
if (sound)
state.mSounds.push_back(sound);
} }
mMagicBolts.push_back(state); mMagicBolts.push_back(state);