mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-21 09:23:53 +00:00
Merge branch 'master' of https://github.com/OpenMW/openmw
This commit is contained in:
commit
777c4b9aad
34 changed files with 471 additions and 206 deletions
|
@ -1,7 +1,7 @@
|
|||
os:
|
||||
- linux
|
||||
- osx
|
||||
osx_image: xcode7.3
|
||||
osx_image: xcode8.2
|
||||
language: cpp
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
|
|
@ -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)
|
||||
|
|
75
CHANGELOG.md
75
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
|
||||
------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 "")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<FNAM> mFactions;
|
||||
|
@ -109,6 +115,9 @@ struct PCDT
|
|||
bool mHasENAM;
|
||||
ENAM mENAM; // last exterior cell
|
||||
|
||||
bool mHasAADT;
|
||||
AADT mAADT;
|
||||
|
||||
void load(ESM::ESMReader& esm);
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace osg
|
||||
|
@ -200,6 +201,10 @@ namespace MWBase
|
|||
|
||||
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 int countSavedGameRecords() const = 0;
|
||||
|
|
|
@ -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<float>(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<float>(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false));
|
||||
if (curDisp + mPermanentDispositionChange < 0)
|
||||
mPermanentDispositionChange = -curDisp;
|
||||
|
||||
MWMechanics::NpcStats& npcStats = mActor.getClass().getNpcStats(mActor);
|
||||
npcStats.setBaseDisposition(static_cast<int>(npcStats.getBaseDisposition() + mPermanentDispositionChange));
|
||||
}
|
||||
|
|
|
@ -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<std::string,int>::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<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
|
||||
if (!info.mCell.empty())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<ESM::GameSetting> &gmst =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
|
@ -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<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);
|
||||
toAdd->setEnabled(price<=playerGold);
|
||||
toAdd->setEnabled(price <= playerGold);
|
||||
mCurrentY += sLineHeight;
|
||||
if(interior)
|
||||
toAdd->setUserString("interior","y");
|
||||
|
|
|
@ -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)
|
||||
// Start combat if target actor is in combat with one of our followers or escorters
|
||||
const std::list<MWWorld::Ptr>& followersAndEscorters = getActorsSidingWith(actor1);
|
||||
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)
|
||||
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"))
|
||||
// Need to check both ways since player doesn't use AI packages
|
||||
if ((creatureStats2.getAiSequence().isInCombat(*it)
|
||||
|| it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2))
|
||||
&& !creatureStats1.getAiSequence().isInCombat(*it))
|
||||
{
|
||||
if (creatureStats.getAiSequence().isInCombat() && MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2))
|
||||
aggressive = true;
|
||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|| it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2))
|
||||
&& !creatureStats.getAiSequence().isInCombat(*it))
|
||||
aggressive = true;
|
||||
}
|
||||
|
||||
// start combat if target actor is in combat with someone we are following
|
||||
for (std::list<MWMechanics::AiPackage*>::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<MWMechanics::AiPackage*>::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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(aggressive)
|
||||
// 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);
|
||||
|
||||
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<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> list;
|
||||
|
|
|
@ -123,6 +123,11 @@ namespace MWMechanics
|
|||
std::list<MWWorld::Ptr> getActorsSidingWith(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
|
||||
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
// 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<MWWorld::Ptr> 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<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
|
||||
{
|
||||
return 1 // Death counter
|
||||
|
|
|
@ -165,6 +165,11 @@ namespace MWMechanics
|
|||
virtual std::list<MWWorld::Ptr> getActorsFighting(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 isAIActive();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 <class Vec3>
|
||||
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<float>::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<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?)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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<float>::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<float>::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;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
// We are touching something.
|
||||
if (tracer.mFraction < 1E-9f)
|
||||
{
|
||||
osg::Vec3f normalizedVelocity = velocity;
|
||||
normalizedVelocity.normalize();
|
||||
result = stepMove(colobj, newPosition, normalizedVelocity*minStep, remainingTime, collisionWorld);
|
||||
// Try to separate by backing off slighly to unstuck the solver
|
||||
const osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-3f;
|
||||
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
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace MWPhysics
|
|||
{
|
||||
osg::Vec3f mEndPos;
|
||||
osg::Vec3f mPlaneNormal;
|
||||
osg::Vec3f mHitPoint;
|
||||
const btCollisionObject* mHitObject;
|
||||
|
||||
float mFraction;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <osg/Group>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/Depth>
|
||||
#include <osg/TexEnvCombine>
|
||||
|
||||
#include <osgDB/WriteFile>
|
||||
|
||||
|
@ -144,6 +145,10 @@ namespace MWRender
|
|||
image->allocateImage(mWidth, mHeight, 1, GL_RGB, GL_UNSIGNED_BYTE);
|
||||
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 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<unsigned char>(0) : static_cast<unsigned char>(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<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);
|
||||
}
|
||||
|
||||
|
|
|
@ -107,6 +107,7 @@ namespace MWRender
|
|||
std::vector< std::pair<int,int> > mExploredCells;
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> mBaseTexture;
|
||||
osg::ref_ptr<osg::Texture2D> mAlphaTexture;
|
||||
|
||||
// GPU copy of overlay
|
||||
// Note, uploads are pushed through a Camera, instead of through mOverlayImage
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -8,23 +8,6 @@
|
|||
|
||||
#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
|
||||
{
|
||||
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<MWWorld::Ptr> followers;
|
||||
getFollowers(actor, followers);
|
||||
for(std::set<MWWorld::Ptr>::iterator it = followers.begin();it != followers.end();++it)
|
||||
{
|
||||
MWWorld::Ptr follower = *it;
|
||||
getFollowersToTeleport(actor, followers);
|
||||
|
||||
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)
|
||||
teleport(*it);
|
||||
}
|
||||
for (std::set<MWWorld::Ptr>::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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef GAME_MWWORLD_ACTIONTELEPORT_H
|
||||
#define GAME_MWWORLD_ACTIONTELEPORT_H
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <components/esm/defs.hpp>
|
||||
|
@ -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<MWWorld::Ptr>& out);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -402,8 +402,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
|||
std::pair<std::vector<int>, bool> itemsSlots =
|
||||
weapon->getClass().getEquipmentSlots (*weapon);
|
||||
|
||||
for (std::vector<int>::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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue