forked from mirror/openmw-tes3mp
Merge remote-tracking branch 'upstream/master' into static-deps-build
This commit is contained in:
commit
43b1f15af9
18 changed files with 278 additions and 234 deletions
|
@ -18,8 +18,8 @@ addons:
|
|||
name: "OpenMW/openmw"
|
||||
description: "<Your project description here>"
|
||||
notification_email: scrawl@baseoftrash.de
|
||||
build_command_prepend: "cmake ."
|
||||
build_command: "make -j3"
|
||||
build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE"
|
||||
build_command: "make"
|
||||
branch_pattern: coverity_scan
|
||||
matrix:
|
||||
include:
|
||||
|
|
12
README.md
12
README.md
|
@ -3,8 +3,9 @@ OpenMW
|
|||
|
||||
[![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740)
|
||||
|
||||
OpenMW is an attempt at recreating the engine for the popular role-playing game
|
||||
Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
|
||||
OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
|
||||
|
||||
OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set.
|
||||
|
||||
* Version: 0.36.0
|
||||
* License: GPL (see docs/license/GPL3.txt for more information)
|
||||
|
@ -14,6 +15,13 @@ Morrowind by Bethesda Softworks. You need to own and install the original game f
|
|||
Font Licenses:
|
||||
* DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information)
|
||||
|
||||
Current Status
|
||||
--------------
|
||||
|
||||
The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://bugs.openmw.org/versions/21) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces.
|
||||
|
||||
Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
|
|
|
@ -166,7 +166,9 @@ namespace ESSImport
|
|||
|
||||
if (i >= file2.mRecords.size())
|
||||
{
|
||||
std::ios::fmtflags f(std::cout.flags());
|
||||
std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl;
|
||||
std::cout.flags(f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -174,7 +176,9 @@ namespace ESSImport
|
|||
|
||||
if (rec.mName != rec2.mName)
|
||||
{
|
||||
std::ios::fmtflags f(std::cout.flags());
|
||||
std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl;
|
||||
std::cout.flags(f);
|
||||
return; // TODO: try to recover
|
||||
}
|
||||
|
||||
|
@ -185,7 +189,9 @@ namespace ESSImport
|
|||
|
||||
if (j >= rec2.mSubrecords.size())
|
||||
{
|
||||
std::ios::fmtflags f(std::cout.flags());
|
||||
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl;
|
||||
std::cout.flags(f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -193,8 +199,10 @@ namespace ESSImport
|
|||
|
||||
if (sub.mName != sub2.mName)
|
||||
{
|
||||
std::ios::fmtflags f(std::cout.flags());
|
||||
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
|
||||
<< " (2) 0x" << sub2.mFileOffset << std::endl;
|
||||
std::cout.flags(f);
|
||||
break; // TODO: try to recover
|
||||
}
|
||||
|
||||
|
@ -203,6 +211,8 @@ namespace ESSImport
|
|||
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
|
||||
continue;
|
||||
|
||||
std::ios::fmtflags f(std::cout.flags());
|
||||
|
||||
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
|
||||
<< " (2) 0x" << sub2.mFileOffset << std::endl;
|
||||
|
||||
|
@ -235,6 +245,7 @@ namespace ESSImport
|
|||
std::cout << "\033[0m";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout.flags(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -319,7 +330,11 @@ namespace ESSImport
|
|||
else
|
||||
{
|
||||
if (unknownRecords.insert(n.val).second)
|
||||
{
|
||||
std::ios::fmtflags f(std::cerr.flags());
|
||||
std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl;
|
||||
std::cerr.flags(f);
|
||||
}
|
||||
|
||||
esm.skipRecord();
|
||||
}
|
||||
|
|
|
@ -49,6 +49,10 @@ namespace ESSImport
|
|||
std::map<std::string, ESM::NPC> mNpcs;
|
||||
|
||||
Context()
|
||||
: mDay(0)
|
||||
, mMonth(0)
|
||||
, mYear(0)
|
||||
, mHour(0.f)
|
||||
{
|
||||
mPlayer.mAutoMove = 0;
|
||||
ESM::CellId playerCellId;
|
||||
|
|
|
@ -203,6 +203,8 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
|
|||
return false; // script does not have a variable of this name.
|
||||
|
||||
int index = localDefs.getIndex (name);
|
||||
if (index < 0)
|
||||
return false; // shouldn't happen, we checked that variable has a type above, so must exist
|
||||
|
||||
const MWScript::Locals& locals = mActor.getRefData().getLocals();
|
||||
|
||||
|
|
|
@ -235,8 +235,9 @@ namespace MWGui
|
|||
|
||||
MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged);
|
||||
|
||||
// Create all cursors in advance
|
||||
createCursors();
|
||||
onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer());
|
||||
|
||||
mCursorManager->setEnabled(true);
|
||||
|
||||
// hide mygui's pointer
|
||||
|
@ -896,6 +897,9 @@ namespace MWGui
|
|||
|
||||
void WindowManager::updateMap()
|
||||
{
|
||||
if (!mLocalMapRender)
|
||||
return;
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
|
||||
osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3();
|
||||
|
@ -1178,31 +1182,7 @@ namespace MWGui
|
|||
|
||||
void WindowManager::onCursorChange(const std::string &name)
|
||||
{
|
||||
if(!mCursorManager->cursorChanged(name))
|
||||
return; //the cursor manager doesn't want any more info about this cursor
|
||||
//See if we can get the information we need out of the cursor resource
|
||||
ResourceImageSetPointerFix* imgSetPtr = dynamic_cast<ResourceImageSetPointerFix*>(MyGUI::PointerManager::getInstance().getByName(name));
|
||||
if(imgSetPtr != NULL)
|
||||
{
|
||||
MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet();
|
||||
|
||||
std::string tex_name = imgSet->getIndexInfo(0,0).texture;
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP);
|
||||
tex->setUnRefImageDataAfterApply(false); // FIXME?
|
||||
|
||||
//everything looks good, send it to the cursor manager
|
||||
if(tex.valid())
|
||||
{
|
||||
Uint8 size_x = imgSetPtr->getSize().width;
|
||||
Uint8 size_y = imgSetPtr->getSize().height;
|
||||
Uint8 hotspot_x = imgSetPtr->getHotSpot().left;
|
||||
Uint8 hotspot_y = imgSetPtr->getHotSpot().top;
|
||||
int rotation = imgSetPtr->getRotation();
|
||||
|
||||
mCursorManager->receiveCursorInfo(name, rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y);
|
||||
}
|
||||
}
|
||||
mCursorManager->cursorChanged(name);
|
||||
}
|
||||
|
||||
void WindowManager::popGuiMode()
|
||||
|
@ -1960,6 +1940,33 @@ namespace MWGui
|
|||
return Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS());
|
||||
}
|
||||
|
||||
void WindowManager::createCursors()
|
||||
{
|
||||
MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator();
|
||||
while (enumerator.next())
|
||||
{
|
||||
MyGUI::IResource* resource = enumerator.current().second;
|
||||
ResourceImageSetPointerFix* imgSetPointer = dynamic_cast<ResourceImageSetPointerFix*>(resource);
|
||||
if (!imgSetPointer)
|
||||
continue;
|
||||
std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture;
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP);
|
||||
|
||||
if(tex.valid())
|
||||
{
|
||||
//everything looks good, send it to the cursor manager
|
||||
Uint8 size_x = imgSetPointer->getSize().width;
|
||||
Uint8 size_y = imgSetPointer->getSize().height;
|
||||
Uint8 hotspot_x = imgSetPointer->getHotSpot().left;
|
||||
Uint8 hotspot_y = imgSetPointer->getHotSpot().top;
|
||||
int rotation = imgSetPointer->getRotation();
|
||||
|
||||
mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::createTextures()
|
||||
{
|
||||
{
|
||||
|
|
|
@ -511,6 +511,7 @@ namespace MWGui
|
|||
void onClipboardRequested(const std::string& _type, std::string& _data);
|
||||
|
||||
void createTextures();
|
||||
void createCursors();
|
||||
void setMenuTransparency(float value);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ namespace MWMechanics
|
|||
|
||||
mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||
|
||||
mStoredAvailableNodes = false;
|
||||
mPopulateAvailableNodes = true;
|
||||
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ namespace MWMechanics
|
|||
if(!currentCell || cellChange)
|
||||
{
|
||||
currentCell = actor.getCell();
|
||||
mStoredAvailableNodes = false; // prob. not needed since mDistance = 0
|
||||
mPopulateAvailableNodes = true;
|
||||
}
|
||||
|
||||
cStats.setDrawState(DrawState_Nothing);
|
||||
|
@ -225,8 +225,6 @@ namespace MWMechanics
|
|||
storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE))
|
||||
{
|
||||
stopWalking(actor, storage);
|
||||
moveNow = false;
|
||||
walking = false;
|
||||
chooseAction = true;
|
||||
mHasReturnPosition = false;
|
||||
}
|
||||
|
@ -239,45 +237,7 @@ namespace MWMechanics
|
|||
zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
|
||||
|
||||
// Returns true if evasive action needs to be taken
|
||||
if(mObstacleCheck.check(actor, duration))
|
||||
{
|
||||
// first check if we're walking into a door
|
||||
if(proximityToDoor(actor)) // NOTE: checks interior cells only
|
||||
{
|
||||
// remove allowed points then select another random destination
|
||||
mTrimCurrentNode = true;
|
||||
trimAllowedNodes(mAllowedNodes, storage.mPathFinder);
|
||||
mObstacleCheck.clear();
|
||||
storage.mPathFinder.clearPath();
|
||||
walking = false;
|
||||
moveNow = true;
|
||||
}
|
||||
else // probably walking into another NPC
|
||||
{
|
||||
// TODO: diagonal should have same animation as walk forward
|
||||
// but doesn't seem to do that?
|
||||
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f;
|
||||
// change the angle a bit, too
|
||||
zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
|
||||
}
|
||||
mStuckCount++; // TODO: maybe no longer needed
|
||||
}
|
||||
//#if 0
|
||||
// TODO: maybe no longer needed
|
||||
if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
|
||||
{
|
||||
//std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl;
|
||||
mObstacleCheck.clear();
|
||||
|
||||
stopWalking(actor, storage);
|
||||
moveNow = false;
|
||||
walking = false;
|
||||
chooseAction = true;
|
||||
mStuckCount = 0;
|
||||
}
|
||||
//#endif
|
||||
evadeObstacles(actor, storage, duration);
|
||||
}
|
||||
|
||||
|
||||
|
@ -325,32 +285,7 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
// Play idle voiced dialogue entries randomly
|
||||
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
|
||||
if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor)
|
||||
&& MWBase::Environment::get().getSoundManager()->sayDone(actor))
|
||||
{
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
|
||||
static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore()
|
||||
.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
|
||||
|
||||
float roll = Misc::Rng::rollProbability() * 10000.0f;
|
||||
|
||||
// In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds
|
||||
// due to the roll being an integer.
|
||||
// Our implementation does not have these issues, so needs to be recalibrated. We chose to
|
||||
// use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration.
|
||||
float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f);
|
||||
|
||||
// Only say Idle voices when player is in LOS
|
||||
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
|
||||
// voices going through walls?
|
||||
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
|
||||
< 3000*3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
|
||||
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
|
||||
}
|
||||
playIdleDialogueRandomly(actor);
|
||||
|
||||
float& lastReaction = storage.mReaction;
|
||||
lastReaction += duration;
|
||||
|
@ -367,17 +302,8 @@ namespace MWMechanics
|
|||
{
|
||||
// End package if duration is complete or mid-night hits:
|
||||
MWWorld::TimeStamp currentTime = world->getTimeStamp();
|
||||
if(currentTime.getHour() >= mStartTime.getHour() + mDuration)
|
||||
{
|
||||
if(!mRepeat)
|
||||
{
|
||||
stopWalking(actor, storage);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
mStartTime = currentTime;
|
||||
}
|
||||
else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())
|
||||
if((currentTime.getHour() >= mStartTime.getHour() + mDuration) ||
|
||||
(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()))
|
||||
{
|
||||
if(!mRepeat)
|
||||
{
|
||||
|
@ -390,7 +316,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
// Initialization to discover & store allowed node points for this actor.
|
||||
if(!mStoredAvailableNodes)
|
||||
if (mPopulateAvailableNodes)
|
||||
{
|
||||
getAllowedNodes(actor, currentCell->getCell());
|
||||
}
|
||||
|
@ -432,71 +358,7 @@ namespace MWMechanics
|
|||
// Allow interrupting a walking actor to trigger a greeting
|
||||
if(idleNow || walking)
|
||||
{
|
||||
// Play a random voice greeting if the player gets too close
|
||||
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
|
||||
float helloDistance = static_cast<float>(hello);
|
||||
static int iGreetDistanceMultiplier =MWBase::Environment::get().getWorld()->getStore()
|
||||
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
|
||||
|
||||
helloDistance *= iGreetDistanceMultiplier;
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
float playerDistSqr = (playerPos - actorPos).length2();
|
||||
|
||||
int& greetingTimer = storage.mGreetingTimer;
|
||||
if (greetingState == Greet_None)
|
||||
{
|
||||
if ((playerDistSqr <= helloDistance*helloDistance) &&
|
||||
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
|
||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
|
||||
greetingTimer++;
|
||||
|
||||
if (greetingTimer >= GREETING_SHOULD_START)
|
||||
{
|
||||
greetingState = Greet_InProgress;
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
|
||||
greetingTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(greetingState == Greet_InProgress)
|
||||
{
|
||||
greetingTimer++;
|
||||
|
||||
if(walking)
|
||||
{
|
||||
stopWalking(actor, storage);
|
||||
moveNow = false;
|
||||
walking = false;
|
||||
mObstacleCheck.clear();
|
||||
idleNow = true;
|
||||
getRandomIdle(playedIdle);
|
||||
}
|
||||
|
||||
if(!rotate)
|
||||
{
|
||||
osg::Vec3f dir = playerPos - actorPos;
|
||||
|
||||
float faceAngleRadians = std::atan2(dir.x(), dir.y());
|
||||
targetAngleRadians = faceAngleRadians;
|
||||
rotate = true;
|
||||
}
|
||||
|
||||
if (greetingTimer >= GREETING_SHOULD_END)
|
||||
{
|
||||
greetingState = Greet_Done;
|
||||
greetingTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (greetingState == MWMechanics::AiWander::Greet_Done)
|
||||
{
|
||||
float resetDist = 2*helloDistance;
|
||||
if (playerDistSqr >= resetDist*resetDist)
|
||||
greetingState = Greet_None;
|
||||
}
|
||||
playGreetingIfPlayerGetsTooClose(actor, storage);
|
||||
}
|
||||
|
||||
if(moveNow && mDistance)
|
||||
|
@ -504,41 +366,187 @@ namespace MWMechanics
|
|||
// Construct a new path if there isn't one
|
||||
if(!storage.mPathFinder.isPathConstructed())
|
||||
{
|
||||
assert(mAllowedNodes.size());
|
||||
unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size());
|
||||
ESM::Pathgrid::Point dest(mAllowedNodes[randNode]);
|
||||
ToWorldCoordinates(dest, currentCell->getCell());
|
||||
|
||||
// actor position is already in world co-ordinates
|
||||
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
|
||||
|
||||
// don't take shortcuts for wandering
|
||||
storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false);
|
||||
|
||||
if(storage.mPathFinder.isPathConstructed())
|
||||
if (mAllowedNodes.size())
|
||||
{
|
||||
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
|
||||
ESM::Pathgrid::Point temp = mAllowedNodes[randNode];
|
||||
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
|
||||
// check if mCurrentNode was taken out of mAllowedNodes
|
||||
if(mTrimCurrentNode && mAllowedNodes.size() > 1)
|
||||
mTrimCurrentNode = false;
|
||||
else
|
||||
mAllowedNodes.push_back(mCurrentNode);
|
||||
mCurrentNode = temp;
|
||||
|
||||
moveNow = false;
|
||||
walking = true;
|
||||
setPathToAnAllowedNode(actor, storage, pos);
|
||||
}
|
||||
// Choose a different node and delete this one from possible nodes because it is uncreachable:
|
||||
else
|
||||
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
|
||||
}
|
||||
}
|
||||
|
||||
return false; // AiWander package not yet completed
|
||||
}
|
||||
|
||||
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration)
|
||||
{
|
||||
if (mObstacleCheck.check(actor, duration))
|
||||
{
|
||||
// first check if we're walking into a door
|
||||
if (proximityToDoor(actor)) // NOTE: checks interior cells only
|
||||
{
|
||||
// remove allowed points then select another random destination
|
||||
mTrimCurrentNode = true;
|
||||
trimAllowedNodes(mAllowedNodes, storage.mPathFinder);
|
||||
mObstacleCheck.clear();
|
||||
storage.mPathFinder.clearPath();
|
||||
storage.mWalking = false;
|
||||
storage.mMoveNow = true;
|
||||
}
|
||||
else // probably walking into another NPC
|
||||
{
|
||||
// TODO: diagonal should have same animation as walk forward
|
||||
// but doesn't seem to do that?
|
||||
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f;
|
||||
// change the angle a bit, too
|
||||
const ESM::Position& pos = actor.getRefData().getPosition();
|
||||
zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
|
||||
}
|
||||
mStuckCount++; // TODO: maybe no longer needed
|
||||
}
|
||||
//#if 0
|
||||
// TODO: maybe no longer needed
|
||||
if (mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
|
||||
{
|
||||
//std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl;
|
||||
mObstacleCheck.clear();
|
||||
|
||||
stopWalking(actor, storage);
|
||||
storage.mChooseAction = true;
|
||||
mStuckCount = 0;
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
|
||||
void AiWander::playIdleDialogueRandomly(const MWWorld::Ptr& actor)
|
||||
{
|
||||
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
|
||||
if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor)
|
||||
&& MWBase::Environment::get().getSoundManager()->sayDone(actor))
|
||||
{
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
|
||||
static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore()
|
||||
.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
|
||||
|
||||
float roll = Misc::Rng::rollProbability() * 10000.0f;
|
||||
|
||||
// In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds
|
||||
// due to the roll being an integer.
|
||||
// Our implementation does not have these issues, so needs to be recalibrated. We chose to
|
||||
// use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration.
|
||||
float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f);
|
||||
|
||||
// Only say Idle voices when player is in LOS
|
||||
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
|
||||
// voices going through walls?
|
||||
const ESM::Position& pos = actor.getRefData().getPosition();
|
||||
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
|
||||
< 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
|
||||
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
|
||||
}
|
||||
}
|
||||
|
||||
void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
{
|
||||
// Play a random voice greeting if the player gets too close
|
||||
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
|
||||
float helloDistance = static_cast<float>(hello);
|
||||
static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
|
||||
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
|
||||
|
||||
helloDistance *= iGreetDistanceMultiplier;
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
float playerDistSqr = (playerPos - actorPos).length2();
|
||||
|
||||
int& greetingTimer = storage.mGreetingTimer;
|
||||
GreetingState& greetingState = storage.mSaidGreeting;
|
||||
if (greetingState == Greet_None)
|
||||
{
|
||||
if ((playerDistSqr <= helloDistance*helloDistance) &&
|
||||
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
|
||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
|
||||
greetingTimer++;
|
||||
|
||||
if (greetingTimer >= GREETING_SHOULD_START)
|
||||
{
|
||||
greetingState = Greet_InProgress;
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
|
||||
greetingTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (greetingState == Greet_InProgress)
|
||||
{
|
||||
greetingTimer++;
|
||||
|
||||
if (storage.mWalking)
|
||||
{
|
||||
stopWalking(actor, storage);
|
||||
mObstacleCheck.clear();
|
||||
storage.mIdleNow = true;
|
||||
getRandomIdle(storage.mPlayedIdle);
|
||||
}
|
||||
|
||||
if (!storage.mRotate)
|
||||
{
|
||||
osg::Vec3f dir = playerPos - actorPos;
|
||||
|
||||
float faceAngleRadians = std::atan2(dir.x(), dir.y());
|
||||
storage.mTargetAngleRadians = faceAngleRadians;
|
||||
storage.mRotate = true;
|
||||
}
|
||||
|
||||
if (greetingTimer >= GREETING_SHOULD_END)
|
||||
{
|
||||
greetingState = Greet_Done;
|
||||
greetingTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (greetingState == MWMechanics::AiWander::Greet_Done)
|
||||
{
|
||||
float resetDist = 2 * helloDistance;
|
||||
if (playerDistSqr >= resetDist*resetDist)
|
||||
greetingState = Greet_None;
|
||||
}
|
||||
}
|
||||
|
||||
void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
|
||||
{
|
||||
unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size());
|
||||
ESM::Pathgrid::Point dest(mAllowedNodes[randNode]);
|
||||
ToWorldCoordinates(dest, storage.mCell->getCell());
|
||||
|
||||
// actor position is already in world co-ordinates
|
||||
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
|
||||
|
||||
// don't take shortcuts for wandering
|
||||
storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false);
|
||||
|
||||
if (storage.mPathFinder.isPathConstructed())
|
||||
{
|
||||
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
|
||||
ESM::Pathgrid::Point temp = mAllowedNodes[randNode];
|
||||
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
|
||||
// check if mCurrentNode was taken out of mAllowedNodes
|
||||
if (mTrimCurrentNode && mAllowedNodes.size() > 1)
|
||||
mTrimCurrentNode = false;
|
||||
else
|
||||
mAllowedNodes.push_back(mCurrentNode);
|
||||
mCurrentNode = temp;
|
||||
|
||||
storage.mMoveNow = false;
|
||||
storage.mWalking = true;
|
||||
}
|
||||
// Choose a different node and delete this one from possible nodes because it is uncreachable:
|
||||
else
|
||||
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
|
||||
}
|
||||
|
||||
void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell)
|
||||
{
|
||||
if (cell->isExterior())
|
||||
|
@ -583,6 +591,8 @@ namespace MWMechanics
|
|||
{
|
||||
storage.mPathFinder.clearPath();
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
storage.mMoveNow = false;
|
||||
storage.mWalking = false;
|
||||
}
|
||||
|
||||
void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
|
||||
|
@ -640,7 +650,7 @@ namespace MWMechanics
|
|||
if (mDistance == 0)
|
||||
return;
|
||||
|
||||
if (!mStoredAvailableNodes)
|
||||
if (mPopulateAvailableNodes)
|
||||
getAllowedNodes(actor, actor.getCell()->getCell());
|
||||
|
||||
if (mAllowedNodes.empty())
|
||||
|
@ -660,7 +670,7 @@ namespace MWMechanics
|
|||
actor.getClass().adjustPosition(actor, false);
|
||||
|
||||
// may have changed cell
|
||||
mStoredAvailableNodes = false;
|
||||
mPopulateAvailableNodes = true;
|
||||
}
|
||||
|
||||
int AiWander::OffsetToPreventOvercrowding()
|
||||
|
@ -722,8 +732,9 @@ namespace MWMechanics
|
|||
{
|
||||
SetCurrentNodeToClosestAllowedNode(npcPos);
|
||||
}
|
||||
mStoredAvailableNodes = true; // set only if successful in finding allowed nodes
|
||||
}
|
||||
|
||||
mPopulateAvailableNodes = false;
|
||||
}
|
||||
|
||||
// When only one path grid point in wander distance,
|
||||
|
|
|
@ -71,6 +71,10 @@ namespace MWMechanics
|
|||
void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
void getRandomIdle(unsigned short& playedIdle);
|
||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration);
|
||||
void playIdleDialogueRandomly(const MWWorld::Ptr& actor);
|
||||
|
||||
int mDistance; // how far the actor can wander from the spawn point
|
||||
int mDuration;
|
||||
|
@ -88,8 +92,8 @@ namespace MWMechanics
|
|||
|
||||
|
||||
|
||||
// if false triggers calculating allowed nodes based on mDistance
|
||||
bool mStoredAvailableNodes;
|
||||
// do we need to calculate allowed nodes based on mDistance
|
||||
bool mPopulateAvailableNodes;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -459,7 +459,7 @@ void NpcAnimation::updateParts()
|
|||
};
|
||||
static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]);
|
||||
|
||||
bool wasArrowAttached = 0;//(mAmmunition.get() != NULL);
|
||||
bool wasArrowAttached = (mAmmunition.get() != NULL);
|
||||
|
||||
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++)
|
||||
|
@ -622,7 +622,7 @@ void NpcAnimation::updateParts()
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!mNpc->isMale() != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female))
|
||||
if ((!mNpc->isMale()) != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female))
|
||||
{
|
||||
// Allow opposite gender's parts as fallback if parts for our gender are missing
|
||||
BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart));
|
||||
|
|
|
@ -59,7 +59,6 @@ namespace MWRender
|
|||
|
||||
private:
|
||||
osg::ref_ptr<osg::Group> mParent;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
|
||||
osg::ref_ptr<osgParticle::ParticleSystem> mParticleSystem;
|
||||
osg::ref_ptr<osg::PositionAttitudeTransform> mParticleNode;
|
||||
|
|
|
@ -1064,11 +1064,11 @@ namespace MWWorld
|
|||
template class MWWorld::Store<ESM::Activator>;
|
||||
template class MWWorld::Store<ESM::Apparatus>;
|
||||
template class MWWorld::Store<ESM::Armor>;
|
||||
template class MWWorld::Store<ESM::Attribute>;
|
||||
//template class MWWorld::Store<ESM::Attribute>;
|
||||
template class MWWorld::Store<ESM::BirthSign>;
|
||||
template class MWWorld::Store<ESM::BodyPart>;
|
||||
template class MWWorld::Store<ESM::Book>;
|
||||
template class MWWorld::Store<ESM::Cell>;
|
||||
//template class MWWorld::Store<ESM::Cell>;
|
||||
template class MWWorld::Store<ESM::Class>;
|
||||
template class MWWorld::Store<ESM::Clothing>;
|
||||
template class MWWorld::Store<ESM::Container>;
|
||||
|
@ -1082,21 +1082,21 @@ template class MWWorld::Store<ESM::GameSetting>;
|
|||
template class MWWorld::Store<ESM::Global>;
|
||||
template class MWWorld::Store<ESM::Ingredient>;
|
||||
template class MWWorld::Store<ESM::ItemLevList>;
|
||||
template class MWWorld::Store<ESM::Land>;
|
||||
//template class MWWorld::Store<ESM::Land>;
|
||||
template class MWWorld::Store<ESM::LandTexture>;
|
||||
template class MWWorld::Store<ESM::Light>;
|
||||
template class MWWorld::Store<ESM::Lockpick>;
|
||||
template class MWWorld::Store<ESM::MagicEffect>;
|
||||
//template class MWWorld::Store<ESM::MagicEffect>;
|
||||
template class MWWorld::Store<ESM::Miscellaneous>;
|
||||
template class MWWorld::Store<ESM::NPC>;
|
||||
template class MWWorld::Store<ESM::Pathgrid>;
|
||||
//template class MWWorld::Store<ESM::Pathgrid>;
|
||||
template class MWWorld::Store<ESM::Potion>;
|
||||
template class MWWorld::Store<ESM::Probe>;
|
||||
template class MWWorld::Store<ESM::Race>;
|
||||
template class MWWorld::Store<ESM::Region>;
|
||||
template class MWWorld::Store<ESM::Repair>;
|
||||
template class MWWorld::Store<ESM::Script>;
|
||||
template class MWWorld::Store<ESM::Skill>;
|
||||
//template class MWWorld::Store<ESM::Skill>;
|
||||
template class MWWorld::Store<ESM::Sound>;
|
||||
template class MWWorld::Store<ESM::SoundGenerator>;
|
||||
template class MWWorld::Store<ESM::Spell>;
|
||||
|
|
|
@ -319,7 +319,7 @@ namespace MWWorld
|
|||
|
||||
public:
|
||||
|
||||
Store<ESM::Pathgrid>();
|
||||
Store();
|
||||
|
||||
void setCells(Store<ESM::Cell>& cells);
|
||||
void load(ESM::ESMReader &esm, const std::string &id);
|
||||
|
|
|
@ -180,6 +180,7 @@ target_link_libraries(components
|
|||
${SDL2_LIBRARY}
|
||||
# For MyGUI platform
|
||||
${OPENGL_gl_LIBRARY}
|
||||
${MYGUI_LIBRARIES}
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
@ -119,7 +119,7 @@ class Drawable : public osg::Drawable {
|
|||
|
||||
// VBOs disabled due to crash in OSG: http://forum.openscenegraph.org/viewtopic.php?t=14909
|
||||
osg::GLBufferObject* bufferobject = 0;//state->isVertexBufferObjectSupported() ? vbo->getOrCreateGLBufferObject(state->getContextID()) : 0;
|
||||
if (bufferobject)
|
||||
if (0)//bufferobject)
|
||||
{
|
||||
state->bindVertexBufferObject(bufferobject);
|
||||
|
||||
|
|
|
@ -407,7 +407,8 @@ FlipController::FlipController(int texSlot, float delta, std::vector<osg::ref_pt
|
|||
}
|
||||
|
||||
FlipController::FlipController()
|
||||
: mDelta(0.f)
|
||||
: mTexSlot(0)
|
||||
, mDelta(0.f)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -175,23 +175,16 @@ namespace SDLUtil
|
|||
}
|
||||
}
|
||||
|
||||
bool SDLCursorManager::cursorChanged(const std::string& name)
|
||||
void SDLCursorManager::cursorChanged(const std::string& name)
|
||||
{
|
||||
mCurrentCursor = name;
|
||||
|
||||
CursorMap::const_iterator curs_iter = mCursorMap.find(name);
|
||||
|
||||
//we have this cursor
|
||||
if(curs_iter != mCursorMap.end())
|
||||
{
|
||||
//we have this cursor
|
||||
_setGUICursor(name);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//they should get back to us with more info
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,7 +193,7 @@ namespace SDLUtil
|
|||
SDL_SetCursor(mCursorMap.find(name)->second);
|
||||
}
|
||||
|
||||
void SDLCursorManager::receiveCursorInfo(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y)
|
||||
void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y)
|
||||
{
|
||||
_createCursorFromResource(name, rotDegrees, image, size_x, size_y, hotspot_x, hotspot_y);
|
||||
}
|
||||
|
|
|
@ -27,11 +27,9 @@ namespace SDLUtil
|
|||
|
||||
/// \brief Tell the manager that the cursor has changed, giving the
|
||||
/// name of the cursor we changed to ("arrow", "ibeam", etc)
|
||||
/// \return Whether the manager is interested in more information about the cursor
|
||||
virtual bool cursorChanged(const std::string &name);
|
||||
virtual void cursorChanged(const std::string &name);
|
||||
|
||||
/// \brief Follow up a cursorChanged() call with enough info to create an cursor.
|
||||
virtual void receiveCursorInfo(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y);
|
||||
virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y);
|
||||
|
||||
private:
|
||||
void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y);
|
||||
|
|
Loading…
Reference in a new issue