mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-20 06:53:52 +00:00
Merge branch 'master' into pineapple/fix-video-incomplete-type-2
Resolve merge issues related to CHANGELOG.md * master: Optimize skinning (task #4605) Update changelog Update some comments Set the OpenAL source offset after setting the buffer Make Move and MoveWorld console commands move actors standing on moving object (bug #2274) Adding Changelog entry Allow messageboxes arguments to have newline characters (bug #3836) Check for impact immediately when launch a projectile (bug #3059) Fix gold count calculation in pickupObject (bug #4604) Correct special case soundgen comparisons Move "land" check earlier Fixes #3681 Play landing sound manually and ignore land soundgen textkeys (bug #2256) Make some more optimizations to actor processing loops Fix freeze in getActorsSidingWith Addiong missing "to" word Adding common problems that were previous on the site FAQ Treat <> and << operators as < and >< and >> as > in scripts stage1: priorities for event music and other minor improvements to the music system # Conflicts: # CHANGELOG.md
This commit is contained in:
commit
a1e076a37e
20 changed files with 257 additions and 120 deletions
|
@ -4,6 +4,8 @@
|
||||||
Bug #1990: Sunrise/sunset not set correct
|
Bug #1990: Sunrise/sunset not set correct
|
||||||
Bug #2131: Lustidrike's spell misses the player every time
|
Bug #2131: Lustidrike's spell misses the player every time
|
||||||
Bug #2222: Fatigue's effect on selling price is backwards
|
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 #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 #2455: Creatures attacks degrade armor
|
||||||
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
|
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 #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 #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 #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 <item> that has a script containing Equip <item>
|
Bug #3072: Fatal error on AddItem <item> that has a script containing Equip <item>
|
||||||
Bug #3249: Fixed revert function not updating views properly
|
Bug #3249: Fixed revert function not updating views properly
|
||||||
Bug #3374: Touch spells not hitting kwama foragers
|
Bug #3374: Touch spells not hitting kwama foragers
|
||||||
|
@ -22,7 +25,9 @@
|
||||||
Bug #3533: GetSpellEffects should detect effects with zero duration
|
Bug #3533: GetSpellEffects should detect effects with zero duration
|
||||||
Bug #3591: Angled hit distance too low
|
Bug #3591: Angled hit distance too low
|
||||||
Bug #3629: DB assassin attack never triggers creature spawning
|
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 #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 #3876: Landscape texture painting is misaligned
|
||||||
Bug #3897: Have Goodbye give all choices the effects of Goodbye
|
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
|
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 #4575: Weird result of attack animation blending with movement animations
|
||||||
Bug #4576: Reset of idle animations when attack can not be started
|
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 #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 #1645: Casting effects from objects
|
||||||
Feature #2606: Editor: Implemented (optional) case sensitive global search
|
Feature #2606: Editor: Implemented (optional) case sensitive global search
|
||||||
Feature #3083: Play animation when NPC is casting spell via script
|
Feature #3083: Play animation when NPC is casting spell via script
|
||||||
|
@ -123,6 +130,8 @@
|
||||||
Feature #4581: Use proper logging system
|
Feature #4581: Use proper logging system
|
||||||
Task #2490: Don't open command prompt window on Release-mode builds automatically
|
Task #2490: Don't open command prompt window on Release-mode builds automatically
|
||||||
Task #4545: Enable is_pod string test
|
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
|
Task #4613: Incomplete type errors when compiling with g++ on OSX 10.9
|
||||||
|
|
||||||
0.44.0
|
0.44.0
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include "coloreditor.hpp"
|
#include "coloreditor.hpp"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QColor>
|
|
||||||
#include <QColorDialog>
|
#include <QColorDialog>
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QRect>
|
|
||||||
#include <QShowEvent>
|
#include <QShowEvent>
|
||||||
|
|
||||||
#include "colorpickerpopup.hpp"
|
#include "colorpickerpopup.hpp"
|
||||||
|
@ -27,9 +25,7 @@ CSVWidget::ColorEditor::ColorEditor(QWidget *parent, const bool popupOnStart)
|
||||||
mColorPicker(new ColorPickerPopup(this)),
|
mColorPicker(new ColorPickerPopup(this)),
|
||||||
mPopupOnStart(popupOnStart)
|
mPopupOnStart(popupOnStart)
|
||||||
{
|
{
|
||||||
setCheckable(true);
|
|
||||||
connect(this, SIGNAL(clicked()), this, SLOT(showPicker()));
|
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 &)));
|
connect(mColorPicker, SIGNAL(colorChanged(const QColor &)), this, SLOT(pickerColorChanged(const QColor &)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,22 +80,9 @@ void CSVWidget::ColorEditor::setColor(const int colorInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVWidget::ColorEditor::showPicker()
|
void CSVWidget::ColorEditor::showPicker()
|
||||||
{
|
|
||||||
if (isChecked())
|
|
||||||
{
|
{
|
||||||
mColorPicker->showPicker(calculatePopupPosition(), mColor);
|
mColorPicker->showPicker(calculatePopupPosition(), mColor);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
mColorPicker->hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSVWidget::ColorEditor::pickerHid()
|
|
||||||
{
|
|
||||||
setChecked(false);
|
|
||||||
emit pickingFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSVWidget::ColorEditor::pickerColorChanged(const QColor &color)
|
void CSVWidget::ColorEditor::pickerColorChanged(const QColor &color)
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,7 +45,6 @@ namespace CSVWidget
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void showPicker();
|
void showPicker();
|
||||||
void pickerHid();
|
|
||||||
void pickerColorChanged(const QColor &color);
|
void pickerColorChanged(const QColor &color);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QMouseEvent>
|
|
||||||
#include <QLayout>
|
#include <QLayout>
|
||||||
#include <QStyleOption>
|
#include <QStyleOption>
|
||||||
|
|
||||||
|
@ -19,7 +18,6 @@ CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent)
|
||||||
mColorPicker->setWindowFlags(Qt::Widget);
|
mColorPicker->setWindowFlags(Qt::Widget);
|
||||||
mColorPicker->setOptions(QColorDialog::NoButtons | QColorDialog::DontUseNativeDialog);
|
mColorPicker->setOptions(QColorDialog::NoButtons | QColorDialog::DontUseNativeDialog);
|
||||||
mColorPicker->installEventFilter(this);
|
mColorPicker->installEventFilter(this);
|
||||||
mColorPicker->open();
|
|
||||||
connect(mColorPicker,
|
connect(mColorPicker,
|
||||||
SIGNAL(currentColorChanged(const QColor &)),
|
SIGNAL(currentColorChanged(const QColor &)),
|
||||||
this,
|
this,
|
||||||
|
@ -39,8 +37,9 @@ void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColo
|
||||||
geometry.moveTo(position);
|
geometry.moveTo(position);
|
||||||
setGeometry(geometry);
|
setGeometry(geometry);
|
||||||
|
|
||||||
mColorPicker->setCurrentColor(initialColor);
|
// Calling getColor() creates a blocking dialog that will continue execution once the user chooses OK or Cancel
|
||||||
show();
|
QColor color = mColorPicker->getColor(initialColor);
|
||||||
|
mColorPicker->setCurrentColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event)
|
void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event)
|
||||||
|
@ -63,12 +62,6 @@ void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event)
|
||||||
QFrame::mousePressEvent(event);
|
QFrame::mousePressEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVWidget::ColorPickerPopup::hideEvent(QHideEvent *event)
|
|
||||||
{
|
|
||||||
QFrame::hideEvent(event);
|
|
||||||
emit hid();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CSVWidget::ColorPickerPopup::eventFilter(QObject *object, QEvent *event)
|
bool CSVWidget::ColorPickerPopup::eventFilter(QObject *object, QEvent *event)
|
||||||
{
|
{
|
||||||
if (object == mColorPicker && event->type() == QEvent::KeyPress)
|
if (object == mColorPicker && event->type() == QEvent::KeyPress)
|
||||||
|
|
|
@ -20,11 +20,9 @@ namespace CSVWidget
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void mousePressEvent(QMouseEvent *event);
|
virtual void mousePressEvent(QMouseEvent *event);
|
||||||
virtual void hideEvent(QHideEvent *event);
|
|
||||||
virtual bool eventFilter(QObject *object, QEvent *event);
|
virtual bool eventFilter(QObject *object, QEvent *event);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void hid();
|
|
||||||
void colorChanged(const QColor &color);
|
void colorChanged(const QColor &color);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -412,6 +412,7 @@ namespace MWBase
|
||||||
/// @note throws an exception when invoked on a teleport door
|
/// @note throws an exception when invoked on a teleport door
|
||||||
virtual void activateDoor(const MWWorld::Ptr& door, int state) = 0;
|
virtual void activateDoor(const MWWorld::Ptr& door, int state) = 0;
|
||||||
|
|
||||||
|
virtual void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr> &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 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 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
|
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 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 launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
|
||||||
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
|
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;
|
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0;
|
||||||
|
|
||||||
virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0;
|
virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0;
|
||||||
|
|
||||||
|
|
|
@ -1243,9 +1243,8 @@ namespace MWClass
|
||||||
osg::Vec3f pos(ptr.getRefData().getPosition().asVec3());
|
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";
|
return "DefaultLandWater";
|
||||||
if(world->isOnGround(ptr))
|
|
||||||
return "DefaultLand";
|
return "DefaultLand";
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
if(name == "swimleft")
|
if(name == "swimleft")
|
||||||
return "Swim Left";
|
return "Swim Left";
|
||||||
|
|
|
@ -681,6 +681,8 @@ namespace MWGui
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int count = object.getRefData().getCount();
|
int count = object.getRefData().getCount();
|
||||||
|
if (object.getClass().isGold(object))
|
||||||
|
count *= object.getClass().getValue(object);
|
||||||
|
|
||||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||||
MWBase::Environment::get().getWorld()->breakInvisibility(player);
|
MWBase::Environment::get().getWorld()->breakInvisibility(player);
|
||||||
|
|
|
@ -1782,6 +1782,8 @@ namespace MWMechanics
|
||||||
if (iteratedActor == getPlayer())
|
if (iteratedActor == getPlayer())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
const bool sameActor = (iteratedActor == actor);
|
||||||
|
|
||||||
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
||||||
if (stats.isDead())
|
if (stats.isDead())
|
||||||
continue;
|
continue;
|
||||||
|
@ -1790,14 +1792,13 @@ namespace MWMechanics
|
||||||
// Actors that are targeted by this actor's Follow or Escort packages also side with them
|
// 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)
|
for (auto package = stats.getAiSequence().begin(); package != stats.getAiSequence().end(); ++package)
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr &target = (*package)->getTarget();
|
if ((*package)->sideWithTarget() && !(*package)->getTarget().isEmpty())
|
||||||
if ((*package)->sideWithTarget() && !target.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);
|
list.push_back(iteratedActor);
|
||||||
}
|
}
|
||||||
|
@ -1816,7 +1817,7 @@ namespace MWMechanics
|
||||||
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr &iteratedActor = iter->first;
|
const MWWorld::Ptr &iteratedActor = iter->first;
|
||||||
if (iteratedActor == getPlayer())
|
if (iteratedActor == getPlayer() || iteratedActor == actor)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
||||||
|
@ -1879,7 +1880,7 @@ namespace MWMechanics
|
||||||
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr &iteratedActor = iter->first;
|
const MWWorld::Ptr &iteratedActor = iter->first;
|
||||||
if (iteratedActor == getPlayer())
|
if (iteratedActor == getPlayer() || iteratedActor == actor)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
||||||
|
@ -1909,8 +1910,11 @@ namespace MWMechanics
|
||||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
||||||
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
|
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
|
||||||
{
|
{
|
||||||
|
if (*neighbor == actor)
|
||||||
|
continue;
|
||||||
|
|
||||||
const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor);
|
const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor);
|
||||||
if (stats.isDead() || *neighbor == actor)
|
if (stats.isDead())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (stats.getAiSequence().isInCombat(actor))
|
if (stats.getAiSequence().isInCombat(actor))
|
||||||
|
|
|
@ -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);
|
std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen);
|
||||||
if(!sound.empty())
|
if(!sound.empty())
|
||||||
{
|
{
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
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
|
// Don't make foot sounds local for the player, it makes sense to keep them
|
||||||
// positioned on the ground.
|
// positioned on the ground.
|
||||||
|
@ -2027,6 +2030,12 @@ void CharacterController::update(float duration)
|
||||||
cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -123,7 +123,8 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
|
||||||
float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat();
|
float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat();
|
||||||
float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * attackStrength;
|
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);
|
showWeapon(false);
|
||||||
|
|
||||||
|
@ -149,9 +150,11 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
|
||||||
float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat();
|
float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat();
|
||||||
float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * attackStrength;
|
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();
|
mAmmunition.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,18 @@ namespace MWScript
|
||||||
{
|
{
|
||||||
namespace Transformation
|
namespace Transformation
|
||||||
{
|
{
|
||||||
|
void moveStandingActors(const MWWorld::Ptr &ptr, const osg::Vec3f& diff)
|
||||||
|
{
|
||||||
|
std::vector<MWWorld::Ptr> 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 R>
|
template<class R>
|
||||||
class OpSetScale : public Interpreter::Opcode0
|
class OpSetScale : public Interpreter::Opcode0
|
||||||
{
|
{
|
||||||
|
@ -666,6 +678,10 @@ namespace MWScript
|
||||||
osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange;
|
osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange;
|
||||||
osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3());
|
osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3());
|
||||||
worldPos += diff;
|
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());
|
MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -688,22 +704,21 @@ namespace MWScript
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
|
|
||||||
const float *objPos = ptr.getRefData().getPosition().pos;
|
const float *objPos = ptr.getRefData().getPosition().pos;
|
||||||
|
osg::Vec3f diff;
|
||||||
|
|
||||||
MWWorld::Ptr updated;
|
|
||||||
if (axis == "x")
|
if (axis == "x")
|
||||||
{
|
diff.x() += movement;
|
||||||
updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+movement, objPos[1], objPos[2]);
|
|
||||||
}
|
|
||||||
else if (axis == "y")
|
else if (axis == "y")
|
||||||
{
|
diff.y() += movement;
|
||||||
updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1]+movement, objPos[2]);
|
|
||||||
}
|
|
||||||
else if (axis == "z")
|
else if (axis == "z")
|
||||||
{
|
diff.z() += movement;
|
||||||
updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1], objPos[2]+movement);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
throw std::runtime_error ("invalid movement axis: " + axis);
|
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());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1145,14 +1145,24 @@ bool OpenAL_Output::playSound(Sound *sound, Sound_Handle data, float offset)
|
||||||
|
|
||||||
initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(),
|
initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(),
|
||||||
sound->getIsLooping(), sound->getUseEnv());
|
sound->getIsLooping(), sound->getUseEnv());
|
||||||
|
alSourcei(source, AL_BUFFER, GET_PTRID(data));
|
||||||
alSourcef(source, AL_SEC_OFFSET, offset);
|
alSourcef(source, AL_SEC_OFFSET, offset);
|
||||||
if(getALError() != AL_NO_ERROR)
|
if(getALError() != AL_NO_ERROR)
|
||||||
|
{
|
||||||
|
alSourceRewind(source);
|
||||||
|
alSourcei(source, AL_BUFFER, 0);
|
||||||
|
alGetError();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
alSourcei(source, AL_BUFFER, GET_PTRID(data));
|
|
||||||
alSourcePlay(source);
|
alSourcePlay(source);
|
||||||
if(getALError() != AL_NO_ERROR)
|
if(getALError() != AL_NO_ERROR)
|
||||||
|
{
|
||||||
|
alSourceRewind(source);
|
||||||
|
alSourcei(source, AL_BUFFER, 0);
|
||||||
|
alGetError();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mFreeSources.pop_front();
|
mFreeSources.pop_front();
|
||||||
sound->mHandle = MAKE_PTRID(source);
|
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(),
|
initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(),
|
||||||
sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(),
|
sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(),
|
||||||
sound->getUseEnv());
|
sound->getUseEnv());
|
||||||
|
alSourcei(source, AL_BUFFER, GET_PTRID(data));
|
||||||
alSourcef(source, AL_SEC_OFFSET, offset);
|
alSourcef(source, AL_SEC_OFFSET, offset);
|
||||||
if(getALError() != AL_NO_ERROR)
|
if(getALError() != AL_NO_ERROR)
|
||||||
|
{
|
||||||
|
alSourceRewind(source);
|
||||||
|
alSourcei(source, AL_BUFFER, 0);
|
||||||
|
alGetError();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
alSourcei(source, AL_BUFFER, GET_PTRID(data));
|
|
||||||
alSourcePlay(source);
|
alSourcePlay(source);
|
||||||
if(getALError() != AL_NO_ERROR)
|
if(getALError() != AL_NO_ERROR)
|
||||||
|
{
|
||||||
|
alSourceRewind(source);
|
||||||
|
alSourcei(source, AL_BUFFER, 0);
|
||||||
|
alGetError();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mFreeSources.pop_front();
|
mFreeSources.pop_front();
|
||||||
sound->mHandle = MAKE_PTRID(source);
|
sound->mHandle = MAKE_PTRID(source);
|
||||||
|
@ -1197,9 +1217,8 @@ void OpenAL_Output::finishSound(Sound *sound)
|
||||||
ALuint source = GET_PTRID(sound->mHandle);
|
ALuint source = GET_PTRID(sound->mHandle);
|
||||||
sound->mHandle = 0;
|
sound->mHandle = 0;
|
||||||
|
|
||||||
// Rewind the stream instead of stopping it, this puts the source into an AL_INITIAL state,
|
// Rewind the stream to put the source back into an AL_INITIAL state, for
|
||||||
// which works around a bug in the MacOS OpenAL implementation which would otherwise think
|
// the next time it's used.
|
||||||
// the initial queue already played when it hasn't.
|
|
||||||
alSourceRewind(source);
|
alSourceRewind(source);
|
||||||
alSourcei(source, AL_BUFFER, 0);
|
alSourcei(source, AL_BUFFER, 0);
|
||||||
getALError();
|
getALError();
|
||||||
|
@ -1302,9 +1321,8 @@ void OpenAL_Output::finishStream(Stream *sound)
|
||||||
sound->mHandle = 0;
|
sound->mHandle = 0;
|
||||||
mStreamThread->remove(stream);
|
mStreamThread->remove(stream);
|
||||||
|
|
||||||
// Rewind the stream instead of stopping it, this puts the source into an AL_INITIAL state,
|
// Rewind the stream to put the source back into an AL_INITIAL state, for
|
||||||
// which works around a bug in the MacOS OpenAL implementation which would otherwise think
|
// the next time it's used.
|
||||||
// the initial queue already played when it hasn't.
|
|
||||||
alSourceRewind(source);
|
alSourceRewind(source);
|
||||||
alSourcei(source, AL_BUFFER, 0);
|
alSourcei(source, AL_BUFFER, 0);
|
||||||
getALError();
|
getALError();
|
||||||
|
|
|
@ -2377,6 +2377,11 @@ namespace MWWorld
|
||||||
return !actors.empty();
|
return !actors.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void World::getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr> &actors)
|
||||||
|
{
|
||||||
|
mPhysics->getActorsStandingOn(object, actors);
|
||||||
|
}
|
||||||
|
|
||||||
bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object)
|
bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object)
|
||||||
{
|
{
|
||||||
MWWorld::Ptr player = getPlayerPtr();
|
MWWorld::Ptr player = getPlayerPtr();
|
||||||
|
@ -2892,9 +2897,33 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
|
void World::launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
|
||||||
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength)
|
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float 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<MWWorld::Ptr> 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);
|
mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -525,6 +525,7 @@ namespace MWWorld
|
||||||
/// @note throws an exception when invoked on a teleport door
|
/// @note throws an exception when invoked on a teleport door
|
||||||
void activateDoor(const MWWorld::Ptr& door, int state) override;
|
void activateDoor(const MWWorld::Ptr& door, int state) override;
|
||||||
|
|
||||||
|
void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr> &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 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 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
|
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 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 launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override;
|
||||||
void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
|
void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
|
||||||
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) override;
|
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override;
|
||||||
|
|
||||||
void applyLoopingParticles(const MWWorld::Ptr& ptr) override;
|
void applyLoopingParticles(const MWWorld::Ptr& ptr) override;
|
||||||
|
|
||||||
|
|
|
@ -306,10 +306,22 @@ namespace Compiler
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
std::string lowerCase = Misc::StringUtils::lowerCase(name);
|
std::string lowerCase = Misc::StringUtils::lowerCase(name);
|
||||||
|
bool isKeyword = false;
|
||||||
for (; sKeywords[i]; ++i)
|
for (; sKeywords[i]; ++i)
|
||||||
if (lowerCase==sKeywords[i])
|
if (lowerCase==sKeywords[i])
|
||||||
|
{
|
||||||
|
isKeyword = true;
|
||||||
break;
|
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])
|
if (sKeywords[i])
|
||||||
{
|
{
|
||||||
|
@ -356,12 +368,17 @@ namespace Compiler
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
else if (c=='\n')
|
else if (c=='\n')
|
||||||
|
{
|
||||||
|
if (mIgnoreNewline)
|
||||||
|
mErrorHandler.warning ("string contains newline character, make sure that it is intended", mLoc);
|
||||||
|
else
|
||||||
{
|
{
|
||||||
error = true;
|
error = true;
|
||||||
mErrorHandler.error ("incomplete string or name", mLoc);
|
mErrorHandler.error ("incomplete string or name", mLoc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (!(c=='"' && name.empty()))
|
else if (!(c=='"' && name.empty()))
|
||||||
{
|
{
|
||||||
if (!isStringCharacter (c) && !(mTolerantNames && (c=='.' || c=='-')))
|
if (!isStringCharacter (c) && !(mTolerantNames && (c=='.' || c=='-')))
|
||||||
|
@ -502,6 +519,11 @@ namespace Compiler
|
||||||
if (get (c) && c!='=') // <== is a allowed as an alternative to <= :(
|
if (get (c) && c!='=') // <== is a allowed as an alternative to <= :(
|
||||||
putback (c);
|
putback (c);
|
||||||
}
|
}
|
||||||
|
else if (c == '<' || c == '>') // Treat <> and << as <
|
||||||
|
{
|
||||||
|
special = S_cmpLT;
|
||||||
|
mErrorHandler.warning (std::string("invalid operator <") + c + ", treating it as <", mLoc);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
putback (c);
|
putback (c);
|
||||||
|
@ -525,6 +547,11 @@ namespace Compiler
|
||||||
if (get (c) && c!='=') // >== is a allowed as an alternative to >= :(
|
if (get (c) && c!='=') // >== is a allowed as an alternative to >= :(
|
||||||
putback (c);
|
putback (c);
|
||||||
}
|
}
|
||||||
|
else if (c == '<' || c == '>') // Treat >< and >> as >
|
||||||
|
{
|
||||||
|
special = S_cmpGT;
|
||||||
|
mErrorHandler.warning (std::string("invalid operator >") + c + ", treating it as >", mLoc);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
putback (c);
|
putback (c);
|
||||||
|
@ -578,7 +605,7 @@ namespace Compiler
|
||||||
const Extensions *extensions)
|
const Extensions *extensions)
|
||||||
: mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions),
|
: mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions),
|
||||||
mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0),
|
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);
|
mExtensions->listKeywords (keywords);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Scanner::enableIgnoreNewlines()
|
||||||
|
{
|
||||||
|
mIgnoreNewline = true;
|
||||||
|
}
|
||||||
|
|
||||||
void Scanner::enableStrictKeywords()
|
void Scanner::enableStrictKeywords()
|
||||||
{
|
{
|
||||||
mStrictKeywords = true;
|
mStrictKeywords = true;
|
||||||
|
|
|
@ -39,6 +39,7 @@ namespace Compiler
|
||||||
TokenLoc mPutbackLoc;
|
TokenLoc mPutbackLoc;
|
||||||
bool mStrictKeywords;
|
bool mStrictKeywords;
|
||||||
bool mTolerantNames;
|
bool mTolerantNames;
|
||||||
|
bool mIgnoreNewline;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -126,6 +127,11 @@ namespace Compiler
|
||||||
void listKeywords (std::vector<std::string>& keywords);
|
void listKeywords (std::vector<std::string>& keywords);
|
||||||
///< Append all known keywords to \a 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.
|
/// Do not accept keywords in quotation marks anymore.
|
||||||
///
|
///
|
||||||
/// \attention This mode lasts only until the next newline is reached.
|
/// \attention This mode lasts only until the next newline is reached.
|
||||||
|
|
|
@ -8,6 +8,31 @@
|
||||||
#include "skeleton.hpp"
|
#include "skeleton.hpp"
|
||||||
#include "util.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
|
namespace SceneUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -141,28 +166,6 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv)
|
||||||
return true;
|
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)
|
void RigGeometry::cull(osg::NodeVisitor* nv)
|
||||||
{
|
{
|
||||||
if (!mSkeleton)
|
if (!mSkeleton)
|
||||||
|
@ -173,7 +176,8 @@ void RigGeometry::cull(osg::NodeVisitor* nv)
|
||||||
return;
|
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);
|
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
|
||||||
nv->pushOntoNodePath(&geom);
|
nv->pushOntoNodePath(&geom);
|
||||||
|
@ -181,10 +185,10 @@ void RigGeometry::cull(osg::NodeVisitor* nv)
|
||||||
nv->popFromNodePath();
|
nv->popFromNodePath();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mLastFrameNumber = nv->getTraversalNumber();
|
mLastFrameNumber = traversalNumber;
|
||||||
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
|
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
|
||||||
|
|
||||||
mSkeleton->updateBoneMatrices(nv->getTraversalNumber());
|
mSkeleton->updateBoneMatrices(traversalNumber);
|
||||||
|
|
||||||
// skinning
|
// skinning
|
||||||
const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
||||||
|
@ -195,34 +199,31 @@ void RigGeometry::cull(osg::NodeVisitor* nv)
|
||||||
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
|
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
|
||||||
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(geom.getTexCoordArray(7));
|
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(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, 0,
|
0, 0, 0, 0,
|
||||||
0, 0, 0, 1);
|
0, 0, 0, 1);
|
||||||
|
|
||||||
for (std::vector<BoneWeight>::const_iterator weightIt = it->first.begin(); weightIt != it->first.end(); ++weightIt)
|
for (auto &weight : pair.first)
|
||||||
{
|
{
|
||||||
Bone* bone = weightIt->first.first;
|
accumulateMatrix(weight.first.second, weight.first.first->mMatrixInSkeletonSpace, weight.second, resultMat);
|
||||||
const osg::Matrix& invBindMatrix = weightIt->first.second;
|
|
||||||
float weight = weightIt->second;
|
|
||||||
const osg::Matrixf& boneMatrix = bone->mMatrixInSkeletonSpace;
|
|
||||||
accumulateMatrix(invBindMatrix, boneMatrix, weight, resultMat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mGeomToSkelMatrix)
|
if (mGeomToSkelMatrix)
|
||||||
resultMat *= (*mGeomToSkelMatrix);
|
resultMat *= (*mGeomToSkelMatrix);
|
||||||
|
|
||||||
for (std::vector<unsigned short>::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]);
|
(*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]);
|
||||||
if (normalDst)
|
if (normalDst)
|
||||||
(*normalDst)[vertex] = osg::Matrix::transform3x3((*normalSrc)[vertex], resultMat);
|
(*normalDst)[vertex] = osg::Matrixf::transform3x3((*normalSrc)[vertex], resultMat);
|
||||||
|
|
||||||
if (tangentDst)
|
if (tangentDst)
|
||||||
{
|
{
|
||||||
osg::Vec4f srcTangent = (*tangentSrc)[vertex];
|
const osg::Vec4f& srcTangent = (*tangentSrc)[vertex];
|
||||||
osg::Vec3f transformedTangent = osg::Matrix::transform3x3(osg::Vec3f(srcTangent.x(), srcTangent.y(), srcTangent.z()), resultMat);
|
osg::Vec3f transformedTangent = osg::Matrixf::transform3x3(osg::Vec3f(srcTangent.x(), srcTangent.y(), srcTangent.z()), resultMat);
|
||||||
(*tangentDst)[vertex] = osg::Vec4f(transformedTangent, srcTangent.w());
|
(*tangentDst)[vertex] = osg::Vec4f(transformedTangent, srcTangent.w());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1383,9 +1383,9 @@ We add several new script instructions:
|
||||||
* PlayBackgroundMusic (t)
|
* PlayBackgroundMusic (t)
|
||||||
* PlaylistBackgroundMusic (pl)
|
* PlaylistBackgroundMusic (pl)
|
||||||
* StopBackgroundMusic
|
* StopBackgroundMusic
|
||||||
* PlayEventMusic (t, loop=0, force=0)
|
* PlayEventMusic (t, priority, loop=0, force=0)
|
||||||
* PlaylistEventMusic (pl, loop=0, force=0)
|
* PlaylistEventMusic (pl, priority, loop=0, force=0)
|
||||||
* StopEventMusic
|
* StopEventMusic (priority)
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
|
@ -1393,8 +1393,14 @@ Arguments:
|
||||||
* pl (string): a playlist (specified by its ID)
|
* pl (string): a playlist (specified by its ID)
|
||||||
* loop (integer): looping or not
|
* 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.
|
* 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
|
### Transition of Existing System
|
||||||
|
|
||||||
|
|
|
@ -48,3 +48,32 @@ Installing game files via Steam on macOS: DISK WRITE ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
Restart the Steam client. The download should now proceed.
|
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".
|
Loading…
Reference in a new issue