mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-21 09:53:50 +00:00
Merge remote-tracking branch 'upstream/master' into mouse-picking
This commit is contained in:
commit
dd9208afeb
87 changed files with 3225 additions and 2402 deletions
|
@ -549,6 +549,7 @@ endif(WIN32)
|
||||||
|
|
||||||
# Extern
|
# Extern
|
||||||
add_subdirectory (extern/shiny)
|
add_subdirectory (extern/shiny)
|
||||||
|
add_subdirectory (extern/ogre-ffmpeg-videoplayer)
|
||||||
add_subdirectory (extern/oics)
|
add_subdirectory (extern/oics)
|
||||||
add_subdirectory (extern/sdl4ogre)
|
add_subdirectory (extern/sdl4ogre)
|
||||||
|
|
||||||
|
|
|
@ -345,7 +345,7 @@ std::auto_ptr<sh::Factory> CS::Editor::setupGraphics()
|
||||||
|
|
||||||
factory->loadAllFiles();
|
factory->loadAllFiles();
|
||||||
|
|
||||||
bool shaders = mUserSettings.setting("Objects/shaders", QString("true")) == "true" ? true : false;
|
bool shaders = mUserSettings.setting("3d-render/shaders", QString("true")) == "true" ? true : false;
|
||||||
sh::Factory::getInstance ().setShadersEnabled (shaders);
|
sh::Factory::getInstance ().setShadersEnabled (shaders);
|
||||||
|
|
||||||
std::string fog = mUserSettings.setting("Shader/fog", QString("true")).toStdString();
|
std::string fog = mUserSettings.setting("Shader/fog", QString("true")).toStdString();
|
||||||
|
@ -364,7 +364,7 @@ std::auto_ptr<sh::Factory> CS::Editor::setupGraphics()
|
||||||
// internal setting - may be switched on or off by the use of shader configurations
|
// internal setting - may be switched on or off by the use of shader configurations
|
||||||
sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false");
|
sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false");
|
||||||
|
|
||||||
std::string num_lights = mUserSettings.setting("Objects/num_lights", QString("8")).toStdString();
|
std::string num_lights = mUserSettings.setting("3d-render-adv/num_lights", QString("8")).toStdString();
|
||||||
sh::Factory::getInstance ().setGlobalSetting ("num_lights", num_lights);
|
sh::Factory::getInstance ().setGlobalSetting ("num_lights", num_lights);
|
||||||
|
|
||||||
/// \todo add more configurable shiny settings
|
/// \todo add more configurable shiny settings
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
#include "support.hpp"
|
#include "support.hpp"
|
||||||
|
|
||||||
CSMSettings::Setting::Setting(SettingType typ, const QString &settingName,
|
CSMSettings::Setting::Setting(SettingType typ, const QString &settingName,
|
||||||
const QString &pageName)
|
const QString &pageName, const QString& label)
|
||||||
: mIsEditorSetting (false)
|
: mIsEditorSetting (true)
|
||||||
{
|
{
|
||||||
buildDefaultSetting();
|
buildDefaultSetting();
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ CSMSettings::Setting::Setting(SettingType typ, const QString &settingName,
|
||||||
setProperty (Property_SettingType, QVariant (settingType).toString());
|
setProperty (Property_SettingType, QVariant (settingType).toString());
|
||||||
setProperty (Property_Page, pageName);
|
setProperty (Property_Page, pageName);
|
||||||
setProperty (Property_Name, settingName);
|
setProperty (Property_Name, settingName);
|
||||||
|
setProperty (Property_Label, label.isEmpty() ? settingName : label);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMSettings::Setting::buildDefaultSetting()
|
void CSMSettings::Setting::buildDefaultSetting()
|
||||||
|
@ -290,14 +291,16 @@ CSMSettings::SettingType CSMSettings::Setting::type() const
|
||||||
Property_SettingType).at(0).toInt());
|
Property_SettingType).at(0).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMSettings::Setting::setMaximum (int value)
|
void CSMSettings::Setting::setRange (int min, int max)
|
||||||
{
|
{
|
||||||
setProperty (Property_Maximum, value);
|
setProperty (Property_Minimum, min);
|
||||||
|
setProperty (Property_Maximum, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMSettings::Setting::setMaximum (double value)
|
void CSMSettings::Setting::setRange (double min, double max)
|
||||||
{
|
{
|
||||||
setProperty (Property_Maximum, value);
|
setProperty (Property_Minimum, min);
|
||||||
|
setProperty (Property_Maximum, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CSMSettings::Setting::maximum() const
|
QString CSMSettings::Setting::maximum() const
|
||||||
|
@ -305,16 +308,6 @@ QString CSMSettings::Setting::maximum() const
|
||||||
return property (Property_Maximum).at(0);
|
return property (Property_Maximum).at(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMSettings::Setting::setMinimum (int value)
|
|
||||||
{
|
|
||||||
setProperty (Property_Minimum, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSMSettings::Setting::setMinimum (double value)
|
|
||||||
{
|
|
||||||
setProperty (Property_Minimum, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString CSMSettings::Setting::minimum() const
|
QString CSMSettings::Setting::minimum() const
|
||||||
{
|
{
|
||||||
return property (Property_Minimum).at(0);
|
return property (Property_Minimum).at(0);
|
||||||
|
@ -372,6 +365,26 @@ bool CSMSettings::Setting::wrapping() const
|
||||||
return (property (Property_Wrapping).at(0) == "true");
|
return (property (Property_Wrapping).at(0) == "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSMSettings::Setting::setLabel (const QString& label)
|
||||||
|
{
|
||||||
|
setProperty (Property_Label, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CSMSettings::Setting::getLabel() const
|
||||||
|
{
|
||||||
|
return property (Property_Label).at (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSMSettings::Setting::setToolTip (const QString& toolTip)
|
||||||
|
{
|
||||||
|
setProperty (Property_ToolTip, toolTip);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CSMSettings::Setting::getToolTip() const
|
||||||
|
{
|
||||||
|
return property (Property_ToolTip).at (0);
|
||||||
|
}
|
||||||
|
|
||||||
void CSMSettings::Setting::setProperty (SettingProperty prop, bool value)
|
void CSMSettings::Setting::setProperty (SettingProperty prop, bool value)
|
||||||
{
|
{
|
||||||
setProperty (prop, QStringList() << QVariant (value).toString());
|
setProperty (prop, QStringList() << QVariant (value).toString());
|
||||||
|
|
|
@ -29,8 +29,8 @@ namespace CSMSettings
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Setting(SettingType typ, const QString &settingName,
|
Setting(SettingType typ, const QString &settingName,
|
||||||
const QString &pageName);
|
const QString &pageName, const QString& label = "");
|
||||||
|
|
||||||
void addProxy (const Setting *setting, const QStringList &vals);
|
void addProxy (const Setting *setting, const QStringList &vals);
|
||||||
void addProxy (const Setting *setting, const QList <QStringList> &list);
|
void addProxy (const Setting *setting, const QList <QStringList> &list);
|
||||||
|
@ -66,12 +66,11 @@ namespace CSMSettings
|
||||||
void setMask (const QString &value);
|
void setMask (const QString &value);
|
||||||
QString mask() const;
|
QString mask() const;
|
||||||
|
|
||||||
void setMaximum (int value);
|
void setRange (int min, int max);
|
||||||
void setMaximum (double value);
|
void setRange (double min, double max);
|
||||||
|
|
||||||
QString maximum() const;
|
QString maximum() const;
|
||||||
|
|
||||||
void setMinimum (int value);
|
|
||||||
void setMinimum (double value);
|
|
||||||
QString minimum() const;
|
QString minimum() const;
|
||||||
|
|
||||||
void setName (const QString &value);
|
void setName (const QString &value);
|
||||||
|
@ -132,6 +131,13 @@ namespace CSMSettings
|
||||||
void setWidgetWidth (int value);
|
void setWidgetWidth (int value);
|
||||||
int widgetWidth() const;
|
int widgetWidth() const;
|
||||||
|
|
||||||
|
/// This is the text the user gets to see.
|
||||||
|
void setLabel (const QString& label);
|
||||||
|
QString getLabel() const;
|
||||||
|
|
||||||
|
void setToolTip (const QString& toolTip);
|
||||||
|
QString getToolTip() const;
|
||||||
|
|
||||||
///returns the specified property value
|
///returns the specified property value
|
||||||
QStringList property (SettingProperty prop) const;
|
QStringList property (SettingProperty prop) const;
|
||||||
|
|
||||||
|
|
|
@ -36,12 +36,14 @@ namespace CSMSettings
|
||||||
Property_TicksAbove = 20,
|
Property_TicksAbove = 20,
|
||||||
Property_TicksBelow = 21,
|
Property_TicksBelow = 21,
|
||||||
Property_StyleSheet = 22,
|
Property_StyleSheet = 22,
|
||||||
|
Property_Label = 23,
|
||||||
|
Property_ToolTip = 24,
|
||||||
|
|
||||||
//Stringlists should always be the last items
|
//Stringlists should always be the last items
|
||||||
Property_DefaultValues = 23,
|
Property_DefaultValues = 25,
|
||||||
Property_DeclaredValues = 24,
|
Property_DeclaredValues = 26,
|
||||||
Property_DefinedValues = 25,
|
Property_DefinedValues = 27,
|
||||||
Property_Proxies = 26
|
Property_Proxies = 28
|
||||||
};
|
};
|
||||||
|
|
||||||
///Basic setting widget types.
|
///Basic setting widget types.
|
||||||
|
|
|
@ -30,183 +30,127 @@ namespace boost
|
||||||
} /* namespace boost */
|
} /* namespace boost */
|
||||||
#endif /* (BOOST_VERSION <= 104600) */
|
#endif /* (BOOST_VERSION <= 104600) */
|
||||||
|
|
||||||
CSMSettings::UserSettings *CSMSettings::UserSettings::mUserSettingsInstance = 0;
|
CSMSettings::UserSettings *CSMSettings::UserSettings::sUserSettingsInstance = 0;
|
||||||
|
|
||||||
CSMSettings::UserSettings::UserSettings (const Files::ConfigurationManager& configurationManager)
|
CSMSettings::UserSettings::UserSettings (const Files::ConfigurationManager& configurationManager)
|
||||||
: mCfgMgr (configurationManager)
|
: mCfgMgr (configurationManager)
|
||||||
, mSettingDefinitions(NULL)
|
, mSettingDefinitions(NULL)
|
||||||
, mSettingCfgDefinitions(NULL)
|
|
||||||
{
|
{
|
||||||
assert(!mUserSettingsInstance);
|
assert(!sUserSettingsInstance);
|
||||||
mUserSettingsInstance = this;
|
sUserSettingsInstance = this;
|
||||||
|
|
||||||
buildSettingModelDefaults();
|
buildSettingModelDefaults();
|
||||||
|
|
||||||
// for overriding opencs.ini settings with those from settings.cfg
|
|
||||||
mSettingCfgDefinitions = new QSettings(QSettings::IniFormat, QSettings::UserScope, "", QString(), this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMSettings::UserSettings::buildSettingModelDefaults()
|
void CSMSettings::UserSettings::buildSettingModelDefaults()
|
||||||
{
|
{
|
||||||
QString section;
|
QString section;
|
||||||
|
|
||||||
section = "Objects";
|
declareSection ("3d-render", "3D Rendering");
|
||||||
{
|
{
|
||||||
Setting *numLights = createSetting (Type_SpinBox, section, "num_lights");
|
Setting *shaders = createSetting (Type_CheckBox, "shaders", "Enable Shaders");
|
||||||
numLights->setDefaultValue(8);
|
shaders->setDefaultValue ("true");
|
||||||
numLights->setEditorSetting(true);
|
|
||||||
numLights->setColumnSpan (1);
|
|
||||||
numLights->setMinimum (0);
|
|
||||||
numLights->setMaximum (100); // FIXME: not sure what the max value should be
|
|
||||||
numLights->setWidgetWidth (10);
|
|
||||||
numLights->setViewLocation(1, 2);
|
|
||||||
|
|
||||||
Setting *shaders = createSetting (Type_CheckBox, section, "shaders");
|
Setting *farClipDist = createSetting (Type_DoubleSpinBox, "far-clip-distance", "Far clipping distance");
|
||||||
shaders->setDeclaredValues(QStringList() << "true" << "false");
|
farClipDist->setDefaultValue (300000);
|
||||||
shaders->setDefaultValue("true");
|
farClipDist->setRange (0, 1000000);
|
||||||
shaders->setEditorSetting(true);
|
farClipDist->setToolTip ("The maximum distance objects are still rendered at.");
|
||||||
shaders->setSpecialValueText("Enable Shaders");
|
|
||||||
shaders->setWidgetWidth(25);
|
QString defaultValue = "None";
|
||||||
shaders->setColumnSpan (3);
|
Setting *antialiasing = createSetting (Type_ComboBox, "antialiasing", "Antialiasing");
|
||||||
shaders->setStyleSheet ("QGroupBox { border: 0px; }");
|
antialiasing->setDeclaredValues (QStringList()
|
||||||
shaders->setViewLocation(2, 1);
|
<< defaultValue << "MSAA 2" << "MSAA 4" << "MSAA 8" << "MSAA 16");
|
||||||
|
antialiasing->setDefaultValue (defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
section = "Scene";
|
declareSection ("3d-render-adv", "3D Rendering (Advanced)");
|
||||||
{
|
{
|
||||||
Setting *fastFactor = createSetting (Type_SpinBox, section, "fast factor");
|
Setting *numLights = createSetting (Type_SpinBox, "num_lights",
|
||||||
fastFactor->setDefaultValue(4);
|
"Number of lights per pass");
|
||||||
fastFactor->setEditorSetting(true);
|
numLights->setDefaultValue (8);
|
||||||
fastFactor->setColumnSpan (1);
|
numLights->setRange (1, 100);
|
||||||
fastFactor->setMinimum (1);
|
|
||||||
fastFactor->setSpecialValueText ("1"); // FIXME: workaround
|
|
||||||
fastFactor->setMaximum (100); // FIXME: not sure what the max value should be
|
|
||||||
fastFactor->setWidgetWidth (10);
|
|
||||||
fastFactor->setViewLocation(1, 2);
|
|
||||||
|
|
||||||
Setting *farClipDist = createSetting (Type_DoubleSpinBox, section, "far clip distance");
|
|
||||||
farClipDist->setDefaultValue(300000);
|
|
||||||
farClipDist->setEditorSetting(true);
|
|
||||||
farClipDist->setColumnSpan (1);
|
|
||||||
farClipDist->setMinimum (0);
|
|
||||||
farClipDist->setMaximum (1000000); // FIXME: not sure what the max value should be
|
|
||||||
farClipDist->setWidgetWidth (10);
|
|
||||||
farClipDist->setViewLocation(2, 2);
|
|
||||||
|
|
||||||
Setting *timerStart = createSetting (Type_SpinBox, section, "timer start");
|
|
||||||
timerStart->setDefaultValue(20);
|
|
||||||
timerStart->setEditorSetting(true);
|
|
||||||
timerStart->setColumnSpan (1);
|
|
||||||
timerStart->setMinimum (0);
|
|
||||||
timerStart->setMaximum (100); // FIXME: not sure what the max value should be
|
|
||||||
timerStart->setWidgetWidth (10);
|
|
||||||
timerStart->setViewLocation(3, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section = "SubView";
|
declareSection ("scene-input", "Scene Input");
|
||||||
{
|
{
|
||||||
Setting *maxSubView = createSetting (Type_SpinBox, section, "max subviews");
|
Setting *timer = createSetting (Type_SpinBox, "timer", "Input responsiveness");
|
||||||
maxSubView->setDefaultValue(256);
|
timer->setDefaultValue (20);
|
||||||
maxSubView->setEditorSetting(true);
|
timer->setRange (1, 100);
|
||||||
maxSubView->setColumnSpan (1);
|
timer->setToolTip ("The time between two checks for user input in milliseconds.<p>"
|
||||||
maxSubView->setMinimum (1);
|
"Lower value result in higher responsiveness.");
|
||||||
maxSubView->setSpecialValueText ("1");
|
|
||||||
maxSubView->setMaximum (256); // FIXME: not sure what the max value should be
|
|
||||||
maxSubView->setWidgetWidth (10);
|
|
||||||
maxSubView->setViewLocation(1, 2);
|
|
||||||
|
|
||||||
Setting *minWidth = createSetting (Type_SpinBox, section, "minimum width");
|
Setting *fastFactor = createSetting (Type_SpinBox, "fast-factor",
|
||||||
minWidth->setDefaultValue(325);
|
"Fast movement factor");
|
||||||
minWidth->setEditorSetting(true);
|
fastFactor->setDefaultValue (4);
|
||||||
minWidth->setColumnSpan (1);
|
fastFactor->setRange (1, 100);
|
||||||
minWidth->setMinimum (50);
|
fastFactor->setToolTip (
|
||||||
minWidth->setSpecialValueText ("50");
|
"Factor by which movement is speed up while the shift key is held down.");
|
||||||
minWidth->setMaximum (10000); // FIXME: not sure what the max value should be
|
|
||||||
minWidth->setWidgetWidth (10);
|
|
||||||
minWidth->setViewLocation(2, 2);
|
|
||||||
|
|
||||||
Setting *reuse = createSetting (Type_CheckBox, section, "reuse");
|
|
||||||
reuse->setDeclaredValues(QStringList() << "true" << "false");
|
|
||||||
reuse->setDefaultValue("true");
|
|
||||||
reuse->setEditorSetting(true);
|
|
||||||
reuse->setSpecialValueText("Reuse SubView");
|
|
||||||
reuse->setWidgetWidth(25);
|
|
||||||
reuse->setColumnSpan (3);
|
|
||||||
reuse->setStyleSheet ("QGroupBox { border: 0px; }");
|
|
||||||
reuse->setViewLocation(3, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section = "Window Size";
|
declareSection ("window", "Window");
|
||||||
{
|
{
|
||||||
Setting *width = createSetting (Type_LineEdit, section, "Width");
|
Setting *preDefined = createSetting (Type_ComboBox, "pre-defined",
|
||||||
Setting *height = createSetting (Type_LineEdit, section, "Height");
|
"Default window size");
|
||||||
|
preDefined->setEditorSetting (false);
|
||||||
width->setWidgetWidth (5);
|
preDefined->setDeclaredValues (
|
||||||
height->setWidgetWidth (8);
|
QStringList() << "640 x 480" << "800 x 600" << "1024 x 768" << "1440 x 900");
|
||||||
|
|
||||||
width->setDefaultValues (QStringList() << "1024");
|
|
||||||
height->setDefaultValues (QStringList() << "768");
|
|
||||||
|
|
||||||
width->setEditorSetting (true);
|
|
||||||
height->setEditorSetting (true);
|
|
||||||
|
|
||||||
height->setViewLocation (2,2);
|
|
||||||
width->setViewLocation (2,1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
*Create the proxy setting for predefined values
|
|
||||||
*/
|
|
||||||
Setting *preDefined = createSetting (Type_ComboBox, section,
|
|
||||||
"Pre-Defined");
|
|
||||||
|
|
||||||
preDefined->setDeclaredValues (QStringList() << "640 x 480"
|
|
||||||
<< "800 x 600" << "1024 x 768" << "1440 x 900");
|
|
||||||
|
|
||||||
preDefined->setViewLocation (1, 1);
|
preDefined->setViewLocation (1, 1);
|
||||||
preDefined->setWidgetWidth (10);
|
|
||||||
preDefined->setColumnSpan (2);
|
preDefined->setColumnSpan (2);
|
||||||
|
preDefined->setToolTip ("Newly opened top-level windows will open with this size "
|
||||||
|
"(picked from a list of pre-defined values)");
|
||||||
|
|
||||||
preDefined->addProxy (width,
|
Setting *width = createSetting (Type_LineEdit, "default-width",
|
||||||
QStringList() << "640" << "800" << "1024" << "1440"
|
"Default window width");
|
||||||
);
|
width->setDefaultValues (QStringList() << "1024");
|
||||||
|
width->setViewLocation (2, 1);
|
||||||
|
width->setColumnSpan (1);
|
||||||
|
width->setToolTip ("Newly opened top-level windows will open with this width.");
|
||||||
|
preDefined->addProxy (width, QStringList() << "640" << "800" << "1024" << "1440");
|
||||||
|
|
||||||
preDefined->addProxy (height,
|
Setting *height = createSetting (Type_LineEdit, "default-height",
|
||||||
QStringList() << "480" << "600" << "768" << "900"
|
"Default window height");
|
||||||
);
|
height->setDefaultValues (QStringList() << "768");
|
||||||
|
height->setViewLocation (2, 2);
|
||||||
|
height->setColumnSpan (1);
|
||||||
|
height->setToolTip ("Newly opened top-level windows will open with this height.");
|
||||||
|
preDefined->addProxy (height, QStringList() << "480" << "600" << "768" << "900");
|
||||||
|
|
||||||
|
Setting *reuse = createSetting (Type_CheckBox, "reuse", "Reuse Subviews");
|
||||||
|
reuse->setDefaultValue ("true");
|
||||||
|
reuse->setToolTip ("When a new subview is requested and a matching subview already "
|
||||||
|
" exist, do not open a new subview and use the existing one instead.");
|
||||||
|
|
||||||
|
Setting *maxSubView = createSetting (Type_SpinBox, "max-subviews",
|
||||||
|
"Maximum number of subviews per top-level window");
|
||||||
|
maxSubView->setDefaultValue (256);
|
||||||
|
maxSubView->setRange (1, 256);
|
||||||
|
maxSubView->setToolTip ("If the maximum number is reached and a new subview is opened "
|
||||||
|
"it will be placed into a new top-level window.");
|
||||||
|
|
||||||
|
Setting *minWidth = createSetting (Type_SpinBox, "minimum-width",
|
||||||
|
"Minimum subview width");
|
||||||
|
minWidth->setDefaultValue (325);
|
||||||
|
minWidth->setRange (50, 10000);
|
||||||
|
minWidth->setToolTip ("Minimum width of subviews.");
|
||||||
}
|
}
|
||||||
|
|
||||||
section = "Display Format";
|
declareSection ("records", "Records");
|
||||||
{
|
{
|
||||||
QString defaultValue = "Icon and Text";
|
QString defaultValue = "Icon and Text";
|
||||||
|
QStringList values = QStringList() << defaultValue << "Icon Only" << "Text Only";
|
||||||
|
|
||||||
QStringList values = QStringList()
|
Setting *rsd = createSetting (Type_RadioButton, "status-format",
|
||||||
<< defaultValue << "Icon Only" << "Text Only";
|
"Modification status display format");
|
||||||
|
rsd->setDefaultValue (defaultValue);
|
||||||
Setting *rsd = createSetting (Type_RadioButton,
|
|
||||||
section, "Record Status Display");
|
|
||||||
|
|
||||||
Setting *ritd = createSetting (Type_RadioButton,
|
|
||||||
section, "Referenceable ID Type Display");
|
|
||||||
|
|
||||||
rsd->setDeclaredValues (values);
|
rsd->setDeclaredValues (values);
|
||||||
|
|
||||||
|
Setting *ritd = createSetting (Type_RadioButton, "type-format",
|
||||||
|
"ID type display format");
|
||||||
|
ritd->setDefaultValue (defaultValue);
|
||||||
ritd->setDeclaredValues (values);
|
ritd->setDeclaredValues (values);
|
||||||
|
|
||||||
rsd->setEditorSetting (true);
|
|
||||||
ritd->setEditorSetting (true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section = "Video";
|
|
||||||
{
|
|
||||||
QString defaultValue = "None";
|
|
||||||
QStringList values = QStringList()
|
|
||||||
<< defaultValue << "MSAA 2" << "MSAA 4" << "MSAA 8" << "MSAA 16";
|
|
||||||
Setting *antialiasing = createSetting (Type_SpinBox, section, "antialiasing");
|
|
||||||
antialiasing->setDeclaredValues (values);
|
|
||||||
antialiasing->setEditorSetting (true);
|
|
||||||
antialiasing->setWidgetWidth(15);
|
|
||||||
}
|
|
||||||
|
|
||||||
section = "Proxy Selection Test";
|
|
||||||
{
|
{
|
||||||
/******************************************************************
|
/******************************************************************
|
||||||
* There are three types of values:
|
* There are three types of values:
|
||||||
|
@ -382,7 +326,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
|
||||||
|
|
||||||
CSMSettings::UserSettings::~UserSettings()
|
CSMSettings::UserSettings::~UserSettings()
|
||||||
{
|
{
|
||||||
mUserSettingsInstance = 0;
|
sUserSettingsInstance = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMSettings::UserSettings::loadSettings (const QString &fileName)
|
void CSMSettings::UserSettings::loadSettings (const QString &fileName)
|
||||||
|
@ -411,13 +355,9 @@ void CSMSettings::UserSettings::loadSettings (const QString &fileName)
|
||||||
|
|
||||||
mSettingDefinitions = new QSettings
|
mSettingDefinitions = new QSettings
|
||||||
(QSettings::IniFormat, QSettings::UserScope, "opencs", QString(), this);
|
(QSettings::IniFormat, QSettings::UserScope, "opencs", QString(), this);
|
||||||
|
|
||||||
// check if override entry exists (default: disabled)
|
|
||||||
if(!mSettingDefinitions->childGroups().contains("Video", Qt::CaseInsensitive))
|
|
||||||
mSettingDefinitions->setValue("Video/use settings.cfg", "false");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the key is not found create one with a defaut value
|
// if the key is not found create one with a default value
|
||||||
QString CSMSettings::UserSettings::setting(const QString &viewKey, const QString &value)
|
QString CSMSettings::UserSettings::setting(const QString &viewKey, const QString &value)
|
||||||
{
|
{
|
||||||
if(mSettingDefinitions->contains(viewKey))
|
if(mSettingDefinitions->contains(viewKey))
|
||||||
|
@ -451,23 +391,10 @@ QString CSMSettings::UserSettings::settingValue (const QString &settingKey)
|
||||||
{
|
{
|
||||||
QStringList defs;
|
QStringList defs;
|
||||||
|
|
||||||
// check if video settings are overriden
|
|
||||||
if(settingKey.contains(QRegExp("^Video\\b", Qt::CaseInsensitive)) &&
|
|
||||||
mSettingDefinitions->value("Video/use settings.cfg") == "true" &&
|
|
||||||
settingKey.contains(QRegExp("^Video/\\brender|antialiasing|vsync|fullscreen\\b", Qt::CaseInsensitive)))
|
|
||||||
{
|
|
||||||
if (!mSettingCfgDefinitions->contains (settingKey))
|
|
||||||
return QString();
|
|
||||||
else
|
|
||||||
defs = mSettingCfgDefinitions->value (settingKey).toStringList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!mSettingDefinitions->contains (settingKey))
|
if (!mSettingDefinitions->contains (settingKey))
|
||||||
return QString();
|
return QString();
|
||||||
|
|
||||||
defs = mSettingDefinitions->value (settingKey).toStringList();
|
defs = mSettingDefinitions->value (settingKey).toStringList();
|
||||||
}
|
|
||||||
|
|
||||||
if (defs.isEmpty())
|
if (defs.isEmpty())
|
||||||
return QString();
|
return QString();
|
||||||
|
@ -477,8 +404,8 @@ QString CSMSettings::UserSettings::settingValue (const QString &settingKey)
|
||||||
|
|
||||||
CSMSettings::UserSettings& CSMSettings::UserSettings::instance()
|
CSMSettings::UserSettings& CSMSettings::UserSettings::instance()
|
||||||
{
|
{
|
||||||
assert(mUserSettingsInstance);
|
assert(sUserSettingsInstance);
|
||||||
return *mUserSettingsInstance;
|
return *sUserSettingsInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMSettings::UserSettings::updateUserSetting(const QString &settingKey,
|
void CSMSettings::UserSettings::updateUserSetting(const QString &settingKey,
|
||||||
|
@ -486,11 +413,11 @@ void CSMSettings::UserSettings::updateUserSetting(const QString &settingKey,
|
||||||
{
|
{
|
||||||
mSettingDefinitions->setValue (settingKey ,list);
|
mSettingDefinitions->setValue (settingKey ,list);
|
||||||
|
|
||||||
if(settingKey == "Objects/num_lights" && !list.empty())
|
if(settingKey == "3d-render-adv/num_lights" && !list.empty())
|
||||||
{
|
{
|
||||||
sh::Factory::getInstance ().setGlobalSetting ("num_lights", list.at(0).toStdString());
|
sh::Factory::getInstance ().setGlobalSetting ("num_lights", list.at(0).toStdString());
|
||||||
}
|
}
|
||||||
else if(settingKey == "Objects/shaders" && !list.empty())
|
else if(settingKey == "3d-render/shaders" && !list.empty())
|
||||||
{
|
{
|
||||||
sh::Factory::getInstance ().setShadersEnabled (list.at(0).toStdString() == "true" ? true : false);
|
sh::Factory::getInstance ().setShadersEnabled (list.at(0).toStdString() == "true" ? true : false);
|
||||||
}
|
}
|
||||||
|
@ -539,24 +466,57 @@ CSMSettings::SettingPageMap CSMSettings::UserSettings::settingPageMap() const
|
||||||
SettingPageMap pageMap;
|
SettingPageMap pageMap;
|
||||||
|
|
||||||
foreach (Setting *setting, mSettings)
|
foreach (Setting *setting, mSettings)
|
||||||
pageMap[setting->page()].append (setting);
|
{
|
||||||
|
SettingPageMap::iterator iter = pageMap.find (setting->page());
|
||||||
|
|
||||||
|
if (iter==pageMap.end())
|
||||||
|
{
|
||||||
|
QPair<QString, QList <Setting *> > value;
|
||||||
|
|
||||||
|
std::map<QString, QString>::const_iterator iter2 =
|
||||||
|
mSectionLabels.find (setting->page());
|
||||||
|
|
||||||
|
value.first = iter2!=mSectionLabels.end() ? iter2->second : "";
|
||||||
|
|
||||||
|
iter = pageMap.insert (setting->page(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
iter->second.append (setting);
|
||||||
|
}
|
||||||
|
|
||||||
return pageMap;
|
return pageMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMSettings::Setting *CSMSettings::UserSettings::createSetting
|
CSMSettings::Setting *CSMSettings::UserSettings::createSetting
|
||||||
(CSMSettings::SettingType typ, const QString &page, const QString &name)
|
(CSMSettings::SettingType type, const QString &name, const QString& label)
|
||||||
{
|
{
|
||||||
//get list of all settings for the current setting name
|
Setting *setting = new Setting (type, name, mSection, label);
|
||||||
if (findSetting (page, name))
|
|
||||||
{
|
|
||||||
qWarning() << "Duplicate declaration encountered: "
|
|
||||||
<< (name + '/' + page);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Setting *setting = new Setting (typ, name, page);
|
// set useful defaults
|
||||||
|
int row = 1;
|
||||||
|
|
||||||
|
if (!mSettings.empty())
|
||||||
|
row = mSettings.back()->viewRow()+1;
|
||||||
|
|
||||||
|
setting->setViewLocation (row, 1);
|
||||||
|
|
||||||
|
setting->setColumnSpan (3);
|
||||||
|
|
||||||
|
int width = 10;
|
||||||
|
|
||||||
|
if (type==Type_CheckBox)
|
||||||
|
width = 40;
|
||||||
|
|
||||||
|
setting->setWidgetWidth (width);
|
||||||
|
|
||||||
|
if (type==Type_CheckBox)
|
||||||
|
setting->setStyleSheet ("QGroupBox { border: 0px; }");
|
||||||
|
|
||||||
|
if (type==Type_CheckBox)
|
||||||
|
setting->setDeclaredValues(QStringList() << "true" << "false");
|
||||||
|
|
||||||
|
if (type==Type_CheckBox)
|
||||||
|
setting->setSpecialValueText (setting->getLabel());
|
||||||
|
|
||||||
//add declaration to the model
|
//add declaration to the model
|
||||||
mSettings.append (setting);
|
mSettings.append (setting);
|
||||||
|
@ -564,6 +524,12 @@ CSMSettings::Setting *CSMSettings::UserSettings::createSetting
|
||||||
return setting;
|
return setting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSMSettings::UserSettings::declareSection (const QString& page, const QString& label)
|
||||||
|
{
|
||||||
|
mSection = page;
|
||||||
|
mSectionLabels[page] = label;
|
||||||
|
}
|
||||||
|
|
||||||
QStringList CSMSettings::UserSettings::definitions (const QString &viewKey) const
|
QStringList CSMSettings::UserSettings::definitions (const QString &viewKey) const
|
||||||
{
|
{
|
||||||
if (mSettingDefinitions->contains (viewKey))
|
if (mSettingDefinitions->contains (viewKey))
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
#ifndef USERSETTINGS_HPP
|
#ifndef USERSETTINGS_HPP
|
||||||
#define USERSETTINGS_HPP
|
#define USERSETTINGS_HPP
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include "support.hpp"
|
#include "support.hpp"
|
||||||
|
@ -22,20 +25,20 @@ class QSettings;
|
||||||
namespace CSMSettings {
|
namespace CSMSettings {
|
||||||
|
|
||||||
class Setting;
|
class Setting;
|
||||||
typedef QMap <QString, QList <Setting *> > SettingPageMap;
|
typedef QMap <QString, QPair<QString, QList <Setting *> > > SettingPageMap;
|
||||||
|
|
||||||
class UserSettings: public QObject
|
class UserSettings: public QObject
|
||||||
{
|
{
|
||||||
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
static UserSettings *mUserSettingsInstance;
|
static UserSettings *sUserSettingsInstance;
|
||||||
const Files::ConfigurationManager& mCfgMgr;
|
const Files::ConfigurationManager& mCfgMgr;
|
||||||
|
|
||||||
QSettings *mSettingDefinitions;
|
QSettings *mSettingDefinitions;
|
||||||
QSettings *mSettingCfgDefinitions;
|
|
||||||
QList <Setting *> mSettings;
|
QList <Setting *> mSettings;
|
||||||
|
QString mSection;
|
||||||
|
std::map<QString, QString> mSectionLabels;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -64,7 +67,7 @@ namespace CSMSettings {
|
||||||
void removeSetting
|
void removeSetting
|
||||||
(const QString &pageName, const QString &settingName);
|
(const QString &pageName, const QString &settingName);
|
||||||
|
|
||||||
///Retreive a map of the settings, keyed by page name
|
///Retrieve a map of the settings, keyed by page name
|
||||||
SettingPageMap settingPageMap() const;
|
SettingPageMap settingPageMap() const;
|
||||||
|
|
||||||
///Returns a string list of defined vlaues for the specified setting
|
///Returns a string list of defined vlaues for the specified setting
|
||||||
|
@ -84,8 +87,13 @@ namespace CSMSettings {
|
||||||
void buildSettingModelDefaults();
|
void buildSettingModelDefaults();
|
||||||
|
|
||||||
///add a new setting to the model and return it
|
///add a new setting to the model and return it
|
||||||
Setting *createSetting (CSMSettings::SettingType typ,
|
Setting *createSetting (CSMSettings::SettingType type, const QString &name,
|
||||||
const QString &page, const QString &name);
|
const QString& label);
|
||||||
|
|
||||||
|
/// Set the section for createSetting calls.
|
||||||
|
///
|
||||||
|
/// Sections can be declared multiple times.
|
||||||
|
void declareSection (const QString& page, const QString& label);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const
|
||||||
{
|
{
|
||||||
mIds = mData.getIds();
|
mIds = mData.getIds();
|
||||||
|
|
||||||
std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::lowerCase);
|
std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::toLower);
|
||||||
std::sort (mIds.begin(), mIds.end());
|
std::sort (mIds.begin(), mIds.end());
|
||||||
|
|
||||||
mIdsUpdated = true;
|
mIdsUpdated = true;
|
||||||
|
|
|
@ -369,10 +369,10 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to
|
||||||
mViewTotal (totalViews)
|
mViewTotal (totalViews)
|
||||||
{
|
{
|
||||||
QString width = CSMSettings::UserSettings::instance().settingValue
|
QString width = CSMSettings::UserSettings::instance().settingValue
|
||||||
("Window Size/Width");
|
("window/default-width");
|
||||||
|
|
||||||
QString height = CSMSettings::UserSettings::instance().settingValue
|
QString height = CSMSettings::UserSettings::instance().settingValue
|
||||||
("Window Size/Height");
|
("window/default-height");
|
||||||
|
|
||||||
// trick to get the window decorations and their sizes
|
// trick to get the window decorations and their sizes
|
||||||
show();
|
show();
|
||||||
|
@ -459,7 +459,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin
|
||||||
|
|
||||||
// User setting to reuse sub views (on a per top level view basis)
|
// User setting to reuse sub views (on a per top level view basis)
|
||||||
bool reuse =
|
bool reuse =
|
||||||
userSettings.setting("SubView/reuse", QString("true")) == "true" ? true : false;
|
userSettings.setting ("window/reuse", QString("true")) == "true" ? true : false;
|
||||||
if(reuse)
|
if(reuse)
|
||||||
{
|
{
|
||||||
foreach(SubView *sb, mSubViews)
|
foreach(SubView *sb, mSubViews)
|
||||||
|
@ -478,7 +478,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin
|
||||||
//
|
//
|
||||||
// If the sub view limit setting is one, the sub view title bar is hidden and the
|
// If the sub view limit setting is one, the sub view title bar is hidden and the
|
||||||
// text in the main title bar is adjusted accordingly
|
// text in the main title bar is adjusted accordingly
|
||||||
int maxSubView = userSettings.setting("SubView/max subviews", QString("256")).toInt();
|
int maxSubView = userSettings.setting("window/max-subviews", QString("256")).toInt();
|
||||||
if(mSubViews.size() >= maxSubView) // create a new top level view
|
if(mSubViews.size() >= maxSubView) // create a new top level view
|
||||||
{
|
{
|
||||||
mViewManager.addView(mDocument, id, hint);
|
mViewManager.addView(mDocument, id, hint);
|
||||||
|
@ -501,7 +501,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin
|
||||||
if (!hint.empty())
|
if (!hint.empty())
|
||||||
view->useHint (hint);
|
view->useHint (hint);
|
||||||
|
|
||||||
int minWidth = userSettings.setting("SubView/minimum width", QString("325")).toInt();
|
int minWidth = userSettings.setting ("window/minimum-width", QString("325")).toInt();
|
||||||
view->setMinimumWidth(minWidth);
|
view->setMinimumWidth(minWidth);
|
||||||
|
|
||||||
view->setStatusBar (mShowStatusBar->isChecked());
|
view->setStatusBar (mShowStatusBar->isChecked());
|
||||||
|
|
|
@ -54,10 +54,10 @@ namespace CSVRender
|
||||||
|
|
||||||
CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance();
|
CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance();
|
||||||
|
|
||||||
float farClipDist = userSettings.setting("Scene/far clip distance", QString("300000")).toFloat();
|
float farClipDist = userSettings.setting("3d-render/far-clip-distance", QString("300000")).toFloat();
|
||||||
mCamera->setFarClipDistance (farClipDist);
|
mCamera->setFarClipDistance (farClipDist);
|
||||||
|
|
||||||
mFastFactor = userSettings.setting("Scene/fast factor", QString("4")).toInt();
|
mFastFactor = userSettings.setting("scene-input/fast-factor", QString("4")).toInt();
|
||||||
|
|
||||||
mCamera->roll (Ogre::Degree (90));
|
mCamera->roll (Ogre::Degree (90));
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ namespace CSVRender
|
||||||
|
|
||||||
connect (timer, SIGNAL (timeout()), this, SLOT (update()));
|
connect (timer, SIGNAL (timeout()), this, SLOT (update()));
|
||||||
|
|
||||||
int timerStart = userSettings.setting("Scene/timer start", QString("20")).toInt();
|
int timerStart = userSettings.setting("scene-input/timer", QString("20")).toInt();
|
||||||
timer->start (timerStart);
|
timer->start (timerStart);
|
||||||
|
|
||||||
/// \todo make shortcut configurable
|
/// \todo make shortcut configurable
|
||||||
|
@ -141,7 +141,7 @@ namespace CSVRender
|
||||||
params.insert(std::make_pair("title", windowTitle.str()));
|
params.insert(std::make_pair("title", windowTitle.str()));
|
||||||
|
|
||||||
std::string antialiasing =
|
std::string antialiasing =
|
||||||
CSMSettings::UserSettings::instance().settingValue("Video/antialiasing").toStdString();
|
CSMSettings::UserSettings::instance().settingValue("3d-render/antialiasing").toStdString();
|
||||||
if(antialiasing == "MSAA 16") antialiasing = "16";
|
if(antialiasing == "MSAA 16") antialiasing = "16";
|
||||||
else if(antialiasing == "MSAA 8") antialiasing = "8";
|
else if(antialiasing == "MSAA 8") antialiasing = "8";
|
||||||
else if(antialiasing == "MSAA 4") antialiasing = "4";
|
else if(antialiasing == "MSAA 4") antialiasing = "4";
|
||||||
|
@ -444,14 +444,14 @@ namespace CSVRender
|
||||||
if(key.contains(QRegExp("^\\b(Objects|Shader|Scene)", Qt::CaseInsensitive)))
|
if(key.contains(QRegExp("^\\b(Objects|Shader|Scene)", Qt::CaseInsensitive)))
|
||||||
flagAsModified();
|
flagAsModified();
|
||||||
|
|
||||||
if(key == "Scene/far clip distance" && !list.empty())
|
if(key == "3d-render/far-clip-distance" && !list.empty())
|
||||||
{
|
{
|
||||||
if(mCamera->getFarClipDistance() != list.at(0).toFloat())
|
if(mCamera->getFarClipDistance() != list.at(0).toFloat())
|
||||||
mCamera->setFarClipDistance(list.at(0).toFloat());
|
mCamera->setFarClipDistance(list.at(0).toFloat());
|
||||||
}
|
}
|
||||||
|
|
||||||
// minimise unnecessary ogre window creation by updating only when there is a change
|
// minimise unnecessary ogre window creation by updating only when there is a change
|
||||||
if(key == "Video/antialiasing")
|
if(key == "3d-render/antialiasing")
|
||||||
{
|
{
|
||||||
unsigned int aa = mWindow->getFSAA();
|
unsigned int aa = mWindow->getFSAA();
|
||||||
unsigned int antialiasing = 0;
|
unsigned int antialiasing = 0;
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
#include "dialog.hpp"
|
#include "dialog.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <QListWidgetItem>
|
#include <QListWidgetItem>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QStackedWidget>
|
#include <QStackedWidget>
|
||||||
#include <QtGui>
|
#include <QtGui>
|
||||||
|
#include <QSplitter>
|
||||||
|
|
||||||
#include "../../model/settings/usersettings.hpp"
|
#include "../../model/settings/usersettings.hpp"
|
||||||
|
|
||||||
|
@ -12,8 +15,6 @@
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
||||||
#include <QSplitter>
|
|
||||||
|
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
#include <QTableView>
|
#include <QTableView>
|
||||||
|
@ -26,6 +27,10 @@ CSVSettings::Dialog::Dialog(QMainWindow *parent)
|
||||||
{
|
{
|
||||||
setWindowTitle(QString::fromUtf8 ("User Settings"));
|
setWindowTitle(QString::fromUtf8 ("User Settings"));
|
||||||
|
|
||||||
|
setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||||
|
|
||||||
|
setMinimumSize (600, 400);
|
||||||
|
|
||||||
setupDialog();
|
setupDialog();
|
||||||
|
|
||||||
connect (mPageListWidget,
|
connect (mPageListWidget,
|
||||||
|
@ -39,20 +44,14 @@ void CSVSettings::Dialog::slotChangePage
|
||||||
{
|
{
|
||||||
mStackedWidget->changePage
|
mStackedWidget->changePage
|
||||||
(mPageListWidget->row (cur), mPageListWidget->row (prev));
|
(mPageListWidget->row (cur), mPageListWidget->row (prev));
|
||||||
|
|
||||||
layout()->activate();
|
|
||||||
setFixedSize(minimumSizeHint());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVSettings::Dialog::setupDialog()
|
void CSVSettings::Dialog::setupDialog()
|
||||||
{
|
{
|
||||||
//create central widget with it's layout and immediate children
|
QSplitter *centralWidget = new QSplitter (this);
|
||||||
QWidget *centralWidget = new QGroupBox (this);
|
centralWidget->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||||
|
|
||||||
centralWidget->setLayout (new QHBoxLayout());
|
|
||||||
centralWidget->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
||||||
setCentralWidget (centralWidget);
|
setCentralWidget (centralWidget);
|
||||||
setDockOptions (QMainWindow::AllowNestedDocks);
|
|
||||||
|
|
||||||
buildPageListWidget (centralWidget);
|
buildPageListWidget (centralWidget);
|
||||||
buildStackedWidget (centralWidget);
|
buildStackedWidget (centralWidget);
|
||||||
|
@ -64,37 +63,39 @@ void CSVSettings::Dialog::buildPages()
|
||||||
|
|
||||||
QFontMetrics fm (QApplication::font());
|
QFontMetrics fm (QApplication::font());
|
||||||
|
|
||||||
|
int maxWidth = 1;
|
||||||
|
|
||||||
foreach (Page *page, SettingWindow::pages())
|
foreach (Page *page, SettingWindow::pages())
|
||||||
{
|
{
|
||||||
QString pageName = page->objectName();
|
maxWidth = std::max (maxWidth, fm.width(page->getLabel()));
|
||||||
|
|
||||||
int textWidth = fm.width(pageName);
|
new QListWidgetItem (page->getLabel(), mPageListWidget);
|
||||||
|
|
||||||
new QListWidgetItem (pageName, mPageListWidget);
|
mStackedWidget->addWidget (page);
|
||||||
mPageListWidget->setFixedWidth (textWidth + 50);
|
|
||||||
|
|
||||||
mStackedWidget->addWidget (&dynamic_cast<QWidget &>(*(page)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mPageListWidget->setMaximumWidth (maxWidth + 10);
|
||||||
|
|
||||||
resize (mStackedWidget->sizeHint());
|
resize (mStackedWidget->sizeHint());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVSettings::Dialog::buildPageListWidget (QWidget *centralWidget)
|
void CSVSettings::Dialog::buildPageListWidget (QSplitter *centralWidget)
|
||||||
{
|
{
|
||||||
mPageListWidget = new QListWidget (centralWidget);
|
mPageListWidget = new QListWidget (centralWidget);
|
||||||
mPageListWidget->setMinimumWidth(50);
|
mPageListWidget->setMinimumWidth(50);
|
||||||
mPageListWidget->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Expanding);
|
mPageListWidget->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
|
||||||
mPageListWidget->setSelectionBehavior (QAbstractItemView::SelectItems);
|
mPageListWidget->setSelectionBehavior (QAbstractItemView::SelectItems);
|
||||||
|
|
||||||
centralWidget->layout()->addWidget(mPageListWidget);
|
centralWidget->addWidget(mPageListWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVSettings::Dialog::buildStackedWidget (QWidget *centralWidget)
|
void CSVSettings::Dialog::buildStackedWidget (QSplitter *centralWidget)
|
||||||
{
|
{
|
||||||
mStackedWidget = new ResizeableStackedWidget (centralWidget);
|
mStackedWidget = new ResizeableStackedWidget (centralWidget);
|
||||||
|
mStackedWidget->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||||
|
|
||||||
centralWidget->layout()->addWidget (mStackedWidget);
|
centralWidget->addWidget (mStackedWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVSettings::Dialog::closeEvent (QCloseEvent *event)
|
void CSVSettings::Dialog::closeEvent (QCloseEvent *event)
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
class QStackedWidget;
|
class QStackedWidget;
|
||||||
class QListWidget;
|
class QListWidget;
|
||||||
class QListWidgetItem;
|
class QListWidgetItem;
|
||||||
|
class QSplitter;
|
||||||
|
|
||||||
namespace CSVSettings {
|
namespace CSVSettings {
|
||||||
|
|
||||||
|
@ -39,8 +40,8 @@ namespace CSVSettings {
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void buildPages();
|
void buildPages();
|
||||||
void buildPageListWidget (QWidget *centralWidget);
|
void buildPageListWidget (QSplitter *centralWidget);
|
||||||
void buildStackedWidget (QWidget *centralWidget);
|
void buildStackedWidget (QSplitter *centralWidget);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ CSVSettings::Frame::Frame (bool isVisible, const QString &title,
|
||||||
{
|
{
|
||||||
// must be Page, not a View
|
// must be Page, not a View
|
||||||
setStyleSheet (sInvisibleBoxStyle);
|
setStyleSheet (sInvisibleBoxStyle);
|
||||||
mLayout->setContentsMargins(10, 15, 10, 15);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayout (mLayout);
|
setLayout (mLayout);
|
||||||
|
@ -39,7 +38,7 @@ void CSVSettings::Frame::hideWidgets()
|
||||||
|
|
||||||
QWidget *widg = static_cast <QWidget *> (obj);
|
QWidget *widg = static_cast <QWidget *> (obj);
|
||||||
if (widg->property("sizePolicy").isValid())
|
if (widg->property("sizePolicy").isValid())
|
||||||
widg->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored);
|
widg->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
}
|
}
|
||||||
|
|
||||||
layout()->activate();
|
layout()->activate();
|
||||||
|
|
|
@ -17,10 +17,9 @@
|
||||||
QMap <CSVSettings::ViewType, CSVSettings::IViewFactory *>
|
QMap <CSVSettings::ViewType, CSVSettings::IViewFactory *>
|
||||||
CSVSettings::Page::mViewFactories;
|
CSVSettings::Page::mViewFactories;
|
||||||
|
|
||||||
CSVSettings::Page::Page(const QString &pageName,
|
CSVSettings::Page::Page (const QString &pageName, QList <CSMSettings::Setting *> settingList,
|
||||||
QList <CSMSettings::Setting *> settingList,
|
SettingWindow *parent, const QString& label)
|
||||||
SettingWindow *parent) :
|
: mParent(parent), mIsEditorPage (false), Frame(false, "", parent), mLabel (label)
|
||||||
mParent(parent), mIsEditorPage (false), Frame(false, "", parent)
|
|
||||||
{
|
{
|
||||||
setObjectName (pageName);
|
setObjectName (pageName);
|
||||||
|
|
||||||
|
@ -104,3 +103,8 @@ void CSVSettings::Page::buildFactories()
|
||||||
mViewFactories[ViewType_List] = new ListViewFactory (this);
|
mViewFactories[ViewType_List] = new ListViewFactory (this);
|
||||||
mViewFactories[ViewType_Range] = new RangeViewFactory (this);
|
mViewFactories[ViewType_Range] = new RangeViewFactory (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString CSVSettings::Page::getLabel() const
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
|
|
@ -25,11 +25,11 @@ namespace CSVSettings
|
||||||
SettingWindow *mParent;
|
SettingWindow *mParent;
|
||||||
static QMap <ViewType, IViewFactory *> mViewFactories;
|
static QMap <ViewType, IViewFactory *> mViewFactories;
|
||||||
bool mIsEditorPage;
|
bool mIsEditorPage;
|
||||||
|
QString mLabel;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Page(const QString &pageName,
|
Page (const QString &pageName, QList <CSMSettings::Setting *> settingList,
|
||||||
QList <CSMSettings::Setting *> settingList,
|
SettingWindow *parent, const QString& label);
|
||||||
SettingWindow *parent);
|
|
||||||
|
|
||||||
///Creates a new view based on the passed setting and adds it to
|
///Creates a new view based on the passed setting and adds it to
|
||||||
///the page.
|
///the page.
|
||||||
|
@ -42,6 +42,8 @@ namespace CSVSettings
|
||||||
///returns the list of views associated with the page
|
///returns the list of views associated with the page
|
||||||
const QList <View *> &views () const { return mViews; }
|
const QList <View *> &views () const { return mViews; }
|
||||||
|
|
||||||
|
QString getLabel() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
///Creates views based on the passed setting list
|
///Creates views based on the passed setting list
|
||||||
|
|
|
@ -126,8 +126,6 @@ void CSVSettings::RangeView::buildSpinBox (CSMSettings::Setting *setting)
|
||||||
mRangeWidget->setProperty ("minimum", setting->minimum());
|
mRangeWidget->setProperty ("minimum", setting->minimum());
|
||||||
mRangeWidget->setProperty ("maximum", setting->maximum());
|
mRangeWidget->setProperty ("maximum", setting->maximum());
|
||||||
mRangeWidget->setProperty ("singleStep", setting->singleStep());
|
mRangeWidget->setProperty ("singleStep", setting->singleStep());
|
||||||
mRangeWidget->setProperty ("specialValueText",
|
|
||||||
setting->specialValueText());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mRangeWidget->setProperty ("prefix", setting->prefix());
|
mRangeWidget->setProperty ("prefix", setting->prefix());
|
||||||
|
|
|
@ -34,7 +34,6 @@ void CSVSettings::ResizeableStackedWidget::changePage
|
||||||
curPage->showWidgets();
|
curPage->showWidgets();
|
||||||
|
|
||||||
layout()->activate();
|
layout()->activate();
|
||||||
setFixedSize(minimumSizeHint());
|
|
||||||
|
|
||||||
setCurrentIndex (current);
|
setCurrentIndex (current);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@ void CSVSettings::SettingWindow::createPages()
|
||||||
|
|
||||||
foreach (const QString &pageName, pageMap.keys())
|
foreach (const QString &pageName, pageMap.keys())
|
||||||
{
|
{
|
||||||
QList <CSMSettings::Setting *> pageSettings = pageMap.value (pageName);
|
QList <CSMSettings::Setting *> pageSettings = pageMap.value (pageName).second;
|
||||||
|
|
||||||
mPages.append (new Page (pageName, pageSettings, this));
|
mPages.append (new Page (pageName, pageSettings, this, pageMap.value (pageName).first));
|
||||||
|
|
||||||
for (int i = 0; i < pageSettings.size(); i++)
|
for (int i = 0; i < pageSettings.size(); i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,8 +17,11 @@ CSVSettings::View::View(CSMSettings::Setting *setting,
|
||||||
mIsMultiValue (setting->isMultiValue()),
|
mIsMultiValue (setting->isMultiValue()),
|
||||||
mViewKey (setting->page() + '/' + setting->name()),
|
mViewKey (setting->page() + '/' + setting->name()),
|
||||||
mSerializable (setting->serializable()),
|
mSerializable (setting->serializable()),
|
||||||
Frame(true, setting->name(), parent)
|
Frame(true, setting->getLabel(), parent)
|
||||||
{
|
{
|
||||||
|
if (!setting->getToolTip().isEmpty())
|
||||||
|
setToolTip (setting->getToolTip());
|
||||||
|
|
||||||
setObjectName (setting->name());
|
setObjectName (setting->name());
|
||||||
buildView();
|
buildView();
|
||||||
buildModel (setting);
|
buildModel (setting);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
CSVWorld::IdTypeDelegate::IdTypeDelegate
|
CSVWorld::IdTypeDelegate::IdTypeDelegate
|
||||||
(const ValueList &values, const IconList &icons, CSMDoc::Document& document, QObject *parent)
|
(const ValueList &values, const IconList &icons, CSMDoc::Document& document, QObject *parent)
|
||||||
: DataDisplayDelegate (values, icons, document,
|
: DataDisplayDelegate (values, icons, document,
|
||||||
"Display Format", "Referenceable ID Type Display",
|
"records", "type-format",
|
||||||
parent)
|
parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values,
|
||||||
const IconList & icons,
|
const IconList & icons,
|
||||||
CSMDoc::Document& document, QObject *parent)
|
CSMDoc::Document& document, QObject *parent)
|
||||||
: DataDisplayDelegate (values, icons, document,
|
: DataDisplayDelegate (values, icons, document,
|
||||||
"Display Format", "Record Status Display",
|
"records", "status-format",
|
||||||
parent)
|
parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
|
||||||
add_openmw_dir (mwrender
|
add_openmw_dir (mwrender
|
||||||
renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation
|
renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation
|
||||||
actors objects renderinginterface localmap occlusionquery water shadows
|
actors objects renderinginterface localmap occlusionquery water shadows
|
||||||
characterpreview globalmap videoplayer ripplesimulation refraction
|
characterpreview globalmap ripplesimulation refraction
|
||||||
terrainstorage renderconst effectmanager weaponanimation
|
terrainstorage renderconst effectmanager weaponanimation
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,13 +38,13 @@ add_openmw_dir (mwgui
|
||||||
itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog
|
itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog
|
||||||
enchantingdialog trainingwindow travelwindow exposedwindow cursor spellicons
|
enchantingdialog trainingwindow travelwindow exposedwindow cursor spellicons
|
||||||
merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks
|
merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks
|
||||||
keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
|
itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
|
||||||
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
|
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
|
||||||
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow
|
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwdialogue
|
add_openmw_dir (mwdialogue
|
||||||
dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper
|
dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper hypertextparser keywordsearch
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwscript
|
add_openmw_dir (mwscript
|
||||||
|
@ -55,7 +55,7 @@ add_openmw_dir (mwscript
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwsound
|
add_openmw_dir (mwsound
|
||||||
soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness libavwrapper
|
soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness libavwrapper movieaudiofactory
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwworld
|
add_openmw_dir (mwworld
|
||||||
|
@ -132,6 +132,7 @@ target_link_libraries(openmw
|
||||||
${MYGUI_LIBRARIES}
|
${MYGUI_LIBRARIES}
|
||||||
${SDL2_LIBRARY}
|
${SDL2_LIBRARY}
|
||||||
${MYGUI_PLATFORM_LIBRARIES}
|
${MYGUI_PLATFORM_LIBRARIES}
|
||||||
|
"ogre-ffmpeg-videoplayer"
|
||||||
"oics"
|
"oics"
|
||||||
"sdl4ogre"
|
"sdl4ogre"
|
||||||
components
|
components
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
|
|
||||||
#include "filter.hpp"
|
#include "filter.hpp"
|
||||||
|
#include "hypertextparser.hpp"
|
||||||
|
|
||||||
namespace MWDialogue
|
namespace MWDialogue
|
||||||
{
|
{
|
||||||
|
@ -82,42 +83,27 @@ namespace MWDialogue
|
||||||
|
|
||||||
void DialogueManager::parseText (const std::string& text)
|
void DialogueManager::parseText (const std::string& text)
|
||||||
{
|
{
|
||||||
std::vector<HyperTextToken> hypertext = ParseHyperText(text);
|
std::vector<HyperTextParser::Token> hypertext = HyperTextParser::parseHyperText(text);
|
||||||
|
|
||||||
//calculation of standard form fir all hyperlinks
|
for (std::vector<HyperTextParser::Token>::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok)
|
||||||
for (size_t i = 0; i < hypertext.size(); ++i)
|
|
||||||
{
|
{
|
||||||
if (hypertext[i].mLink)
|
std::string topicId = Misc::StringUtils::lowerCase(tok->mText);
|
||||||
|
|
||||||
|
if (tok->isExplicitLink())
|
||||||
{
|
{
|
||||||
size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText);
|
// calculation of standard form for all hyperlinks
|
||||||
|
size_t asterisk_count = HyperTextParser::removePseudoAsterisks(topicId);
|
||||||
for(; asterisk_count > 0; --asterisk_count)
|
for(; asterisk_count > 0; --asterisk_count)
|
||||||
hypertext[i].mText.append("*");
|
topicId.append("*");
|
||||||
|
|
||||||
hypertext[i].mText = mTranslationDataStorage.topicStandardForm(hypertext[i].mText);
|
topicId = mTranslationDataStorage.topicStandardForm(topicId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < hypertext.size(); ++i)
|
if (tok->isImplicitKeyword() && mTranslationDataStorage.hasTranslation())
|
||||||
{
|
continue;
|
||||||
std::list<std::string>::iterator it;
|
|
||||||
for(it = mActorKnownTopics.begin(); it != mActorKnownTopics.end(); ++it)
|
if (std::find(mActorKnownTopics.begin(), mActorKnownTopics.end(), topicId) != mActorKnownTopics.end())
|
||||||
{
|
mKnownTopics[topicId] = true;
|
||||||
if (hypertext[i].mLink)
|
|
||||||
{
|
|
||||||
if( hypertext[i].mText == *it )
|
|
||||||
{
|
|
||||||
mKnownTopics[hypertext[i].mText] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( !mTranslationDataStorage.hasTranslation() )
|
|
||||||
{
|
|
||||||
size_t pos = Misc::StringUtils::lowerCase(hypertext[i].mText).find(*it, 0);
|
|
||||||
if(pos !=std::string::npos)
|
|
||||||
{
|
|
||||||
mKnownTopics[*it] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTopics();
|
updateTopics();
|
||||||
|
@ -724,55 +710,4 @@ namespace MWDialogue
|
||||||
mLastTopic, actor.getClass().getName(actor));
|
mLastTopic, actor.getClass().getName(actor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<HyperTextToken> ParseHyperText(const std::string& text)
|
|
||||||
{
|
|
||||||
std::vector<HyperTextToken> result;
|
|
||||||
MyGUI::UString utext(text);
|
|
||||||
size_t pos_end, iteration_pos = 0;
|
|
||||||
for(;;)
|
|
||||||
{
|
|
||||||
size_t pos_begin = utext.find('@', iteration_pos);
|
|
||||||
if (pos_begin != std::string::npos)
|
|
||||||
pos_end = utext.find('#', pos_begin);
|
|
||||||
|
|
||||||
if (pos_begin != std::string::npos && pos_end != std::string::npos)
|
|
||||||
{
|
|
||||||
result.push_back( HyperTextToken(utext.substr(iteration_pos, pos_begin - iteration_pos), false) );
|
|
||||||
|
|
||||||
std::string link = utext.substr(pos_begin + 1, pos_end - pos_begin - 1);
|
|
||||||
result.push_back( HyperTextToken(link, true) );
|
|
||||||
|
|
||||||
iteration_pos = pos_end + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.push_back( HyperTextToken(utext.substr(iteration_pos), false) );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t RemovePseudoAsterisks(std::string& phrase)
|
|
||||||
{
|
|
||||||
size_t pseudoAsterisksCount = 0;
|
|
||||||
|
|
||||||
if( !phrase.empty() )
|
|
||||||
{
|
|
||||||
std::string::reverse_iterator rit = phrase.rbegin();
|
|
||||||
|
|
||||||
const char specialPseudoAsteriskCharacter = 127;
|
|
||||||
while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter )
|
|
||||||
{
|
|
||||||
pseudoAsterisksCount++;
|
|
||||||
++rit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount);
|
|
||||||
|
|
||||||
return pseudoAsterisksCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,21 +103,6 @@ namespace MWDialogue
|
||||||
/// Removes the last added topic response for the given actor from the journal
|
/// Removes the last added topic response for the given actor from the journal
|
||||||
virtual void clearInfoActor (const MWWorld::Ptr& actor) const;
|
virtual void clearInfoActor (const MWWorld::Ptr& actor) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct HyperTextToken
|
|
||||||
{
|
|
||||||
HyperTextToken(const std::string& text, bool link) : mText(text), mLink(link) {}
|
|
||||||
|
|
||||||
std::string mText;
|
|
||||||
bool mLink;
|
|
||||||
};
|
|
||||||
|
|
||||||
// In translations (at least Russian) the links are marked with @#, so
|
|
||||||
// it should be a function to parse it
|
|
||||||
std::vector<HyperTextToken> ParseHyperText(const std::string& text);
|
|
||||||
|
|
||||||
size_t RemovePseudoAsterisks(std::string& phrase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
89
apps/openmw/mwdialogue/hypertextparser.cpp
Normal file
89
apps/openmw/mwdialogue/hypertextparser.cpp
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#include <components/esm/loaddial.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/store.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
|
#include "keywordsearch.hpp"
|
||||||
|
|
||||||
|
#include "hypertextparser.hpp"
|
||||||
|
|
||||||
|
namespace MWDialogue
|
||||||
|
{
|
||||||
|
namespace HyperTextParser
|
||||||
|
{
|
||||||
|
std::vector<Token> parseHyperText(const std::string & text)
|
||||||
|
{
|
||||||
|
std::vector<Token> result;
|
||||||
|
size_t pos_end, iteration_pos = 0;
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
size_t pos_begin = text.find('@', iteration_pos);
|
||||||
|
if (pos_begin != std::string::npos)
|
||||||
|
pos_end = text.find('#', pos_begin);
|
||||||
|
|
||||||
|
if (pos_begin != std::string::npos && pos_end != std::string::npos)
|
||||||
|
{
|
||||||
|
if (pos_begin != iteration_pos)
|
||||||
|
tokenizeKeywords(text.substr(iteration_pos, pos_begin - iteration_pos), result);
|
||||||
|
|
||||||
|
std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1);
|
||||||
|
result.push_back(Token(link, Token::ExplicitLink));
|
||||||
|
|
||||||
|
iteration_pos = pos_end + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (iteration_pos != text.size())
|
||||||
|
tokenizeKeywords(text.substr(iteration_pos), result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tokenizeKeywords(const std::string & text, std::vector<Token> & tokens)
|
||||||
|
{
|
||||||
|
const MWWorld::Store<ESM::Dialogue> & dialogs =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
|
||||||
|
|
||||||
|
std::list<std::string> keywordList;
|
||||||
|
for (MWWorld::Store<ESM::Dialogue>::iterator it = dialogs.begin(); it != dialogs.end(); ++it)
|
||||||
|
keywordList.push_back(Misc::StringUtils::lowerCase(it->mId));
|
||||||
|
keywordList.sort(Misc::StringUtils::ciLess);
|
||||||
|
|
||||||
|
KeywordSearch<std::string, int /*unused*/> keywordSearch;
|
||||||
|
KeywordSearch<std::string, int /*unused*/>::Match match;
|
||||||
|
|
||||||
|
for (std::list<std::string>::const_iterator it = keywordList.begin(); it != keywordList.end(); ++it)
|
||||||
|
keywordSearch.seed(*it, 0 /*unused*/);
|
||||||
|
|
||||||
|
for (std::string::const_iterator it = text.begin(); it != text.end() && keywordSearch.search(it, text.end(), match, text.begin()); it = match.mEnd)
|
||||||
|
tokens.push_back(Token(std::string(match.mBeg, match.mEnd), Token::ImplicitKeyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t removePseudoAsterisks(std::string & phrase)
|
||||||
|
{
|
||||||
|
size_t pseudoAsterisksCount = 0;
|
||||||
|
|
||||||
|
if( !phrase.empty() )
|
||||||
|
{
|
||||||
|
std::string::reverse_iterator rit = phrase.rbegin();
|
||||||
|
|
||||||
|
const char specialPseudoAsteriskCharacter = 127;
|
||||||
|
while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter )
|
||||||
|
{
|
||||||
|
pseudoAsterisksCount++;
|
||||||
|
++rit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount);
|
||||||
|
|
||||||
|
return pseudoAsterisksCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
apps/openmw/mwdialogue/hypertextparser.hpp
Normal file
36
apps/openmw/mwdialogue/hypertextparser.hpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef GAME_MWDIALOGUE_HYPERTEXTPARSER_H
|
||||||
|
#define GAME_MWDIALOGUE_HYPERTEXTPARSER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MWDialogue
|
||||||
|
{
|
||||||
|
namespace HyperTextParser
|
||||||
|
{
|
||||||
|
struct Token
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
ExplicitLink, // enclosed in @#
|
||||||
|
ImplicitKeyword
|
||||||
|
};
|
||||||
|
|
||||||
|
Token(const std::string & text, Type type) : mText(text), mType(type) {}
|
||||||
|
|
||||||
|
bool isExplicitLink() { return mType == ExplicitLink; }
|
||||||
|
bool isImplicitKeyword() { return mType == ImplicitKeyword; }
|
||||||
|
|
||||||
|
std::string mText;
|
||||||
|
Type mType;
|
||||||
|
};
|
||||||
|
|
||||||
|
// In translations (at least Russian) the links are marked with @#, so
|
||||||
|
// it should be a function to parse it
|
||||||
|
std::vector<Token> parseHyperText(const std::string & text);
|
||||||
|
void tokenizeKeywords(const std::string & text, std::vector<Token> & tokens);
|
||||||
|
size_t removePseudoAsterisks(std::string & phrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef MWGUI_KEYWORDSEARCH_H
|
#ifndef GAME_MWDIALOGUE_KEYWORDSEARCH_H
|
||||||
#define MWGUI_KEYWORDSEARCH_H
|
#define GAME_MWDIALOGUE_KEYWORDSEARCH_H
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWDialogue
|
||||||
{
|
{
|
||||||
|
|
||||||
template <typename string_t, typename value_t>
|
template <typename string_t, typename value_t>
|
||||||
|
@ -141,7 +141,6 @@ public:
|
||||||
match.mValue = candidate->second.mValue;
|
match.mValue = candidate->second.mValue;
|
||||||
match.mBeg = i;
|
match.mBeg = i;
|
||||||
match.mEnd = k;
|
match.mEnd = k;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -134,11 +134,12 @@ namespace MWGui
|
||||||
for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy.beginTools());
|
for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy.beginTools());
|
||||||
iter!=mAlchemy.endTools() && index<static_cast<int> (mApparatus.size()); ++iter, ++index)
|
iter!=mAlchemy.endTools() && index<static_cast<int> (mApparatus.size()); ++iter, ++index)
|
||||||
{
|
{
|
||||||
|
mApparatus.at (index)->setItem(*iter);
|
||||||
|
mApparatus.at (index)->clearUserStrings();
|
||||||
if (!iter->isEmpty())
|
if (!iter->isEmpty())
|
||||||
{
|
{
|
||||||
mApparatus.at (index)->setUserString ("ToolTipType", "ItemPtr");
|
mApparatus.at (index)->setUserString ("ToolTipType", "ItemPtr");
|
||||||
mApparatus.at (index)->setUserData (*iter);
|
mApparatus.at (index)->setUserData (*iter);
|
||||||
mApparatus.at (index)->setItem(*iter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,6 +174,11 @@ namespace MWGui
|
||||||
|
|
||||||
void AlchemyWindow::update()
|
void AlchemyWindow::update()
|
||||||
{
|
{
|
||||||
|
std::string suggestedName = mAlchemy.suggestPotionName();
|
||||||
|
if (suggestedName != mSuggestedPotionName)
|
||||||
|
mNameEdit->setCaptionWithReplacing(suggestedName);
|
||||||
|
mSuggestedPotionName = suggestedName;
|
||||||
|
|
||||||
mSortModel->clearDragItems();
|
mSortModel->clearDragItems();
|
||||||
|
|
||||||
MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy.beginIngredients ();
|
MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy.beginIngredients ();
|
||||||
|
|
|
@ -23,6 +23,8 @@ namespace MWGui
|
||||||
virtual void exit();
|
virtual void exit();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string mSuggestedPotionName;
|
||||||
|
|
||||||
ItemView* mItemView;
|
ItemView* mItemView;
|
||||||
SortFilterItemModel* mSortModel;
|
SortFilterItemModel* mSortModel;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
#include "bookpage.hpp"
|
#include "bookpage.hpp"
|
||||||
|
|
||||||
#include "keywordsearch.hpp"
|
#include "../mwdialogue/keywordsearch.hpp"
|
||||||
|
|
||||||
namespace Gui
|
namespace Gui
|
||||||
{
|
{
|
||||||
|
@ -76,7 +76,7 @@ namespace MWGui
|
||||||
virtual void activated ();
|
virtual void activated ();
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef KeywordSearch <std::string, intptr_t> KeywordSearchT;
|
typedef MWDialogue::KeywordSearch <std::string, intptr_t> KeywordSearchT;
|
||||||
|
|
||||||
struct DialogueText
|
struct DialogueText
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,9 +12,9 @@
|
||||||
#include "../mwbase/journal.hpp"
|
#include "../mwbase/journal.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwdialogue/journalentry.hpp"
|
|
||||||
|
|
||||||
#include "keywordsearch.hpp"
|
#include "../mwdialogue/journalentry.hpp"
|
||||||
|
#include "../mwdialogue/keywordsearch.hpp"
|
||||||
|
|
||||||
namespace MWGui {
|
namespace MWGui {
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ struct JournalViewModelImpl;
|
||||||
|
|
||||||
struct JournalViewModelImpl : JournalViewModel
|
struct JournalViewModelImpl : JournalViewModel
|
||||||
{
|
{
|
||||||
typedef KeywordSearch <std::string, intptr_t> KeywordSearchT;
|
typedef MWDialogue::KeywordSearch <std::string, intptr_t> KeywordSearchT;
|
||||||
|
|
||||||
mutable bool mKeywordSearchLoaded;
|
mutable bool mKeywordSearchLoaded;
|
||||||
mutable KeywordSearchT mKeywordSearch;
|
mutable KeywordSearchT mKeywordSearch;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "videowidget.hpp"
|
#include "videowidget.hpp"
|
||||||
|
|
||||||
|
#include "../mwsound/movieaudiofactory.hpp"
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -10,6 +12,7 @@ VideoWidget::VideoWidget()
|
||||||
|
|
||||||
void VideoWidget::playVideo(const std::string &video)
|
void VideoWidget::playVideo(const std::string &video)
|
||||||
{
|
{
|
||||||
|
mPlayer.setAudioFactory(new MWSound::MovieAudioFactory());
|
||||||
mPlayer.playVideo(video);
|
mPlayer.playVideo(video);
|
||||||
|
|
||||||
setImageTexture(mPlayer.getTextureName());
|
setImageTexture(mPlayer.getTextureName());
|
||||||
|
@ -36,4 +39,9 @@ void VideoWidget::stop()
|
||||||
mPlayer.close();
|
mPlayer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VideoWidget::hasAudioStream()
|
||||||
|
{
|
||||||
|
return mPlayer.hasAudioStream();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#include <MyGUI_ImageBox.h>
|
#include <MyGUI_ImageBox.h>
|
||||||
|
|
||||||
#include "../mwrender/videoplayer.hpp"
|
#include <extern/ogre-ffmpeg-videoplayer/videoplayer.hpp>
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
|
@ -26,11 +26,14 @@ namespace MWGui
|
||||||
/// @return Is the video still playing?
|
/// @return Is the video still playing?
|
||||||
bool update();
|
bool update();
|
||||||
|
|
||||||
|
/// Return true if a video is currently playing and it has an audio stream.
|
||||||
|
bool hasAudioStream();
|
||||||
|
|
||||||
/// Stop video and free resources (done automatically on destruction)
|
/// Stop video and free resources (done automatically on destruction)
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MWRender::VideoPlayer mPlayer;
|
Video::VideoPlayer mPlayer;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1664,6 +1664,10 @@ namespace MWGui
|
||||||
bool cursorWasVisible = mCursorVisible;
|
bool cursorWasVisible = mCursorVisible;
|
||||||
setCursorVisible(false);
|
setCursorVisible(false);
|
||||||
|
|
||||||
|
if (mVideoWidget->hasAudioStream())
|
||||||
|
MWBase::Environment::get().getSoundManager()->pauseSounds(
|
||||||
|
MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie));
|
||||||
|
|
||||||
while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest())
|
while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest())
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getInputManager()->update(0, true, false);
|
MWBase::Environment::get().getInputManager()->update(0, true, false);
|
||||||
|
@ -1672,6 +1676,8 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
mVideoWidget->stop();
|
mVideoWidget->stop();
|
||||||
|
|
||||||
|
MWBase::Environment::get().getSoundManager()->resumeSounds();
|
||||||
|
|
||||||
setCursorVisible(cursorWasVisible);
|
setCursorVisible(cursorWasVisible);
|
||||||
|
|
||||||
// Restore normal rendering
|
// Restore normal rendering
|
||||||
|
|
|
@ -205,7 +205,7 @@ void MWMechanics::Alchemy::updateEffects()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ESM::Potion *MWMechanics::Alchemy::getRecord() const
|
const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) const
|
||||||
{
|
{
|
||||||
const MWWorld::Store<ESM::Potion> &potions =
|
const MWWorld::Store<ESM::Potion> &potions =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>();
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>();
|
||||||
|
@ -216,6 +216,18 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord() const
|
||||||
if (iter->mEffects.mList.size() != mEffects.size())
|
if (iter->mEffects.mList.size() != mEffects.size())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (iter->mName != toFind.mName
|
||||||
|
|| iter->mScript != toFind.mScript
|
||||||
|
|| iter->mData.mWeight != toFind.mData.mWeight
|
||||||
|
|| iter->mData.mValue != toFind.mData.mValue
|
||||||
|
|| iter->mData.mAutoCalc != toFind.mData.mAutoCalc)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Don't choose an ID that came from the content files, would have unintended side effects
|
||||||
|
// where alchemy can be used to produce quest-relevant items
|
||||||
|
if (!potions.isDynamic(iter->mId))
|
||||||
|
continue;
|
||||||
|
|
||||||
bool mismatch = false;
|
bool mismatch = false;
|
||||||
|
|
||||||
for (int i=0; i<static_cast<int> (iter->mEffects.mList.size()); ++i)
|
for (int i=0; i<static_cast<int> (iter->mEffects.mList.size()); ++i)
|
||||||
|
@ -266,10 +278,6 @@ void MWMechanics::Alchemy::removeIngredients()
|
||||||
|
|
||||||
void MWMechanics::Alchemy::addPotion (const std::string& name)
|
void MWMechanics::Alchemy::addPotion (const std::string& name)
|
||||||
{
|
{
|
||||||
const ESM::Potion *record = getRecord();
|
|
||||||
|
|
||||||
if (!record)
|
|
||||||
{
|
|
||||||
ESM::Potion newRecord;
|
ESM::Potion newRecord;
|
||||||
|
|
||||||
newRecord.mData.mWeight = 0;
|
newRecord.mData.mWeight = 0;
|
||||||
|
@ -288,15 +296,16 @@ void MWMechanics::Alchemy::addPotion (const std::string& name)
|
||||||
int index = static_cast<int> (std::rand()/(static_cast<double> (RAND_MAX)+1)*6);
|
int index = static_cast<int> (std::rand()/(static_cast<double> (RAND_MAX)+1)*6);
|
||||||
assert (index>=0 && index<6);
|
assert (index>=0 && index<6);
|
||||||
|
|
||||||
static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" };
|
static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" };
|
||||||
|
|
||||||
newRecord.mModel = "m\\misc_potion_" + std::string (name[index]) + "_01.nif";
|
newRecord.mModel = "m\\misc_potion_" + std::string (meshes[index]) + "_01.nif";
|
||||||
newRecord.mIcon = "m\\tx_potion_" + std::string (name[index]) + "_01.dds";
|
newRecord.mIcon = "m\\tx_potion_" + std::string (meshes[index]) + "_01.dds";
|
||||||
|
|
||||||
newRecord.mEffects.mList = mEffects;
|
newRecord.mEffects.mList = mEffects;
|
||||||
|
|
||||||
|
const ESM::Potion* record = getRecord(newRecord);
|
||||||
|
if (!record)
|
||||||
record = MWBase::Environment::get().getWorld()->createRecord (newRecord);
|
record = MWBase::Environment::get().getWorld()->createRecord (newRecord);
|
||||||
}
|
|
||||||
|
|
||||||
mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist);
|
mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist);
|
||||||
}
|
}
|
||||||
|
@ -436,14 +445,6 @@ MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const
|
||||||
return mEffects.end();
|
return mEffects.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string MWMechanics::Alchemy::getPotionName() const
|
|
||||||
{
|
|
||||||
if (const ESM::Potion *potion = getRecord())
|
|
||||||
return potion->mName;
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name)
|
MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name)
|
||||||
{
|
{
|
||||||
if (mTools[ESM::Apparatus::MortarPestle].isEmpty())
|
if (mTools[ESM::Apparatus::MortarPestle].isEmpty())
|
||||||
|
@ -452,7 +453,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na
|
||||||
if (countIngredients()<2)
|
if (countIngredients()<2)
|
||||||
return Result_LessThanTwoIngredients;
|
return Result_LessThanTwoIngredients;
|
||||||
|
|
||||||
if (name.empty() && getPotionName().empty())
|
if (name.empty())
|
||||||
return Result_NoName;
|
return Result_NoName;
|
||||||
|
|
||||||
if (listEffects().empty())
|
if (listEffects().empty())
|
||||||
|
@ -479,3 +480,14 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na
|
||||||
|
|
||||||
return Result_Success;
|
return Result_Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string MWMechanics::Alchemy::suggestPotionName()
|
||||||
|
{
|
||||||
|
std::set<MWMechanics::EffectKey> effects = listEffects();
|
||||||
|
if (effects.empty())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
int effectId = effects.begin()->mId;
|
||||||
|
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||||
|
ESM::MagicEffect::effectIdToString(effectId))->getString();
|
||||||
|
}
|
||||||
|
|
|
@ -56,8 +56,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
void updateEffects();
|
void updateEffects();
|
||||||
|
|
||||||
const ESM::Potion *getRecord() const;
|
const ESM::Potion *getRecord(const ESM::Potion& toFind) const;
|
||||||
///< Return existing record for created potion (may return 0)
|
///< Try to find a potion record similar to \a toFind in the record store, or return 0 if not found
|
||||||
|
/// \note Does not account for record ID, model or icon
|
||||||
|
|
||||||
void removeIngredients();
|
void removeIngredients();
|
||||||
///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and
|
///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and
|
||||||
|
@ -108,15 +109,13 @@ namespace MWMechanics
|
||||||
void removeIngredient (int index);
|
void removeIngredient (int index);
|
||||||
///< Remove ingredient from slot (calling this function on an empty slot is a no-op).
|
///< Remove ingredient from slot (calling this function on an empty slot is a no-op).
|
||||||
|
|
||||||
std::string getPotionName() const;
|
std::string suggestPotionName ();
|
||||||
///< Return the name of the potion that would be created when calling create (if a record for such
|
///< Suggest a name for the potion, based on the current effects
|
||||||
/// a potion already exists) or return an empty string.
|
|
||||||
|
|
||||||
Result create (const std::string& name);
|
Result create (const std::string& name);
|
||||||
///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and
|
///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and
|
||||||
/// adjust the skills of the alchemist accordingly.
|
/// adjust the skills of the alchemist accordingly.
|
||||||
/// \param name must not be an empty string, unless there is already a potion record (
|
/// \param name must not be an empty string, or Result_NoName is returned
|
||||||
/// getPotionName() does not return an empty string).
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -387,7 +387,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
||||||
{
|
{
|
||||||
float vel, speedmult = 1.0f;
|
float vel, speedmult = 1.0f;
|
||||||
|
|
||||||
bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run);
|
bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run)
|
||||||
|
&& !MWBase::Environment::get().getWorld()->isFlying(mPtr);
|
||||||
|
|
||||||
// For non-flying creatures, MW uses the Walk animation to calculate the animation velocity
|
// For non-flying creatures, MW uses the Walk animation to calculate the animation velocity
|
||||||
// even if we are running. This must be replicated, otherwise the observed speed would differ drastically.
|
// even if we are running. This must be replicated, otherwise the observed speed would differ drastically.
|
||||||
|
@ -1269,9 +1270,10 @@ void CharacterController::update(float duration)
|
||||||
{
|
{
|
||||||
bool onground = world->isOnGround(mPtr);
|
bool onground = world->isOnGround(mPtr);
|
||||||
bool inwater = world->isSwimming(mPtr);
|
bool inwater = world->isSwimming(mPtr);
|
||||||
bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run);
|
|
||||||
bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak);
|
bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak);
|
||||||
bool flying = world->isFlying(mPtr);
|
bool flying = world->isFlying(mPtr);
|
||||||
|
// Can't run while flying (see speed formula in Npc/Creature::getSpeed)
|
||||||
|
bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying;
|
||||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||||
|
|
||||||
//Force Jump Logic
|
//Force Jump Logic
|
||||||
|
|
|
@ -298,7 +298,7 @@ void Animation::addAnimSource(const std::string &model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (grp == 0 && dstval->getNode()->getName() == "Bip01")
|
if (grp == 0 && (dstval->getNode()->getName() == "Bip01" || dstval->getNode()->getName() == "Root Bone"))
|
||||||
{
|
{
|
||||||
mNonAccumRoot = dstval->getNode();
|
mNonAccumRoot = dstval->getNode();
|
||||||
mAccumRoot = mNonAccumRoot->getParent();
|
mAccumRoot = mNonAccumRoot->getParent();
|
||||||
|
|
|
@ -200,10 +200,10 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v
|
||||||
mPartPriorities[i] = 0;
|
mPartPriorities[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateNpcBase();
|
||||||
|
|
||||||
if (!disableListener)
|
if (!disableListener)
|
||||||
mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr);
|
mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr);
|
||||||
|
|
||||||
updateNpcBase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,37 +0,0 @@
|
||||||
#ifndef VIDEOPLAYER_H
|
|
||||||
#define VIDEOPLAYER_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace MWRender
|
|
||||||
{
|
|
||||||
struct VideoState;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Plays a video on an Ogre texture.
|
|
||||||
*/
|
|
||||||
class VideoPlayer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
VideoPlayer();
|
|
||||||
~VideoPlayer();
|
|
||||||
|
|
||||||
void playVideo (const std::string& resourceName);
|
|
||||||
|
|
||||||
void update();
|
|
||||||
|
|
||||||
void close();
|
|
||||||
|
|
||||||
bool isPlaying();
|
|
||||||
|
|
||||||
std::string getTextureName();
|
|
||||||
int getVideoWidth();
|
|
||||||
int getVideoHeight();
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
VideoState* mState;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#ifndef HAVE_LIBSWRESAMPLE
|
#ifndef HAVE_LIBSWRESAMPLE
|
||||||
/* FIXME: remove this section once libswresample is available on all platforms */
|
// FIXME: remove this section once libswresample is packaged for Debian
|
||||||
int swr_init(AVAudioResampleContext *avr);
|
int swr_init(AVAudioResampleContext *avr);
|
||||||
void swr_free(AVAudioResampleContext **avr);
|
void swr_free(AVAudioResampleContext **avr);
|
||||||
int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples);
|
int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples);
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
// FIXME: This can't be right? The headers refuse to build without UINT64_C,
|
// FIXME: This can't be right? The headers refuse to build without UINT64_C,
|
||||||
// which only gets defined in stdint.h in either C99 mode or with this macro
|
// which only gets defined in stdint.h in either C99 mode or with this macro
|
||||||
// defined...
|
// defined...
|
||||||
|
#ifndef __STDC_CONSTANT_MACROS
|
||||||
#define __STDC_CONSTANT_MACROS
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
#endif
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#ifndef HAVE_LIBSWRESAMPLE
|
#ifndef HAVE_LIBSWRESAMPLE
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
|
#ifndef __STDC_CONSTANT_MACROS
|
||||||
#define __STDC_CONSTANT_MACROS
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
#endif
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
|
@ -16,7 +18,7 @@ extern "C"
|
||||||
#include <libavresample/avresample.h>
|
#include <libavresample/avresample.h>
|
||||||
#include <libavutil/opt.h>
|
#include <libavutil/opt.h>
|
||||||
|
|
||||||
/* FIXME: delete this file once libswresample is available on all platforms */
|
/* FIXME: delete this file once libswresample is packaged for Debian */
|
||||||
|
|
||||||
int swr_init(AVAudioResampleContext *avr) { return 1; }
|
int swr_init(AVAudioResampleContext *avr) { return 1; }
|
||||||
|
|
||||||
|
|
173
apps/openmw/mwsound/movieaudiofactory.cpp
Normal file
173
apps/openmw/mwsound/movieaudiofactory.cpp
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
#include "movieaudiofactory.hpp"
|
||||||
|
|
||||||
|
#include <extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp>
|
||||||
|
#include <extern/ogre-ffmpeg-videoplayer/videostate.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
|
||||||
|
#include "sound_decoder.hpp"
|
||||||
|
#include "sound.hpp"
|
||||||
|
|
||||||
|
namespace MWSound
|
||||||
|
{
|
||||||
|
|
||||||
|
class MovieAudioDecoder;
|
||||||
|
class MWSoundDecoderBridge : public Sound_Decoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder)
|
||||||
|
: mDecoder(decoder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MWSound::MovieAudioDecoder* mDecoder;
|
||||||
|
|
||||||
|
virtual void open(const std::string &fname);
|
||||||
|
virtual void close();
|
||||||
|
virtual void rewind();
|
||||||
|
virtual std::string getName();
|
||||||
|
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
|
||||||
|
virtual size_t read(char *buffer, size_t bytes);
|
||||||
|
virtual size_t getSampleOffset();
|
||||||
|
};
|
||||||
|
|
||||||
|
class MovieAudioDecoder : public Video::MovieAudioDecoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MovieAudioDecoder(Video::VideoState *videoState)
|
||||||
|
: Video::MovieAudioDecoder(videoState)
|
||||||
|
{
|
||||||
|
mDecoderBridge.reset(new MWSoundDecoderBridge(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getSampleOffset()
|
||||||
|
{
|
||||||
|
ssize_t clock_delay = (mFrameSize-mFramePos) / mAVStream->codec->channels /
|
||||||
|
av_get_bytes_per_sample(mAVStream->codec->sample_fmt);
|
||||||
|
return (size_t)(mAudioClock*mAVStream->codec->sample_rate) - clock_delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getStreamName()
|
||||||
|
{
|
||||||
|
return mVideoState->stream->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// MovieAudioDecoder overrides
|
||||||
|
|
||||||
|
virtual double getAudioClock()
|
||||||
|
{
|
||||||
|
return mAudioTrack->getTimeOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void adjustAudioSettings(AVSampleFormat& sampleFormat, uint64_t& channelLayout, int& sampleRate)
|
||||||
|
{
|
||||||
|
if (sampleFormat == AV_SAMPLE_FMT_U8P)
|
||||||
|
sampleFormat = AV_SAMPLE_FMT_U8;
|
||||||
|
else if (sampleFormat == AV_SAMPLE_FMT_S16P)
|
||||||
|
sampleFormat = AV_SAMPLE_FMT_S16;
|
||||||
|
else if (sampleFormat == AV_SAMPLE_FMT_FLTP)
|
||||||
|
sampleFormat = AV_SAMPLE_FMT_FLT;
|
||||||
|
else
|
||||||
|
sampleFormat = AV_SAMPLE_FMT_FLT;
|
||||||
|
|
||||||
|
if (channelLayout != AV_CH_LAYOUT_MONO
|
||||||
|
&& channelLayout != AV_CH_LAYOUT_5POINT1
|
||||||
|
&& channelLayout != AV_CH_LAYOUT_7POINT1
|
||||||
|
&& channelLayout != AV_CH_LAYOUT_STEREO
|
||||||
|
&& channelLayout != AV_CH_LAYOUT_QUAD)
|
||||||
|
channelLayout = AV_CH_LAYOUT_STEREO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
~MovieAudioDecoder()
|
||||||
|
{
|
||||||
|
mAudioTrack.reset();
|
||||||
|
mDecoderBridge.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
MWBase::SoundPtr mAudioTrack;
|
||||||
|
boost::shared_ptr<MWSoundDecoderBridge> mDecoderBridge;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void MWSoundDecoderBridge::open(const std::string &fname)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("unimplemented");
|
||||||
|
}
|
||||||
|
void MWSoundDecoderBridge::close() {}
|
||||||
|
void MWSoundDecoderBridge::rewind() {}
|
||||||
|
|
||||||
|
std::string MWSoundDecoderBridge::getName()
|
||||||
|
{
|
||||||
|
return mDecoder->getStreamName();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MWSoundDecoderBridge::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
||||||
|
{
|
||||||
|
*samplerate = mDecoder->getOutputSampleRate();
|
||||||
|
|
||||||
|
uint64_t outputChannelLayout = mDecoder->getOutputChannelLayout();
|
||||||
|
if (outputChannelLayout == AV_CH_LAYOUT_MONO)
|
||||||
|
*chans = ChannelConfig_Mono;
|
||||||
|
else if (outputChannelLayout == AV_CH_LAYOUT_5POINT1)
|
||||||
|
*chans = ChannelConfig_5point1;
|
||||||
|
else if (outputChannelLayout == AV_CH_LAYOUT_7POINT1)
|
||||||
|
*chans = ChannelConfig_7point1;
|
||||||
|
else if (outputChannelLayout == AV_CH_LAYOUT_STEREO)
|
||||||
|
*chans = ChannelConfig_Stereo;
|
||||||
|
else if (outputChannelLayout == AV_CH_LAYOUT_QUAD)
|
||||||
|
*chans = ChannelConfig_Quad;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "Unsupported channel layout: " << outputChannelLayout;
|
||||||
|
throw std::runtime_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
AVSampleFormat outputSampleFormat = mDecoder->getOutputSampleFormat();
|
||||||
|
if (outputSampleFormat == AV_SAMPLE_FMT_U8)
|
||||||
|
*type = SampleType_UInt8;
|
||||||
|
else if (outputSampleFormat == AV_SAMPLE_FMT_FLT)
|
||||||
|
*type = SampleType_Float32;
|
||||||
|
else if (outputSampleFormat == AV_SAMPLE_FMT_S16)
|
||||||
|
*type = SampleType_Int16;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char str[1024];
|
||||||
|
av_get_sample_fmt_string(str, sizeof(str), outputSampleFormat);
|
||||||
|
throw std::runtime_error(std::string("Unsupported sample format: ") + str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MWSoundDecoderBridge::read(char *buffer, size_t bytes)
|
||||||
|
{
|
||||||
|
return mDecoder->read(buffer, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MWSoundDecoderBridge::getSampleOffset()
|
||||||
|
{
|
||||||
|
return mDecoder->getSampleOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
boost::shared_ptr<Video::MovieAudioDecoder> MovieAudioFactory::createDecoder(Video::VideoState* videoState)
|
||||||
|
{
|
||||||
|
boost::shared_ptr<MWSound::MovieAudioDecoder> decoder(new MWSound::MovieAudioDecoder(videoState));
|
||||||
|
decoder->setupFormat();
|
||||||
|
|
||||||
|
MWBase::SoundPtr sound = MWBase::Environment::get().getSoundManager()->playTrack(decoder->mDecoderBridge, MWBase::SoundManager::Play_TypeMovie);
|
||||||
|
if (!sound.get())
|
||||||
|
{
|
||||||
|
decoder.reset();
|
||||||
|
return decoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder->mAudioTrack = sound;
|
||||||
|
return decoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
apps/openmw/mwsound/movieaudiofactory.hpp
Normal file
16
apps/openmw/mwsound/movieaudiofactory.hpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H
|
||||||
|
#define OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H
|
||||||
|
|
||||||
|
#include <extern/ogre-ffmpeg-videoplayer/audiofactory.hpp>
|
||||||
|
|
||||||
|
namespace MWSound
|
||||||
|
{
|
||||||
|
|
||||||
|
class MovieAudioFactory : public Video::MovieAudioFactory
|
||||||
|
{
|
||||||
|
virtual boost::shared_ptr<Video::MovieAudioDecoder> createDecoder(Video::VideoState* videoState);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -135,6 +135,7 @@ void ESMStore::setUp()
|
||||||
mSkills.setUp();
|
mSkills.setUp();
|
||||||
mMagicEffects.setUp();
|
mMagicEffects.setUp();
|
||||||
mAttributes.setUp();
|
mAttributes.setUp();
|
||||||
|
mDialogs.setUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
int ESMStore::countSavedGameRecords() const
|
int ESMStore::countSavedGameRecords() const
|
||||||
|
|
|
@ -1174,6 +1174,25 @@ namespace MWWorld
|
||||||
mShared.erase(mShared.begin() + mStatic.size(), mShared.end());
|
mShared.erase(mShared.begin() + mStatic.size(), mShared.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline void Store<ESM::Dialogue>::setUp()
|
||||||
|
{
|
||||||
|
// DialInfos marked as deleted are kept during the loading phase, so that the linked list
|
||||||
|
// structure is kept intact for inserting further INFOs. Delete them now that loading is done.
|
||||||
|
for (Static::iterator it = mStatic.begin(); it != mStatic.end(); ++it)
|
||||||
|
{
|
||||||
|
ESM::Dialogue& dial = it->second;
|
||||||
|
dial.clearDeletedInfos();
|
||||||
|
}
|
||||||
|
|
||||||
|
mShared.clear();
|
||||||
|
mShared.reserve(mStatic.size());
|
||||||
|
std::map<std::string, ESM::Dialogue>::iterator it = mStatic.begin();
|
||||||
|
for (; it != mStatic.end(); ++it) {
|
||||||
|
mShared.push_back(&(it->second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} //end namespace
|
} //end namespace
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -19,7 +19,7 @@ add_component_dir (bsa
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (nif
|
add_component_dir (nif
|
||||||
controlled effect niftypes record controller extra node record_ptr data niffile property nifkey data node
|
controlled effect niftypes record controller extra node record_ptr data niffile property nifkey data node base
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (nifcache
|
add_component_dir (nifcache
|
||||||
|
|
|
@ -226,7 +226,7 @@ namespace Compiler
|
||||||
extensions.registerInstruction ("togglefullhelp", "", opcodeToggleFullHelp);
|
extensions.registerInstruction ("togglefullhelp", "", opcodeToggleFullHelp);
|
||||||
extensions.registerInstruction ("tfh", "", opcodeToggleFullHelp);
|
extensions.registerInstruction ("tfh", "", opcodeToggleFullHelp);
|
||||||
|
|
||||||
extensions.registerInstruction ("showmap", "S", opcodeShowMap);
|
extensions.registerInstruction ("showmap", "Sxxxx", opcodeShowMap);
|
||||||
extensions.registerInstruction ("fillmap", "", opcodeFillMap);
|
extensions.registerInstruction ("fillmap", "", opcodeFillMap);
|
||||||
extensions.registerInstruction ("menutest", "/l", opcodeMenuTest);
|
extensions.registerInstruction ("menutest", "/l", opcodeMenuTest);
|
||||||
extensions.registerInstruction ("togglemenus", "", opcodeToggleMenus);
|
extensions.registerInstruction ("togglemenus", "", opcodeToggleMenus);
|
||||||
|
|
|
@ -110,4 +110,15 @@ void Dialogue::readInfo(ESMReader &esm, bool merge)
|
||||||
std::cerr << "Failed to insert info " << id << std::endl;
|
std::cerr << "Failed to insert info " << id << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Dialogue::clearDeletedInfos()
|
||||||
|
{
|
||||||
|
for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); )
|
||||||
|
{
|
||||||
|
if (it->mQuestStatus == DialInfo::QS_Deleted)
|
||||||
|
it = mInfo.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,9 @@ struct Dialogue
|
||||||
void load(ESMReader &esm);
|
void load(ESMReader &esm);
|
||||||
void save(ESMWriter &esm) const;
|
void save(ESMWriter &esm) const;
|
||||||
|
|
||||||
|
/// Remove all INFOs marked as QS_Deleted from mInfos.
|
||||||
|
void clearDeletedInfos();
|
||||||
|
|
||||||
/// Read the next info record
|
/// Read the next info record
|
||||||
/// @param merge Merge with existing list, or just push each record to the end of the list?
|
/// @param merge Merge with existing list, or just push each record to the end of the list?
|
||||||
void readInfo (ESM::ESMReader& esm, bool merge);
|
void readInfo (ESM::ESMReader& esm, bool merge);
|
||||||
|
|
91
components/nif/base.hpp
Normal file
91
components/nif/base.hpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
///This file holds the main classes of NIF Records used by everything else.
|
||||||
|
#ifndef OPENMW_COMPONENTS_NIF_BASE_HPP
|
||||||
|
#define OPENMW_COMPONENTS_NIF_BASE_HPP
|
||||||
|
|
||||||
|
#include "record.hpp"
|
||||||
|
#include "niffile.hpp"
|
||||||
|
#include "recordptr.hpp"
|
||||||
|
#include "nifstream.hpp"
|
||||||
|
#include "nifkey.hpp"
|
||||||
|
|
||||||
|
namespace Nif
|
||||||
|
{
|
||||||
|
/** A record that can have extra data. The extra data objects
|
||||||
|
themselves descend from the Extra class, and all the extra data
|
||||||
|
connected to an object form a linked list
|
||||||
|
*/
|
||||||
|
class Extra : public Record
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExtraPtr extra;
|
||||||
|
|
||||||
|
void read(NIFStream *nif) { extra.read(nif); }
|
||||||
|
void post(NIFFile *nif) { extra.post(nif); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Controller : public Record
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ControllerPtr next;
|
||||||
|
int flags;
|
||||||
|
float frequency, phase;
|
||||||
|
float timeStart, timeStop;
|
||||||
|
ControlledPtr target;
|
||||||
|
|
||||||
|
void read(NIFStream *nif)
|
||||||
|
{
|
||||||
|
next.read(nif);
|
||||||
|
|
||||||
|
flags = nif->getUShort();
|
||||||
|
|
||||||
|
frequency = nif->getFloat();
|
||||||
|
phase = nif->getFloat();
|
||||||
|
timeStart = nif->getFloat();
|
||||||
|
timeStop = nif->getFloat();
|
||||||
|
|
||||||
|
target.read(nif);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post(NIFFile *nif)
|
||||||
|
{
|
||||||
|
Record::post(nif);
|
||||||
|
next.post(nif);
|
||||||
|
target.post(nif);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Anything that has a controller
|
||||||
|
class Controlled : public Extra
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ControllerPtr controller;
|
||||||
|
|
||||||
|
void read(NIFStream *nif)
|
||||||
|
{
|
||||||
|
Extra::read(nif);
|
||||||
|
controller.read(nif);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post(NIFFile *nif)
|
||||||
|
{
|
||||||
|
Extra::post(nif);
|
||||||
|
controller.post(nif);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Has name, extra-data and controller
|
||||||
|
class Named : public Controlled
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
void read(NIFStream *nif)
|
||||||
|
{
|
||||||
|
name = nif->getString();
|
||||||
|
Controlled::read(nif);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
typedef Named NiSequenceStreamHelper;
|
||||||
|
|
||||||
|
} // Namespace
|
||||||
|
#endif
|
|
@ -24,45 +24,71 @@
|
||||||
#ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
|
#ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
|
||||||
#define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
|
#define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
|
||||||
|
|
||||||
#include "extra.hpp"
|
#include "base.hpp"
|
||||||
#include "controller.hpp"
|
|
||||||
|
|
||||||
namespace Nif
|
namespace Nif
|
||||||
{
|
{
|
||||||
|
|
||||||
/// Anything that has a controller
|
class NiSourceTexture : public Named
|
||||||
class Controlled : public Extra
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ControllerPtr controller;
|
// Is this an external (references a separate texture file) or
|
||||||
|
// internal (data is inside the nif itself) texture?
|
||||||
|
bool external;
|
||||||
|
|
||||||
|
std::string filename; // In case of external textures
|
||||||
|
NiPixelDataPtr data; // In case of internal textures
|
||||||
|
|
||||||
|
/* Pixel layout
|
||||||
|
0 - Palettised
|
||||||
|
1 - High color 16
|
||||||
|
2 - True color 32
|
||||||
|
3 - Compressed
|
||||||
|
4 - Bumpmap
|
||||||
|
5 - Default */
|
||||||
|
int pixel;
|
||||||
|
|
||||||
|
/* Mipmap format
|
||||||
|
0 - no
|
||||||
|
1 - yes
|
||||||
|
2 - default */
|
||||||
|
int mipmap;
|
||||||
|
|
||||||
|
/* Alpha
|
||||||
|
0 - none
|
||||||
|
1 - binary
|
||||||
|
2 - smooth
|
||||||
|
3 - default (use material alpha, or multiply material with texture if present)
|
||||||
|
*/
|
||||||
|
int alpha;
|
||||||
|
|
||||||
void read(NIFStream *nif)
|
void read(NIFStream *nif)
|
||||||
{
|
{
|
||||||
Extra::read(nif);
|
Named::read(nif);
|
||||||
controller.read(nif);
|
|
||||||
|
external = !!nif->getChar();
|
||||||
|
if(external)
|
||||||
|
filename = nif->getString();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nif->getChar(); // always 1
|
||||||
|
data.read(nif);
|
||||||
|
}
|
||||||
|
|
||||||
|
pixel = nif->getInt();
|
||||||
|
mipmap = nif->getInt();
|
||||||
|
alpha = nif->getInt();
|
||||||
|
|
||||||
|
nif->getChar(); // always 1
|
||||||
}
|
}
|
||||||
|
|
||||||
void post(NIFFile *nif)
|
void post(NIFFile *nif)
|
||||||
{
|
{
|
||||||
Extra::post(nif);
|
Named::post(nif);
|
||||||
controller.post(nif);
|
data.post(nif);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Has name, extra-data and controller
|
|
||||||
class Named : public Controlled
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
void read(NIFStream *nif)
|
|
||||||
{
|
|
||||||
name = nif->getString();
|
|
||||||
Controlled::read(nif);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
typedef Named NiSequenceStreamHelper;
|
|
||||||
|
|
||||||
class NiParticleGrowFade : public Controlled
|
class NiParticleGrowFade : public Controlled
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -147,5 +173,7 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // Namespace
|
} // Namespace
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -24,44 +24,11 @@
|
||||||
#ifndef OPENMW_COMPONENTS_NIF_CONTROLLER_HPP
|
#ifndef OPENMW_COMPONENTS_NIF_CONTROLLER_HPP
|
||||||
#define OPENMW_COMPONENTS_NIF_CONTROLLER_HPP
|
#define OPENMW_COMPONENTS_NIF_CONTROLLER_HPP
|
||||||
|
|
||||||
#include "record.hpp"
|
#include "base.hpp"
|
||||||
#include "niffile.hpp"
|
|
||||||
#include "recordptr.hpp"
|
|
||||||
|
|
||||||
namespace Nif
|
namespace Nif
|
||||||
{
|
{
|
||||||
|
|
||||||
class Controller : public Record
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ControllerPtr next;
|
|
||||||
int flags;
|
|
||||||
float frequency, phase;
|
|
||||||
float timeStart, timeStop;
|
|
||||||
ControlledPtr target;
|
|
||||||
|
|
||||||
void read(NIFStream *nif)
|
|
||||||
{
|
|
||||||
next.read(nif);
|
|
||||||
|
|
||||||
flags = nif->getUShort();
|
|
||||||
|
|
||||||
frequency = nif->getFloat();
|
|
||||||
phase = nif->getFloat();
|
|
||||||
timeStart = nif->getFloat();
|
|
||||||
timeStop = nif->getFloat();
|
|
||||||
|
|
||||||
target.read(nif);
|
|
||||||
}
|
|
||||||
|
|
||||||
void post(NIFFile *nif)
|
|
||||||
{
|
|
||||||
Record::post(nif);
|
|
||||||
next.post(nif);
|
|
||||||
target.post(nif);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class NiParticleSystemController : public Controller
|
class NiParticleSystemController : public Controller
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -24,73 +24,11 @@
|
||||||
#ifndef OPENMW_COMPONENTS_NIF_DATA_HPP
|
#ifndef OPENMW_COMPONENTS_NIF_DATA_HPP
|
||||||
#define OPENMW_COMPONENTS_NIF_DATA_HPP
|
#define OPENMW_COMPONENTS_NIF_DATA_HPP
|
||||||
|
|
||||||
#include "controlled.hpp"
|
#include "base.hpp"
|
||||||
#include "nifstream.hpp"
|
|
||||||
#include "nifkey.hpp"
|
|
||||||
|
|
||||||
namespace Nif
|
namespace Nif
|
||||||
{
|
{
|
||||||
|
|
||||||
class NiSourceTexture : public Named
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// Is this an external (references a separate texture file) or
|
|
||||||
// internal (data is inside the nif itself) texture?
|
|
||||||
bool external;
|
|
||||||
|
|
||||||
std::string filename; // In case of external textures
|
|
||||||
NiPixelDataPtr data; // In case of internal textures
|
|
||||||
|
|
||||||
/* Pixel layout
|
|
||||||
0 - Palettised
|
|
||||||
1 - High color 16
|
|
||||||
2 - True color 32
|
|
||||||
3 - Compressed
|
|
||||||
4 - Bumpmap
|
|
||||||
5 - Default */
|
|
||||||
int pixel;
|
|
||||||
|
|
||||||
/* Mipmap format
|
|
||||||
0 - no
|
|
||||||
1 - yes
|
|
||||||
2 - default */
|
|
||||||
int mipmap;
|
|
||||||
|
|
||||||
/* Alpha
|
|
||||||
0 - none
|
|
||||||
1 - binary
|
|
||||||
2 - smooth
|
|
||||||
3 - default (use material alpha, or multiply material with texture if present)
|
|
||||||
*/
|
|
||||||
int alpha;
|
|
||||||
|
|
||||||
void read(NIFStream *nif)
|
|
||||||
{
|
|
||||||
Named::read(nif);
|
|
||||||
|
|
||||||
external = !!nif->getChar();
|
|
||||||
if(external)
|
|
||||||
filename = nif->getString();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nif->getChar(); // always 1
|
|
||||||
data.read(nif);
|
|
||||||
}
|
|
||||||
|
|
||||||
pixel = nif->getInt();
|
|
||||||
mipmap = nif->getInt();
|
|
||||||
alpha = nif->getInt();
|
|
||||||
|
|
||||||
nif->getChar(); // always 1
|
|
||||||
}
|
|
||||||
|
|
||||||
void post(NIFFile *nif)
|
|
||||||
{
|
|
||||||
Named::post(nif);
|
|
||||||
data.post(nif);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Common ancestor for several data classes
|
// Common ancestor for several data classes
|
||||||
class ShapeData : public Record
|
class ShapeData : public Record
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,26 +24,11 @@
|
||||||
#ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP
|
#ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP
|
||||||
#define OPENMW_COMPONENTS_NIF_EXTRA_HPP
|
#define OPENMW_COMPONENTS_NIF_EXTRA_HPP
|
||||||
|
|
||||||
#include "record.hpp"
|
#include "base.hpp"
|
||||||
#include "niffile.hpp"
|
|
||||||
#include "recordptr.hpp"
|
|
||||||
|
|
||||||
namespace Nif
|
namespace Nif
|
||||||
{
|
{
|
||||||
|
|
||||||
/** A record that can have extra data. The extra data objects
|
|
||||||
themselves decend from the Extra class, and all the extra data
|
|
||||||
connected to an object form a linked list
|
|
||||||
*/
|
|
||||||
class Extra : public Record
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ExtraPtr extra;
|
|
||||||
|
|
||||||
void read(NIFStream *nif) { extra.read(nif); }
|
|
||||||
void post(NIFFile *nif) { extra.post(nif); }
|
|
||||||
};
|
|
||||||
|
|
||||||
class NiVertWeightsExtraData : public Extra
|
class NiVertWeightsExtraData : public Extra
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -109,6 +109,23 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
|
||||||
///Make the factory map used for parsing the file
|
///Make the factory map used for parsing the file
|
||||||
static const std::map<std::string,RecordFactoryEntry> factories = makeFactory();
|
static const std::map<std::string,RecordFactoryEntry> factories = makeFactory();
|
||||||
|
|
||||||
|
/// Get the file's version in a human readable form
|
||||||
|
std::string NIFFile::printVersion(unsigned int version)
|
||||||
|
{
|
||||||
|
union ver_quad
|
||||||
|
{
|
||||||
|
uint32_t full;
|
||||||
|
uint8_t quad[4];
|
||||||
|
} version_out;
|
||||||
|
|
||||||
|
version_out.full = version;
|
||||||
|
|
||||||
|
return Ogre::StringConverter::toString(version_out.quad[3])
|
||||||
|
+"." + Ogre::StringConverter::toString(version_out.quad[2])
|
||||||
|
+"." + Ogre::StringConverter::toString(version_out.quad[1])
|
||||||
|
+"." + Ogre::StringConverter::toString(version_out.quad[0]);
|
||||||
|
}
|
||||||
|
|
||||||
void NIFFile::parse()
|
void NIFFile::parse()
|
||||||
{
|
{
|
||||||
NIFStream nif (this, Ogre::ResourceGroupManager::getSingleton().openResource(filename));
|
NIFStream nif (this, Ogre::ResourceGroupManager::getSingleton().openResource(filename));
|
||||||
|
@ -119,10 +136,9 @@ void NIFFile::parse()
|
||||||
fail("Invalid NIF header");
|
fail("Invalid NIF header");
|
||||||
|
|
||||||
// Get BCD version
|
// Get BCD version
|
||||||
ver = nif.getInt();
|
ver = nif.getUInt();
|
||||||
if(ver != VER_MW)
|
if(ver != VER_MW)
|
||||||
fail("Unsupported NIF version");
|
fail("Unsupported NIF version: " + printVersion(ver));
|
||||||
|
|
||||||
// Number of records
|
// Number of records
|
||||||
size_t recNum = nif.getInt();
|
size_t recNum = nif.getInt();
|
||||||
records.resize(recNum);
|
records.resize(recNum);
|
||||||
|
|
|
@ -19,7 +19,7 @@ class NIFFile
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Nif file version
|
/// Nif file version
|
||||||
int ver;
|
unsigned int ver;
|
||||||
|
|
||||||
/// File name, used for error messages and opening the file
|
/// File name, used for error messages and opening the file
|
||||||
std::string filename;
|
std::string filename;
|
||||||
|
@ -33,6 +33,10 @@ class NIFFile
|
||||||
/// Parse the file
|
/// Parse the file
|
||||||
void parse();
|
void parse();
|
||||||
|
|
||||||
|
/// Get the file's version in a human readable form
|
||||||
|
///\returns A string containing a human readable NIF version number
|
||||||
|
std::string printVersion(unsigned int version);
|
||||||
|
|
||||||
///Private Copy Constructor
|
///Private Copy Constructor
|
||||||
NIFFile (NIFFile const &);
|
NIFFile (NIFFile const &);
|
||||||
///\overload
|
///\overload
|
||||||
|
@ -74,6 +78,9 @@ public:
|
||||||
}
|
}
|
||||||
/// Number of roots
|
/// Number of roots
|
||||||
size_t numRoots() const { return roots.size(); }
|
size_t numRoots() const { return roots.size(); }
|
||||||
|
|
||||||
|
/// Get the name of the file
|
||||||
|
std::string getFilename(){ return filename; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <OgreVector4.h>
|
#include <OgreVector4.h>
|
||||||
#include <OgreMatrix3.h>
|
#include <OgreMatrix3.h>
|
||||||
#include <OgreQuaternion.h>
|
#include <OgreQuaternion.h>
|
||||||
|
#include <OgreStringConverter.h>
|
||||||
|
|
||||||
#include "niftypes.hpp"
|
#include "niftypes.hpp"
|
||||||
|
|
||||||
|
@ -142,7 +143,8 @@ public:
|
||||||
std::vector<char> str (length+1, 0);
|
std::vector<char> str (length+1, 0);
|
||||||
|
|
||||||
if(inp->read(&str[0], length) != length)
|
if(inp->read(&str[0], length) != length)
|
||||||
throw std::runtime_error ("string length in NIF file does not match");
|
throw std::runtime_error (": String length in NIF file "+ file->getFilename() +" does not match! Expected length: "
|
||||||
|
+ Ogre::StringConverter::toString(length));
|
||||||
|
|
||||||
return &str[0];
|
return &str[0];
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
#include <OgreMatrix4.h>
|
#include <OgreMatrix4.h>
|
||||||
|
|
||||||
#include "controlled.hpp"
|
#include "controlled.hpp"
|
||||||
|
#include "extra.hpp"
|
||||||
#include "data.hpp"
|
#include "data.hpp"
|
||||||
#include "property.hpp"
|
#include "property.hpp"
|
||||||
#include "niftypes.hpp"
|
#include "niftypes.hpp"
|
||||||
|
#include "controller.hpp"
|
||||||
|
#include "base.hpp"
|
||||||
|
|
||||||
namespace Nif
|
namespace Nif
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#ifndef OPENMW_COMPONENTS_NIF_PROPERTY_HPP
|
#ifndef OPENMW_COMPONENTS_NIF_PROPERTY_HPP
|
||||||
#define OPENMW_COMPONENTS_NIF_PROPERTY_HPP
|
#define OPENMW_COMPONENTS_NIF_PROPERTY_HPP
|
||||||
|
|
||||||
#include "controlled.hpp"
|
#include "base.hpp"
|
||||||
|
|
||||||
namespace Nif
|
namespace Nif
|
||||||
{
|
{
|
||||||
|
|
|
@ -355,11 +355,17 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
|
||||||
|
|
||||||
if((alphaFlags>>9)&1)
|
if((alphaFlags>>9)&1)
|
||||||
{
|
{
|
||||||
|
#ifndef ANDROID
|
||||||
std::string reject;
|
std::string reject;
|
||||||
reject += getTestMode((alphaFlags>>10)&0x7);
|
reject += getTestMode((alphaFlags>>10)&0x7);
|
||||||
reject += " ";
|
reject += " ";
|
||||||
reject += Ogre::StringConverter::toString(alphaTest);
|
reject += Ogre::StringConverter::toString(alphaTest);
|
||||||
instance->setProperty("alpha_rejection", sh::makeProperty(new sh::StringValue(reject)));
|
instance->setProperty("alpha_rejection", sh::makeProperty(new sh::StringValue(reject)));
|
||||||
|
#else
|
||||||
|
// alpha test not supported in OpenGL ES 2, use manual implementation in shader
|
||||||
|
instance->setProperty("alphaTestMode", sh::makeProperty(new sh::IntValue((alphaFlags>>10)&0x7)));
|
||||||
|
instance->setProperty("alphaTestValue", sh::makeProperty(new sh::FloatValue(alphaTest/255.f)));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
instance->getMaterial()->setShadowCasterMaterial("openmw_shadowcaster_noalpha");
|
instance->getMaterial()->setShadowCasterMaterial("openmw_shadowcaster_noalpha");
|
||||||
|
|
39
extern/ogre-ffmpeg-videoplayer/CMakeLists.txt
vendored
Normal file
39
extern/ogre-ffmpeg-videoplayer/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
set(OGRE_FFMPEG_VIDEOPLAYER_LIBRARY "ogre-ffmpeg-videoplayer")
|
||||||
|
|
||||||
|
# Sources
|
||||||
|
|
||||||
|
set(OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES
|
||||||
|
videoplayer.cpp
|
||||||
|
videostate.cpp
|
||||||
|
videodefs.hpp
|
||||||
|
libavwrapper.cpp
|
||||||
|
audiodecoder.cpp
|
||||||
|
audiofactory.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Find FFMPEG
|
||||||
|
set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE AVRESAMPLE)
|
||||||
|
unset(FFMPEG_LIBRARIES CACHE)
|
||||||
|
find_package(FFmpeg)
|
||||||
|
if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND )
|
||||||
|
message(FATAL_ERROR "FFmpeg component required, but not found!")
|
||||||
|
endif()
|
||||||
|
set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES})
|
||||||
|
if( SWRESAMPLE_FOUND )
|
||||||
|
add_definitions(-DHAVE_LIBSWRESAMPLE)
|
||||||
|
set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES})
|
||||||
|
else()
|
||||||
|
if( AVRESAMPLE_FOUND )
|
||||||
|
set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES})
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Install either libswresample (FFmpeg) or libavresample (Libav).")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include_directories(${FFMPEG_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
add_library(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} STATIC ${OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES})
|
||||||
|
target_link_libraries(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} ${VIDEO_FFMPEG_LIBRARIES})
|
||||||
|
|
||||||
|
link_directories(${CMAKE_CURRENT_BINARY_DIR})
|
9
extern/ogre-ffmpeg-videoplayer/License.txt
vendored
Normal file
9
extern/ogre-ffmpeg-videoplayer/License.txt
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Copyright (c) 2014 Jannik Heller <scrawl@baseoftrash.de>, Chris Robinson
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
322
extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp
vendored
Normal file
322
extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp
vendored
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
#include "audiodecoder.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBSWRESAMPLE
|
||||||
|
#include <libswresample/swresample.h>
|
||||||
|
#else
|
||||||
|
// FIXME: remove this section once libswresample is packaged for Debian
|
||||||
|
#include <libavresample/avresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#define SwrContext AVAudioResampleContext
|
||||||
|
int swr_init(AVAudioResampleContext *avr);
|
||||||
|
void swr_free(AVAudioResampleContext **avr);
|
||||||
|
int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples);
|
||||||
|
AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
|
||||||
|
#define av_frame_alloc avcodec_alloc_frame
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "videostate.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void fail(const std::string &str)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
const double AUDIO_DIFF_AVG_NB = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Video
|
||||||
|
{
|
||||||
|
|
||||||
|
// Moved to implementation file, so that HAVE_SWRESAMPLE is used at library compile time only
|
||||||
|
struct AudioResampler
|
||||||
|
{
|
||||||
|
AudioResampler()
|
||||||
|
: mSwr(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~AudioResampler()
|
||||||
|
{
|
||||||
|
swr_free(&mSwr);
|
||||||
|
}
|
||||||
|
|
||||||
|
SwrContext* mSwr;
|
||||||
|
};
|
||||||
|
|
||||||
|
MovieAudioDecoder::MovieAudioDecoder(VideoState* videoState)
|
||||||
|
: mVideoState(videoState)
|
||||||
|
, mAVStream(*videoState->audio_st)
|
||||||
|
, mFrame(av_frame_alloc())
|
||||||
|
, mFramePos(0)
|
||||||
|
, mFrameSize(0)
|
||||||
|
, mAudioClock(0.0)
|
||||||
|
, mAudioDiffAccum(0.0)
|
||||||
|
, mAudioDiffAvgCoef(exp(log(0.01 / AUDIO_DIFF_AVG_NB)))
|
||||||
|
/* Correct audio only if larger error than this */
|
||||||
|
, mAudioDiffThreshold(2.0 * 0.050/* 50 ms */)
|
||||||
|
, mAudioDiffAvgCount(0)
|
||||||
|
, mOutputSampleFormat(AV_SAMPLE_FMT_NONE)
|
||||||
|
, mOutputSampleRate(0)
|
||||||
|
, mOutputChannelLayout(0)
|
||||||
|
, mDataBuf(NULL)
|
||||||
|
, mFrameData(NULL)
|
||||||
|
, mDataBufLen(0)
|
||||||
|
{
|
||||||
|
mAudioResampler.reset(new AudioResampler());
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieAudioDecoder::~MovieAudioDecoder()
|
||||||
|
{
|
||||||
|
av_freep(&mFrame);
|
||||||
|
av_freep(&mDataBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MovieAudioDecoder::setupFormat()
|
||||||
|
{
|
||||||
|
if (mAudioResampler->mSwr)
|
||||||
|
return; // already set up
|
||||||
|
|
||||||
|
AVSampleFormat inputSampleFormat = mAVStream->codec->sample_fmt;
|
||||||
|
|
||||||
|
uint64_t inputChannelLayout = mAVStream->codec->channel_layout;
|
||||||
|
if (inputChannelLayout == 0)
|
||||||
|
{
|
||||||
|
/* Unknown channel layout. Try to guess. */
|
||||||
|
if(mAVStream->codec->channels == 1)
|
||||||
|
inputChannelLayout = AV_CH_LAYOUT_MONO;
|
||||||
|
else if(mAVStream->codec->channels == 2)
|
||||||
|
inputChannelLayout = AV_CH_LAYOUT_STEREO;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::stringstream sstr("Unsupported raw channel count: ");
|
||||||
|
sstr << mAVStream->codec->channels;
|
||||||
|
fail(sstr.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int inputSampleRate = mAVStream->codec->sample_rate;
|
||||||
|
|
||||||
|
mOutputSampleRate = inputSampleRate;
|
||||||
|
mOutputSampleFormat = inputSampleFormat;
|
||||||
|
mOutputChannelLayout = inputChannelLayout;
|
||||||
|
adjustAudioSettings(mOutputSampleFormat, mOutputChannelLayout, mOutputSampleRate);
|
||||||
|
|
||||||
|
if (inputSampleFormat != mOutputSampleFormat
|
||||||
|
|| inputChannelLayout != mOutputChannelLayout
|
||||||
|
|| inputSampleRate != mOutputSampleRate)
|
||||||
|
{
|
||||||
|
mAudioResampler->mSwr = swr_alloc_set_opts(mAudioResampler->mSwr,
|
||||||
|
mOutputChannelLayout,
|
||||||
|
mOutputSampleFormat,
|
||||||
|
mOutputSampleRate,
|
||||||
|
inputChannelLayout,
|
||||||
|
inputSampleFormat,
|
||||||
|
inputSampleRate,
|
||||||
|
0, // logging level offset
|
||||||
|
NULL); // log context
|
||||||
|
if(!mAudioResampler->mSwr)
|
||||||
|
fail(std::string("Couldn't allocate SwrContext"));
|
||||||
|
if(swr_init(mAudioResampler->mSwr) < 0)
|
||||||
|
fail(std::string("Couldn't initialize SwrContext"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int MovieAudioDecoder::synchronize_audio()
|
||||||
|
{
|
||||||
|
if(mVideoState->av_sync_type == AV_SYNC_AUDIO_MASTER)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int sample_skip = 0;
|
||||||
|
|
||||||
|
// accumulate the clock difference
|
||||||
|
double diff = mVideoState->get_master_clock() - mVideoState->get_audio_clock();
|
||||||
|
mAudioDiffAccum = diff + mAudioDiffAvgCoef * mAudioDiffAccum;
|
||||||
|
if(mAudioDiffAvgCount < AUDIO_DIFF_AVG_NB)
|
||||||
|
mAudioDiffAvgCount++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double avg_diff = mAudioDiffAccum * (1.0 - mAudioDiffAvgCoef);
|
||||||
|
if(fabs(avg_diff) >= mAudioDiffThreshold)
|
||||||
|
{
|
||||||
|
int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) *
|
||||||
|
mAVStream->codec->channels;
|
||||||
|
sample_skip = ((int)(diff * mAVStream->codec->sample_rate) * n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sample_skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MovieAudioDecoder::audio_decode_frame(AVFrame *frame)
|
||||||
|
{
|
||||||
|
AVPacket *pkt = &mPacket;
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
while(pkt->size > 0)
|
||||||
|
{
|
||||||
|
int len1, got_frame;
|
||||||
|
|
||||||
|
len1 = avcodec_decode_audio4(mAVStream->codec, frame, &got_frame, pkt);
|
||||||
|
if(len1 < 0) break;
|
||||||
|
|
||||||
|
if(len1 <= pkt->size)
|
||||||
|
{
|
||||||
|
/* Move the unread data to the front and clear the end bits */
|
||||||
|
int remaining = pkt->size - len1;
|
||||||
|
memmove(pkt->data, &pkt->data[len1], remaining);
|
||||||
|
av_shrink_packet(pkt, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No data yet? Look for more frames */
|
||||||
|
if(!got_frame || frame->nb_samples <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(mAudioResampler->mSwr)
|
||||||
|
{
|
||||||
|
if(!mDataBuf || mDataBufLen < frame->nb_samples)
|
||||||
|
{
|
||||||
|
av_freep(&mDataBuf);
|
||||||
|
if(av_samples_alloc(&mDataBuf, NULL, mAVStream->codec->channels,
|
||||||
|
frame->nb_samples, mOutputSampleFormat, 0) < 0)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
mDataBufLen = frame->nb_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(swr_convert(mAudioResampler->mSwr, (uint8_t**)&mDataBuf, frame->nb_samples,
|
||||||
|
(const uint8_t**)frame->extended_data, frame->nb_samples) < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mFrameData = &mDataBuf;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mFrameData = &frame->data[0];
|
||||||
|
|
||||||
|
mAudioClock += (double)frame->nb_samples /
|
||||||
|
(double)mAVStream->codec->sample_rate;
|
||||||
|
|
||||||
|
/* We have data, return it and come back for more later */
|
||||||
|
return frame->nb_samples * mAVStream->codec->channels *
|
||||||
|
av_get_bytes_per_sample(mAVStream->codec->sample_fmt);
|
||||||
|
}
|
||||||
|
av_free_packet(pkt);
|
||||||
|
|
||||||
|
/* next packet */
|
||||||
|
if(mVideoState->audioq.get(pkt, mVideoState) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* if update, update the audio clock w/pts */
|
||||||
|
if((uint64_t)pkt->pts != AV_NOPTS_VALUE)
|
||||||
|
mAudioClock = av_q2d(mAVStream->time_base)*pkt->pts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MovieAudioDecoder::read(char *stream, size_t len)
|
||||||
|
{
|
||||||
|
int sample_skip = synchronize_audio();
|
||||||
|
size_t total = 0;
|
||||||
|
|
||||||
|
while(total < len)
|
||||||
|
{
|
||||||
|
if(mFramePos >= mFrameSize)
|
||||||
|
{
|
||||||
|
/* We have already sent all our data; get more */
|
||||||
|
mFrameSize = audio_decode_frame(mFrame);
|
||||||
|
if(mFrameSize < 0)
|
||||||
|
{
|
||||||
|
/* If error, we're done */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mFramePos = std::min<ssize_t>(mFrameSize, sample_skip);
|
||||||
|
if(sample_skip > 0 || mFrameSize > -sample_skip)
|
||||||
|
sample_skip -= mFramePos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len1 = len - total;
|
||||||
|
if(mFramePos >= 0)
|
||||||
|
{
|
||||||
|
len1 = std::min<size_t>(len1, mFrameSize-mFramePos);
|
||||||
|
memcpy(stream, mFrameData[0]+mFramePos, len1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
len1 = std::min<size_t>(len1, -mFramePos);
|
||||||
|
|
||||||
|
int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) *
|
||||||
|
mAVStream->codec->channels;
|
||||||
|
|
||||||
|
/* add samples by copying the first sample*/
|
||||||
|
if(n == 1)
|
||||||
|
memset(stream, *mFrameData[0], len1);
|
||||||
|
else if(n == 2)
|
||||||
|
{
|
||||||
|
const int16_t val = *((int16_t*)mFrameData[0]);
|
||||||
|
for(size_t nb = 0;nb < len1;nb += n)
|
||||||
|
*((int16_t*)(stream+nb)) = val;
|
||||||
|
}
|
||||||
|
else if(n == 4)
|
||||||
|
{
|
||||||
|
const int32_t val = *((int32_t*)mFrameData[0]);
|
||||||
|
for(size_t nb = 0;nb < len1;nb += n)
|
||||||
|
*((int32_t*)(stream+nb)) = val;
|
||||||
|
}
|
||||||
|
else if(n == 8)
|
||||||
|
{
|
||||||
|
const int64_t val = *((int64_t*)mFrameData[0]);
|
||||||
|
for(size_t nb = 0;nb < len1;nb += n)
|
||||||
|
*((int64_t*)(stream+nb)) = val;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(size_t nb = 0;nb < len1;nb += n)
|
||||||
|
memcpy(stream+nb, mFrameData[0], n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total += len1;
|
||||||
|
stream += len1;
|
||||||
|
mFramePos += len1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
double MovieAudioDecoder::getAudioClock()
|
||||||
|
{
|
||||||
|
return mAudioClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MovieAudioDecoder::getOutputSampleRate() const
|
||||||
|
{
|
||||||
|
return mOutputSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t MovieAudioDecoder::getOutputChannelLayout() const
|
||||||
|
{
|
||||||
|
return mOutputChannelLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVSampleFormat MovieAudioDecoder::getOutputSampleFormat() const
|
||||||
|
{
|
||||||
|
return mOutputSampleFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
109
extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp
vendored
Normal file
109
extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp
vendored
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#ifndef VIDEOPLAYER_AUDIODECODER_H
|
||||||
|
#define VIDEOPLAYER_AUDIODECODER_H
|
||||||
|
|
||||||
|
#ifndef __STDC_CONSTANT_MACROS
|
||||||
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
#endif
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include <libavutil/avutil.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
|
||||||
|
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
|
||||||
|
#include <libavutil/channel_layout.h>
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <BaseTsd.h>
|
||||||
|
|
||||||
|
typedef SSIZE_T ssize_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Video
|
||||||
|
{
|
||||||
|
|
||||||
|
struct AudioResampler;
|
||||||
|
|
||||||
|
struct VideoState;
|
||||||
|
|
||||||
|
class MovieAudioDecoder
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
VideoState *mVideoState;
|
||||||
|
AVStream *mAVStream;
|
||||||
|
enum AVSampleFormat mOutputSampleFormat;
|
||||||
|
uint64_t mOutputChannelLayout;
|
||||||
|
int mOutputSampleRate;
|
||||||
|
ssize_t mFramePos;
|
||||||
|
ssize_t mFrameSize;
|
||||||
|
double mAudioClock;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct AutoAVPacket : public AVPacket {
|
||||||
|
AutoAVPacket(int size=0)
|
||||||
|
{
|
||||||
|
if(av_new_packet(this, size) < 0)
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
~AutoAVPacket()
|
||||||
|
{ av_free_packet(this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
std::auto_ptr<AudioResampler> mAudioResampler;
|
||||||
|
|
||||||
|
uint8_t *mDataBuf;
|
||||||
|
uint8_t **mFrameData;
|
||||||
|
int mDataBufLen;
|
||||||
|
|
||||||
|
AutoAVPacket mPacket;
|
||||||
|
AVFrame *mFrame;
|
||||||
|
|
||||||
|
/* averaging filter for audio sync */
|
||||||
|
double mAudioDiffAccum;
|
||||||
|
const double mAudioDiffAvgCoef;
|
||||||
|
const double mAudioDiffThreshold;
|
||||||
|
int mAudioDiffAvgCount;
|
||||||
|
|
||||||
|
/* Add or subtract samples to get a better sync, return number of bytes to
|
||||||
|
* skip (negative means to duplicate). */
|
||||||
|
int synchronize_audio();
|
||||||
|
|
||||||
|
int audio_decode_frame(AVFrame *frame);
|
||||||
|
|
||||||
|
public:
|
||||||
|
MovieAudioDecoder(VideoState *is);
|
||||||
|
virtual ~MovieAudioDecoder();
|
||||||
|
|
||||||
|
int getOutputSampleRate() const;
|
||||||
|
AVSampleFormat getOutputSampleFormat() const;
|
||||||
|
uint64_t getOutputChannelLayout() const;
|
||||||
|
|
||||||
|
void setupFormat();
|
||||||
|
|
||||||
|
/// Adjust the given audio settings to the application's needs. The data given by the read() function will
|
||||||
|
/// be in the desired format written to this function's parameters.
|
||||||
|
/// @par Depending on the application, we may want either fixed settings, or a "closest supported match"
|
||||||
|
/// for the input that does not incur precision loss (e.g. planar -> non-planar format).
|
||||||
|
virtual void adjustAudioSettings(AVSampleFormat& sampleFormat, uint64_t& channelLayout, int& sampleRate) = 0;
|
||||||
|
|
||||||
|
/// Return the current offset in seconds from the beginning of the audio stream.
|
||||||
|
/// @par An internal clock exists in the mAudioClock member, and is used in the default implementation. However,
|
||||||
|
/// for an accurate clock, it's best to also take the current offset in the audio buffer into account.
|
||||||
|
virtual double getAudioClock();
|
||||||
|
|
||||||
|
/// This is the main interface to be used by the user's audio library.
|
||||||
|
size_t read(char *stream, size_t len);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
19
extern/ogre-ffmpeg-videoplayer/audiofactory.hpp
vendored
Normal file
19
extern/ogre-ffmpeg-videoplayer/audiofactory.hpp
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef VIDEO_MOVIEAUDIOFACTORY_H
|
||||||
|
#define VIDEO_MOVIEAUDIOFACTORY_H
|
||||||
|
|
||||||
|
#include "audiodecoder.hpp"
|
||||||
|
|
||||||
|
#include <boost/shared_ptr.hpp>
|
||||||
|
|
||||||
|
namespace Video
|
||||||
|
{
|
||||||
|
|
||||||
|
class MovieAudioFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual boost::shared_ptr<MovieAudioDecoder> createDecoder(VideoState* videoState) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
113
extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp
vendored
Normal file
113
extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
// This file is a wrapper around the libavresample library (the API-incompatible swresample replacement in the libav fork of ffmpeg), to make it look like swresample to the user.
|
||||||
|
|
||||||
|
#ifndef HAVE_LIBSWRESAMPLE
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#ifndef __STDC_CONSTANT_MACROS
|
||||||
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
#endif
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
// From libavutil version 52.2.0 and onward the declaration of
|
||||||
|
// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to
|
||||||
|
// libavutil/channel_layout.h
|
||||||
|
#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
|
||||||
|
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
|
||||||
|
#include <libavutil/channel_layout.h>
|
||||||
|
#endif
|
||||||
|
#include <libavresample/avresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
|
||||||
|
/* FIXME: delete this file once libswresample is packaged for Debian */
|
||||||
|
|
||||||
|
int swr_init(AVAudioResampleContext *avr) { return 1; }
|
||||||
|
|
||||||
|
void swr_free(AVAudioResampleContext **avr) { avresample_free(avr); }
|
||||||
|
|
||||||
|
int swr_convert(
|
||||||
|
AVAudioResampleContext *avr,
|
||||||
|
uint8_t** output,
|
||||||
|
int out_samples,
|
||||||
|
const uint8_t** input,
|
||||||
|
int in_samples)
|
||||||
|
{
|
||||||
|
// FIXME: potential performance hit
|
||||||
|
int out_plane_size = 0;
|
||||||
|
int in_plane_size = 0;
|
||||||
|
return avresample_convert(avr, output, out_plane_size, out_samples,
|
||||||
|
(uint8_t **)input, in_plane_size, in_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
AVAudioResampleContext * swr_alloc_set_opts(
|
||||||
|
AVAudioResampleContext *avr,
|
||||||
|
int64_t out_ch_layout,
|
||||||
|
AVSampleFormat out_fmt,
|
||||||
|
int out_rate,
|
||||||
|
int64_t in_ch_layout,
|
||||||
|
AVSampleFormat in_fmt,
|
||||||
|
int in_rate,
|
||||||
|
int o,
|
||||||
|
void* l)
|
||||||
|
{
|
||||||
|
avr = avresample_alloc_context();
|
||||||
|
if(!avr)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int res;
|
||||||
|
res = av_opt_set_int(avr, "out_channel_layout", out_ch_layout, 0);
|
||||||
|
if(res < 0)
|
||||||
|
{
|
||||||
|
av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_ch_layout = %d\n", res);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
res = av_opt_set_int(avr, "out_sample_fmt", out_fmt, 0);
|
||||||
|
if(res < 0)
|
||||||
|
{
|
||||||
|
av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_fmt = %d\n", res);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
res = av_opt_set_int(avr, "out_sample_rate", out_rate, 0);
|
||||||
|
if(res < 0)
|
||||||
|
{
|
||||||
|
av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_rate = %d\n", res);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
res = av_opt_set_int(avr, "in_channel_layout", in_ch_layout, 0);
|
||||||
|
if(res < 0)
|
||||||
|
{
|
||||||
|
av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_ch_layout = %d\n", res);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
res = av_opt_set_int(avr, "in_sample_fmt", in_fmt, 0);
|
||||||
|
if(res < 0)
|
||||||
|
{
|
||||||
|
av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_fmt = %d\n", res);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
res = av_opt_set_int(avr, "in_sample_rate", in_rate, 0);
|
||||||
|
if(res < 0)
|
||||||
|
{
|
||||||
|
av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_rate = %d\n", res);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
res = av_opt_set_int(avr, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
|
||||||
|
if(res < 0)
|
||||||
|
{
|
||||||
|
av_log(avr, AV_LOG_ERROR, "av_opt_set_int: internal_sample_fmt\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(avresample_open(avr) < 0)
|
||||||
|
{
|
||||||
|
av_log(avr, AV_LOG_ERROR, "Error opening context\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return avr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
17
extern/ogre-ffmpeg-videoplayer/videodefs.hpp
vendored
Normal file
17
extern/ogre-ffmpeg-videoplayer/videodefs.hpp
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef VIDEOPLAYER_DEFS_H
|
||||||
|
#define VIDEOPLAYER_DEFS_H
|
||||||
|
|
||||||
|
namespace Video
|
||||||
|
{
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AV_SYNC_AUDIO_MASTER, // Play audio with no frame drops, sync video to audio
|
||||||
|
AV_SYNC_VIDEO_MASTER, // Play video with no frame drops, sync audio to video
|
||||||
|
AV_SYNC_EXTERNAL_MASTER, // Sync audio and video to an external clock
|
||||||
|
|
||||||
|
AV_SYNC_DEFAULT = AV_SYNC_EXTERNAL_MASTER
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
95
extern/ogre-ffmpeg-videoplayer/videoplayer.cpp
vendored
Normal file
95
extern/ogre-ffmpeg-videoplayer/videoplayer.cpp
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
#include "videoplayer.hpp"
|
||||||
|
|
||||||
|
#include "videostate.hpp"
|
||||||
|
|
||||||
|
namespace Video
|
||||||
|
{
|
||||||
|
|
||||||
|
VideoPlayer::VideoPlayer()
|
||||||
|
: mState(NULL)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoPlayer::~VideoPlayer()
|
||||||
|
{
|
||||||
|
if(mState)
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoPlayer::setAudioFactory(MovieAudioFactory *factory)
|
||||||
|
{
|
||||||
|
mAudioFactory.reset(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoPlayer::playVideo(const std::string &resourceName)
|
||||||
|
{
|
||||||
|
if(mState)
|
||||||
|
close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
mState = new VideoState;
|
||||||
|
mState->setAudioFactory(mAudioFactory.get());
|
||||||
|
mState->init(resourceName);
|
||||||
|
}
|
||||||
|
catch(std::exception& e) {
|
||||||
|
std::cerr<< "Failed to play video: "<<e.what() <<std::endl;
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoPlayer::update ()
|
||||||
|
{
|
||||||
|
if(mState)
|
||||||
|
{
|
||||||
|
if(!mState->update())
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VideoPlayer::getTextureName()
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
if (mState)
|
||||||
|
name = mState->mTexture->getName();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VideoPlayer::getVideoWidth()
|
||||||
|
{
|
||||||
|
int width=0;
|
||||||
|
if (mState)
|
||||||
|
width = mState->mTexture->getWidth();
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VideoPlayer::getVideoHeight()
|
||||||
|
{
|
||||||
|
int height=0;
|
||||||
|
if (mState)
|
||||||
|
height = mState->mTexture->getHeight();
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoPlayer::close()
|
||||||
|
{
|
||||||
|
if(mState)
|
||||||
|
{
|
||||||
|
mState->deinit();
|
||||||
|
|
||||||
|
delete mState;
|
||||||
|
mState = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoPlayer::isPlaying ()
|
||||||
|
{
|
||||||
|
return mState != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoPlayer::hasAudioStream()
|
||||||
|
{
|
||||||
|
return mState && mState->audio_st != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
extern/ogre-ffmpeg-videoplayer/videoplayer.hpp
vendored
Normal file
58
extern/ogre-ffmpeg-videoplayer/videoplayer.hpp
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#ifndef VIDEOPLAYER_H
|
||||||
|
#define VIDEOPLAYER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Video
|
||||||
|
{
|
||||||
|
|
||||||
|
struct VideoState;
|
||||||
|
class MovieAudioFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Plays a video on an Ogre texture.
|
||||||
|
*/
|
||||||
|
class VideoPlayer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VideoPlayer();
|
||||||
|
~VideoPlayer();
|
||||||
|
|
||||||
|
/// @brief Set the MovieAudioFactory to use.
|
||||||
|
/// @par This class must be implemented by the user and is responsible for reading the decoded audio data.
|
||||||
|
/// @note If you do not set up a MovieAudioFactory, then audio streams will be ignored and the video will be played with no sound.
|
||||||
|
/// @note Takes ownership of the passed pointer.
|
||||||
|
void setAudioFactory (MovieAudioFactory* factory);
|
||||||
|
|
||||||
|
/// Return true if a video is currently playing and it has an audio stream.
|
||||||
|
bool hasAudioStream();
|
||||||
|
|
||||||
|
/// Play the given video. If a video is already playing, the old video is closed first.
|
||||||
|
void playVideo (const std::string& resourceName);
|
||||||
|
|
||||||
|
/// This should be called every frame by the user to update the video texture.
|
||||||
|
void update();
|
||||||
|
|
||||||
|
/// Stop the currently playing video, if a video is playing.
|
||||||
|
void close();
|
||||||
|
|
||||||
|
bool isPlaying();
|
||||||
|
|
||||||
|
/// Return the texture name of the currently playing video, or "" if no video is playing.
|
||||||
|
std::string getTextureName();
|
||||||
|
/// Return the width of the currently playing video, or 0 if no video is playing.
|
||||||
|
int getVideoWidth();
|
||||||
|
/// Return the height of the currently playing video, or 0 if no video is playing.
|
||||||
|
int getVideoHeight();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
VideoState* mState;
|
||||||
|
|
||||||
|
std::auto_ptr<MovieAudioFactory> mAudioFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
671
extern/ogre-ffmpeg-videoplayer/videostate.cpp
vendored
Normal file
671
extern/ogre-ffmpeg-videoplayer/videostate.cpp
vendored
Normal file
|
@ -0,0 +1,671 @@
|
||||||
|
#include "videostate.hpp"
|
||||||
|
|
||||||
|
#ifndef __STDC_CONSTANT_MACROS
|
||||||
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
#endif
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Has to be included *before* ffmpeg, due to a macro collision with ffmpeg (#define PixelFormat in avformat.h - grumble)
|
||||||
|
#include <OgreTextureManager.h>
|
||||||
|
#include <OgreHardwarePixelBuffer.h>
|
||||||
|
#include <OgreResourceGroupManager.h>
|
||||||
|
#include <OgreStringConverter.h>
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
|
||||||
|
// From libavformat version 55.0.100 and onward the declaration of av_gettime() is
|
||||||
|
// removed from libavformat/avformat.h and moved to libavutil/time.h
|
||||||
|
// https://github.com/FFmpeg/FFmpeg/commit/06a83505992d5f49846c18507a6c3eb8a47c650e
|
||||||
|
#if AV_VERSION_INT(55, 0, 100) <= AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
|
||||||
|
LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO)
|
||||||
|
#include <libavutil/time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
|
||||||
|
#define av_frame_alloc avcodec_alloc_frame
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "videoplayer.hpp"
|
||||||
|
#include "audiodecoder.hpp"
|
||||||
|
#include "audiofactory.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const int MAX_AUDIOQ_SIZE = (5 * 16 * 1024);
|
||||||
|
const int MAX_VIDEOQ_SIZE = (5 * 256 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Video
|
||||||
|
{
|
||||||
|
|
||||||
|
VideoState::VideoState()
|
||||||
|
: format_ctx(NULL), av_sync_type(AV_SYNC_DEFAULT)
|
||||||
|
, external_clock_base(0.0)
|
||||||
|
, audio_st(NULL)
|
||||||
|
, video_st(NULL), frame_last_pts(0.0)
|
||||||
|
, video_clock(0.0), sws_context(NULL), rgbaFrame(NULL), pictq_size(0)
|
||||||
|
, pictq_rindex(0), pictq_windex(0)
|
||||||
|
, quit(false)
|
||||||
|
, mAudioFactory(NULL)
|
||||||
|
{
|
||||||
|
// Register all formats and codecs
|
||||||
|
av_register_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoState::~VideoState()
|
||||||
|
{
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoState::setAudioFactory(MovieAudioFactory *factory)
|
||||||
|
{
|
||||||
|
mAudioFactory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PacketQueue::put(AVPacket *pkt)
|
||||||
|
{
|
||||||
|
AVPacketList *pkt1;
|
||||||
|
pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
|
||||||
|
if(!pkt1) throw std::bad_alloc();
|
||||||
|
pkt1->pkt = *pkt;
|
||||||
|
pkt1->next = NULL;
|
||||||
|
|
||||||
|
if(pkt1->pkt.destruct == NULL)
|
||||||
|
{
|
||||||
|
if(av_dup_packet(&pkt1->pkt) < 0)
|
||||||
|
{
|
||||||
|
av_free(pkt1);
|
||||||
|
throw std::runtime_error("Failed to duplicate packet");
|
||||||
|
}
|
||||||
|
av_free_packet(pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->mutex.lock ();
|
||||||
|
|
||||||
|
if(!last_pkt)
|
||||||
|
this->first_pkt = pkt1;
|
||||||
|
else
|
||||||
|
this->last_pkt->next = pkt1;
|
||||||
|
this->last_pkt = pkt1;
|
||||||
|
this->nb_packets++;
|
||||||
|
this->size += pkt1->pkt.size;
|
||||||
|
this->cond.notify_one();
|
||||||
|
|
||||||
|
this->mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
int PacketQueue::get(AVPacket *pkt, VideoState *is)
|
||||||
|
{
|
||||||
|
boost::unique_lock<boost::mutex> lock(this->mutex);
|
||||||
|
while(!is->quit)
|
||||||
|
{
|
||||||
|
AVPacketList *pkt1 = this->first_pkt;
|
||||||
|
if(pkt1)
|
||||||
|
{
|
||||||
|
this->first_pkt = pkt1->next;
|
||||||
|
if(!this->first_pkt)
|
||||||
|
this->last_pkt = NULL;
|
||||||
|
this->nb_packets--;
|
||||||
|
this->size -= pkt1->pkt.size;
|
||||||
|
|
||||||
|
*pkt = pkt1->pkt;
|
||||||
|
av_free(pkt1);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this->flushing)
|
||||||
|
break;
|
||||||
|
this->cond.wait(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PacketQueue::flush()
|
||||||
|
{
|
||||||
|
this->flushing = true;
|
||||||
|
this->cond.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PacketQueue::clear()
|
||||||
|
{
|
||||||
|
AVPacketList *pkt, *pkt1;
|
||||||
|
|
||||||
|
this->mutex.lock();
|
||||||
|
for(pkt = this->first_pkt; pkt != NULL; pkt = pkt1)
|
||||||
|
{
|
||||||
|
pkt1 = pkt->next;
|
||||||
|
av_free_packet(&pkt->pkt);
|
||||||
|
av_freep(&pkt);
|
||||||
|
}
|
||||||
|
this->last_pkt = NULL;
|
||||||
|
this->first_pkt = NULL;
|
||||||
|
this->nb_packets = 0;
|
||||||
|
this->size = 0;
|
||||||
|
this->mutex.unlock ();
|
||||||
|
}
|
||||||
|
|
||||||
|
int VideoState::OgreResource_Read(void *user_data, uint8_t *buf, int buf_size)
|
||||||
|
{
|
||||||
|
Ogre::DataStreamPtr stream = static_cast<VideoState*>(user_data)->stream;
|
||||||
|
return stream->read(buf, buf_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int VideoState::OgreResource_Write(void *user_data, uint8_t *buf, int buf_size)
|
||||||
|
{
|
||||||
|
Ogre::DataStreamPtr stream = static_cast<VideoState*>(user_data)->stream;
|
||||||
|
return stream->write(buf, buf_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t VideoState::OgreResource_Seek(void *user_data, int64_t offset, int whence)
|
||||||
|
{
|
||||||
|
Ogre::DataStreamPtr stream = static_cast<VideoState*>(user_data)->stream;
|
||||||
|
|
||||||
|
whence &= ~AVSEEK_FORCE;
|
||||||
|
if(whence == AVSEEK_SIZE)
|
||||||
|
return stream->size();
|
||||||
|
if(whence == SEEK_SET)
|
||||||
|
stream->seek(offset);
|
||||||
|
else if(whence == SEEK_CUR)
|
||||||
|
stream->seek(stream->tell()+offset);
|
||||||
|
else if(whence == SEEK_END)
|
||||||
|
stream->seek(stream->size()+offset);
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return stream->tell();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoState::video_display(VideoPicture *vp)
|
||||||
|
{
|
||||||
|
if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(static_cast<int>(mTexture->getWidth()) != (*this->video_st)->codec->width ||
|
||||||
|
static_cast<int>(mTexture->getHeight()) != (*this->video_st)->codec->height)
|
||||||
|
{
|
||||||
|
mTexture->unload();
|
||||||
|
mTexture->setWidth((*this->video_st)->codec->width);
|
||||||
|
mTexture->setHeight((*this->video_st)->codec->height);
|
||||||
|
mTexture->createInternalResources();
|
||||||
|
}
|
||||||
|
Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]);
|
||||||
|
Ogre::HardwarePixelBufferSharedPtr buffer = mTexture->getBuffer();
|
||||||
|
buffer->blitFromMemory(pb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoState::video_refresh()
|
||||||
|
{
|
||||||
|
if(this->pictq_size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this->av_sync_type == AV_SYNC_VIDEO_MASTER)
|
||||||
|
{
|
||||||
|
VideoPicture* vp = &this->pictq[this->pictq_rindex];
|
||||||
|
this->video_display(vp);
|
||||||
|
this->pictq_rindex = (pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE;
|
||||||
|
this->frame_last_pts = vp->pts;
|
||||||
|
this->pictq_mutex.lock();
|
||||||
|
this->pictq_size--;
|
||||||
|
this->pictq_cond.notify_one();
|
||||||
|
this->pictq_mutex.unlock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const float threshold = 0.03;
|
||||||
|
if (this->pictq[pictq_rindex].pts > this->get_master_clock() + threshold)
|
||||||
|
return; // not ready yet to show this picture
|
||||||
|
|
||||||
|
// TODO: the conversion to RGBA is done in the decoding thread, so if a picture is skipped here, then it was
|
||||||
|
// unnecessarily converted. But we may want to replace the conversion by a pixel shader anyway (see comment in queue_picture)
|
||||||
|
int i=0;
|
||||||
|
for (; i<this->pictq_size-1; ++i)
|
||||||
|
{
|
||||||
|
if (this->pictq[pictq_rindex].pts + threshold <= this->get_master_clock())
|
||||||
|
this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; // not enough time to show this picture
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoPicture* vp = &this->pictq[this->pictq_rindex];
|
||||||
|
|
||||||
|
this->video_display(vp);
|
||||||
|
|
||||||
|
this->frame_last_pts = vp->pts;
|
||||||
|
|
||||||
|
this->pictq_mutex.lock();
|
||||||
|
this->pictq_size -= i;
|
||||||
|
// update queue for next picture
|
||||||
|
this->pictq_size--;
|
||||||
|
this->pictq_rindex++;
|
||||||
|
this->pictq_cond.notify_one();
|
||||||
|
this->pictq_mutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int VideoState::queue_picture(AVFrame *pFrame, double pts)
|
||||||
|
{
|
||||||
|
VideoPicture *vp;
|
||||||
|
|
||||||
|
/* wait until we have a new pic */
|
||||||
|
{
|
||||||
|
boost::unique_lock<boost::mutex> lock(this->pictq_mutex);
|
||||||
|
while(this->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !this->quit)
|
||||||
|
this->pictq_cond.timed_wait(lock, boost::posix_time::milliseconds(1));
|
||||||
|
}
|
||||||
|
if(this->quit)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// windex is set to 0 initially
|
||||||
|
vp = &this->pictq[this->pictq_windex];
|
||||||
|
|
||||||
|
// Convert the image into RGBA format for Ogre
|
||||||
|
// TODO: we could do this in a pixel shader instead, if the source format
|
||||||
|
// matches a commonly used format (ie YUV420P)
|
||||||
|
if(this->sws_context == NULL)
|
||||||
|
{
|
||||||
|
int w = (*this->video_st)->codec->width;
|
||||||
|
int h = (*this->video_st)->codec->height;
|
||||||
|
this->sws_context = sws_getContext(w, h, (*this->video_st)->codec->pix_fmt,
|
||||||
|
w, h, PIX_FMT_RGBA, SWS_BICUBIC,
|
||||||
|
NULL, NULL, NULL);
|
||||||
|
if(this->sws_context == NULL)
|
||||||
|
throw std::runtime_error("Cannot initialize the conversion context!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
vp->pts = pts;
|
||||||
|
vp->data.resize((*this->video_st)->codec->width * (*this->video_st)->codec->height * 4);
|
||||||
|
|
||||||
|
uint8_t *dst = &vp->data[0];
|
||||||
|
sws_scale(this->sws_context, pFrame->data, pFrame->linesize,
|
||||||
|
0, (*this->video_st)->codec->height, &dst, this->rgbaFrame->linesize);
|
||||||
|
|
||||||
|
// now we inform our display thread that we have a pic ready
|
||||||
|
this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_QUEUE_SIZE;
|
||||||
|
this->pictq_mutex.lock();
|
||||||
|
this->pictq_size++;
|
||||||
|
this->pictq_mutex.unlock();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double VideoState::synchronize_video(AVFrame *src_frame, double pts)
|
||||||
|
{
|
||||||
|
double frame_delay;
|
||||||
|
|
||||||
|
/* if we have pts, set video clock to it */
|
||||||
|
if(pts != 0)
|
||||||
|
this->video_clock = pts;
|
||||||
|
else
|
||||||
|
pts = this->video_clock;
|
||||||
|
|
||||||
|
/* update the video clock */
|
||||||
|
frame_delay = av_q2d((*this->video_st)->codec->time_base);
|
||||||
|
|
||||||
|
/* if we are repeating a frame, adjust clock accordingly */
|
||||||
|
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
|
||||||
|
this->video_clock += frame_delay;
|
||||||
|
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* These are called whenever we allocate a frame
|
||||||
|
* buffer. We use this to store the global_pts in
|
||||||
|
* a frame at the time it is allocated.
|
||||||
|
*/
|
||||||
|
static uint64_t global_video_pkt_pts = static_cast<uint64_t>(AV_NOPTS_VALUE);
|
||||||
|
static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic)
|
||||||
|
{
|
||||||
|
int ret = avcodec_default_get_buffer(c, pic);
|
||||||
|
uint64_t *pts = (uint64_t*)av_malloc(sizeof(uint64_t));
|
||||||
|
*pts = global_video_pkt_pts;
|
||||||
|
pic->opaque = pts;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
static void our_release_buffer(struct AVCodecContext *c, AVFrame *pic)
|
||||||
|
{
|
||||||
|
if(pic) av_freep(&pic->opaque);
|
||||||
|
avcodec_default_release_buffer(c, pic);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VideoState::video_thread_loop(VideoState *self)
|
||||||
|
{
|
||||||
|
AVPacket pkt1, *packet = &pkt1;
|
||||||
|
int frameFinished;
|
||||||
|
AVFrame *pFrame;
|
||||||
|
|
||||||
|
pFrame = av_frame_alloc();
|
||||||
|
|
||||||
|
self->rgbaFrame = av_frame_alloc();
|
||||||
|
avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height);
|
||||||
|
|
||||||
|
while(self->videoq.get(packet, self) >= 0)
|
||||||
|
{
|
||||||
|
// Save global pts to be stored in pFrame
|
||||||
|
global_video_pkt_pts = packet->pts;
|
||||||
|
// Decode video frame
|
||||||
|
if(avcodec_decode_video2((*self->video_st)->codec, pFrame, &frameFinished, packet) < 0)
|
||||||
|
throw std::runtime_error("Error decoding video frame");
|
||||||
|
|
||||||
|
double pts = 0;
|
||||||
|
if((uint64_t)packet->dts != AV_NOPTS_VALUE)
|
||||||
|
pts = packet->dts;
|
||||||
|
else if(pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE)
|
||||||
|
pts = *(uint64_t*)pFrame->opaque;
|
||||||
|
pts *= av_q2d((*self->video_st)->time_base);
|
||||||
|
|
||||||
|
av_free_packet(packet);
|
||||||
|
|
||||||
|
// Did we get a video frame?
|
||||||
|
if(frameFinished)
|
||||||
|
{
|
||||||
|
pts = self->synchronize_video(pFrame, pts);
|
||||||
|
if(self->queue_picture(pFrame, pts) < 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
av_free(pFrame);
|
||||||
|
|
||||||
|
avpicture_free((AVPicture*)self->rgbaFrame);
|
||||||
|
av_free(self->rgbaFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoState::decode_thread_loop(VideoState *self)
|
||||||
|
{
|
||||||
|
AVFormatContext *pFormatCtx = self->format_ctx;
|
||||||
|
AVPacket pkt1, *packet = &pkt1;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(!self->video_st && !self->audio_st)
|
||||||
|
throw std::runtime_error("No streams to decode");
|
||||||
|
|
||||||
|
// main decode loop
|
||||||
|
while(!self->quit)
|
||||||
|
{
|
||||||
|
if((self->audio_st && self->audioq.size > MAX_AUDIOQ_SIZE) ||
|
||||||
|
(self->video_st && self->videoq.size > MAX_VIDEOQ_SIZE))
|
||||||
|
{
|
||||||
|
boost::this_thread::sleep(boost::posix_time::milliseconds(10));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(av_read_frame(pFormatCtx, packet) < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Is this a packet from the video stream?
|
||||||
|
if(self->video_st && packet->stream_index == self->video_st-pFormatCtx->streams)
|
||||||
|
self->videoq.put(packet);
|
||||||
|
else if(self->audio_st && packet->stream_index == self->audio_st-pFormatCtx->streams)
|
||||||
|
self->audioq.put(packet);
|
||||||
|
else
|
||||||
|
av_free_packet(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* all done - wait for it */
|
||||||
|
self->videoq.flush();
|
||||||
|
self->audioq.flush();
|
||||||
|
while(!self->quit)
|
||||||
|
{
|
||||||
|
// EOF reached, all packets processed, we can exit now
|
||||||
|
if(self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0 && self->pictq_size == 0)
|
||||||
|
break;
|
||||||
|
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(std::runtime_error& e) {
|
||||||
|
std::cerr << "An error occured playing the video: " << e.what () << std::endl;
|
||||||
|
}
|
||||||
|
catch(Ogre::Exception& e) {
|
||||||
|
std::cerr << "An error occured playing the video: " << e.getFullDescription () << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->quit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool VideoState::update()
|
||||||
|
{
|
||||||
|
if(this->quit)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this->video_refresh();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx)
|
||||||
|
{
|
||||||
|
AVCodecContext *codecCtx;
|
||||||
|
AVCodec *codec;
|
||||||
|
|
||||||
|
if(stream_index < 0 || stream_index >= static_cast<int>(pFormatCtx->nb_streams))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Get a pointer to the codec context for the video stream
|
||||||
|
codecCtx = pFormatCtx->streams[stream_index]->codec;
|
||||||
|
codec = avcodec_find_decoder(codecCtx->codec_id);
|
||||||
|
if(!codec || (avcodec_open2(codecCtx, codec, NULL) < 0))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Unsupported codec!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(codecCtx->codec_type)
|
||||||
|
{
|
||||||
|
case AVMEDIA_TYPE_AUDIO:
|
||||||
|
this->audio_st = pFormatCtx->streams + stream_index;
|
||||||
|
|
||||||
|
if (!mAudioFactory)
|
||||||
|
{
|
||||||
|
std::cerr << "No audio factory registered, can not play audio stream" << std::endl;
|
||||||
|
avcodec_close((*this->audio_st)->codec);
|
||||||
|
this->audio_st = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioDecoder = mAudioFactory->createDecoder(this);
|
||||||
|
if (!mAudioDecoder.get())
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to create audio decoder, can not play audio stream" << std::endl;
|
||||||
|
avcodec_close((*this->audio_st)->codec);
|
||||||
|
this->audio_st = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
mAudioDecoder->setupFormat();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AVMEDIA_TYPE_VIDEO:
|
||||||
|
this->video_st = pFormatCtx->streams + stream_index;
|
||||||
|
|
||||||
|
codecCtx->get_buffer = our_get_buffer;
|
||||||
|
codecCtx->release_buffer = our_release_buffer;
|
||||||
|
this->video_thread = boost::thread(video_thread_loop, this);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoState::init(const std::string& resourceName)
|
||||||
|
{
|
||||||
|
int video_index = -1;
|
||||||
|
int audio_index = -1;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
this->av_sync_type = AV_SYNC_DEFAULT;
|
||||||
|
this->quit = false;
|
||||||
|
|
||||||
|
this->stream = Ogre::ResourceGroupManager::getSingleton().openResource(resourceName);
|
||||||
|
if(this->stream.isNull())
|
||||||
|
throw std::runtime_error("Failed to open video resource");
|
||||||
|
|
||||||
|
AVIOContext *ioCtx = avio_alloc_context(NULL, 0, 0, this, OgreResource_Read, OgreResource_Write, OgreResource_Seek);
|
||||||
|
if(!ioCtx) throw std::runtime_error("Failed to allocate AVIOContext");
|
||||||
|
|
||||||
|
this->format_ctx = avformat_alloc_context();
|
||||||
|
if(this->format_ctx)
|
||||||
|
this->format_ctx->pb = ioCtx;
|
||||||
|
|
||||||
|
// Open video file
|
||||||
|
///
|
||||||
|
/// format_ctx->pb->buffer must be freed by hand,
|
||||||
|
/// if not, valgrind will show memleak, see:
|
||||||
|
///
|
||||||
|
/// https://trac.ffmpeg.org/ticket/1357
|
||||||
|
///
|
||||||
|
if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL))
|
||||||
|
{
|
||||||
|
if (this->format_ctx != NULL)
|
||||||
|
{
|
||||||
|
if (this->format_ctx->pb != NULL)
|
||||||
|
{
|
||||||
|
av_free(this->format_ctx->pb->buffer);
|
||||||
|
this->format_ctx->pb->buffer = NULL;
|
||||||
|
|
||||||
|
av_free(this->format_ctx->pb);
|
||||||
|
this->format_ctx->pb = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// "Note that a user-supplied AVFormatContext will be freed on failure."
|
||||||
|
this->format_ctx = NULL;
|
||||||
|
av_free(ioCtx);
|
||||||
|
throw std::runtime_error("Failed to open video input");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve stream information
|
||||||
|
if(avformat_find_stream_info(this->format_ctx, NULL) < 0)
|
||||||
|
throw std::runtime_error("Failed to retrieve stream information");
|
||||||
|
|
||||||
|
// Dump information about file onto standard error
|
||||||
|
av_dump_format(this->format_ctx, 0, resourceName.c_str(), 0);
|
||||||
|
|
||||||
|
for(i = 0;i < this->format_ctx->nb_streams;i++)
|
||||||
|
{
|
||||||
|
if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0)
|
||||||
|
video_index = i;
|
||||||
|
if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0)
|
||||||
|
audio_index = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->external_clock_base = av_gettime();
|
||||||
|
|
||||||
|
if(audio_index >= 0)
|
||||||
|
this->stream_open(audio_index, this->format_ctx);
|
||||||
|
|
||||||
|
if(video_index >= 0)
|
||||||
|
{
|
||||||
|
this->stream_open(video_index, this->format_ctx);
|
||||||
|
|
||||||
|
int width = (*this->video_st)->codec->width;
|
||||||
|
int height = (*this->video_st)->codec->height;
|
||||||
|
static int i = 0;
|
||||||
|
this->mTexture = Ogre::TextureManager::getSingleton().createManual(
|
||||||
|
"ffmpeg/VideoTexture" + Ogre::StringConverter::toString(++i),
|
||||||
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||||
|
Ogre::TEX_TYPE_2D,
|
||||||
|
width, height,
|
||||||
|
0,
|
||||||
|
Ogre::PF_BYTE_RGBA,
|
||||||
|
Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
|
||||||
|
|
||||||
|
// initialize to (0,0,0,0)
|
||||||
|
std::vector<Ogre::uint32> buffer;
|
||||||
|
buffer.resize(width * height, 0);
|
||||||
|
Ogre::PixelBox pb(width, height, 1, Ogre::PF_BYTE_RGBA, &buffer[0]);
|
||||||
|
this->mTexture->getBuffer()->blitFromMemory(pb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this->parse_thread = boost::thread(decode_thread_loop, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoState::deinit()
|
||||||
|
{
|
||||||
|
this->quit = true;
|
||||||
|
|
||||||
|
mAudioDecoder.reset();
|
||||||
|
|
||||||
|
this->audioq.cond.notify_one();
|
||||||
|
this->videoq.cond.notify_one();
|
||||||
|
|
||||||
|
if (this->parse_thread.joinable())
|
||||||
|
this->parse_thread.join();
|
||||||
|
if (this->video_thread.joinable())
|
||||||
|
this->video_thread.join();
|
||||||
|
|
||||||
|
if(this->audio_st)
|
||||||
|
avcodec_close((*this->audio_st)->codec);
|
||||||
|
this->audio_st = NULL;
|
||||||
|
if(this->video_st)
|
||||||
|
avcodec_close((*this->video_st)->codec);
|
||||||
|
this->video_st = NULL;
|
||||||
|
|
||||||
|
if(this->sws_context)
|
||||||
|
sws_freeContext(this->sws_context);
|
||||||
|
this->sws_context = NULL;
|
||||||
|
|
||||||
|
if(this->format_ctx)
|
||||||
|
{
|
||||||
|
///
|
||||||
|
/// format_ctx->pb->buffer must be freed by hand,
|
||||||
|
/// if not, valgrind will show memleak, see:
|
||||||
|
///
|
||||||
|
/// https://trac.ffmpeg.org/ticket/1357
|
||||||
|
///
|
||||||
|
if (this->format_ctx->pb != NULL)
|
||||||
|
{
|
||||||
|
av_free(this->format_ctx->pb->buffer);
|
||||||
|
this->format_ctx->pb->buffer = NULL;
|
||||||
|
|
||||||
|
av_free(this->format_ctx->pb);
|
||||||
|
this->format_ctx->pb = NULL;
|
||||||
|
}
|
||||||
|
avformat_close_input(&this->format_ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double VideoState::get_external_clock()
|
||||||
|
{
|
||||||
|
return ((uint64_t)av_gettime()-this->external_clock_base) / 1000000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double VideoState::get_master_clock()
|
||||||
|
{
|
||||||
|
if(this->av_sync_type == AV_SYNC_VIDEO_MASTER)
|
||||||
|
return this->get_video_clock();
|
||||||
|
if(this->av_sync_type == AV_SYNC_AUDIO_MASTER)
|
||||||
|
return this->get_audio_clock();
|
||||||
|
return this->get_external_clock();
|
||||||
|
}
|
||||||
|
|
||||||
|
double VideoState::get_video_clock()
|
||||||
|
{
|
||||||
|
return this->frame_last_pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
double VideoState::get_audio_clock()
|
||||||
|
{
|
||||||
|
if (!mAudioDecoder.get())
|
||||||
|
return 0.0;
|
||||||
|
return mAudioDecoder->getAudioClock();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
125
extern/ogre-ffmpeg-videoplayer/videostate.hpp
vendored
Normal file
125
extern/ogre-ffmpeg-videoplayer/videostate.hpp
vendored
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
#ifndef VIDEOPLAYER_VIDEOSTATE_H
|
||||||
|
#define VIDEOPLAYER_VIDEOSTATE_H
|
||||||
|
|
||||||
|
#include <boost/thread.hpp>
|
||||||
|
|
||||||
|
#include <OgreTexture.h>
|
||||||
|
|
||||||
|
#include "videodefs.hpp"
|
||||||
|
|
||||||
|
#define VIDEO_PICTURE_QUEUE_SIZE 50
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
struct SwsContext;
|
||||||
|
struct AVPacketList;
|
||||||
|
struct AVPacket;
|
||||||
|
struct AVFormatContext;
|
||||||
|
struct AVStream;
|
||||||
|
struct AVFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Video
|
||||||
|
{
|
||||||
|
|
||||||
|
struct VideoState;
|
||||||
|
|
||||||
|
class MovieAudioFactory;
|
||||||
|
class MovieAudioDecoder;
|
||||||
|
|
||||||
|
struct PacketQueue {
|
||||||
|
PacketQueue()
|
||||||
|
: first_pkt(NULL), last_pkt(NULL), flushing(false), nb_packets(0), size(0)
|
||||||
|
{ }
|
||||||
|
~PacketQueue()
|
||||||
|
{ clear(); }
|
||||||
|
|
||||||
|
AVPacketList *first_pkt, *last_pkt;
|
||||||
|
volatile bool flushing;
|
||||||
|
int nb_packets;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
boost::mutex mutex;
|
||||||
|
boost::condition_variable cond;
|
||||||
|
|
||||||
|
void put(AVPacket *pkt);
|
||||||
|
int get(AVPacket *pkt, VideoState *is);
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
void clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoPicture {
|
||||||
|
VideoPicture() : pts(0.0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
double pts;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoState {
|
||||||
|
VideoState();
|
||||||
|
~VideoState();
|
||||||
|
|
||||||
|
void setAudioFactory(MovieAudioFactory* factory);
|
||||||
|
|
||||||
|
void init(const std::string& resourceName);
|
||||||
|
void deinit();
|
||||||
|
|
||||||
|
int stream_open(int stream_index, AVFormatContext *pFormatCtx);
|
||||||
|
|
||||||
|
bool update();
|
||||||
|
|
||||||
|
static void video_thread_loop(VideoState *is);
|
||||||
|
static void decode_thread_loop(VideoState *is);
|
||||||
|
|
||||||
|
void video_display(VideoPicture* vp);
|
||||||
|
void video_refresh();
|
||||||
|
|
||||||
|
int queue_picture(AVFrame *pFrame, double pts);
|
||||||
|
double synchronize_video(AVFrame *src_frame, double pts);
|
||||||
|
|
||||||
|
double get_audio_clock();
|
||||||
|
double get_video_clock();
|
||||||
|
double get_external_clock();
|
||||||
|
double get_master_clock();
|
||||||
|
|
||||||
|
static int OgreResource_Read(void *user_data, uint8_t *buf, int buf_size);
|
||||||
|
static int OgreResource_Write(void *user_data, uint8_t *buf, int buf_size);
|
||||||
|
static int64_t OgreResource_Seek(void *user_data, int64_t offset, int whence);
|
||||||
|
|
||||||
|
Ogre::TexturePtr mTexture;
|
||||||
|
|
||||||
|
MovieAudioFactory* mAudioFactory;
|
||||||
|
boost::shared_ptr<MovieAudioDecoder> mAudioDecoder;
|
||||||
|
|
||||||
|
Ogre::DataStreamPtr stream;
|
||||||
|
AVFormatContext* format_ctx;
|
||||||
|
|
||||||
|
int av_sync_type;
|
||||||
|
uint64_t external_clock_base;
|
||||||
|
|
||||||
|
AVStream** audio_st;
|
||||||
|
PacketQueue audioq;
|
||||||
|
|
||||||
|
AVStream** video_st;
|
||||||
|
double frame_last_pts;
|
||||||
|
double video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame
|
||||||
|
PacketQueue videoq;
|
||||||
|
SwsContext* sws_context;
|
||||||
|
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
|
||||||
|
AVFrame* rgbaFrame; // used as buffer for the frame converted from its native format to RGBA
|
||||||
|
int pictq_size, pictq_rindex, pictq_windex;
|
||||||
|
boost::mutex pictq_mutex;
|
||||||
|
boost::condition_variable pictq_cond;
|
||||||
|
|
||||||
|
|
||||||
|
boost::thread parse_thread;
|
||||||
|
boost::thread video_thread;
|
||||||
|
|
||||||
|
volatile bool quit;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -29,6 +29,9 @@ material openmw_objects_base
|
||||||
env_map false
|
env_map false
|
||||||
env_map_color 1 1 1
|
env_map_color 1 1 1
|
||||||
|
|
||||||
|
alphaTestMode 0
|
||||||
|
alphaTestValue 0
|
||||||
|
|
||||||
pass
|
pass
|
||||||
{
|
{
|
||||||
vertex_program openmw_objects_vertex
|
vertex_program openmw_objects_vertex
|
||||||
|
@ -50,6 +53,8 @@ material openmw_objects_base
|
||||||
env_map $env_map
|
env_map $env_map
|
||||||
env_map_color $env_map_color
|
env_map_color $env_map_color
|
||||||
use_parallax $use_parallax
|
use_parallax $use_parallax
|
||||||
|
alphaTestMode $alphaTestMode
|
||||||
|
alphaTestValue $alphaTestValue
|
||||||
}
|
}
|
||||||
|
|
||||||
diffuse $diffuse
|
diffuse $diffuse
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#define DARK_MAP @shPropertyHasValue(darkMap)
|
#define DARK_MAP @shPropertyHasValue(darkMap)
|
||||||
#define SPEC_MAP @shPropertyHasValue(specMap) && SPECULAR
|
#define SPEC_MAP @shPropertyHasValue(specMap) && SPECULAR
|
||||||
|
|
||||||
|
#define ALPHATEST_MODE @shPropertyString(alphaTestMode)
|
||||||
|
|
||||||
#define PARALLAX @shPropertyBool(use_parallax)
|
#define PARALLAX @shPropertyBool(use_parallax)
|
||||||
#define PARALLAX_SCALE 0.04
|
#define PARALLAX_SCALE 0.04
|
||||||
#define PARALLAX_BIAS -0.02
|
#define PARALLAX_BIAS -0.02
|
||||||
|
@ -354,6 +356,10 @@
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ALPHATEST_MODE != 0
|
||||||
|
shUniform(float, alphaTestValue) @shUniformProperty1f(alphaTestValue, alphaTestValue)
|
||||||
|
#endif
|
||||||
|
|
||||||
SH_START_PROGRAM
|
SH_START_PROGRAM
|
||||||
{
|
{
|
||||||
float4 newUV = UV;
|
float4 newUV = UV;
|
||||||
|
@ -399,6 +405,29 @@
|
||||||
float4 diffuse = float4(1,1,1,1);
|
float4 diffuse = float4(1,1,1,1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ALPHATEST_MODE == 1
|
||||||
|
if (diffuse.a >= alphaTestValue)
|
||||||
|
discard;
|
||||||
|
#elif ALPHATEST_MODE == 2
|
||||||
|
if (diffuse.a != alphaTestValue)
|
||||||
|
discard;
|
||||||
|
#elif ALPHATEST_MODE == 3
|
||||||
|
if (diffuse.a > alphaTestValue)
|
||||||
|
discard;
|
||||||
|
#elif ALPHATEST_MODE == 4
|
||||||
|
if (diffuse.a <= alphaTestValue)
|
||||||
|
discard;
|
||||||
|
#elif ALPHATEST_MODE == 5
|
||||||
|
if (diffuse.a == alphaTestValue)
|
||||||
|
discard;
|
||||||
|
#elif ALPHATEST_MODE == 6
|
||||||
|
if (diffuse.a < alphaTestValue)
|
||||||
|
discard;
|
||||||
|
#elif ALPHATEST_MODE == 7
|
||||||
|
discard;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if DETAIL_MAP
|
#if DETAIL_MAP
|
||||||
#if @shPropertyString(detailMapUVSet)
|
#if @shPropertyString(detailMapUVSet)
|
||||||
diffuse *= shSample(detailMap, newUV.zw)*2;
|
diffuse *= shSample(detailMap, newUV.zw)*2;
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
[Display%20Format]
|
|
||||||
Record%20Status%20Display=Icon Only
|
|
||||||
Referenceable%20ID%20Type%20Display=Text Only
|
|
||||||
|
|
||||||
[Window%20Size]
|
|
||||||
Height=900
|
|
||||||
Width=1440
|
|
|
@ -116,10 +116,9 @@ This is probably enough to create around 90 string filters you will eventually n
|
||||||
-- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched
|
-- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched
|
||||||
by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression:
|
by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression:
|
||||||
\mono{string("armor type", ".* gauntlet"))} because \mono{.*} in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet.
|
\mono{string("armor type", ".* gauntlet"))} because \mono{.*} in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet.
|
||||||
There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it?
|
There are left and right gauntlets in the \MW{} so this will evaluate to true for both. Simple, isn't it?
|
||||||
|
|
||||||
Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, we are under impression
|
Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, we are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned
|
||||||
that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned
|
|
||||||
\mono{.*} is needed and therefore the following description of regexps can be skipped by vast majority of readers.
|
\mono{.*} is needed and therefore the following description of regexps can be skipped by vast majority of readers.
|
||||||
|
|
||||||
Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple:
|
Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple:
|
||||||
|
@ -131,19 +130,18 @@ Before introducing other ways to choose between characters, I want explain ancho
|
||||||
You surely should know about \mono{\textasciicircum} anchor and \mono{\textdollar}. Putting \mono{\textasciicircum} will tell to \OCS{}
|
You surely should know about \mono{\textasciicircum} anchor and \mono{\textdollar}. Putting \mono{\textasciicircum} will tell to \OCS{}
|
||||||
to look on the beginning of string, while \mono{\textdollar} is used to mark the end of it. For instance, pattern
|
to look on the beginning of string, while \mono{\textdollar} is used to mark the end of it. For instance, pattern
|
||||||
\mono{\textasciicircum{}Pink.* elephant.\textdollar} will match any sentence beginning with the word \mono{Pink} and ending with
|
\mono{\textasciicircum{}Pink.* elephant.\textdollar} will match any sentence beginning with the word \mono{Pink} and ending with
|
||||||
\mono{ elephant.}. Pink fat elephant. Pink cute elephant. It does not matter what is in between, because \mono{.*} is used.
|
\mono{ elephant.}. Pink fat elephant. Pink cute elephant. It does not matter what is in between because \mono{.*} is used.
|
||||||
|
|
||||||
You have already seen the power of the simple \mono{.*}. But what if you want to chose between only two (or more) letters? Well, this is when
|
You have already seen the power of the simple \mono{.*}. But what if you want to chose between only two (or more) letters? Well, this is when
|
||||||
\mono{[|]} comes in handy. If you write something like: \mono{\textasciicircum[a|k].*} you are simply telling \OCS{} to filter anything that
|
\mono{[|]} comes in handy. If you write something like: \mono{\textasciicircum[a|k].*} you are simply telling \OCS{} to filter anything that
|
||||||
starts with either \mono{a} or \mono{k}. Using \mono{\textasciicircum[a|k|l].*} will work in the same manner, but it will also cover
|
starts with either \mono{a} or \mono{k}. Using \mono{\textasciicircum[a|k|l].*} will work in the same manner, that is; it will also cover strings starting with \mono{l} as well.
|
||||||
strings starting with \mono{l}.
|
|
||||||
|
|
||||||
What if you want to match more than just one latter? Just use \mono{(|)}. it is pretty similar to the above one letter as you see, but it is
|
What if you want to match more than just one latter? Just use \mono{(|)}. It is pretty similar to the above one letter as you see, but it is
|
||||||
used to fit more than just one character. For instance: \mono{\textasciicircum(Pink|Green).* (elephant|crocodile).\textdollar} will be
|
used to fit more than just one character. For instance: \mono{\textasciicircum(Pink|Green).* (elephant|crocodile).\textdollar} will be
|
||||||
true for all sentences starting with \mono{Pink} or \mono{Green} and ending with either \mono{elephant.} or \mono{crocodile.}.
|
true for all sentences starting with \mono{Pink} or \mono{Green} and ending with either \mono{elephant.} or \mono{crocodile.}.
|
||||||
|
|
||||||
Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on
|
Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on
|
||||||
Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use \OCS{} effectively to be sure.
|
Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use \OCS{} effectively.
|
||||||
|
|
||||||
\paragraph{Value -- value(``value'', (``open'', ``close''))}
|
\paragraph{Value -- value(``value'', (``open'', ``close''))}
|
||||||
While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like
|
While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like
|
||||||
|
@ -169,12 +167,11 @@ belonging to the same group of logical expressions.
|
||||||
|
|
||||||
\paragraph{not -- not expression()}
|
\paragraph{not -- not expression()}
|
||||||
Sometimes you may be in need of reversing the output of the expression. This is where \textit{not} comes in handy. Adding \textit{not} before
|
Sometimes you may be in need of reversing the output of the expression. This is where \textit{not} comes in handy. Adding \textit{not} before
|
||||||
expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets
|
expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Parenthesis are not needed: \textit{not} will revert only the first expression following it.
|
||||||
are not needed: \textit{not} will revert only the first expression following it.
|
|
||||||
|
|
||||||
To show this on know example, let's consider the \mono{string("armor type", ".* gauntlet"))} filter. As we mentioned earlier this will return true
|
To show this on know example, let's consider the \mono{string("armor type", ".* gauntlet"))} filter. As we mentioned earlier this will return true
|
||||||
for every gauntlet found in game. In order to show everything, but gauntlets we simply do \mono{not string("armor type", ".* gauntlet"))}.
|
for every gauntlet found in game. In order to show everything, but gauntlets we simply do \mono{not string("armor type", ".* gauntlet"))}.
|
||||||
This is probably not the most useful filter on earth, but this is not a surprise: real value of \textit{not} expression shines when combined with
|
This is probably not the most useful filter on earth. The real value of \textit{not} expression shines when combined with
|
||||||
\textit{or}, \textit{and} filters.
|
\textit{or}, \textit{and} filters.
|
||||||
|
|
||||||
\paragraph{or -- or(expression1(), expression2())}
|
\paragraph{or -- or(expression1(), expression2())}
|
||||||
|
|
|
@ -30,5 +30,6 @@
|
||||||
\input{creating_file}
|
\input{creating_file}
|
||||||
\input{windows}
|
\input{windows}
|
||||||
\input{tables}
|
\input{tables}
|
||||||
|
\input{recordtypes}
|
||||||
\input{filters}
|
\input{filters}
|
||||||
\end{document}
|
\end{document}
|
||||||
|
|
26
manual/opencs/recordmodification.tex
Normal file
26
manual/opencs/recordmodification.tex
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
\section{Records Modification}
|
||||||
|
|
||||||
|
\subsection{Introduction}
|
||||||
|
So far you learned how to browse trough records stored inside the content files, but not how to modify them using the \OCS{} editor. Although browsing is certainly a usefull ability on it's own, You probabbly counted on doing actual editing with this editor. There are few ways user can alter records stored in the content files, each suited for certain class of a problem. In this section We will describe how to do change records using tables interface and edit panel.
|
||||||
|
|
||||||
|
\subsubsection{Glossary}
|
||||||
|
\begin{description}
|
||||||
|
\item[Edit Panel] Interface element used inside the \OCS{} to present records data for editing. Unlike table it showes only one record at the time. However it also presents fields that are not visible inside the table. It is also safe to say that Edit Panel presents data in way that is easier to read thanks to it's horizontal layout.
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
\subsection{Edit Panel Interface}
|
||||||
|
Edit Panel is designed to aid you with record modification tasks. As It has been said, it uses vertical layout and presents some additional fields when compared with the table -- and some fields, even if they are actually displayed in the table, clearly ill-suited for modification isnide of them (this applies to fields that holds long text strings -- like descriptions). It also displays visual difference beetween non-editable field and editable.\\
|
||||||
|
To open edit panel, please open context menu on any record and choose edit action. This will open edit panel in the same window as your table and will present you the record fields. First data fields are actually not user editable and presented in the form of the text labels at the top of the edit panel. Lower data fields are presented in the form of actually user-editable widgets. Those includes spinboxes, text edits and text fields\footnote{Those are actually a valid terms used to describe classes of the user interface elements. If you don't understand those, don't worry -- those are very standard {GUI} elements present in almost every application since the rise of the desktop metaphor.}. Once you will finish editing one of those fields, data will be updated. There is no apply button of any sort -- simply use one of those widgets and be merry.\\
|
||||||
|
In addition to that you probabbly noticed some icons in the bar located at the very bottom of the edit panel. Those can be used to perform the following actions:
|
||||||
|
|
||||||
|
\begin{description}
|
||||||
|
\item[Preview] This will launch simple preview panel -- which will be described later.
|
||||||
|
\item[Next] This will switch edit panel to the next record. It is worth noticing that edit panel will skip deleted records.
|
||||||
|
\item[Prev] Do We really need to say what this button does? I guess we should! Well, this will switch edit panel to former record. Deleted records are skipped.
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
\subsection{Verification tool}
|
||||||
|
As you could notice there is nothing that can stop you from breaking the game by violating record fields logic, and yet -- it is something that you are always trying to avoid. To adress this problem \OCS{} utilizes so called verification tool (or verifer as many prefer to call it) that basicly goes trough all records and checks if it contains any illogical fields. This includes for instance torch duration equal 0\footnote{Interestingly negative values are perfectly fine: they indicate that light source has no duration limit at all. There are records like this in the original game.} or characters without name, race or any other record with a mandatory field missing.\\
|
||||||
|
This tool is even more usefull than it seems. If you somehow delete race that is used by some of the characters, all those characters will be suddenly broken. As a rule of thumb it is a good idea to use verifer before saving your content file.\\
|
||||||
|
To launch this usefull tool %don't remember, todo...
|
||||||
|
Resoults are presented as a yet another table with short (and hopefully descriptive enough) description of the identified problem. It is worth noticing that some records located in the \MW{} esm files are listed by the verification tool -- it is not fault of our tool: those records are just broken. For instance, you actually may find the 0 duration torch. However, those records are usually not placed in game world itself -- and that's good since \MW{} game engine will crash if player equip light source like this!\footnote{We would like to thanks \BS{} for such a usefull testing material. It makes us feel special.}
|
18
manual/opencs/recordtypes.tex
Normal file
18
manual/opencs/recordtypes.tex
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
\section{Record Types}
|
||||||
|
|
||||||
|
\subsection{Introduction}
|
||||||
|
A gameworld contains many objects, such as chests, weapons and monsters. All these objects are merely instances of templates that we call Referenceables. The OpenCS Referenceables table contains information about each of these template objects, eg. its value and weight in the case of items and an aggression level in the case of NPCs.
|
||||||
|
|
||||||
|
Let's go through all Record Types and discuss what you can tell OpenCS about them.
|
||||||
|
|
||||||
|
\begin{description}
|
||||||
|
\item[Activator:] When the player enters the same cell as this object, a script is started. Often it also has a \textbf{Script} attached to it, though it not mandatory. These scripts are small bits of code written in a special scripting language that OpenCS can read and interpret.
|
||||||
|
\item[Potion:] This is a potion that is not self-made. It has an \textbf{Icon} for your inventory, Aside from the self-explanatory \textbf{Weight} and \textbf{Coin Value}, it has an attribute called \textbf{Auto Calc} set to ``False''. This means that the effects of this potion are preconfigured. This does not happen when the player makes their own potion.
|
||||||
|
\item[Apparatus:] This is a tool to make potions. Again there's an icon for your inventory as well as a weight and a coin value. It also has a \textbf{Quality} value attached to it: higher the number, the better the effect on your potions will be. The \textbf{Apparatus Type} describes if the item is a Calcinator, Retort, Alembir or Mortar & Pestal. Each has a different effect on the potion the player makes. For more information on this subject, please refer to the \href{http://www.uesp.net/wiki/Morrowind:Alchemy#Tools}{UESP page on Alchemy Tools}.
|
||||||
|
\item[Armor:] This type of item adds \textbf{Enchantment Points} to the mix. Every piece of clothing or armor has a ''pool'' of potential magicka that gets unlocked when you enchant it. Strong enchantments consume more magicka from this pool: the stronger the enchantment, the more Enchantment Points each cast will take up. For more information on this subject, please refer to the \href{Enchant page on UESP}{http://www.uesp.net/wiki/Morrowind:Enchant}. \textbf{Health} means the amount of hit points this piece of armor has. If it sustains enough damage, the armor will be destroyed. Finally, \textbf{Armor Value} tells the game how much points to add to the player character's Armor Rating.
|
||||||
|
\item[Book:] This includes scrolls and notes. For the game to make the distinction between books and scrolls, an extra property, \textbf{Scroll}, has been added. Under the \textbf{Skill} column a scroll or book can have an in-game skill listed. Reading this item will raise the player's level in that specific skill. For more information on this, please refer to the \href{Skill Books page on UESP}{http://www.uesp.net/wiki/Morrowind:Skill_Books}.
|
||||||
|
\item[Clothing:] These items work just like Armors, but confer no protective properties. Rather than ``Armor Type'', these items have a ``Clothing Type''.
|
||||||
|
\item[Container:] This is all the stuff that stores items, from chests to sacks to plants. Its \textbf{Capacity} shows how much stuff you can put in the container. You can compare it to the maximum allowed load a player character can carry (who will get over-encumbered and unable to move if he crosses this threshold). A container, however, will just refuse to take the item in question when it gets ''over-encumbered''. \textbf{Organic Container}s are containers such as plants. Containers that \textbf{Respawn} are not safe to store stuff in. After a certain amount of time they will reset to their default contents, meaning that everything in it is gone forever.
|
||||||
|
\item[Creature:] These can be monsters, animals and the like.
|
||||||
|
|
||||||
|
\end{description}
|
|
@ -14,21 +14,14 @@ Let's browse through the various screens and see what all these tables show.
|
||||||
\begin{description}
|
\begin{description}
|
||||||
\item[Record:] An entry in \OCS{} representing an item, location, sound, NPC or anything else.
|
\item[Record:] An entry in \OCS{} representing an item, location, sound, NPC or anything else.
|
||||||
|
|
||||||
\item[Reference, Referenceable:] When an item is placed in the world, it does not create a new record each time. For example, the game world might
|
\item[Reference, Referenceable:] When an item is placed in the world, it isn't an isolated and unique object. For example, the game world might contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record in the game's library: the Exquisite Belt record. In this case, all those belts in crates and on NPCs are references. The central Exquisite Belt record is called a referenceable. This allows modders to make changes to all items of the same type. For example, if you want all exquisite belts to have 4000 enchantment points rather than 400, you will only need to change the referenceable Exquisite Belt rather than all exquisite belts references individually.
|
||||||
contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record: the Exquisite Belt record.
|
|
||||||
In this case, all those belts in crates and on NPCs are references. The central Exquisite Belt record is called a referenceable. This allows modders
|
|
||||||
to make changes to all items of the same type. For example, if you want all exquisite belts to have 4000 enchantment points rather than 400, you will
|
|
||||||
only need to change the referenceable Exquisite Belt rather than all exquisite belts references individually.
|
|
||||||
\end{description}
|
\end{description}
|
||||||
|
|
||||||
\subsubsection{Recurring Terms}
|
\subsubsection{Recurring Terms}
|
||||||
Some columns are recurring throughout \OCS. They show up in (nearly) every table in \OCS.
|
Some columns are recurring throughout \OCS. They show up in (nearly) every table in \OCS.
|
||||||
|
|
||||||
\begin{description}
|
\begin{description}
|
||||||
\item[ID] Each item, location, sound, etc. gets the same unique identifier in both \OCS{} and \MW. This is usually a very self-explanatory name.
|
\item[ID] Many items in the OpenCS database have a unique identifier in both OpenCS and Morrowind. This is usually a very self-explanatory name. For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example, you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind and OpenCS, the ID is the primary way to identify all these different parts of the game.
|
||||||
For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example,
|
|
||||||
you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind
|
|
||||||
and \OCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name.
|
|
||||||
\item[Modified] This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist.
|
\item[Modified] This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist.
|
||||||
\item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with
|
\item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with
|
||||||
optionally the Bloodmoon and Tribunal expansions.
|
optionally the Bloodmoon and Tribunal expansions.
|
||||||
|
@ -96,8 +89,8 @@ a record is, it will need specific information to function. For example, an NPC
|
||||||
does not. All Record Types contain at least a~model. How else would the player see them? Usually they also have a Name, which is what you see
|
does not. All Record Types contain at least a~model. How else would the player see them? Usually they also have a Name, which is what you see
|
||||||
when you hover your reticle over the object.
|
when you hover your reticle over the object.
|
||||||
|
|
||||||
Let's go through all Record Types and discuss what you can tell \OCS{} about them.
|
This is a library of all the items, triggers, containers, NPCs, etc. in the game. There are several kinds of Record Types. Depending on which type a record is, it will need specific information to function. For example, an NPC needs a value attached to its aggression level. A chest, of course, does not. All Record Types contain at least a model. How else would the player see them? Usually they also have a Name, which is what you see when you hover your reticle over the object.
|
||||||
|
|
||||||
|
Please refer to the Record Types section for an overview of what each type of Referenceable does and what you can tell OpenCS about these objects.
|
||||||
|
|
||||||
\begin{description}
|
|
||||||
\item[Activator:] This is an item that, when activated, starts a script or even just shows a~tooltip.
|
|
||||||
\end{description}
|
\end{description}
|
117
readme.txt
117
readme.txt
|
@ -98,6 +98,123 @@ Allowed options:
|
||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
0.33.0
|
||||||
|
|
||||||
|
Bug #371: If console assigned to ` (probably to any symbolic key), "`" symbol will be added to console every time it closed
|
||||||
|
Bug #1148: Some books'/scrolls' contents are displayed incorrectly
|
||||||
|
Bug #1290: Editor: status bar is not updated when record filter is changed
|
||||||
|
Bug #1292: Editor: Documents are not removed on closing the last view
|
||||||
|
Bug #1301: Editor: File->Exit only checks the document it was issued from.
|
||||||
|
Bug #1353: Bluetooth on with no speaker connected results in significantly longer initial load times
|
||||||
|
Bug #1436: NPCs react from too far distance
|
||||||
|
Bug #1472: PC is placed on top of following NPC when changing cell
|
||||||
|
Bug #1487: Tall PC can get stuck in staircases
|
||||||
|
Bug #1565: Editor: Subviews are deleted on shutdown instead when they are closed
|
||||||
|
Bug #1623: Door marker on Ghorak Manor's balcony makes PC stuck
|
||||||
|
Bug #1633: Loaddoor to Sadrith Mora, Telvanni Council House spawns PC in the air
|
||||||
|
Bug #1655: Use Appropriate Application Icons on Windows
|
||||||
|
Bug #1679: Tribunal expansion, Meryn Othralas the backstage manager in the theatre group in Mournhold in the great bazaar district is floating a good feet above the ground.
|
||||||
|
Bug #1705: Rain is broken in third person
|
||||||
|
Bug #1706: Thunder and lighting still occurs while the game is paused during the rain
|
||||||
|
Bug #1708: No long jumping
|
||||||
|
Bug #1710: Editor: ReferenceableID drag to references record filter field creates incorrect filter
|
||||||
|
Bug #1712: Rest on Water
|
||||||
|
Bug #1715: "Cancel" button is not always on the same side of menu
|
||||||
|
Bug #1725: Editor: content file can be opened multiple times from the same dialogue
|
||||||
|
Bug #1730: [MOD: Less Generic Nerevarine] Compile failure attempting to enter the Corprusarium.
|
||||||
|
Bug #1733: Unhandled ffmpeg sample formats
|
||||||
|
Bug #1735: Editor: "Edit Record" context menu button not opening subview for journal infos
|
||||||
|
Bug #1750: Editor: record edits result in duplicate entries
|
||||||
|
Bug #1789: Editor: Some characters cannot be used in addon name
|
||||||
|
Bug #1803: Resizing the map does not keep the pre-resize center at the post-resize center
|
||||||
|
Bug #1821: Recovering Cloudcleaver quest: attacking Sosia is considered a crime when you side with Hlormar
|
||||||
|
Bug #1838: Editor: Preferences window appears off screen
|
||||||
|
Bug #1839: Editor: Record filter title should be moved two pixels to the right
|
||||||
|
Bug #1849: Subrecord error in MAO_Containers
|
||||||
|
Bug #1854: Knocked-out actors don't fully act knocked out
|
||||||
|
Bug #1855: "Soul trapped" sound doesn't play
|
||||||
|
Bug #1857: Missing sound effect for enchanted items with empty charge
|
||||||
|
Bug #1859: Missing console command: ResetActors (RA)
|
||||||
|
Bug #1861: Vendor category "MagicItems" is unhandled
|
||||||
|
Bug #1862: Launcher doesn't start if a file listed in launcher.cfg has correct name but wrong capitalization
|
||||||
|
Bug #1864: Editor: Region field for cell record in dialogue subview not working
|
||||||
|
Bug #1869: Editor: Change label "Musics" to "Music"
|
||||||
|
Bug #1870: Goblins killed while knocked down remain in knockdown-pose
|
||||||
|
Bug #1874: CellChanged events should not trigger when crossing exterior cell border
|
||||||
|
Bug #1877: Spriggans killed instantly if hit while regening
|
||||||
|
Bug #1878: Magic Menu text not un-highlighting correctly when going from spell to item as active magic
|
||||||
|
Bug #1881: Stuck in ceiling when entering castle karstaags tower
|
||||||
|
Bug #1884: Unlit torches still produce a burning sound
|
||||||
|
Bug #1885: Can type text in price field in barter window
|
||||||
|
Bug #1887: Equipped items do not emit sounds
|
||||||
|
Bug #1889: draugr lord aesliip will attack you and remain non-hostile
|
||||||
|
Bug #1892: Guard asks player to pay bounty of 0 gold
|
||||||
|
Bug #1895: getdistance should only return max float if ref and target are in different worldspaces
|
||||||
|
Bug #1896: Crash Report
|
||||||
|
Bug #1897: Conjured Equipment cant be re-equipped if removed
|
||||||
|
Bug #1898: Only Gidar Verothan follows you during establish the mine quest
|
||||||
|
Bug #1900: Black screen when you open the door and breath underwater
|
||||||
|
Bug #1904: Crash on casting recall spell
|
||||||
|
Bug #1906: Bound item checks should use the GMSTs
|
||||||
|
Bug #1907: Bugged door. Mournhold, The Winged Guar
|
||||||
|
Bug #1908: Crime reported for attacking Drathas Nerus's henchmen while they attack Dilborn
|
||||||
|
Bug #1909: Weird Quest Flow Infidelities quest
|
||||||
|
Bug #1910: Follower fighting with gone npc
|
||||||
|
Bug #1911: Npcs will drown themselves
|
||||||
|
Bug #1912: World map arrow stays static when inside a building
|
||||||
|
Bug #1920: Ulyne Henim disappears when game is loaded inside Vas
|
||||||
|
Bug #1922: alchemy-> potion of paralyze
|
||||||
|
Bug #1923: "levitation magic cannot be used here" shows outside of tribunal
|
||||||
|
Bug #1927: AI prefer melee over magic.
|
||||||
|
Bug #1929: Tamriel Rebuilt: Named cells that lie within the overlap with Morrowind.esm are not shown
|
||||||
|
Bug #1932: BTB - Spells 14.1 magic effects don´t overwrite the Vanilla ones but are added
|
||||||
|
Bug #1935: Stacks of items are worth more when sold individually
|
||||||
|
Bug #1940: Launcher does not list addon files if base game file is renamed to a different case
|
||||||
|
Bug #1946: Mod "Tel Nechim - moved" breaks savegames
|
||||||
|
Bug #1947: Buying/Selling price doesn't properly affect the growth of mercantile skill
|
||||||
|
Bug #1950: followers from east empire company quest will fight each other if combat happens with anything
|
||||||
|
Bug #1958: Journal can be scrolled indefinitely with a mouse wheel
|
||||||
|
Bug #1959: Follower not leaving party on quest end
|
||||||
|
Bug #1960: Key bindings not always saved correctly
|
||||||
|
Bug #1961: Spell merchants selling racial bonus spells
|
||||||
|
Bug #1967: segmentation fault on load saves
|
||||||
|
Bug #1968: Jump sounds are not controlled by footsteps slider, sound weird compared to footsteps
|
||||||
|
Bug #1970: PC suffers silently when taking damage from lava
|
||||||
|
Bug #1971: Dwarven Sceptre collision area is not removed after killing one
|
||||||
|
Bug #1974: Dalin/Daris Norvayne follows player indefinitely
|
||||||
|
Bug #1975: East Empire Company faction rank breaks during Raven Rock questline
|
||||||
|
Bug #1979: 0 strength = permanently over encumbered
|
||||||
|
Bug #1993: Shrine blessing in Maar Gan doesn't work
|
||||||
|
Bug #2008: Enchanted items do not recharge
|
||||||
|
Bug #2011: Editor: OpenCS script compiler doesn't handle member variable access properly
|
||||||
|
Bug #2016: Dagoth Ur already dead in Facility Cavern
|
||||||
|
Bug #2017: Fighters Guild Quest: The Code Book - dialogue loop when UMP is loaded.
|
||||||
|
Bug #2019: Animation of 'Correct UV Mudcrabs' broken
|
||||||
|
Bug #2022: Alchemy window - Removing ingredient doesn't remove the number of ingredients
|
||||||
|
Bug #2025: Missing mouse-over text for non affordable items
|
||||||
|
Bug #2028: [MOD: Tamriel Rebuilt] Crashing when trying to enter interior cell "Ruinous Keep, Great Hall"
|
||||||
|
Bug #2029: Ienith Brothers Thiev's Guild quest journal entry not adding
|
||||||
|
Feature #471: Editor: Special case implementation for top-level window with single sub-window
|
||||||
|
Feature #472: Editor: Sub-Window re-use settings
|
||||||
|
Feature #704: Font colors import from fallback settings
|
||||||
|
Feature #854: Editor: Add user setting to show status bar
|
||||||
|
Feature #879: Editor: Open sub-views in a new top-level window
|
||||||
|
Feature #932: Editor: magic effect table
|
||||||
|
Feature #937: Editor: Path Grid table
|
||||||
|
Feature #938: Editor: Sound Gen table
|
||||||
|
Feature #1117: Death and LevelUp music
|
||||||
|
Feature #1226: Editor: Request UniversalId editing from table columns
|
||||||
|
Feature #1310: Editor: Rendering User Settings
|
||||||
|
Feature #1545: Targeting console on player
|
||||||
|
Feature #1597: Editor: Render terrain
|
||||||
|
Feature #1695: Editor: add column for CellRef's global variable
|
||||||
|
Feature #1696: Editor: use ESM::Cell's RefNum counter
|
||||||
|
Feature #1697: Redden player's vision when hit
|
||||||
|
Feature #1856: Spellcasting for non-biped creatures
|
||||||
|
Feature #1879: Editor: Run OpenMW with the currently edited content list
|
||||||
|
Task #1851: Move AI temporary state out of AI packages
|
||||||
|
Task #1865: Replace char type in records
|
||||||
|
|
||||||
0.32.0
|
0.32.0
|
||||||
|
|
||||||
Bug #1132: Unable to jump when facing a wall
|
Bug #1132: Unable to jump when facing a wall
|
||||||
|
|
Loading…
Reference in a new issue