Merge remote-tracking branch 'upstream/master' into bugfix-3617

This commit is contained in:
NeveHanter 2017-01-15 09:49:45 +01:00
commit e82d542d89
66 changed files with 1722 additions and 282 deletions

View file

@ -1,7 +1,7 @@
os:
- linux
# - osx
osx_image: xcode7.2
- osx
osx_image: xcode8.2
language: cpp
sudo: required
dist: trusty
@ -15,6 +15,7 @@ env:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE="
- macos_qt_formula=qt@5.5
addons:
apt:
sources:
@ -62,9 +63,9 @@ script:
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
notifications:
recipients:
- corrmage+travis-ci@gmail.com
email:
recipients:
- corrmage+travis-ci@gmail.com
on_success: change
on_failure: always
irc:

View file

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

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

View file

@ -5,7 +5,7 @@ brew update
brew rm cmake || true
brew rm pkgconfig || true
brew rm qt5 || true
brew install cmake pkgconfig qt55
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

View file

@ -4,7 +4,7 @@ export CXX=clang++
export CC=clang
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
QT_PATH="/usr/local/opt/qt55"
QT_PATH=`brew --prefix $macos_qt_formula`
mkdir build
cd build
@ -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 \

View file

@ -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 "")

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.
* 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

View file

@ -47,6 +47,42 @@ namespace ESSImport
controls.mVanityModeDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_VanityModeDisabled;
controls.mWeaponDrawingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_WeaponDrawingDisabled;
controls.mSpellDrawingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_SpellDrawingDisabled;
if (pcdt.mHasMark)
{
out.mHasMark = 1;
const PCDT::PNAM::MarkLocation& mark = pcdt.mPNAM.mMarkLocation;
ESM::CellId cell;
cell.mWorldspace = ESM::CellId::sDefaultWorldspace;
cell.mPaged = true;
cell.mIndex.mX = mark.mCellX;
cell.mIndex.mY = mark.mCellY;
// TODO: Figure out a better way to detect interiors. (0, 0) is a valid exterior cell.
if (mark.mCellX == 0 && mark.mCellY == 0)
{
cell.mWorldspace = pcdt.mMNAM;
cell.mPaged = false;
}
out.mMarkedCell = cell;
out.mMarkedPosition.pos[0] = mark.mX;
out.mMarkedPosition.pos[1] = mark.mY;
out.mMarkedPosition.pos[2] = mark.mZ;
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;
}
}
}

View file

@ -62,8 +62,11 @@ namespace ESSImport
playerCellId.mPaged = true;
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
mPlayer.mCellId = playerCellId;
//mPlayer.mLastKnownExteriorPosition
mPlayer.mHasMark = 0; // TODO
mPlayer.mLastKnownExteriorPosition[0]
= mPlayer.mLastKnownExteriorPosition[1]
= mPlayer.mLastKnownExteriorPosition[2]
= 0.0f;
mPlayer.mHasMark = 0;
mPlayer.mCurrentCrimeId = 0; // TODO
mPlayer.mObject.blank();
mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame

View file

@ -23,9 +23,12 @@ namespace ESSImport
mKnownDialogueTopics.push_back(esm.getHString());
}
mHasMark = false;
if (esm.isNextSub("MNAM"))
esm.skipHSub(); // If this field is here it seems to specify the interior cell the player is in,
// but it's not always here, so it's kinda useless
{
mHasMark = true;
mMNAM = esm.getHString();
}
esm.getHNT(mPNAM, "PNAM");
@ -34,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");
@ -50,8 +61,12 @@ namespace ESSImport
if (esm.isNextSub("NAM3"))
esm.skipHSub();
mHasENAM = false;
if (esm.isNextSub("ENAM"))
esm.skipHSub();
{
mHasENAM = true;
esm.getHT(mENAM);
}
if (esm.isNextSub("LNAM"))
esm.skipHSub();
@ -63,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
@ -76,10 +98,6 @@ namespace ESSImport
esm.getSubHeader();
esm.skip(152);
}
// unsure if before or after WERE
if (esm.isNextSub("ANIS"))
esm.skipHSub();
}
}

View file

@ -42,8 +42,11 @@ struct PCDT
{
PlayerFlags_ViewSwitchDisabled = 0x1,
PlayerFlags_ControlsDisabled = 0x4,
PlayerFlags_Sleeping = 0x10,
PlayerFlags_Waiting = 0x40,
PlayerFlags_WeaponDrawn = 0x80,
PlayerFlags_SpellDrawn = 0x100,
PlayerFlags_InJail = 0x200,
PlayerFlags_JumpingDisabled = 0x1000,
PlayerFlags_LookingDisabled = 0x2000,
PlayerFlags_VanityModeDisabled = 0x4000,
@ -68,19 +71,53 @@ struct PCDT
struct PNAM
{
struct MarkLocation
{
float mX, mY, mZ; // worldspace position
float mRotZ; // Z angle in radians
int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0)
};
int mPlayerFlags; // controls, camera and draw state
unsigned int mLevelProgress;
float mSkillProgress[27]; // skill progress, non-uniform scaled
unsigned char mSkillIncreases[8]; // number of skill increases for each attribute
unsigned char mUnknown3[84];
int mTelekinesisRangeBonus; // in units; seems redundant
float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus
int mDetectKeyMagnitude; // seems redundant
int mDetectEnchantmentMagnitude; // seems redundant
int mDetectAnimalMagnitude; // seems redundant
MarkLocation mMarkLocation;
unsigned char mUnknown3[40];
unsigned char mSpecIncreases[3]; // number of skill increases for each specialization
unsigned char mUnknown4;
};
struct ENAM
{
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;
PNAM mPNAM;
bool mHasMark;
std::string mMNAM; // mark cell name; can also be sDefaultCellname or region name
bool mHasENAM;
ENAM mENAM; // last exterior cell
bool mHasAADT;
AADT mAADT;
void load(ESM::ESMReader& esm);
};

View file

@ -215,7 +215,6 @@ if (MSVC)
if (CMAKE_CL_64)
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj")
endif (CMAKE_CL_64)
add_definitions("-D_USE_MATH_DEFINES")
endif (MSVC)
if (WIN32)

View file

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

View file

@ -164,7 +164,7 @@ namespace MWClass
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
if (hasInventory)
getInventoryStore(ptr).autoEquip(ptr);
getInventoryStore(ptr).autoEquipShield(ptr);
}
}

View file

@ -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));
}

View file

@ -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())

View file

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

View file

@ -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");

View file

@ -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"
@ -83,15 +84,14 @@ public:
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1)
{
MWWorld::Ptr player = MWMechanics::getPlayer();
if ( ((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc())
|| (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name()))
&& casterActorId == player.getClass().getCreatureStats(player).getActorId()
if (((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc())
|| (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name()))
&& magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel())
mCommanded = true;
}
};
// Check for command effects having ended and remove package if necessary
void adjustCommandedActor (const MWWorld::Ptr& actor)
{
CheckActorCommanded check(actor);
@ -111,13 +111,7 @@ void adjustCommandedActor (const MWWorld::Ptr& actor)
}
}
if (check.mCommanded && !hasCommandPackage)
{
// FIXME: don't use refid string
MWMechanics::AiFollow package("player", true);
stats.getAiSequence().stack(package, actor);
}
else if (!check.mCommanded && hasCommandPackage)
if (!check.mCommanded && hasCommandPackage)
{
stats.getAiSequence().erase(it);
}
@ -286,10 +280,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 +294,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 +323,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 +1492,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;

View file

@ -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);

View file

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

View file

@ -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);

View file

