diff --git a/CHANGELOG.md b/CHANGELOG.md index a8a072105..0584445c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Bug #1990: Sunrise/sunset not set correct Bug #2131: Lustidrike's spell misses the player every time Bug #2222: Fatigue's effect on selling price is backwards + Bug #2256: Landing sound not playing when jumping immediately after landing + Bug #2274: Thin platform clips through player character instead of lifting Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #2455: Creatures attacks degrade armor Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash @@ -15,6 +17,7 @@ Bug #2872: Tab completion in console doesn't work with explicit reference Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y Bug #3049: Drain and Fortify effects are not properly applied on health, magicka and fatigue + Bug #3059: Unable to hit with marksman weapons when too close to an enemy Bug #3072: Fatal error on AddItem that has a script containing Equip Bug #3249: Fixed revert function not updating views properly Bug #3374: Touch spells not hitting kwama foragers @@ -22,7 +25,9 @@ Bug #3533: GetSpellEffects should detect effects with zero duration Bug #3591: Angled hit distance too low Bug #3629: DB assassin attack never triggers creature spawning + Bug #3681: OpenMW-CS: Clicking Scripts in Preferences spawns many color pickers Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla + Bug #3836: Script fails to compile when command argument contains "\n" Bug #3876: Landscape texture painting is misaligned Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters @@ -98,6 +103,8 @@ Bug #4575: Weird result of attack animation blending with movement animations Bug #4576: Reset of idle animations when attack can not be started Bug #4591: Attack strength should be 0 if player did not hold the attack button + Bug #4597: <> operator causes a compile error + Bug #4604: Picking up gold from the ground only makes 1 grabbed Feature #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search Feature #3083: Play animation when NPC is casting spell via script @@ -123,6 +130,8 @@ Feature #4581: Use proper logging system Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test + Task #4605: Optimize skinning + Task #4606: Support Rapture3D's OpenAL driver Task #4613: Incomplete type errors when compiling with g++ on OSX 10.9 0.44.0 diff --git a/apps/opencs/view/widget/coloreditor.cpp b/apps/opencs/view/widget/coloreditor.cpp index 5f82f8e26..82ca2b0c9 100644 --- a/apps/opencs/view/widget/coloreditor.cpp +++ b/apps/opencs/view/widget/coloreditor.cpp @@ -1,11 +1,9 @@ #include "coloreditor.hpp" #include -#include #include #include #include -#include #include #include "colorpickerpopup.hpp" @@ -27,9 +25,7 @@ CSVWidget::ColorEditor::ColorEditor(QWidget *parent, const bool popupOnStart) mColorPicker(new ColorPickerPopup(this)), mPopupOnStart(popupOnStart) { - setCheckable(true); connect(this, SIGNAL(clicked()), this, SLOT(showPicker())); - connect(mColorPicker, SIGNAL(hid()), this, SLOT(pickerHid())); connect(mColorPicker, SIGNAL(colorChanged(const QColor &)), this, SLOT(pickerColorChanged(const QColor &))); } @@ -85,20 +81,7 @@ void CSVWidget::ColorEditor::setColor(const int colorInt) void CSVWidget::ColorEditor::showPicker() { - if (isChecked()) - { - mColorPicker->showPicker(calculatePopupPosition(), mColor); - } - else - { - mColorPicker->hide(); - } -} - -void CSVWidget::ColorEditor::pickerHid() -{ - setChecked(false); - emit pickingFinished(); + mColorPicker->showPicker(calculatePopupPosition(), mColor); } void CSVWidget::ColorEditor::pickerColorChanged(const QColor &color) diff --git a/apps/opencs/view/widget/coloreditor.hpp b/apps/opencs/view/widget/coloreditor.hpp index 668f22cc9..368896e42 100644 --- a/apps/opencs/view/widget/coloreditor.hpp +++ b/apps/opencs/view/widget/coloreditor.hpp @@ -45,7 +45,6 @@ namespace CSVWidget private slots: void showPicker(); - void pickerHid(); void pickerColorChanged(const QColor &color); signals: diff --git a/apps/opencs/view/widget/colorpickerpopup.cpp b/apps/opencs/view/widget/colorpickerpopup.cpp index 8e71ce39e..47aab8981 100644 --- a/apps/opencs/view/widget/colorpickerpopup.cpp +++ b/apps/opencs/view/widget/colorpickerpopup.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -19,7 +18,6 @@ CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent) mColorPicker->setWindowFlags(Qt::Widget); mColorPicker->setOptions(QColorDialog::NoButtons | QColorDialog::DontUseNativeDialog); mColorPicker->installEventFilter(this); - mColorPicker->open(); connect(mColorPicker, SIGNAL(currentColorChanged(const QColor &)), this, @@ -39,8 +37,9 @@ void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColo geometry.moveTo(position); setGeometry(geometry); - mColorPicker->setCurrentColor(initialColor); - show(); + // Calling getColor() creates a blocking dialog that will continue execution once the user chooses OK or Cancel + QColor color = mColorPicker->getColor(initialColor); + mColorPicker->setCurrentColor(color); } void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) @@ -63,12 +62,6 @@ void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) QFrame::mousePressEvent(event); } -void CSVWidget::ColorPickerPopup::hideEvent(QHideEvent *event) -{ - QFrame::hideEvent(event); - emit hid(); -} - bool CSVWidget::ColorPickerPopup::eventFilter(QObject *object, QEvent *event) { if (object == mColorPicker && event->type() == QEvent::KeyPress) diff --git a/apps/opencs/view/widget/colorpickerpopup.hpp b/apps/opencs/view/widget/colorpickerpopup.hpp index 602bbdb6d..eb5653f46 100644 --- a/apps/opencs/view/widget/colorpickerpopup.hpp +++ b/apps/opencs/view/widget/colorpickerpopup.hpp @@ -20,11 +20,9 @@ namespace CSVWidget protected: virtual void mousePressEvent(QMouseEvent *event); - virtual void hideEvent(QHideEvent *event); virtual bool eventFilter(QObject *object, QEvent *event); signals: - void hid(); void colorChanged(const QColor &color); }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 3c46298b0..72c4c3a2a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -412,6 +412,7 @@ namespace MWBase /// @note throws an exception when invoked on a teleport door virtual void activateDoor(const MWWorld::Ptr& door, int state) = 0; + virtual void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) = 0; ///< get a list of actors standing on \a object virtual bool getPlayerStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is standing on \a object virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is colliding with \a object @@ -490,8 +491,8 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0; virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; - virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, - const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; + virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c06c3f67c..95d7fa66d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1241,11 +1241,10 @@ namespace MWClass { MWBase::World *world = MWBase::Environment::get().getWorld(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); - if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) + if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return "DefaultLandWater"; - if(world->isOnGround(ptr)) - return "DefaultLand"; - return ""; + + return "DefaultLand"; } if(name == "swimleft") return "Swim Left"; diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 0b35bd9de..523409ceb 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -681,6 +681,8 @@ namespace MWGui return; int count = object.getRefData().getCount(); + if (object.getClass().isGold(object)) + count *= object.getClass().getValue(object); MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->breakInvisibility(player); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6f5b4eeb8..d203dccf4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1782,6 +1782,8 @@ namespace MWMechanics if (iteratedActor == getPlayer()) continue; + const bool sameActor = (iteratedActor == actor); + const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); if (stats.isDead()) continue; @@ -1790,14 +1792,13 @@ namespace MWMechanics // Actors that are targeted by this actor's Follow or Escort packages also side with them for (auto package = stats.getAiSequence().begin(); package != stats.getAiSequence().end(); ++package) { - const MWWorld::Ptr &target = (*package)->getTarget(); - if ((*package)->sideWithTarget() && !target.isEmpty()) + if ((*package)->sideWithTarget() && !(*package)->getTarget().isEmpty()) { - if (iteratedActor == actor) + if (sameActor) { - list.push_back(target); + list.push_back((*package)->getTarget()); } - else if (target == actor) + else if ((*package)->getTarget() == actor) { list.push_back(iteratedActor); } @@ -1816,7 +1817,7 @@ namespace MWMechanics for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { const MWWorld::Ptr &iteratedActor = iter->first; - if (iteratedActor == getPlayer()) + if (iteratedActor == getPlayer() || iteratedActor == actor) continue; const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); @@ -1879,7 +1880,7 @@ namespace MWMechanics for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { const MWWorld::Ptr &iteratedActor = iter->first; - if (iteratedActor == getPlayer()) + if (iteratedActor == getPlayer() || iteratedActor == actor) continue; const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); @@ -1909,8 +1910,11 @@ namespace MWMechanics getObjectsInRange(position, aiProcessingDistance, neighbors); for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor) { + if (*neighbor == actor) + continue; + const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor); - if (stats.isDead() || *neighbor == actor) + if (stats.isDead()) continue; if (stats.getAiSequence().isInCombat(actor)) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c7c6b57d0..49e2c4e6d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -938,11 +938,14 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: } } + if (soundgen == "land") // Morrowind ignores land soundgen for some reason + return; + std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) + if(soundgen == "left" || soundgen == "right") { // Don't make foot sounds local for the player, it makes sense to keep them // positioned on the ground. @@ -2027,6 +2030,12 @@ void CharacterController::update(float duration) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); } } + + // Play landing sound + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + std::string sound = cls.getSoundIdFromSndGen(mPtr, "land"); + if (!sound.empty()) + sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); } else { diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 0b3d000f3..054ce1b1d 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -123,7 +123,8 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * attackStrength; - MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed, attackStrength); + MWWorld::Ptr weaponPtr = *weapon; + MWBase::Environment::get().getWorld()->launchProjectile(actor, weaponPtr, launchPos, orient, weaponPtr, speed, attackStrength); showWeapon(false); @@ -149,9 +150,11 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * attackStrength; - MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed, attackStrength); + MWWorld::Ptr weaponPtr = *weapon; + MWWorld::Ptr ammoPtr = *ammo; + MWBase::Environment::get().getWorld()->launchProjectile(actor, ammoPtr, launchPos, orient, weaponPtr, speed, attackStrength); - inv.remove(*ammo, 1, actor); + inv.remove(ammoPtr, 1, actor); mAmmunition.reset(); } } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 8f0c72519..16d7e8036 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -29,6 +29,18 @@ namespace MWScript { namespace Transformation { + void moveStandingActors(const MWWorld::Ptr &ptr, const osg::Vec3f& diff) + { + std::vector actors; + MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); + for (auto& actor : actors) + { + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + actorPos += diff; + MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z()); + } + } + template class OpSetScale : public Interpreter::Opcode0 { @@ -666,6 +678,10 @@ namespace MWScript osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3()); worldPos += diff; + + // We should move actors, standing on moving object, too. + // This approach can be used to create elevators. + moveStandingActors(ptr, diff); MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z()); } }; @@ -688,22 +704,21 @@ namespace MWScript runtime.pop(); const float *objPos = ptr.getRefData().getPosition().pos; + osg::Vec3f diff; - MWWorld::Ptr updated; if (axis == "x") - { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+movement, objPos[1], objPos[2]); - } + diff.x() += movement; else if (axis == "y") - { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1]+movement, objPos[2]); - } + diff.y() += movement; else if (axis == "z") - { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1], objPos[2]+movement); - } + diff.z() += movement; else throw std::runtime_error ("invalid movement axis: " + axis); + + // We should move actors, standing on moving object, too. + // This approach can be used to create elevators. + moveStandingActors(ptr, diff); + MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z()); } }; diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 3569cd3da..42dd6b5fd 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1145,14 +1145,24 @@ bool OpenAL_Output::playSound(Sound *sound, Sound_Handle data, float offset) initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), sound->getUseEnv()); + alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); if(getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); return false; + } - alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcePlay(source); if(getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); return false; + } mFreeSources.pop_front(); sound->mHandle = MAKE_PTRID(source); @@ -1175,14 +1185,24 @@ bool OpenAL_Output::playSound3D(Sound *sound, Sound_Handle data, float offset) initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), sound->getUseEnv()); + alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); if(getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); return false; + } - alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcePlay(source); if(getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); return false; + } mFreeSources.pop_front(); sound->mHandle = MAKE_PTRID(source); @@ -1197,9 +1217,8 @@ void OpenAL_Output::finishSound(Sound *sound) ALuint source = GET_PTRID(sound->mHandle); sound->mHandle = 0; - // 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. + // Rewind the stream to put the source back into an AL_INITIAL state, for + // the next time it's used. alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); getALError(); @@ -1302,9 +1321,8 @@ void OpenAL_Output::finishStream(Stream *sound) sound->mHandle = 0; mStreamThread->remove(stream); - // 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. + // Rewind the stream to put the source back into an AL_INITIAL state, for + // the next time it's used. alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); getALError(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c2ebac8fc..a3d7873d7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2377,6 +2377,11 @@ namespace MWWorld return !actors.empty(); } + void World::getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) + { + mPhysics->getActorsStandingOn(object, actors); + } + bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object) { MWWorld::Ptr player = getPlayerPtr(); @@ -2892,10 +2897,34 @@ namespace MWWorld } } - void World::launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, - const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) + void World::launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) { - mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); + // An initial position of projectile can be outside shooter's collision box, so any object between shooter and launch position will be ignored. + // To avoid this issue, we should check for impact immediately before launch the projectile. + // So we cast a 1-yard-length ray from shooter to launch position and check if there are collisions in this area. + // TODO: as a better solutuon we should handle projectiles during physics update, not during world update. + const osg::Vec3f sourcePos = worldPos + orient * osg::Vec3f(0,-1,0) * 64.f; + + // Early out if the launch position is underwater + bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); + if (underwater) + { + mRendering->emitWaterRipple(worldPos); + return; + } + + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + std::vector targetActors; + if (!actor.isEmpty() && actor.getClass().isActor() && actor != MWMechanics::getPlayer()) + actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); + + // Check for impact, if yes, handle hit, if not, launch projectile + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(sourcePos, worldPos, actor, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + if (result.mHit) + MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength); + else + mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 432991059..2f732ef97 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -525,6 +525,7 @@ namespace MWWorld /// @note throws an exception when invoked on a teleport door void activateDoor(const MWWorld::Ptr& door, int state) override; + void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors); ///< get a list of actors standing on \a object bool getPlayerStandingOn (const MWWorld::ConstPtr& object) override; ///< @return true if the player is standing on \a object bool getActorStandingOn (const MWWorld::ConstPtr& object) override; ///< @return true if any actor is standing on \a object bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) override; ///< @return true if the player is colliding with \a object @@ -607,8 +608,8 @@ namespace MWWorld void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) override; void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override; - void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, - const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) override; + void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; void applyLoopingParticles(const MWWorld::Ptr& ptr) override; diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index bb0fb9374..b56dbb95b 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -306,10 +306,22 @@ namespace Compiler int i = 0; std::string lowerCase = Misc::StringUtils::lowerCase(name); - + bool isKeyword = false; for (; sKeywords[i]; ++i) if (lowerCase==sKeywords[i]) + { + isKeyword = true; break; + } + + // Russian localization and some mods use a quirk - add newline character directly + // to compiled bytecode via HEX-editor to implement multiline messageboxes. + // Of course, original editor will not compile such script. + // Allow messageboxes to bybass the "incomplete string or name" error. + if (lowerCase == "messagebox") + enableIgnoreNewlines(); + else if (isKeyword) + mIgnoreNewline = false; if (sKeywords[i]) { @@ -357,9 +369,14 @@ namespace Compiler // } else if (c=='\n') { - error = true; - mErrorHandler.error ("incomplete string or name", mLoc); - break; + if (mIgnoreNewline) + mErrorHandler.warning ("string contains newline character, make sure that it is intended", mLoc); + else + { + error = true; + mErrorHandler.error ("incomplete string or name", mLoc); + break; + } } } else if (!(c=='"' && name.empty())) @@ -502,6 +519,11 @@ namespace Compiler if (get (c) && c!='=') // <== is a allowed as an alternative to <= :( putback (c); } + else if (c == '<' || c == '>') // Treat <> and << as < + { + special = S_cmpLT; + mErrorHandler.warning (std::string("invalid operator <") + c + ", treating it as <", mLoc); + } else { putback (c); @@ -525,6 +547,11 @@ namespace Compiler if (get (c) && c!='=') // >== is a allowed as an alternative to >= :( putback (c); } + else if (c == '<' || c == '>') // Treat >< and >> as > + { + special = S_cmpGT; + mErrorHandler.warning (std::string("invalid operator >") + c + ", treating it as >", mLoc); + } else { putback (c); @@ -578,7 +605,7 @@ namespace Compiler const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), - mStrictKeywords (false), mTolerantNames (false) + mStrictKeywords (false), mTolerantNames (false), mIgnoreNewline(false) { } @@ -631,6 +658,11 @@ namespace Compiler mExtensions->listKeywords (keywords); } + void Scanner::enableIgnoreNewlines() + { + mIgnoreNewline = true; + } + void Scanner::enableStrictKeywords() { mStrictKeywords = true; diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 49fbaa96a..a431cabb2 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -39,6 +39,7 @@ namespace Compiler TokenLoc mPutbackLoc; bool mStrictKeywords; bool mTolerantNames; + bool mIgnoreNewline; public: @@ -126,6 +127,11 @@ namespace Compiler void listKeywords (std::vector& keywords); ///< Append all known keywords to \a keywords. + /// Treat newline character as a part of script command. + /// + /// \attention This mode lasts only until the next keyword is reached. + void enableIgnoreNewlines(); + /// Do not accept keywords in quotation marks anymore. /// /// \attention This mode lasts only until the next newline is reached. diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index ee30f1c85..c409bcd5c 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -8,6 +8,31 @@ #include "skeleton.hpp" #include "util.hpp" +namespace +{ + inline void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, const float weight, osg::Matrixf& result) + { + osg::Matrixf m = invBindMatrix * matrix; + float* ptr = m.ptr(); + float* ptrresult = result.ptr(); + ptrresult[0] += ptr[0] * weight; + ptrresult[1] += ptr[1] * weight; + ptrresult[2] += ptr[2] * weight; + + ptrresult[4] += ptr[4] * weight; + ptrresult[5] += ptr[5] * weight; + ptrresult[6] += ptr[6] * weight; + + ptrresult[8] += ptr[8] * weight; + ptrresult[9] += ptr[9] * weight; + ptrresult[10] += ptr[10] * weight; + + ptrresult[12] += ptr[12] * weight; + ptrresult[13] += ptr[13] * weight; + ptrresult[14] += ptr[14] * weight; + } +} + namespace SceneUtil { @@ -141,28 +166,6 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) return true; } -void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, float weight, osg::Matrixf& result) -{ - osg::Matrixf m = invBindMatrix * matrix; - float* ptr = m.ptr(); - float* ptrresult = result.ptr(); - ptrresult[0] += ptr[0] * weight; - ptrresult[1] += ptr[1] * weight; - ptrresult[2] += ptr[2] * weight; - - ptrresult[4] += ptr[4] * weight; - ptrresult[5] += ptr[5] * weight; - ptrresult[6] += ptr[6] * weight; - - ptrresult[8] += ptr[8] * weight; - ptrresult[9] += ptr[9] * weight; - ptrresult[10] += ptr[10] * weight; - - ptrresult[12] += ptr[12] * weight; - ptrresult[13] += ptr[13] * weight; - ptrresult[14] += ptr[14] * weight; -} - void RigGeometry::cull(osg::NodeVisitor* nv) { if (!mSkeleton) @@ -173,7 +176,8 @@ void RigGeometry::cull(osg::NodeVisitor* nv) return; } - if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber()) + unsigned int traversalNumber = nv->getTraversalNumber(); + if (mLastFrameNumber == traversalNumber || (mLastFrameNumber != 0 && !mSkeleton->getActive())) { osg::Geometry& geom = *getGeometry(mLastFrameNumber); nv->pushOntoNodePath(&geom); @@ -181,10 +185,10 @@ void RigGeometry::cull(osg::NodeVisitor* nv) nv->popFromNodePath(); return; } - mLastFrameNumber = nv->getTraversalNumber(); + mLastFrameNumber = traversalNumber; osg::Geometry& geom = *getGeometry(mLastFrameNumber); - mSkeleton->updateBoneMatrices(nv->getTraversalNumber()); + mSkeleton->updateBoneMatrices(traversalNumber); // skinning const osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); @@ -195,34 +199,31 @@ void RigGeometry::cull(osg::NodeVisitor* nv) osg::Vec3Array* normalDst = static_cast(geom.getNormalArray()); osg::Vec4Array* tangentDst = static_cast(geom.getTexCoordArray(7)); - for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it) + for (auto &pair : mBone2VertexMap) { - osg::Matrixf resultMat (0, 0, 0, 0, + osg::Matrixf resultMat (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1); - for (std::vector::const_iterator weightIt = it->first.begin(); weightIt != it->first.end(); ++weightIt) + for (auto &weight : pair.first) { - Bone* bone = weightIt->first.first; - const osg::Matrix& invBindMatrix = weightIt->first.second; - float weight = weightIt->second; - const osg::Matrixf& boneMatrix = bone->mMatrixInSkeletonSpace; - accumulateMatrix(invBindMatrix, boneMatrix, weight, resultMat); + accumulateMatrix(weight.first.second, weight.first.first->mMatrixInSkeletonSpace, weight.second, resultMat); } + if (mGeomToSkelMatrix) resultMat *= (*mGeomToSkelMatrix); - for (std::vector::const_iterator vertexIt = it->second.begin(); vertexIt != it->second.end(); ++vertexIt) + for (auto &vertex : pair.second) { - unsigned short vertex = *vertexIt; (*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]); if (normalDst) - (*normalDst)[vertex] = osg::Matrix::transform3x3((*normalSrc)[vertex], resultMat); + (*normalDst)[vertex] = osg::Matrixf::transform3x3((*normalSrc)[vertex], resultMat); + if (tangentDst) { - osg::Vec4f srcTangent = (*tangentSrc)[vertex]; - osg::Vec3f transformedTangent = osg::Matrix::transform3x3(osg::Vec3f(srcTangent.x(), srcTangent.y(), srcTangent.z()), resultMat); + const osg::Vec4f& srcTangent = (*tangentSrc)[vertex]; + osg::Vec3f transformedTangent = osg::Matrixf::transform3x3(osg::Vec3f(srcTangent.x(), srcTangent.y(), srcTangent.z()), resultMat); (*tangentDst)[vertex] = osg::Vec4f(transformedTangent, srcTangent.w()); } } diff --git a/docs/openmw-stage1.md b/docs/openmw-stage1.md index 4707a4435..4290080b8 100644 --- a/docs/openmw-stage1.md +++ b/docs/openmw-stage1.md @@ -1383,9 +1383,9 @@ We add several new script instructions: * PlayBackgroundMusic (t) * PlaylistBackgroundMusic (pl) * StopBackgroundMusic -* PlayEventMusic (t, loop=0, force=0) -* PlaylistEventMusic (pl, loop=0, force=0) -* StopEventMusic +* PlayEventMusic (t, priority, loop=0, force=0) +* PlaylistEventMusic (pl, priority, loop=0, force=0) +* StopEventMusic (priority) Arguments: @@ -1393,8 +1393,14 @@ Arguments: * pl (string): a playlist (specified by its ID) * loop (integer): looping or not * force (integer): Terminate any other running event music first. If this flag is 0 and there is already event music playing the new event music is ignored. Event music provided by content developers will usually not set this flag. +* priority (integer): Priority for event music. If there are multiple instances of active event music only the one with the highest priority will play. There can only ever be one instance of event music for any priority level. If event music of higher priority stops and there is event music of lower priority the event music of lower priority starts to play. -Note: An attempt to play a track or playlist that is currently playing is ignored. In this case the track or playlist does not restart. +Note: An attempt to play a track or playlist that is currently playing is ignored. In this case the track or playlist does not restart. Changes in looping behaviour are considered though. + +New games starting with a new format omwgame file can use priorities as the developer sees fit. For old games where automatic injection of scripts and music records take place we define the following priorities: + +* 10: Combat music +* 0: Basic event music ### Transition of Existing System diff --git a/docs/source/manuals/installation/common-problems.rst b/docs/source/manuals/installation/common-problems.rst index 543b8ec9b..0967ea9ef 100644 --- a/docs/source/manuals/installation/common-problems.rst +++ b/docs/source/manuals/installation/common-problems.rst @@ -47,4 +47,33 @@ Installing game files via Steam on macOS: DISK WRITE ERROR [other entries] } - Restart the Steam client. The download should now proceed. \ No newline at end of file + Restart the Steam client. The download should now proceed. + +In-game textures show up as pink +################################ + +:Symptoms: + Some textures don't show up and are replaced by pink "filler" textures. + +:Cause: + Textures shipped with Morrowind are compressed with S3TC, a texture compression algorithm that was patented in + the United States until October 2017. Software drivers and operating system distributions released before that date + may not include support for this algorithm. + +:Fix: + Upgrade your graphics drivers and/or operating system to the latest version. + +Music plays, but only a black screen is shown +############################################# + +:Symptoms: + When launching OpenMW, audio is heard but there is only a black screen. + +:Cause: + This can occur when you did not import the Morrowind.ini file when the launcher asked for it. + +:Fix: + To fix it, you need to delete the `launcher.cfg` file from your configuration path + ([Path description on Wiki](https://wiki.openmw.org/index.php?title=Paths)), then start the launcher, select your + Morrowind installation, and when the launcher asks whether the `Morrowind.ini` file should be imported, make sure + to select "Yes". \ No newline at end of file