diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d3f86c03b..641edcc83 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -969,6 +969,9 @@ namespace MWClass float Npc::getJump(const MWWorld::Ptr &ptr) const { + if(getEncumbrance(ptr) > getCapacity(ptr)) + return 0.f; + const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const GMST& gmst = getGmst(); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 6526b200f..eb548d596 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -533,9 +533,11 @@ namespace MWGui if (mGoodbye) { + Goodbye* link = new Goodbye(); + mLinks.push_back(link); std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->getString(); BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, linkNormal, linkHot, linkActive, - TypesetBook::InteractiveId(mLinks.back())); + TypesetBook::InteractiveId(link)); typesetter->lineBreak(); typesetter->write(questionStyle, to_utf8_span(goodbye.c_str())); } @@ -654,7 +656,6 @@ namespace MWGui void DialogueWindow::goodbye() { - mLinks.push_back(new Goodbye()); mGoodbye = true; mEnabled = false; updateHistory(); diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 444b572b6..bcb766f8f 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -128,11 +128,17 @@ namespace MWGui setRaceId(proto.mRace); recountParts(); - std::string index = proto.mHead.substr(proto.mHead.size() - 2, 2); - mFaceIndex = boost::lexical_cast(index) - 1; + for (unsigned int i=0; i(index) - 1; + for (unsigned int i=0; isetImageTexture (textureName); @@ -307,7 +313,15 @@ namespace MWGui record.mHead = mAvailableHeads[mFaceIndex]; record.mHair = mAvailableHairs[mHairIndex]; - mPreview->setPrototype(record); + try + { + mPreview->setPrototype(record); + } + catch (std::exception& e) + { + std::cerr << "Error creating preview: " << e.what() << std::endl; + } + mPreviewDirty = true; } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 68682abea..b0d2bfeff 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -371,6 +371,7 @@ namespace MWInput { mPlayer->setUpDown (1); triedToMove = true; + mOverencumberedMessageDelay = 0.f; } if (mAlwaysRunActive) @@ -623,7 +624,9 @@ namespace MWInput if (arg.zrel && mControlSwitch["playerviewswitch"] && mControlSwitch["playercontrols"]) //Check to make sure you are allowed to zoomout and there is a change { MWBase::Environment::get().getWorld()->changeVanityModeScale(arg.zrel); - MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true); + + if (Settings::Manager::getBool("allow third person zoom", "Input")) + MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true); } } } diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index cc8279b1e..6b4ede305 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -9,6 +9,7 @@ #include "../mwworld/actionequip.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellcasting.hpp" #include #include @@ -166,6 +167,9 @@ namespace MWMechanics { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); + if (MWMechanics::getSpellSuccessChance(spell, actor) == 0) + return 0.f; + if (spell->mData.mType != ESM::Spell::ST_Spell) return 0.f; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index df970f801..f1c9ec7d2 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -69,6 +69,7 @@ namespace MWMechanics /** \return If the actor has arrived at his destination **/ bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a70200833..c8a0c85d5 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -57,6 +57,8 @@ namespace MWMechanics bool mWalking; unsigned short mPlayedIdle; + + PathFinder mPathFinder; AiWanderStorage(): mTargetAngle(0), @@ -211,9 +213,9 @@ namespace MWMechanics // Are we there yet? bool& chooseAction = storage.mChooseAction; if(walking && - mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) + storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { - stopWalking(actor); + stopWalking(actor, storage); moveNow = false; walking = false; chooseAction = true; @@ -225,7 +227,7 @@ namespace MWMechanics if(walking) // have not yet reached the destination { // turn towards the next point in mPath - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + zTurn(actor, Ogre::Degree(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 @@ -236,9 +238,9 @@ namespace MWMechanics { // remove allowed points then select another random destination mTrimCurrentNode = true; - trimAllowedNodes(mAllowedNodes, mPathFinder); + trimAllowedNodes(mAllowedNodes, storage.mPathFinder); mObstacleCheck.clear(); - mPathFinder.clearPath(); + storage.mPathFinder.clearPath(); walking = false; moveNow = true; } @@ -249,7 +251,7 @@ namespace MWMechanics actor.getClass().getMovementSettings(actor).mPosition[0] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; // change the angle a bit, too - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); + zTurn(actor, Ogre::Degree(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); } mStuckCount++; // TODO: maybe no longer needed } @@ -260,7 +262,7 @@ namespace MWMechanics //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; mObstacleCheck.clear(); - stopWalking(actor); + stopWalking(actor, storage); moveNow = false; walking = false; chooseAction = true; @@ -280,6 +282,58 @@ namespace MWMechanics rotate = false; } + // Check if idle animation finished + short unsigned& playedIdle = storage.mPlayedIdle; + GreetingState& greetingState = storage.mSaidGreeting; + if(idleNow && !checkIdle(actor, playedIdle) && (greetingState == Greet_Done || greetingState == Greet_None)) + { + playedIdle = 0; + idleNow = false; + chooseAction = true; + } + + MWBase::World *world = MWBase::Environment::get().getWorld(); + + if(chooseAction) + { + playedIdle = 0; + getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection + + if(!playedIdle && mDistance) + { + chooseAction = false; + moveNow = true; + } + else + { + // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: + MWWorld::TimeStamp currentTime = world->getTimeStamp(); + mStartTime = currentTime; + playIdle(actor, playedIdle); + chooseAction = false; + idleNow = true; + + // Play idle voiced dialogue entries randomly + int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); + if (hello > 0) + { + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + // Don't bother if the player is out of hearing range + static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() + .get().find("fVoiceIdleOdds")->getFloat(); + + // 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 < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500 + && MWBase::Environment::get().getWorld()->getLOS(player, actor)) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } + } + } + float& lastReaction = storage.mReaction; lastReaction += duration; if(lastReaction < REACTION_INTERVAL) @@ -291,7 +345,6 @@ namespace MWMechanics // NOTE: everything below get updated every REACTION_INTERVAL seconds - MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { // End package if duration is complete or mid-night hits: @@ -300,7 +353,7 @@ namespace MWMechanics { if(!mRepeat) { - stopWalking(actor); + stopWalking(actor, storage); return true; } else @@ -310,7 +363,7 @@ namespace MWMechanics { if(!mRepeat) { - stopWalking(actor); + stopWalking(actor, storage); return true; } else @@ -411,7 +464,7 @@ namespace MWMechanics chooseAction = false; idleNow = false; - if (!mPathFinder.isPathConstructed()) + if (!storage.mPathFinder.isPathConstructed()) { Ogre::Vector3 destNodePos = mReturnPosition; @@ -427,9 +480,9 @@ namespace MWMechanics start.mZ = pos.pos[2]; // don't take shortcuts for wandering - mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); - if(mPathFinder.isPathConstructed()) + if(storage.mPathFinder.isPathConstructed()) { moveNow = false; walking = true; @@ -437,48 +490,6 @@ namespace MWMechanics } } - AiWander::GreetingState& greetingState = storage.mSaidGreeting; - short unsigned& playedIdle = storage.mPlayedIdle; - if(chooseAction) - { - playedIdle = 0; - getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection - - if(!playedIdle && mDistance) - { - chooseAction = false; - moveNow = true; - } - else - { - // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: - MWWorld::TimeStamp currentTime = world->getTimeStamp(); - mStartTime = currentTime; - playIdle(actor, playedIdle); - chooseAction = false; - idleNow = true; - - // Play idle voiced dialogue entries randomly - int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); - if (hello > 0) - { - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - // Don't bother if the player is out of hearing range - static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() - .get().find("fVoiceIdleOdds")->getFloat(); - - // 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 < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500 - && MWBase::Environment::get().getWorld()->getLOS(player, actor)) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); - } - } - } - // Allow interrupting a walking actor to trigger a greeting if(idleNow || walking) { @@ -494,7 +505,7 @@ namespace MWMechanics Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); float playerDistSqr = playerPos.squaredDistance(actorPos); - + int& greetingTimer = storage.mGreetingTimer; if (greetingState == Greet_None) { @@ -517,7 +528,7 @@ namespace MWMechanics if(walking) { - stopWalking(actor); + stopWalking(actor, storage); moveNow = false; walking = false; mObstacleCheck.clear(); @@ -554,20 +565,12 @@ namespace MWMechanics if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset) greetingState = Greet_None; } - - // Check if idle animation finished - if(!checkIdle(actor, playedIdle) && (playerDistSqr > helloDistance*helloDistance || greetingState == MWMechanics::AiWander::Greet_Done)) - { - playedIdle = 0; - idleNow = false; - chooseAction = true; - } } if(moveNow && mDistance) { // Construct a new path if there isn't one - if(!mPathFinder.isPathConstructed()) + if(!storage.mPathFinder.isPathConstructed()) { assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); @@ -589,16 +592,16 @@ namespace MWMechanics start.mZ = pos.pos[2]; // don't take shortcuts for wandering - mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); - if(mPathFinder.isPathConstructed()) + if(storage.mPathFinder.isPathConstructed()) { // buildPath inserts dest in case it is not a pathgraph point // index which is a duplicate for AiWander. However below code // does not work since getPath() returns a copy of path not a // reference - //if(mPathFinder.getPathSize() > 1) - //mPathFinder.getPath().pop_back(); + //if(storage.mPathFinder.getPathSize() > 1) + //storage.mPathFinder.getPath().pop_back(); // 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]; @@ -616,7 +619,7 @@ namespace MWMechanics // 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 @@ -653,9 +656,9 @@ namespace MWMechanics return TypeIdWander; } - void AiWander::stopWalking(const MWWorld::Ptr& actor) + void AiWander::stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage) { - mPathFinder.clearPath(); + storage.mPathFinder.clearPath(); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 0600909ba..b9b394a26 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -27,6 +27,8 @@ namespace MWMechanics { + struct AiWanderStorage; + /// \brief Causes the Actor to wander within a specified range class AiWander : public AiPackage { @@ -65,7 +67,7 @@ namespace MWMechanics // NOTE: mDistance and mDuration must be set already void init(); - void stopWalking(const MWWorld::Ptr& actor); + void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); void getRandomIdle(unsigned short& playedIdle); @@ -101,7 +103,6 @@ namespace MWMechanics void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); -// PathFinder mPathFinder; // ObstacleCheck mObstacleCheck; float mDoorCheckDuration; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 11484ac49..2e4f76c1a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1414,29 +1414,32 @@ void CharacterController::update(float duration) { // Started a jump. float z = cls.getJump(mPtr); - if(vec.x == 0 && vec.y == 0) - vec = Ogre::Vector3(0.0f, 0.0f, z); - else + if (z > 0) { - Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); - vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; - } + if(vec.x == 0 && vec.y == 0) + vec = Ogre::Vector3(0.0f, 0.0f, z); + else + { + Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); + vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; + } - // advance acrobatics - if (mPtr.getRefData().getHandle() == "player") - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); - - // decrease fatigue - const MWWorld::Store &gmst = world->getStore().get(); - const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); - const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); - float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); - if (normalizedEncumbrance > 1) - normalizedEncumbrance = 1; - const int fatigueDecrease = fatigueJumpBase + (1 - normalizedEncumbrance) * fatigueJumpMult; - DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); - fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); - cls.getCreatureStats(mPtr).setFatigue(fatigue); + // advance acrobatics + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + + // decrease fatigue + const MWWorld::Store &gmst = world->getStore().get(); + const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); + const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); + float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); + if (normalizedEncumbrance > 1) + normalizedEncumbrance = 1; + const int fatigueDecrease = fatigueJumpBase + (1 - normalizedEncumbrance) * fatigueJumpMult; + DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + cls.getCreatureStats(mPtr).setFatigue(fatigue); + } } else if(mJumpState == JumpState_InAir) { diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 7bf027732..21fd2203f 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -147,6 +147,11 @@ namespace MWMechanics if (value != currentValue) { + if(!mIsWerewolf) + mAttributes[index] = value; + else + mWerewolfAttributes[index] = value; + if (index == ESM::Attribute::Intelligence) mRecalcMagicka = true; else if (index == ESM::Attribute::Strength || @@ -164,11 +169,6 @@ namespace MWMechanics setFatigue(fatigue); } } - - if(!mIsWerewolf) - mAttributes[index] = value; - else - mWerewolfAttributes[index] = value; } void CreatureStats::setHealth(const DynamicStat &value) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f7949f1ec..b4edf44aa 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -930,20 +930,6 @@ namespace MWMechanics const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - // What amount of alarm did this crime generate? - int alarm = 0; - if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) - alarm = esmStore.get().find("iAlarmTresspass")->getInt(); - else if (type == OT_Pickpocket) - alarm = esmStore.get().find("iAlarmPickPocket")->getInt(); - else if (type == OT_Assault) - alarm = esmStore.get().find("iAlarmAttack")->getInt(); - else if (type == OT_Murder) - alarm = esmStore.get().find("iAlarmKilling")->getInt(); - else if (type == OT_Theft) - alarm = esmStore.get().find("iAlarmStealing")->getInt(); - - bool reported = false; // Find all the actors within the alarm radius std::vector neighbors; @@ -960,6 +946,8 @@ namespace MWMechanics bool victimAware = false; // Find actors who directly witnessed the crime + bool crimeSeen = false; + bool reported = false; for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { if (*it == player) @@ -987,15 +975,17 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) continue; - // Will the witness report the crime? - if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) - { - reported = true; - } + crimeSeen = true; + } + + // Will the witness report the crime? + if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) + { + reported = true; } } - if (reported) + if (crimeSeen && reported) reportCrime(player, victim, type, arg); else if (victimAware && !victim.isEmpty() && type == OT_Assault) startCombat(victim, player); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index fc147ce59..548906cdf 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -574,7 +574,8 @@ float Animation::getVelocity(const std::string &groupname) const static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bone) { - if(skelsrc->hasBone(bone->getName())) + if(bone->getName() != " " // really should be != "", but see workaround in skeleton.cpp for empty node names + && skelsrc->hasBone(bone->getName())) { Ogre::Bone *srcbone = skelsrc->getBone(bone->getName()); if(!srcbone->getParent() || !bone->getParent()) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index d9c953133..92d0bcd55 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -115,8 +115,8 @@ namespace MWRender void CharacterPreview::rebuild() { - assert(mAnimation); delete mAnimation; + mAnimation = NULL; mAnimation = new NpcAnimation(mCharacter, mNode, 0, true, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); @@ -187,6 +187,9 @@ namespace MWRender void InventoryPreview::update() { + if (!mAnimation) + return; + mAnimation->updateParts(); MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c43d3663e..d20e89324 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -60,6 +60,21 @@ std::string getVampireHead(const std::string& race, bool female) return "meshes\\" + sVampireMapping[thisCombination]->mModel; } +bool isSkinned (NifOgre::ObjectScenePtr scene) +{ + if (scene->mSkelBase == NULL) + return false; + for(size_t j = 0; j < scene->mEntities.size(); j++) + { + Ogre::Entity *ent = scene->mEntities[j]; + if(scene->mSkelBase != ent && ent->hasSkeleton()) + { + return true; + } + } + return false; +} + } @@ -611,10 +626,11 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) for(;ctrl != mObjectParts[i]->mControllers.end();++ctrl) ctrl->update(); - Ogre::Entity *ent = mObjectParts[i]->mSkelBase; - if(!ent) continue; - updateSkeletonInstance(baseinst, ent->getSkeleton()); - ent->getAllAnimationStates()->_notifyDirty(); + if (!isSkinned(mObjectParts[i])) + continue; + + updateSkeletonInstance(baseinst, mObjectParts[i]->mSkelBase->getSkeleton()); + mObjectParts[i]->mSkelBase->getAllAnimationStates()->_notifyDirty(); } return ret; @@ -660,7 +676,15 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g removeIndividualPart(type); mPartslots[type] = group; mPartPriorities[type] = priority; - mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); + try + { + mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); + } + catch (std::exception& e) + { + std::cerr << "Error adding NPC part: " << e.what() << std::endl; + return false; + } if (!mSoundsDisabled) { @@ -697,7 +721,8 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g } } - updateSkeletonInstance(mSkelBase->getSkeleton(), skel); + if (isSkinned(mObjectParts[type])) + updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index ccef74efb..184102127 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -34,6 +34,41 @@ using namespace MWRender; using namespace Ogre; +namespace +{ + +void setAlpha (NifOgre::ObjectScenePtr scene, Ogre::MovableObject* movable, float alpha) +{ + Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(movable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.a = alpha; + pass->setDiffuse(diffuse); + } + } + +} + +void setAlpha (NifOgre::ObjectScenePtr scene, float alpha) +{ + for(size_t i = 0; i < scene->mParticles.size(); ++i) + setAlpha(scene, scene->mParticles[i], alpha); + for(size_t i = 0; i < scene->mEntities.size(); ++i) + { + if (scene->mEntities[i] != scene->mSkelBase) + setAlpha(scene, scene->mEntities[i], alpha); + } +} + +} + BillboardObject::BillboardObject( const String& textureName, const float initialSize, const Vector3& position, @@ -660,6 +695,11 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) mSun->setVisibility(weather.mGlareView * strength); mAtmosphereNight->setVisible(weather.mNight && mEnabled); + + if (mParticle.get()) + setAlpha(mParticle, weather.mEffectFade); + for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end(); ++it) + setAlpha(it->second, weather.mEffectFade); } void SkyManager::setGlare(const float glare) diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index afc745beb..1d34adbca 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -210,6 +210,12 @@ namespace MWScript { bool state = MWBase::Environment::get().getWindowManager()->toggleGui(); runtime.getContext().report(state ? "GUI -> On" : "GUI -> Off"); + + if (!state) + { + while (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_None) // don't use isGuiMode, or we get an infinite loop for modal message boxes! + MWBase::Environment::get().getWindowManager()->popGuiMode(); + } } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index e74388eff..8e6d925b7 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -319,12 +319,11 @@ namespace MWScript ptr = MWWorld::Ptr(ptr.getBase(), store); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); - if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity - { - ax = ax/60.; - ay = ay/60.; + // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) + // except for when you position the player, then degrees must be used. + // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. + if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) zRot = zRot/60.; - } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); ptr.getClass().adjustPosition(ptr, false); @@ -378,12 +377,11 @@ namespace MWScript float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); - if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity - { - ax = ax/60.; - ay = ay/60.; + // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) + // except for when you position the player, then degrees must be used. + // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. + if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) zRot = zRot/60.; - } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); ptr.getClass().adjustPosition(ptr, false); } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 55c5b8191..469b93f88 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -876,8 +876,36 @@ namespace MWWorld public: void load(ESM::ESMReader &esm, const std::string &id) { - mStatic.push_back(ESM::Pathgrid()); - mStatic.back().load(esm); + + ESM::Pathgrid pathgrid; + pathgrid.load(esm); + + // Try to overwrite existing record + // Can't use search() because we aren't sorted yet + if (!pathgrid.mCell.empty()) + { + for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + if ((*it).mCell == pathgrid.mCell) + { + (*it) = pathgrid; + return; + } + } + } + else + { + for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + if ((*it).mData.mX == pathgrid.mData.mX && (*it).mData.mY == pathgrid.mData.mY) + { + (*it) = pathgrid; + return; + } + } + } + + mStatic.push_back(pathgrid); } size_t getSize() const { diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index f738734b1..3f9b7d562 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -6,6 +6,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwsound/sound.hpp" + #include "../mwrender/renderingmanager.hpp" #include "player.hpp" @@ -152,12 +154,12 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa setFallbackWeather(foggy,"foggy"); Weather thunderstorm; - thunderstorm.mRainLoopSoundID = "rain heavy"; + thunderstorm.mAmbientLoopSoundID = "rain heavy"; thunderstorm.mRainEffect = "meshes\\raindrop.nif"; setFallbackWeather(thunderstorm,"thunderstorm"); Weather rain; - rain.mRainLoopSoundID = "rain"; + rain.mAmbientLoopSoundID = "rain"; rain.mRainEffect = "meshes\\raindrop.nif"; setFallbackWeather(rain,"rain"); @@ -186,7 +188,7 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa WeatherManager::~WeatherManager() { - stopSounds(true); + stopSounds(); } void WeatherManager::setWeather(const String& weather, bool instant) @@ -228,6 +230,8 @@ void WeatherManager::setResult(const String& weatherType) mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mAmbientSoundVolume = 1.f; + mResult.mEffectFade = 1.f; mResult.mSunColor = current.mSunDiscSunsetColor; mResult.mIsStorm = current.mIsStorm; @@ -341,11 +345,30 @@ void WeatherManager::transition(float factor) mResult.mNight = current.mNight; - mResult.mIsStorm = current.mIsStorm; - mResult.mParticleEffect = current.mParticleEffect; - mResult.mRainEffect = current.mRainEffect; - mResult.mRainSpeed = current.mRainSpeed; - mResult.mRainFrequency = current.mRainFrequency; + if (factor < 0.5) + { + mResult.mIsStorm = current.mIsStorm; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainSpeed = current.mRainSpeed; + mResult.mRainFrequency = current.mRainFrequency; + mResult.mAmbientSoundVolume = 1-(factor*2); + mResult.mEffectFade = mResult.mAmbientSoundVolume; + mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + } + else + { + mResult.mIsStorm = other.mIsStorm; + mResult.mParticleEffect = other.mParticleEffect; + mResult.mRainEffect = other.mRainEffect; + mResult.mParticleEffect = other.mParticleEffect; + mResult.mRainSpeed = other.mRainSpeed; + mResult.mRainFrequency = other.mRainFrequency; + mResult.mAmbientSoundVolume = 2*(factor-0.5); + mResult.mEffectFade = mResult.mAmbientSoundVolume; + mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; + } } void WeatherManager::update(float duration, bool paused) @@ -361,7 +384,7 @@ void WeatherManager::update(float duration, bool paused) { mRendering->skyDisable(); mRendering->getSkyManager()->setLightningStrength(0.f); - stopSounds(true); + stopSounds(); return; } @@ -541,40 +564,25 @@ void WeatherManager::update(float duration, bool paused) mRendering->getSkyManager()->setWeather(mResult); // Play sounds - if (mNextWeather == "") + if (mPlayingSoundID != mResult.mAmbientLoopSoundID) { - std::string ambientSnd = mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID; - if (!ambientSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end()) - { - mSoundsPlaying.push_back(ambientSnd); - MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - } + stopSounds(); + if (!mResult.mAmbientLoopSoundID.empty()) + mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - std::string rainSnd = mWeatherSettings[mCurrentWeather].mRainLoopSoundID; - if (!rainSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end()) - { - mSoundsPlaying.push_back(rainSnd); - MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - } + mPlayingSoundID = mResult.mAmbientLoopSoundID; } - - stopSounds(false); + if (mAmbientSound.get()) + mAmbientSound->setVolume(mResult.mAmbientSoundVolume); } -void WeatherManager::stopSounds(bool stopAll) +void WeatherManager::stopSounds() { - std::vector::iterator it = mSoundsPlaying.begin(); - while (it!=mSoundsPlaying.end()) + if (mAmbientSound.get()) { - if (stopAll || - !((*it == mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID) || - (*it == mWeatherSettings[mCurrentWeather].mRainLoopSoundID))) - { - MWBase::Environment::get().getSoundManager()->stopSound(*it); - it = mSoundsPlaying.erase(it); - } - else - ++it; + MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + mAmbientSound.reset(); + mPlayingSoundID.clear(); } } @@ -764,7 +772,7 @@ bool WeatherManager::readRecord(ESM::ESMReader& reader, int32_t type) state.load(reader); // reset other temporary state, now that we loaded successfully - stopSounds(true); // let's hope this never throws + stopSounds(); // let's hope this never throws mRegionOverrides.clear(); mRegionMods.clear(); mThunderFlash = 0.0; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 97897fda9..dee9fc52a 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -7,6 +7,8 @@ #include #include +#include "../mwbase/soundmanager.hpp" + namespace ESM { struct Region; @@ -61,10 +63,12 @@ namespace MWWorld bool mIsStorm; std::string mAmbientLoopSoundID; + float mAmbientSoundVolume; std::string mParticleEffect; - std::string mRainEffect; + float mEffectFade; + float mRainSpeed; float mRainFrequency; }; @@ -125,9 +129,6 @@ namespace MWWorld // This is used for Blight, Ashstorm and Blizzard (Bloodmoon) std::string mAmbientLoopSoundID; - // Rain sound effect - std::string mRainLoopSoundID; - // Is this an ash storm / blight storm? If so, the following will happen: // - The particles and clouds will be oriented so they appear to come from the Red Mountain. // - Characters will animate their hand to protect eyes from the storm when looking in its direction (idlestorm animation) @@ -173,7 +174,7 @@ namespace MWWorld */ void update(float duration, bool paused = false); - void stopSounds(bool stopAll); + void stopSounds(); void setHour(const float hour); @@ -206,6 +207,9 @@ namespace MWWorld bool mIsStorm; Ogre::Vector3 mStormDirection; + MWBase::SoundPtr mAmbientSound; + std::string mPlayingSoundID; + MWWorld::Fallback* mFallback; void setFallbackWeather(Weather& weather,const std::string& name); MWRender::RenderingManager* mRendering; @@ -214,8 +218,6 @@ namespace MWWorld std::map mRegionOverrides; - std::vector mSoundsPlaying; - std::string mCurrentWeather; std::string mNextWeather; diff --git a/components/nif/data.hpp b/components/nif/data.hpp index f3b5a27f8..5efd0d6c9 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -272,7 +272,7 @@ class NiSkinData : public Record public: struct BoneTrafo { - Ogre::Matrix3 rotationScale; // Rotation offset from bone, non-uniform scale + Ogre::Matrix3 rotation; // Rotation offset from bone? Ogre::Vector3 trans; // Translation float scale; // Probably scale (always 1) }; @@ -295,7 +295,7 @@ public: void read(NIFStream *nif) { - trafo.rotationScale = nif->getMatrix3(); + trafo.rotation = nif->getMatrix3(); trafo.trans = nif->getVector3(); trafo.scale = nif->getFloat(); @@ -307,7 +307,7 @@ public: { BoneInfo &bi = bones[i]; - bi.trafo.rotationScale = nif->getMatrix3(); + bi.trafo.rotation = nif->getMatrix3(); bi.trafo.trans = nif->getVector3(); bi.trafo.scale = nif->getFloat(); bi.unknown = nif->getVector4(); diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 527d1a2af..a6fd5ef5a 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -76,7 +76,7 @@ Transformation NIFStream::getTrafo() { Transformation t; t.pos = getVector3(); - t.rotationScale = getMatrix3(); + t.rotation = getMatrix3(); t.scale = getFloat(); return t; } diff --git a/components/nif/niftypes.hpp b/components/nif/niftypes.hpp index f9235ec45..786c48b65 100644 --- a/components/nif/niftypes.hpp +++ b/components/nif/niftypes.hpp @@ -35,7 +35,7 @@ namespace Nif struct Transformation { Ogre::Vector3 pos; - Ogre::Matrix3 rotationScale; + Ogre::Matrix3 rotation; float scale; static const Transformation& getIdentity() diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 7529e602d..fb68da548 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -9,10 +9,11 @@ void Node::getProperties(const Nif::NiTexturingProperty *&texprop, const Nif::NiVertexColorProperty *&vertprop, const Nif::NiZBufferProperty *&zprop, const Nif::NiSpecularProperty *&specprop, - const Nif::NiWireframeProperty *&wireprop) const + const Nif::NiWireframeProperty *&wireprop, + const Nif::NiStencilProperty *&stencilprop) const { if(parent) - parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); for(size_t i = 0;i < props.length();i++) { @@ -35,6 +36,8 @@ void Node::getProperties(const Nif::NiTexturingProperty *&texprop, specprop = static_cast(pr); else if(pr->recType == Nif::RC_NiWireframeProperty) wireprop = static_cast(pr); + else if (pr->recType == Nif::RC_NiStencilProperty) + stencilprop = static_cast(pr); else std::cerr<< "Unhandled property type: "<recName <setProperty(textureSlotName + "UVSet", sh::makeProperty(new sh::IntValue(tex.uvSet))); + const std::string clampMode = textureSlotName + "ClampMode"; + switch (tex.clamp) + { + case 0: + material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("clamp clamp"))); + break; + case 1: + material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("clamp wrap"))); + break; + case 2: + material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("wrap clamp"))); + break; + case 3: + default: + material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("wrap wrap"))); + break; + } +} + Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, @@ -63,6 +85,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, + const Nif::NiStencilProperty *stencilprop, bool &needTangents, bool particleMaterial) { Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); @@ -84,6 +107,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, // Default should be 1, but Bloodmoon's models are broken int specFlags = 0; int wireFlags = 0; + int drawMode = 1; Ogre::String texName[7]; bool vertexColour = (shapedata->colors.size() != 0); @@ -183,6 +207,20 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, } } + if(stencilprop) + { + drawMode = stencilprop->data.drawMode; + if (stencilprop->data.enabled) + warn("Unhandled stencil test in "+name); + + Nif::ControllerPtr ctrls = stencilprop->controller; + while(!ctrls.empty()) + { + warn("Unhandled stencil controller "+ctrls->recName+" in "+name); + ctrls = ctrls->next; + } + } + // Material if(matprop) { @@ -227,8 +265,13 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, for(int i = 0;i < 7;i++) { if(!texName[i].empty()) + { boost::hash_combine(h, texName[i]); + boost::hash_combine(h, texprop->textures[i].clamp); + boost::hash_combine(h, texprop->textures[i].uvSet); + } } + boost::hash_combine(h, drawMode); boost::hash_combine(h, vertexColour); boost::hash_combine(h, alphaFlags); boost::hash_combine(h, alphaTest); @@ -286,6 +329,13 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("polygon_mode", sh::makeProperty(new sh::StringValue("wireframe"))); } + if (drawMode == 1) + instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("clockwise"))); + else if (drawMode == 2) + instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("anticlockwise"))); + else if (drawMode == 3) + instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("none"))); + instance->setProperty("diffuseMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BaseTexture])); instance->setProperty("normalMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BumpTexture])); instance->setProperty("detailMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DetailTexture])); @@ -294,22 +344,22 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, if (!texName[Nif::NiTexturingProperty::BaseTexture].empty()) { instance->setProperty("use_diffuse_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("diffuseMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::BaseTexture].uvSet))); + setTextureProperties(instance, "diffuseMap", texprop->textures[Nif::NiTexturingProperty::BaseTexture]); } if (!texName[Nif::NiTexturingProperty::GlowTexture].empty()) { instance->setProperty("use_emissive_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("emissiveMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::GlowTexture].uvSet))); + setTextureProperties(instance, "emissiveMap", texprop->textures[Nif::NiTexturingProperty::GlowTexture]); } if (!texName[Nif::NiTexturingProperty::DetailTexture].empty()) { instance->setProperty("use_detail_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("detailMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DetailTexture].uvSet))); + setTextureProperties(instance, "detailMap", texprop->textures[Nif::NiTexturingProperty::DetailTexture]); } if (!texName[Nif::NiTexturingProperty::DarkTexture].empty()) { instance->setProperty("use_dark_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("darkMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DarkTexture].uvSet))); + setTextureProperties(instance, "darkMap", texprop->textures[Nif::NiTexturingProperty::DarkTexture]); } bool useParallax = !texName[Nif::NiTexturingProperty::BumpTexture].empty() @@ -332,7 +382,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true))); // Override alpha flags based on our override list (transparency-overrides.cfg) - if (!texName[0].empty()) + if ((alphaFlags&1) && !texName[0].empty()) { NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName[0]); if (result.first) diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index d485439cf..6be52d1a5 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -18,6 +18,7 @@ namespace Nif class NiZBufferProperty; class NiSpecularProperty; class NiWireframeProperty; + class NiStencilProperty; } namespace NifOgre @@ -41,6 +42,7 @@ public: const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, + const Nif::NiStencilProperty *stencilprop, bool &needTangents, bool particleMaterial=false); }; diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index c952e664d..4932dd009 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -138,10 +138,9 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape const Nif::NodeList &bones = skin->bones; for(size_t b = 0;b < bones.length();b++) { - const Ogre::Matrix3& rotationScale = data->bones[b].trafo.rotationScale; - Ogre::Matrix4 mat (rotationScale); - mat.setTrans(data->bones[b].trafo.trans); - mat.setScale(Ogre::Vector3(rotationScale[0][0], rotationScale[1][1], rotationScale[2][2]) * data->bones[b].trafo.scale); + Ogre::Matrix4 mat; + mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale), + Ogre::Quaternion(data->bones[b].trafo.rotation)); mat = bones[b]->getWorldTransform() * mat; const std::vector &weights = data->bones[b].weights; @@ -321,13 +320,14 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape const Nif::NiZBufferProperty *zprop = NULL; const Nif::NiSpecularProperty *specprop = NULL; const Nif::NiWireframeProperty *wireprop = NULL; + const Nif::NiStencilProperty *stencilprop = NULL; bool needTangents = false; - shape->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + shape->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); std::string matname = NIFMaterialLoader::getMaterial(data, mesh->getName(), mGroup, texprop, matprop, alphaprop, vertprop, zprop, specprop, - wireprop, needTangents); + wireprop, stencilprop, needTangents); if(matname.length() > 0) sub->setMaterialName(matname); diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index dcc34f627..053adfb5b 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -732,7 +732,8 @@ class NIFObjectLoader const Nif::NiZBufferProperty *zprop = NULL; const Nif::NiSpecularProperty *specprop = NULL; const Nif::NiWireframeProperty *wireprop = NULL; - node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + const Nif::NiStencilProperty *stencilprop = NULL; + node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : @@ -889,13 +890,14 @@ class NIFObjectLoader const Nif::NiZBufferProperty *zprop = NULL; const Nif::NiSpecularProperty *specprop = NULL; const Nif::NiWireframeProperty *wireprop = NULL; + const Nif::NiStencilProperty *stencilprop = NULL; bool needTangents = false; - partnode->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + partnode->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); partsys->setMaterialName(NIFMaterialLoader::getMaterial(particledata, fullname, group, texprop, matprop, alphaprop, vertprop, zprop, specprop, - wireprop, needTangents, + wireprop, stencilprop, needTangents, // MW doesn't light particles, but the MaterialProperty // used still has lighting, so that must be ignored. true)); diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index a3fade5b2..db6a753c5 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -36,17 +36,9 @@ void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, if(parent) parent->addChild(bone); mNifToOgreHandleMap[node->recIndex] = bone->getHandle(); - // decompose the local transform into position, scale and orientation. - // this is required for cases where the rotationScale matrix includes scaling, which the NIF format allows :( - // the code would look a bit nicer if Ogre allowed setting the transform matrix of a Bone directly, but we can't do that. - Ogre::Matrix4 mat(node->getLocalTransform()); - Ogre::Vector3 position, scale; - Ogre::Quaternion orientation; - mat.decomposition(position, scale, orientation); - bone->setOrientation(orientation); - bone->setPosition(position); - bone->setScale(scale); - + bone->setOrientation(node->trafo.rotation); + bone->setPosition(node->trafo.pos); + bone->setScale(Ogre::Vector3(node->trafo.scale)); bone->setBindingPose(); if(!(node->recType == Nif::RC_NiNode || /* Nothing special; children traversed below */ diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 932c7e25f..7d3085b0f 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -67,12 +67,14 @@ material openmw_objects_base depth_check $depth_check transparent_sorting $transparent_sorting polygon_mode $polygon_mode + cull_hardware $cullmode texture_unit diffuseMap { direct_texture $diffuseMap create_in_ffp $use_diffuse_map tex_coord_set $diffuseMapUVSet + tex_address_mode $diffuseMapClampMode } texture_unit normalMap @@ -89,6 +91,7 @@ material openmw_objects_base alpha_op_ex modulate src_current src_texture direct_texture $darkMap tex_coord_set $darkMapUVSet + tex_address_mode $darkMapClampMode } texture_unit detailMap @@ -97,6 +100,7 @@ material openmw_objects_base colour_op_ex modulate_x2 src_current src_texture direct_texture $detailMap tex_coord_set $detailMapUVSet + tex_address_mode $detailMapClampMode } texture_unit emissiveMap @@ -105,6 +109,7 @@ material openmw_objects_base colour_op add direct_texture $emissiveMap tex_coord_set $emissiveMapUVSet + tex_address_mode $emissiveMapClampMode } texture_unit envMap diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 12b52d3db..7566994e2 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -179,6 +179,8 @@ camera y multiplier = 1.0 always run = false +allow third person zoom = false + [Game] # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false