@ -37,6 +37,7 @@ namespace MWMechanics
MWWorld::Ptr getTarget() const;
virtual bool sideWithTarget() const { return true; }
virtual bool followTargetThroughDoors() const { return true; }
virtual bool shouldCancelPreviousAi() const { return !mCommanded; }
virtual AiFollow *clone() const;

View file

@ -281,6 +281,10 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor)
}
}
// Stop combat when a non-combat AI package is added
if (isActualAiPackage(package.getTypeId()))
stopCombat();
// remove previous packages if required
if (package.shouldCancelPreviousAi())
{

View file

@ -473,8 +473,8 @@ namespace MWMechanics
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
float duration, AiWanderStorage& storage, ESM::Position& pos)
{
// Are we there yet?
if (pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE))
// Is there no destination or are we there yet?
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE))
{
stopWalking(actor, storage);
storage.setState(Wander_ChooseAction);

View file

@ -70,12 +70,14 @@ std::string getBestAttack (const ESM::Weapon* weapon)
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
if (slash >= chop && slash >= thrust)
if (slash == chop && slash == thrust)
return "slash";
else if (chop >= slash && chop >= thrust)
return "chop";
else
else if (thrust >= chop && thrust >= slash)
return "thrust";
else if (slash >= chop && slash >= thrust)
return "slash";
else
return "chop";
}
// Converts a movement Run state to its equivalent Walk state.

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)
{
// 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

View file

@ -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();

View file

@ -27,6 +27,7 @@
#include "magiceffects.hpp"
#include "npcstats.hpp"
#include "actorutil.hpp"
#include "aifollow.hpp"
namespace MWMechanics
{
@ -491,6 +492,15 @@ namespace MWMechanics
appliedLastingEffects.push_back(effect);
// Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target
if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc())
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel())
{
MWMechanics::AiFollow package(caster.getCellRef().getRefId(), true);
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
}
// For absorb effects, also apply the effect to the caster - but with a negative
// magnitude, since we're transferring stats from the target to the caster
if (!caster.isEmpty() && caster.getClass().isActor())
@ -616,6 +626,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);
@ -1148,9 +1163,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;

View file

@ -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);
@ -1349,7 +1384,8 @@ namespace MWPhysics
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(iter->first, waterlevel))
{
const osg::Vec3f actorPosition = physicActor->getPosition();
physicActor->setPosition(osg::Vec3f(actorPosition.x(), actorPosition.y(), waterlevel));
physicActor->setPosition(osg::Vec3f(actorPosition.x(), actorPosition.y(), waterlevel));
waterCollision = true;
}
}
physicActor->setCanWaterWalk(waterCollision);

View file

@ -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;
}
}

View file

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

View file

@ -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);
}

View file

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

View file

@ -943,8 +943,8 @@ public:
void setState(const MoonState& state)
{
float radsX = ((state.mRotationFromHorizon) * M_PI) / 180.0f;
float radsZ = ((state.mRotationFromNorth) * M_PI) / 180.0f;
float radsX = ((state.mRotationFromHorizon) * static_cast<float>(osg::PI)) / 180.0f;
float radsZ = ((state.mRotationFromNorth) * static_cast<float>(osg::PI)) / 180.0f;
osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f));
osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f));
@ -954,7 +954,7 @@ public:
// The moon quad is initially oriented facing down, so we need to offset its X-axis
// rotation to rotate it to face the camera when sitting at the horizon.
osg::Quat attX((-M_PI / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f));
osg::Quat attX((-static_cast<float>(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f));
mTransform->setAttitude(attX * rotZ);
setPhase(state.mPhase);

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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);
};
}

View file

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

View file

@ -139,7 +139,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr,
// Auto-equip items if an armor/clothing or weapon item is added, but not for the player nor werewolves
if (actorPtr != MWMechanics::getPlayer()
&& !(actorPtr.getClass().isNpc() && actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()))
&& actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())
{
std::string type = itemPtr.getTypeName();
if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name())
@ -237,10 +237,6 @@ bool MWWorld::InventoryStore::canActorAutoEquip(const MWWorld::Ptr& actor, const
void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
{
if (!actor.getClass().isNpc())
// autoEquip is no-op for creatures
return;
const MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor);
@ -402,8 +398,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 +408,8 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
}
}
slots_[*slot] = weapon;
break;
int slot = itemsSlots.first.front();
slots_[slot] = weapon;
}
break;
@ -444,6 +439,50 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
}
}
void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor)
{
bool updated = false;
mUpdatesEnabled = false;
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Armor)); iter != end(); ++iter)
{
if (iter->get<ESM::Armor>()->mBase->mData.mType != ESM::Armor::Shield)
continue;
if (iter->getClass().canBeEquipped(*iter, actor).first != 1)
continue;
if (iter->getClass().getItemHealth(*iter) <= 0)
continue;
std::pair<std::vector<int>, bool> shieldSlots =
iter->getClass().getEquipmentSlots(*iter);
if (shieldSlots.first.empty())
continue;
int slot = shieldSlots.first[0];
const ContainerStoreIterator& shield = mSlots[slot];
if (shield != end()
&& shield.getType() == Type_Armor && shield->get<ESM::Armor>()->mBase->mData.mType == ESM::Armor::Shield)
{
if (shield->getClass().getItemHealth(*shield) >= iter->getClass().getItemHealth(*iter))
continue;
}
equip(slot, iter, actor);
updated = true;
}
mUpdatesEnabled = true;
if (updated)
{
fireEquipmentChangedEvent(actor);
updateMagicEffects(actor);
}
}
const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const
{
return mMagicEffects;
@ -623,7 +662,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
// If an armor/clothing item is removed, try to find a replacement,
// but not for the player nor werewolves.
if (wasEquipped && (actor != MWMechanics::getPlayer())
&& !(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()))
&& actor.getClass().isNpc() && !actor.getClass().getNpcStats(actor).isWerewolf())
{
std::string type = item.getTypeName();
if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name())

View file

@ -162,6 +162,9 @@ namespace MWWorld
void autoEquip (const MWWorld::Ptr& actor);
///< Auto equip items according to stats and item value.
void autoEquipShield(const MWWorld::Ptr& actor);
///< Auto-equip the shield with most health.
const MWMechanics::MagicEffects& getMagicEffects() const;
///< Return magic effects from worn items.

View file

@ -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);
@ -568,8 +570,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);

View file

@ -13,7 +13,8 @@ namespace
enum RefDataFlags
{
Flag_SuppressActivate = 1, // If set, activation will be suppressed and redirected to the OnActivate flag, which can then be handled by a script.
Flag_OnActivate = 2
Flag_OnActivate = 2,
Flag_ActivationBuffered = 4
};
}
@ -83,6 +84,7 @@ namespace MWWorld
try
{
copy (refData);
mFlags &= ~(Flag_SuppressActivate|Flag_OnActivate|Flag_ActivationBuffered);
}
catch (...)
{
@ -241,38 +243,32 @@ namespace MWWorld
return mChanged || !mAnimationState.empty();
}
bool RefData::activateByScript()
{
bool ret = (mFlags & Flag_ActivationBuffered);
mFlags &= ~(Flag_SuppressActivate|Flag_OnActivate);
return ret;
}
bool RefData::activate()
{
if (!(mFlags & Flag_SuppressActivate))
return true;
if (mFlags & Flag_SuppressActivate)
{
mFlags |= Flag_OnActivate|Flag_ActivationBuffered;
return false;
}
else
{
mFlags |= Flag_OnActivate;
return false;
return true;
}
}
bool RefData::onActivate()
{
bool ret = mFlags & Flag_OnActivate;
mFlags |= Flag_SuppressActivate;
if (mFlags & Flag_OnActivate)
{
mFlags &= (~Flag_OnActivate);
return true;
}
return false;
}
bool RefData::activateByScript()
{
if (mFlags & Flag_SuppressActivate)
{
mFlags &= (~Flag_SuppressActivate);
return true;
}
else
return false;
mFlags &= (~Flag_OnActivate);
return ret;
}
const ESM::AnimationState& RefData::getAnimationState() const

View file

@ -695,9 +695,9 @@ void WeatherManager::update(float duration, bool paused)
double theta;
if ( !is_night ) {
theta = M_PI * (adjustedHour - mSunriseTime) / dayDuration;
theta = static_cast<float>(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration;
} else {
theta = M_PI * (1.f - (adjustedHour - adjustedNightStart) / nightDuration);
theta = static_cast<float>(osg::PI) * (1.f - (adjustedHour - adjustedNightStart) / nightDuration);
}
osg::Vec3f final(

View file

@ -320,11 +320,14 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv)
box.expandBy(bs);
}
_boundingBox = box;
_boundingSphere = osg::BoundingSphere(_boundingBox);
_boundingSphereComputed = true;
for (unsigned int i=0; i<getNumParents(); ++i)
getParent(i)->dirtyBound();
if (box != _boundingBox)
{
_boundingBox = box;
_boundingSphere = osg::BoundingSphere(_boundingBox);
_boundingSphereComputed = true;
for (unsigned int i=0; i<getNumParents(); ++i)
getParent(i)->dirtyBound();
}
}
void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath)

View file

@ -108,6 +108,11 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
case SDL_TEXTINPUT:
mKeyboardListener->textInput(evt.text);
break;
#if SDL_VERSION_ATLEAST(2, 0, 4)
case SDL_KEYMAPCHANGED:
break;
#endif
case SDL_JOYHATMOTION: //As we manage everything with GameController, don't even bother with these.
case SDL_JOYAXISMOTION:
case SDL_JOYBUTTONDOWN:

View file

@ -6,6 +6,7 @@
#include <osg/ref_ptr>
#include <SDL_events.h>
#include <SDL_version.h>
#include "OISCompat.hpp"
#include "events.hpp"

View file

@ -19,9 +19,9 @@ The largest difference between OpenMW and Morrowind in terms of data structure i
To install mods via this new feature:
#. Open ``openmw.cfg`` with your preffered text editor. It is located as described in https://wiki.openmw.org/index.php?title=Paths and *not* in your OpenMW root directory.
#. Open ``openmw.cfg`` with your preffered text editor. It is located as described in :doc:`paths` and *not* in your OpenMW root directory.
#. Find or search for ``data=``. This is located very near the bottom of the file.
#. Add a new line below this line and make a new entry of the format ``data=path/to/your/mod``
#. Add a new line below this line and make a new entry of the format ``data="path/to/your/mod"``
#. Make as many of these entries as you need for each mod folder you want to include.
#. Save ``openmw.cfg``

View file

@ -0,0 +1,77 @@
Fonts
#####
Morrowind .fnt fonts
--------------------
Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format, nor compatible with ``.fnt`` formats from any other Bethesda games. To our knowledge, the format is undocumented and no tools for viewing or editing these fonts exist.
OpenMW can load this format and convert it on the fly into something usable (see font loader `source code <https://github.com/OpenMW/openmw/blob/master/components/fontloader/fontloader.cpp#L210>`_). In OpenMW 0.32, an --export-fonts command line option was added to write the converted font (a PNG image and an XML file describing the position of each glyph in the image) to the current directory.
TrueType fonts
--------------
Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. This is the recommended way to create new fonts.
- To replace the primary "Magic Cards" font:
#. Download `Pelagiad <http://isaskar.github.io/Pelagiad/>`_ by Isak Larborn (aka Isaskar).
#. Install the ``openmw_font.xml`` file into ``resources/mygui/openmw_font.xml`` in your OpenMW installation.
#. Copy ``Pelagiad.ttf`` into ``resources/mygui/`` as well.
#. If desired, you can now delete the original ``Magic_Cards.*`` files from your Data Files/Fonts directory.
- You can also replace the Daedric font:
#. Download `Ayembedt <https://github.com/georgd/OpenMW-Fonts>`_ by Georg Duffner.
#. Install ``OMWAyembedt.otf`` into ``/resources/mygui/`` folder in your OpenMW installation.
#. Add the following lines to openmw_font.xml::
<Resource type="ResourceTrueTypeFont" name="Daedric">
<Property key="Source" value="OMWAyembedt.otf"/>
<Property key="Size" value="24"/>
<Property key="Resolution" value="50"/>
<Property key="Antialias" value="false"/>
<Property key="TabWidth" value="8"/>
<Property key="OffsetHeight" value="0"/>
<Codes>
<Code range="32"/>
<Code range="65 90"/>
<Code range="97 122"/>
</Codes>
</Resource>
#. This font is missing a few glyphs (mostly punctuation), but is complete in the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your Data Files/Fonts directory.
- Another replacement for the Daedric font is `Oblivion <http://www.uesp.net/wiki/File:Obliviontt.zip>`_ by Dongle.
#. Install the ``Oblivion.ttf`` file resources/mygui/.
#. The openmw_fonts.xml entry is::
<Resource type="ResourceTrueTypeFont" name="Daedric">
<Property key="Source" value="Oblivion.ttf"/>
<Property key="Size" value="30"/>
<Property key="Resolution" value="50"/>
<Property key="Antialias" value="false"/>
<Property key="TabWidth" value="8"/>
<Property key="OffsetHeight" value="0"/>
<Codes>
<Code range="32 34"/>
<Code range="39"/>
<Code range="44 46"/>
<Code range="48 59"/>
<Code range="63"/>
<Code range="65 90"/>
<Code range="97 122"/>
<Code range="172 173"/>
<Code range="255"/>
<Code range="376"/>
<Code range="894"/>
<Code range="8211 8212"/>
<Code range="8216 8217"/>
<Code range="8220 8221"/>
</Codes>
</Resource>
Bitmap fonts
------------
Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), which converts Morrowind ``.fnt`` to a MyGUI bitmap font. This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font.

View file

@ -14,4 +14,7 @@ The following document is the complete reference guide to modifying, or modding,
foreword
differences
mod-install
settings/index
fonts
convert_bump_mapped_mods
paths

View file

@ -8,13 +8,20 @@ Install
#. Your mod probably comes in some kind of archive, such as ``.zip``, ``.rar``, ``.7z``, or something along those lines. Unpack this archive into its own folder.
#. Ensure the structure of this folder is correct.
#. Locate the plugin files, ``.esp`` or ``.omwaddon``. The folder containing the plugin files we will call your *data folder*
#. Check that all resource folders (``Meshes``, ``Textures``, etc.) containing additional resource files (the actual meshes, textures, etc.) are in the *data folder*.
.. note::
There may be multiple levels of folders, but the location of the plugins must be the same as the resource folders.
#. Open your ``openmw.cfg`` file in your preferred plain text editor. It is located as described in https://wiki.openmw.org/index.php?title=Paths and *not* in your OpenMW root directory.
.. note::
There may be multiple levels of folders, but the location of the plugins must be the same as the resource folders.
#. Open your ``openmw.cfg`` file in your preferred plain text editor. It is located as described in :doc:`paths` and *not* in your OpenMW root directory.
#. Find or search for ``data=``. This is located very near the bottom of the file. If you are using Morrowind, this first entry should already point to your Morrowind data directory, ``Data Files``; otherwise it will point to your game file, ``.omwgame``.
#. Create a new line underneath and type: ``data="path/to/your/data folder"`` Remember, the *data folder* is where your mod's plugin files are. The double quotes around this path name are *required*.
.. note::
Some text editors, such as TextEdit on Mac, will autocorrect your double quotes to typographical "curly" quotes instead of leaving them as the propper neutral vertical quotes ``""``.
#. Save your ``openmw.cfg`` file.
You have now installed your mod. Any simple replacer mods that only contain resource files such as meshes or textures will now automatically be loaded in the order of their ``data=*`` entry. This is important to note because replacer mods that replace the same resource will overwrite previous ones as you go down the list.
@ -22,6 +29,15 @@ You have now installed your mod. Any simple replacer mods that only contain reso
Enable
------
Any mods that have plugin files must be enabled to work.
Any mods that have plugin files must be enabled to work. Master game files and plugin files can only be enabled if they have been properly installed within a *data folder* as described above.
#.
#. Open the OpenMW Launcher.
#. Click on the Data Files tab.
#. In the Content List box, select the content list you wish to modify in the dropdown menu, or make a new one by:
#. Click the New Content List button and enter the name of your content list, then click OK. New lists are useful for keeping track of the mods used for different characters or for different games if you play more than one game using OpenMW.
#. In the Content box, select your game file (``.esm`` or ``.omwgame``) from the dropdown menu.
#. Now you must activate the plugins you wish to use by checking the box next to their entry in the Content box list.
#. Load order can be changed simply by dragging the entries around within the list. Mods are loaded from the top down, so if one plugin depends on another, it must be lower on the list.
#. Click Play to run OpenMW with your game and enabled mods!

View file

@ -0,0 +1,28 @@
Paths
#####
The following describes the locations for the various OpenMW file paths for different types of files on different operating systems.
.. note::
Actual location depends on your computer's setup. Username, harddrive, and language may vary.
Configuration files and log files
---------------------------------
:Linux: ``$HOME/.config/openmw``
:Mac: ``$HOME/Library/Preferences/openmw``
:Windows: ``C:\Users\Username\Documents\my games\openmw``
Savegames
---------
:Linux: ``$HOME/.local/share/openmw/saves``
:Mac: ``$HOME/Library/Application Support/openmw/saves``
:Windows: ``C:\Users\Username\Documents\my games\openmw\saves``
Screenshots
-----------
:Linux: ``$HOME/.local/share/openmw``
:Mac: ``$HOME/Library/Application Support/openmw``
:Windows: ``C:\Users\Username\Documents\my games\openmw``

View file

@ -0,0 +1,104 @@
GUI Settings
############
scaling factor
--------------
:Type: floating point
:Range: > 0.0
:Default: 1.0
This floating point setting scales the GUI interface windows. The value must be greater than 0.0. A value of 1.0 results in the normal scale. Values much larger than 2.0 may result in user interface components being inaccessible. Until a gamepad interface is created, increasing this setting is helpful for simulating the larger interface used in console games.
The default value is 1.0. This setting can only be configured by editing the settings configuration file.
menu transparency
-----------------
:Type: floating point
:Range: 0.0 (transparent) to 1.0 (opaque)
:Default: 0.84
This floating point setting controls the transparency of the GUI windows. The value should be between 0.0 (transparent) and 1.0 (opaque).
The default value is 0.84. This setting can be adjusted in game with the Menu Transparency slider in the Prefs panel of the Options menu.
tooltip delay
-------------
:Type: floating point
:Range: > 0.0
:Default: 0.0
This floating point value determines the number of seconds between when you begin hovering over an item and when its tooltip appears. This setting only affects the tooltip delay for objects under the crosshair in GUI mode windows. There does not appear to be a setting to control the tool tip delay in outside of GUI mode.
The tooltip displays context sensitive information on the selected GUI element, such as weight, value, damage, armor rating, magical effects, and detailed description.
The default value is 0.0. This setting can be adjusted between 0.0 and 1.0 in game with the Menu Help Delay slider in the Prefs panel of the Options menu.
stretch menu background
-----------------------
:Type: boolean
:Range: True/False
:Default: False
Stretch or shrink the main menu screen, loading splash screens, introductory movie, and cut scenes to fill the specified video resolution, distorting their aspect ratio. The Bethesda provided assets have a 4:3 aspect ratio, but other assets are permitted to have other aspect ratios. If this setting is false, the assets will be centered in their correct aspect ratio, with black bars filling the remainder of the screen.
The default value is false. This setting can only be configured by editing the settings configuration file.
subtitles
---------
:Type: boolean
:Range: True/False
:Default: False
Enable or disable subtitles for NPC spoken dialog (and some sound effects). Subtitles will appear in a tool tip box in the lower center of the screen.
The default value is false. This setting can be toggled in game with the Subtitles button in the Prefs panel of Options menu.
hit fader
---------
:Type: boolean
:Range: True/False
:Default: True
This boolean setting enables or disables the "red flash" overlay that provides a visual clue when the character has taken damage.
If this setting is disabled, the player will "bleed" like NPCs do.
The default value is true. This setting can only be configured by editing the settings configuration file.
werewolf overlay
----------------
:Type: boolean
:Range: True/False
:Default: True
Enable or disable the werewolf overlay.
The default value is true. This setting can only be configured by editing the settings configuration file.
color background owned
----------------------
:Type: RGBA floating point
:Range: 0.0 to 1.0
:Default: 0.15 0.0 0.0 1.0
The following two settings determine the background color of the tool tip and the crosshair when hovering over an item owned by an NPC. The color definitions are composed of four floating point values between 0.0 and 1.0 inclusive, representing the red, green, blue and alpha channels. The alpha value is currently ignored. The crosshair color will have no effect if the crosshair setting in the HUD section is disabled.
The default value is "0.15 0.0 0.0 1.0", which is a dark red color. This setting can only be configured by editing the settings configuration file. This setting has no effect if the show owned setting in the Game Settings Section is false.
color crosshair owned
---------------------
:Type: RGBA floating point
:Range: 0.0 to 1.0
:Default: 1.0 0.15 0.15 1.0
This setting sets the color of the crosshair when hovering over an item owned by an NPC. The value is composed of four floating point values representing the red, green, blue and alpha channels. The alpha value is currently ignored.
The default value is "1.0 0.15 0.15 1.0" which is a bright red color. This setting can only be configured by editing the settings configuration file. This setting has no effect if the crosshair setting in the HUD Settings Section is false. This setting has no effect if the show owned setting in the Game Settings Section is false.

View file

@ -0,0 +1,13 @@
HUD Settings
############
crosshair
---------
:Type: boolean
:Range: True/False
:Default: True
This boolean setting determines whether the crosshair or reticle is displayed. Some players perceive that disabling the crosshair provides a more immersive experience. Another common use is to disable the crosshair for screen shots. Enabling the crosshair provides more immediate feedback about which object that is currently the focus of actions.
The default value is true. This setting can be toggled with the Crosshair button in the Prefs panel of the Options menu.

View file

@ -0,0 +1,75 @@
Camera Settings
###############
near clip
---------
:Type: floating point
:Range: > 0
:Default: 1.0
This floating point setting controls the distance to the near clipping plane. The value must be greater than zero. Values greater than approximately 18.0 will occasionally clip objects in the world in front of the character. Values greater than approximately 8.0 will clip the character's hands in first person view and/or the back of their head in third person view.
The default value is 1.0. This setting can only be configured by editing the settings configuration file. The value must be greater than 0.0, but it's unclear if the engine enforces this limitation.
small feature culling
---------------------
:Type: boolean
:Range: True/False
:Default: True
This boolean setting determines whether objects that render to a few pixels or smaller will be culled (not drawn). It generally improves performance to enable this feature, and by definition the culled objects will be very small on screen. It appears that the default definition of "small" in OpenSceneGraph is 2x2 pixels.
The default value is true. This setting can only be configured by editing the settings configuration file.
viewing distance
----------------
:Type: floating point
:Range: > 0
:Default: 6666.0
This floating point values controls the maximum visible distance (also called the far clipping plane). Larger values significantly improve rendering in exterior spaces, but also increase the amount of rendered geometry and significantly reduce the frame rate. This value interacts with the exterior cell load distance setting in that it's probably undesired for this value to provide visibility into cells that have not yet been loaded. When cells are visible before loading, the geometry will "pop-in" suddenly, creating a jarring visual effect. To prevent this effect, this value must be less than::
(8192 * exterior cell load distance - 1024) * 0.93
The constant 8192 is the size of a cell, and 1024 is the threshold distance for loading a new cell. Additionally, the field of view setting also interacts with this setting because the view frustrum end is a plane, so you can see further at the edges of the screen than you should be able to. This can be observed in game by looking at distant objects and rotating the camera so the objects are near the edge of the screen. As a result, this setting should further be reduced by a factor that depends on the field of view setting. In the default configuration this reduction is 7%, hence the factor of 0.93 above. Using this factor, approximate values recommended for other exterior cell load distance settings are:
======= ========
Cells Viewing
Distance
======= ========
2 14285
3 21903
4 29522
5 35924
======= ========
Reductions of up to 25% or more can be required to completely eliminate pop-in for wide fields of view and long viewing distances near the edges of the screen, but such situations are unusual and probably not worth the performance penalty introduced by loading geometry obscured by fog in the center of the screen. See RenderingManager::configureFog for the relevant source code.
Enabling the distant land setting is an alternative to increasing exterior cell load distance. Note that the distant land setting does not include rendering of distant static objects, so the resulting visual effect is not the same.
The default value is 6666.0. This setting can be adjusted in game from the ridiculously low value of 2000.0 to a maximum of 6666.0, using the View Distance slider in the Detail tab of the Video panel of the Options menu.
field of view
-------------
:Type: floating point
:Range: 0-360
:Default: 55.0
Sets the camera field of view in degrees. Recommended values range from 30 degrees to 110 degrees. Small values provide a very narrow field of view that creates a "zoomed in" effect, while large values cause distortion at the edges of the screen. The "field of view" setting interacts with aspect ratio of your video resolution in that more square aspect ratios (e.g. 4:3) need a wider field of view to more resemble the same field of view on a widescreen (e.g. 16:9) monitor.
The default value is 55.0. This setting can be changed in game using the Field of View slider from the Video tab of the Video panel of the Options menu.
first person field of view
--------------------------
:Type: floating point
:Range: 0-360
:Default: 55.0
The floating point setting controls the field of view for first person meshes such as the player's hands and held objects. It is not recommended to change this value from its default value because the Bethesda provided Morrowind assets do not adapt well to large values, while small values can result in the hands not being visible.
The default value is 55.0. This setting can only be configured by editing the settings configuration file.

View file

@ -0,0 +1,17 @@
Cells Settings
##############
exterior cell load distance
---------------------------
:Type: integer
:Range: >= 1
:Default: 1
This integer setting determines the number of exterior cells adjacent to the character that will be loaded for rendering. Values greater than one may significantly affect loading times when exiting interior spaces or loading additional exterior cells. Caution is advised when increasing this setting.
This setting interacts with viewing distance and field of view settings.
It is generally very wasteful for this value to load geometry than will almost never be visible due to viewing distance and fog. For low frame rate screen shots of scenic vistas, this setting should be set high, and viewing distances adjusted accordingly.
The default value is 1. This value must be greater than or equal to 1. This setting can only be configured by editing the settings configuration file.

View file

@ -0,0 +1,46 @@
Game Settings
#############
show owned
----------
:Type: integer
:Range: 0, 1, 2, 3
:Default: 0
Enable visual clues for items owned by NPCs when the crosshair is on the object. If the setting is 0, no clues are provided which is the default Morrowind behavior. If the setting is 1, the background of the tool tip for the object is highlight in the color specified by the color background owned setting in the GUI Settings Section. If the setting is 2, the crosshair is the color of the color crosshair owned setting in the GUI Settings section. If the setting is 3, both the tool tip background and the crosshair are colored. The crosshair is not visible if crosshair is false.
The default value is 0 (no clues). This setting can only be configured by editing the settings configuration file.
best attack
-----------
:Type: boolean
:Range: True/False
:Default: False
If this boolean setting is true, the player character will always use the most powerful attack when striking with a weapon (chop, slash or thrust). If this setting is false, the type of attack is determined by the direction that the character is moving at the time the attack begins.
The default value is false. This setting can be toggled with the Always Use Best Attack button in the Prefs panel of the Options menu.
difficulty
----------
:Type: integer
:Range: -500 to 500
:Default: 0
This integer setting adjusts the difficulty of the game and is intended to be in the range -100 to 100 inclusive. Given the default game setting for fDifficultyMult of 5.0, a value of -100 results in the player taking 80% of the usual damage, doing 6 times the normal damage. A value of 100 results in the player taking 6 times as much damage, but inflicting only 80% of the usual damage. Values less than -500 will result in the player receiving no damage, and values greater than 500 will result in the player inflicting no damage.
The default value is 0. This setting can be controlled in game with the Difficulty slider in the Prefs panel of the Options menu.
show effect duration
--------------------
:Type: boolean
:Range: True/False
:Default: False
Show the remaining duration of magic effects and lights if this boolean setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect.
The default value is false. This setting can only be configured by editing the settings configuration file.

View file

@ -0,0 +1,35 @@
General Settings
################
anisotropy
----------
:Type: integer
:Range: 0 to 16
:Default: 4
Set the maximum anisotropic filtering on textures. Anisotropic filtering is a method of enhancing the image quality of textures on surfaces that are at oblique viewing angles with respect to the camera. Valid values range from 0 to 16. Modern video cards can often perform 8 or 16 anisotropic filtering with a minimal performance impact. This effect of this setting can be seen in the Video panel of the Options menu by finding a location with straight lines (striped rugs and Balmora cobblestones work well) radiating into the distance, and adjusting the anisotropy slider.
The default value is 4. This setting can be changed in game using the Anisotropy slider in the Detail tab of the Video panel of the Options menu.
screenshot format
-----------------
:Type: string
:Range: jpg, png, tga
:Default: png
Specify the format for screen shots taken by pressing the screen shot key (bound to F12 by default). This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but "jpg", "png", and "tga" should be allowed.
The default value is "png". This setting can only be configured by editing the settings configuration file.
texture filtering
-----------------
:Type: string
:Range: bilinear, trilinear
:Default: trilinear
Set the isotropic texture filtering mode to bilinear or trilinear. Bilinear filtering is a texture filtering method used to smooth textures when displayed larger or smaller than they actually are. Bilinear filtering is reasonably accurate until the scaling of the texture gets below half or above double the original size of the texture. Trilinear filtering is an extension of the bilinear texture filtering method, which also performs linear interpolation between mipmaps. Both methods use mipmaps in OpenMW, and the corresponding OpenGL modes are LINEAR_MIPMAP_NEAREST and LINEAR_MIPMAP_LINEAR. Trilinear filtering produces better texturing at a minimal cost on modern video cards.
The default value is trilinear. This setting can be changed in game using the Texture filtering pull down in the Detail tab of the Video panel of the Options menu.

View file

@ -0,0 +1,28 @@
###############################
Advanced Settings Configuration
###############################
This part of the guide will cover how to make modifications to the more arcane settings in OpenMW, most of which are not available from in-game menus, to optimize or customize your OpenMW experience. If you are familiar with ``.ini`` tweaks in Morrowind or the other games, this will be quite similar. All settings described in this section are changed in ``settings.cfg``, located in your OpenMW user directory. See :doc:`paths` for this location.
Although this guide attempts to be comprehensive and up to date. You will always be able to find the full list of settings available and their default values in ``settings-default.cfg`` in your main OpenMW installation directory. The ranges I have included with each setting are the physically possible ranges, not recommendations.
.. warning::
As the title suggests, these are advanced settings. If digging around plain text files and manually editing settings sounds scary to you, you may want to stear clear of altering these files. That being said, this guide should be plenty clear enough that you can find the setting you want to change and safely edit it.
.. toctree::
:caption: Table of Contents
:maxdepth: 2
camera
cells
map
GUI
HUD
game
general
input
saves
sound
video
water
windows

View file

@ -0,0 +1,83 @@
Input Settings
##############
grab cursor
-----------
:Type: boolean
:Range: True/False
:Default: True
OpenMW will capture control of the cursor if this boolean setting is true.
In "look mode", OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.
This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting "Alt-Tab" or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.
Note for developers: it's desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.
The default value is true. This setting can only be configured by editing the settings configuration file.
toggle sneak
------------
:Type: boolean
:Range: True/False
:Default: False
This boolean setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled.
The default value is false. This setting can only be configured by editing the settings configuration file.
always run
----------
:Type: boolean
:Range: True/False
:Default: False
If this boolean setting is true, the character is running by default, otherwise the character is walking by default. The shift key will temporarily invert this setting, and the caps lock key will invert this setting while it's "locked". This setting is updated every time you exit the game, based on whether the caps lock key was on or off at the time you exited.
The default value is false. This settings can be toggled in game by pressing the CapsLock key and exiting.
allow third person zoom
-----------------------
:Type: boolean
:Range: True/False
:Default: False
Allow zooming in and out using the middle mouse wheel in third person view. This feature may not work correctly if the mouse wheel is bound to other actions, and may be triggered accidentally in some cases, so is disabled by default. This setting can only be configured by editing the settings configuration file.
camera sensitivity
------------------
:Type: floating point
:Range: > 0
:Default: 1.0
This floating point setting controls the overall camera/mouse sensitivity when not in GUI mode. The default sensitivity is 1.0, with smaller values requiring more mouse movement, and larger values requiring less. This setting is multiplicative in magnitude. This setting does not affect mouse speed in GUI mode, which is instead controlled by your operating system mouse speed setting.
The default value is 1.0. This setting can be changed with the Camera Sensitivity slider in the Controls panel of the Options menu.
camera y multiplier
-------------------
:Type: floating point
:Range: > 0
:Default: 1.0
This floating point setting controls the vertical camera/mouse sensitivity relative to the horizontal sensitivity (see camera sensitivity above). It is multiplicative with the previous setting, meaning that it should remain set at 1.0 unless the player desires to have different sensitivities in the two axes.
The default value is 1.0. This setting can only be configured by editing the settings configuration file.
invert y axis
-------------
:Type: boolean
:Range: True/False
:Default: False
Invert the vertical axis while not in GUI mode. If this setting is true, moving the mouse away from the player will look down, while moving it towards the player will look up. This setting does not affect cursor movement in GUI mode.
The default value is false. This setting can be toggled in game with the Invert Y Axis button in the Controls panel of the Options menu.

View file

@ -0,0 +1,53 @@
Map Settings
############
global map size
---------------
:Type: integer
:Range: >= 1
:Default: 18
This integer setting adjusts the scale of the world map in the GUI mode map window. The value is the width in pixels of each cell in the map, so larger values result in larger more detailed world maps, while smaller values result in smaller less detailed world maps. However, the native resolution of the map source material appears to be 9 pixels per unexplored cell and approximately 18 pixels per explored cell, so values larger than 36 don't produce much additional detail. Similarly, the size of place markers is currently fixed at 12 pixels, so values smaller than this result in overlapping place markers. Values from 12 to 36 are recommended. For reference, Vvardenfell is approximately 41x36 cells.
Warning: Changing this setting affects saved games. The currently explored area is stored as an image in the save file that's overlayed on the default world map in game. When you increase the resolution of the map, the overlay of earlier saved games will be scaled up on load, and appear blurry. When you visit the cell again, the overlay for that cell is regenerated at the new resolution, so the blurry areas can be corrected by revisiting all the cells you've already visited.
The default value for this setting is 18. This setting can not be configured except by editing the settings configuration file.
local map hud widget size
-------------------------
:Type: integer
:Range: >= 1
:Default: 256
This integer setting controls the zoom level for the HUD map widget (the map in the lower right corner of the window). A value of 64 results in the HUD map widget displaying one entire exterior cell. Since the GUI mode map displays 3x3 cells, a value of approximately 21 displays the same area as the GUI mode map. Larger values increase the level of zoom, while smaller values are wasteful since there's no map data to display beyond the 3x3 cell grid.
Note that the actual size of the widget is always the same on the screen unless the scaling factor setting in the "GUI" section is changed. Increasing both the scaling factor of the GUI and this setting does result in a higher resolution HUD map, but unfortunately with a scaled direction pointer on top of it.
The default value for this setting is 256. This setting can not be configured except by editing the settings configuration file.
local map resolution
--------------------
:Type: integer
:Range: >= 1
:Default: 256
This integer setting controls the resolution of the GUI mode local map window. Larger values generally increase the visible detail in map. If this setting is half the local map widget size or smaller, the map will generally be be fairly blurry. Setting both options to the same value results in a map with good detail. Values that exceed the local map widget size setting by more than a factor of two are unlikely to provide much of an improvement in detail since they're subsequently scaled back to the approximately the map widget size before display. The video resolution settings interacts with this setting in that regard.
.. warning::
Increasing this setting can increase cell load times, because the map is rendered on demand each time you enter a new cell. Large values may exceed video card limits or exhaust VRAM.
The default value for this setting is 256. This setting can not be configured except by editing the settings configuration file.
local map widget size
---------------------
:Type: integer
:Range: >= 1
:Default: 512
This integer setting controls the canvas size of the GUI mode local map window. Larger values result in a larger physical map size on screen, and typically require more panning to see all available portions of the map. This larger size also enables an overall greater level of detail if the local map resolution setting is also increased.
The default value for this setting is 512. This setting can not be configured except by editing the settings configuration file.

View file

@ -0,0 +1,35 @@
Saves Settings
##############
character
---------
:Type: string
:Range:
:Default: ""
This string setting contains the default character name for loading saved games.
The default value is the empty string, which results in no character being selected by default. This setting is automatically updated from the Load menu when a different character is selected.
autosave
--------
:Type: boolean
:Range: True/False
:Default: True
This boolean setting determines whether the game will be automatically saved when the character rests.
The default value is true. This setting can be toggled in game with the Auto-Save when Rest button in the Prefs panel of the Options menu.
timeplayed
----------
:Type: boolean
:Range: True/False
:Default: False
This boolean setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.
The default value is false. This setting can only be configured by editing the settings configuration file. This setting was added in OpenMW 0.37.

View file

@ -0,0 +1,113 @@
Sound Settings
##############
device
------
:Type: string
:Range:
:Default: ""
This string setting determines which audio device to use. A blank or missing setting means to use the default device, which should usually be sufficient, but if you need to explicitly specify a device use this setting.
The names of detected devices can be found in the openmw.log file in your configuration directory.
The default value is the empty string. This setting can only be configured by editing the settings configuration file.
master volume
-------------
:Type: floating point
:Range: 0.0 to 1.0
:Default: 1.0
This floating point setting controls the overall volume. The master volume is multiplied with all other volume settings to determine the final volume.
The default value is 1.0. Valid values range from 0.0 (silent) to 1.0 (maximum volume). This setting can be changed in game using the Master slider from the Audio panel of the Options menu.
footsteps volume
----------------
:Type: floating point
:Range: 0.0 to 1.0
:Default: 0.2
This floating point setting controls the volume of footsteps from the character and other actors.
The default value is 0.2. Valid values range from 0.0 (silent) to 1.0 (maximum volume). This setting can be changed in game using the Footsteps slider from the Audio panel of the Options menu.
music volume
------------
:Type: floating point
:Range: 0.0 to 1.0
:Default: 0.5
This floating point setting controls the volume for music tracks.
The default value is 0.5. Valid values range from 0.0 (silent) to 1.0 (maximum volume). This setting can be changed in game using the Music slider from the Audio panel of the Options menu.
sfx volume
----------
:Type: floating point
:Range: 0.0 to 1.0
:Default: 1.0
This floating point setting controls the volume for special sound effects such as combat noises.
The default value is 1.0. Valid values range from 0.0 (silent) to 1.0 (maximum volume). This setting can be changed in game using the Effects slider from the Audio panel of the Options menu.
voice volume
------------
:Type: floating point
:Range: 0.0 to 1.0
:Default: 0.8
This floating point setting controls the volume for spoken dialog from NPCs.
The default value is 0.8. Valid values range from 0.0 (silent) to 1.0 (maximum volume). This setting can be changed in game using the Voice slider from the Audio panel of the Options menu.
buffer cache min
----------------
:Type: integer
:Range: > 0
:Default: 14
This integer setting determines the minimum size of the sound buffer cache in megabytes. When the cache reaches the size specified by the buffer cache max setting, old buffers will be unloaded until it's using no more memory than specified by this setting. This setting must be less than or equal to the buffer cache max setting.
The default value is 14. This setting can only be configured by editing the settings configuration file. This setting was added in OpenMW version 0.38.
buffer cache max
----------------
:Type: integer
:Range: > 0
:Default: 16
This integer setting determines the maximum size of the sound buffer cache in megabytes. When the cache reaches this size, old buffers will be unloaded until it reaches the size specified by the buffer cache min setting. This setting must be greater than or equal to the buffer cache min setting.
The default value is 16. This setting can only be configured by editing the settings configuration file. This setting was added in OpenMW version 0.38.
hrtf enable
-----------
:Type: integer
:Range: -1, 0, 1
:Default: -1
This integer setting determines whether to enable head-related transfer function (HRTF) audio processing. HRTF audio processing creates the perception of sounds occurring in a three dimensional space when wearing headphones. Enabling HRTF may also require an OpenAL Soft version greater than 1.17.0, and possibly some operating system configuration. A value of 0 disables HRTF processing, while a value of 1 explicitly enables HRTF processing.
The default value is -1, which should enable the feature automatically for most users when possible. This setting can only be configured by editing the settings configuration file. This setting was added in OpenMW version 0.38.
hrtf
----
:Type: string
:Range:
:Default: ""
This string setting specifies which HRTF profile to use when HRTF is enabled. Blank means use the default. This setting has no effect if HRTF is not enabled based on the hrtf enable setting. Allowed values for this field are enumerated in openmw.log file is an HRTF enabled ausio system is installed.
The default value is the empty string, which uses the default profile. This setting can only be configured by editing the settings configuration file. This setting was added in OpenMW version 0.38.

View file

@ -0,0 +1,135 @@
Video Settings
##############
resolution x
------------
:Type: integer
:Range: > 0
:Default: 800
This setting determines the horizontal resolution of the OpenMW game window. Larger values produce more detailed images within the constraints of your graphics hardware but also significantly reduce the frame rate.
The default value is 800. The window resolution can be selected from a menu of common screen sizes in the Video tab of the Video Panel of the Options menu, or in the Graphics tab of the OpenMW Launcher. The horizontal resolution can also be set to a custom value in the Graphics tab of the OpenMW Launcher.
resolution y
------------
:Type: integer
:Range: > 0
:Default: 600
This setting determines the vertical resolution of the OpenMW game window. Larger values produce more detailed images within the constraints of your graphics hardware but also significantly reduce the frame rate.
The default value is 600. The window resolution can be selected from a menu of common screen sizes in the Video tab of the Video Panel of the Options menu, or in the Graphics tab of the OpenMW Launcher. The vertical resolution can also be set to a custom value in the Graphics tab of the OpenMW Launcher.
fullscreen
----------
:Type: boolean
:Range: True/False
:Default: False
This boolean setting determines whether the entire screen is used for the specified resolution.
The default value is false. This setting can be toggled in game using the Fullscreen button in the Video tab of the Video panel in the Options menu. It can also be toggled with the Full Screen check box in the Graphics tab of the OpenMW Launcher.
screen
------
:Type: integer
:Range: >= 0
:Default: 0
This integer setting determines which screen the game will open on in multi-monitor configurations. This setting is particularly important when the fullscreen setting is true, since this is the only way to control which screen is used, but it can also be used to control which screen a normal window or a borderless window opens on as well. The screens are numbered in increasing order, beginning with 0.
The default value is 0. This setting can be selected from a pull down menu in the Graphics tab of the OpenMW Launcher, but cannot be changed during game play.
minimize on focus loss
----------------------
:Type: boolean
:Range: True/False
:Default: False
Minimize the OpenMW window if it loses cursor focus. This setting is primarily useful for single screen configurations, so that the OpenMW screen in full screen mode can be minimized when the operating system regains control of the mouse and keyboard. On multiple screen configurations, disabling this option makes it easier to switch between screens while playing OpenMW.
Note that a minimized game window consumes less system resources and produces less heat, since the game does not need to render in minimized state. It is therefore advisable to minimize the game during pauses (either via use of this setting, or by minimizing the window manually).
This setting has no effect if the fullscreen setting is false.
Developer note: corresponds to SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS.
The default value is true. This setting can only be configured by editing the settings configuration file.
window border
-------------
:Type: boolean
:Range: True/False
:Default: True
This boolean setting determines whether there's an operating system border drawn around the OpenMW window. If this setting is true, the window can be moved and resized with the operating system window controls. If this setting is false, the window has no operating system border.
This setting has no effect if the fullscreen setting is true.
The default value is true. This setting can be toggled in game using the Window Border button in the Video tab of the Video panel in the Options menu. It can also be toggled with the Window Border check box in the OpenMW Launcher.
antialiasing
------------
:Type: integer
:Range: 0, 2, 4, 8, 16
:Default: 0
This integer setting controls anti-aliasing. Anti-aliasing is a technique designed to improve the appearance of polygon edges, so they do not appear to be "jagged". Anti-aliasing can smooth these edges at the cost of a minor reduction in the frame rate. A value of 0 disables anti-aliasing. Other powers of two (e.g. 2, 4, 8, 16) are supported according to the capabilities of your graphics hardware. Higher values do a better job of smoothing out the image but have a greater impact on frame rate.
This setting can be configured from a list of valid choices in the Graphics panel of the OpenMW Launcher, but cannot be changed during game play - due to a technical limitation that may be addressed in a future version of OpenMW.
vsync
-----
:Type: boolean
:Range: True/False
:Default: False
This boolean setting determines whether frame draws are synchronized with the vertical refresh rate of your monitor. Enabling this setting can reduce screen tearing, a visual defect caused by updating the image buffer in the middle of a screen draw. Enabling this option typically implies limiting the framerate to 60 frames per second, but may also introduce additional delays caused by having to wait until the appropriate time (the vertical blanking interval) to draw a frame.
The default value is false. This setting can be adjusted in game using the VSync button in the Video tab of the Video panel in the Options menu. It can also be changed by toggling the Vertical Sync check box in the Graphics tab of the OpenMW Launcher.
framerate limit
---------------
:Type: floating point
:Range: >= 0.0
:Default: 0.0
This floating point setting determines the maximum frame rate in frames per second. If this setting is 0.0, the frame rate is unlimited.
There are several reasons to consider capping your frame rate, especially if you're already experiencing a relatively high frame rate (greater than 60 frames per second). Lower frame rates will consume less power and generate less heat and noise. Frame rates above 60 frames per second rarely produce perceptible improvements in visual quality, but may improve input responsiveness. Capping the frame rate may in some situations reduce the perception of choppiness (highly variable frame rates during game play) by lowering the peak frame rates.
This setting interacts with the vsync setting in the Video section in the sense that enabling vertical sync limits the frame rate to the refresh rate of your monitor (often 60 frames per second). Choosing to limit the frame rate using this setting instead of vsync may reduce input lag due to the game not having to wait for the vertical blanking interval.
The default value is 0.0. This setting can only be configured by editing the settings configuration file. This setting was added in OpenMW 0.37.
contrast
--------
:Type: floating point
:Range: > 0.0
:Default: 1.0
This floating point setting controls the contrast correction for all video in the game.
The default value is 1.0. This setting can only be configured by editing the settings configuration file. This setting does not currently work under Linux.
gamma
-----
:Type: floating point
:Range: > 0.0
:Default: 1.0
This floating point setting controls the gamma correction for all video in the game. Gamma is an exponent that makes colors brighter if greater than 1.0 and darker if less than 1.0.
The default value is 1.0. This setting can be changed in the Detail tab of the Video panel of the Options menu. This setting does not currently work under Linux, and the in-game setting in the Options menu has been disabled.

View file

@ -0,0 +1,43 @@
Water Settings
############
.. note::
The settings for the water shader are difficult to describe, but can be seen immediately in the Water tab of the Video panel in the Options menu. Changes there will be saved to these settings. It is suggested to stand on the shore of a moderately broad body of water with trees or other objects on the far shore to test reflection textures, underwater plants in shallow water near by to test refraction textures, and some deep water visible from your location to test deep water visibility.
shader
------
:Type: boolean
:Range: True/False
:Default: False
This boolean setting enables or disables the water shader, which results in much more realistic looking water surfaces, including reflected objects and a more detailed wavy surface.
The default value is false. This setting can be toggled with the Shader button in the Water tab of the Video panel of the Options menu.
rtt size
--------
:Type: integer
:Range: > 0
:Default: 512
The integer setting determines the size of the texture used for reflection and refraction (if enabled). For reflection, the texture size determines the detail of reflected images on the surface of the water. For refraction, the texture size determines the detail of the objects on the other side of the plane of water (which have a wavy appearance caused by the refraction). RTT is an acronym for Render to Texture which allows rendering of the scene to be saved as a texture.
Higher values produces better visuals and result in a marginally lower frame rate depending on your graphics hardware.
In the Water tab of the Video panel of the Options menu, the choices are Low (512), Medium (1024) and High (2048). This setting has no effect if the shader setting is false. It is recommended to use values that are a power of two because this results in more efficient use of video hardware.
This setting has no effect if the shader setting is false.
refraction
----------
:Type: boolean
:Range: True/False
:Default: False
This boolean setting enables the refraction rendering feature of the water shader. Refraction causes deep water to be more opaque and objects seen through the plane of the water to have a wavy appearance. Enabling this feature results in better visuals, and a marginally lower frame rate depending on your graphics hardware.
This setting has no effect if the shader setting is false.
The default setting is false. This setting can be toggled with the Refraction button in the Water tab of the Video panel of the Options menu.

View file

@ -0,0 +1,149 @@
Windows Settings
############
:Type: floating point
:Range: 0.0 to 1.0
This section controls the location and size of each window in GUI mode. Each setting is a floating point number representing a *fraction* of the resolution x or resolution y setting in the Video Settings Section. The X and Y values locate the top left corner of the window, while the W value determines the width of the window and the H value determines the height of the window.
Unlike the documentation for most sections which lists the exact setting name, this page instead lists the names of the windows. For example, to configure the alchemy window, the actual settings would be::
alchemy x = 0.25
alchemy y = 0.25
alchemy h = 0.5
alchemy w = 0.5
Each window in the GUI mode remembers it's previous location when exiting the game. By far the easiest way to configure these settings is to simply move the windows around in game. Hand editing the configuration file might result in some fine tuning for alignment, but the settings will be overwritten if a window is moved.
.. note::
To scale the windows, making the widgets proportionally larger, see the scaling factor setting instead.
stats
-----
:Default: x = 0.0
y = 0.0
h = 0.375
w = 0.4275
The stats window, displaying level, race, class, skills and stats. Activated by clicking on any of the three bars in the lower left corner of the HUD.
spells
------
:Default: x = 0.625
y = 0.5725
h = 0.375
w = 0.4275
The spells window, displaying powers, spells, and magical items. Activated by clicking on the spells widget (third from left) in the bottom left corner of the HUD.
map
---
:Default: x = 0.625
y = 0.0
h = 0.375
w = 0.5725
The local and world map window. Activated by clicking on the map widget in the bottom right corner of the HUD.
dialogue
--------
:Default: x = 0.095
y = 0.095
h = 0.810
w = 0.810
The dialog window, for talking with NPCs. Activated by clicking on a NPC.
alchemy
-------
:Default: x = 0.25
y = 0.25
h = 0.5
w = 0.5
The alchemy window, for crafting potions. Activated by dragging an alchemy tool on to the rag doll. Unlike most other windows, this window hides all other windows when opened.
console
-------
:Default: x = 0.0
y = 0.0
h = 1.0
w = 0.5
The console command window. Activated by pressing the tilde (~) key.
inventory
---------
:Default: x = 0.0
y = 0.4275
h = 0.6225
w = 0.5725
The inventory window, displaying the paper doll and possessions, when activated by clicking on the inventory widget (second from left) in the bottom left corner of the HUD.
inventory container
-------------------
:Default: x = 0.0
y = 0.4275
h = 0.6225
w = 0.5725
The player's inventory window while searching a container, showing the contents of the character's inventory. Activated by clicking on a container. The same window is used for searching dead bodies, and pickpocketing people.
inventory barter
----------------
:Default: x = 0.0
y = 0.4275
h = 0.6225
w = 0.5725
The player's inventory window while bartering. It displays goods owned by the character while bartering. Activated by clicking on the Barter choice in the dialog window for an NPC.
inventory companion
-------------------
:Default: x = 0.0
y = 0.4275
h = 0.6225
w = 0.5725
The player's inventory window while interacting with a companion. The companion windows were added in the Tribunal expansion, but are available everywhere in the OpenMW engine.
container
---------
:Default: x = 0.25
y = 0.0
h = 0.75
w = 0.375
The container window, showing the contents of the container. Activated by clicking on a container. The same window is used for searching dead bodies, and pickpocketing people.
barter
------
:Default: x = 0.25
y = 0.0
h = 0.75
w = 0.375
The NPC bartering window, displaying goods owned by the shopkeeper while bartering. Activated by clicking on the Barter choice in the dialog window for an NPC.
companion
---------
:Default: x = 0.25
y = 0.0
h = 0.75
w = 0.375
The NPC's inventory window while interacting with a companion. The companion windows were added in the Tribunal expansion, but are available everywhere in the OpenMW engine.