mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-10 03:39:55 +00:00
Merge branch 'master' of git://github.com/zinnschlag/openmw into graphics
This commit is contained in:
commit
3ec703e6af
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,3 +15,5 @@ makefile
|
||||
data
|
||||
*.kdev4
|
||||
CMakeLists.txt.user
|
||||
*.swp
|
||||
*.swo
|
||||
|
@ -1,123 +0,0 @@
|
||||
Bitstream Vera Fonts Copyright
|
||||
|
||||
The fonts have a generous copyright, allowing derivative works (as
|
||||
long as "Bitstream" or "Vera" are not in the names), and full
|
||||
redistribution (so long as they are not *sold* by themselves). They
|
||||
can be be bundled, redistributed and sold with any software.
|
||||
|
||||
The fonts are distributed under the following copyright:
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
|
||||
Vera is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the fonts accompanying this license ("Fonts") and associated
|
||||
documentation files (the "Font Software"), to reproduce and distribute
|
||||
the Font Software, including without limitation the rights to use,
|
||||
copy, merge, publish, distribute, and/or sell copies of the Font
|
||||
Software, and to permit persons to whom the Font Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be included in all copies of one or more of the Font Software
|
||||
typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in
|
||||
particular the designs of glyphs or characters in the Fonts may be
|
||||
modified and additional glyphs or characters may be added to the
|
||||
Fonts, only if the fonts are renamed to names not containing either
|
||||
the words "Bitstream" or the word "Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts
|
||||
or Font Software that has been modified and is distributed under the
|
||||
"Bitstream Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy of one or more of the Font Software typefaces may be sold by
|
||||
itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
|
||||
BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
|
||||
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
|
||||
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome
|
||||
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||
otherwise to promote the sale, use or other dealings in this Font
|
||||
Software without prior written authorization from the Gnome Foundation
|
||||
or Bitstream Inc., respectively. For further information, contact:
|
||||
fonts at gnome dot org.
|
||||
|
||||
Copyright FAQ
|
||||
=============
|
||||
|
||||
1. I don't understand the resale restriction... What gives?
|
||||
|
||||
Bitstream is giving away these fonts, but wishes to ensure its
|
||||
competitors can't just drop the fonts as is into a font sale system
|
||||
and sell them as is. It seems fair that if Bitstream can't make money
|
||||
from the Bitstream Vera fonts, their competitors should not be able to
|
||||
do so either. You can sell the fonts as part of any software package,
|
||||
however.
|
||||
|
||||
2. I want to package these fonts separately for distribution and
|
||||
sale as part of a larger software package or system. Can I do so?
|
||||
|
||||
Yes. A RPM or Debian package is a "larger software package" to begin
|
||||
with, and you aren't selling them independently by themselves.
|
||||
See 1. above.
|
||||
|
||||
3. Are derivative works allowed?
|
||||
Yes!
|
||||
|
||||
4. Can I change or add to the font(s)?
|
||||
Yes, but you must change the name(s) of the font(s).
|
||||
|
||||
5. Under what terms are derivative works allowed?
|
||||
|
||||
You must change the name(s) of the fonts. This is to ensure the
|
||||
quality of the fonts, both to protect Bitstream and Gnome. We want to
|
||||
ensure that if an application has opened a font specifically of these
|
||||
names, it gets what it expects (though of course, using fontconfig,
|
||||
substitutions could still could have occurred during font
|
||||
opening). You must include the Bitstream copyright. Additional
|
||||
copyrights can be added, as per copyright law. Happy Font Hacking!
|
||||
|
||||
6. If I have improvements for Bitstream Vera, is it possible they might get
|
||||
adopted in future versions?
|
||||
|
||||
Yes. The contract between the Gnome Foundation and Bitstream has
|
||||
provisions for working with Bitstream to ensure quality additions to
|
||||
the Bitstream Vera font family. Please contact us if you have such
|
||||
additions. Note, that in general, we will want such additions for the
|
||||
entire family, not just a single font, and that you'll have to keep
|
||||
both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
|
||||
glyphs to the font, they must be stylistically in keeping with Vera's
|
||||
design. Vera cannot become a "ransom note" font. Jim Lyles will be
|
||||
providing a document describing the design elements used in Vera, as a
|
||||
guide and aid for people interested in contributing to Vera.
|
||||
|
||||
7. I want to sell a software package that uses these fonts: Can I do so?
|
||||
|
||||
Sure. Bundle the fonts with your software and sell your software
|
||||
with the fonts. That is the intent of the copyright.
|
||||
|
||||
8. If applications have built the names "Bitstream Vera" into them,
|
||||
can I override this somehow to use fonts of my choosing?
|
||||
|
||||
This depends on exact details of the software. Most open source
|
||||
systems and software (e.g., Gnome, KDE, etc.) are now converting to
|
||||
use fontconfig (see www.fontconfig.org) to handle font configuration,
|
||||
selection and substitution; it has provisions for overriding font
|
||||
names and subsituting alternatives. An example is provided by the
|
||||
supplied local.conf file, which chooses the family Bitstream Vera for
|
||||
"sans", "serif" and "monospace". Other software (e.g., the XFree86
|
||||
core server) has other mechanisms for font substitution.
|
@ -67,35 +67,6 @@ endif()
|
||||
# We probably support older versions than this.
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
#
|
||||
# Pre-built binaries being used?
|
||||
#
|
||||
IF(EXISTS "${CMAKE_SOURCE_DIR}/prebuilt/vc100-mt-gd/ogre_1_7_1")
|
||||
set(PREBUILT_DIR "${CMAKE_SOURCE_DIR}/prebuilt/vc100-mt-gd")
|
||||
message (STATUS "OpenMW pre-built binaries found at ${PREBUILT_DIR}.")
|
||||
|
||||
SET(ENV{OGRE_HOME} "${PREBUILT_DIR}/ogre_1_7_1")
|
||||
|
||||
SET(ENV{BOOST_ROOT} "${PREBUILT_DIR}/boost_1_42_0")
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
set(ENV{BOOST_INCLUDEDIR} "${BOOST_ROOT}/include")
|
||||
set(ENV{BOOST_LIBRARYDIR} "${BOOST_ROOT}/lib")
|
||||
|
||||
set(ENV{FREETYPE_DIR} "${PREBUILT_DIR}/freetype-2.3.5-1")
|
||||
|
||||
set(USE_MPG123 OFF)
|
||||
set(USE_AUDIERE ON)
|
||||
set(AUDIERE_INCLUDE_DIR "${PREBUILT_DIR}/audiere-1.9.4/include")
|
||||
set(AUDIERE_LIBRARY "${PREBUILT_DIR}/audiere-1.9.4/lib/audiere.lib")
|
||||
|
||||
set(ENV{OPENALDIR} "${PREBUILT_DIR}/OpenAL 1.1 SDK")
|
||||
|
||||
set(BULLET_ROOT "${PREBUILT_DIR}/bullet")
|
||||
ELSE()
|
||||
message (STATUS "OpenMW pre-built binaries not found. Using standard locations.")
|
||||
ENDIF()
|
||||
|
||||
# source directory: libs
|
||||
|
||||
set(LIBDIR ${CMAKE_SOURCE_DIR}/libs)
|
||||
|
99
DejaVu Font License.txt
Normal file
99
DejaVu Font License.txt
Normal file
@ -0,0 +1,99 @@
|
||||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
|
||||
|
||||
Bitstream Vera Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
|
||||
a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the fonts accompanying this license ("Fonts") and associated
|
||||
documentation files (the "Font Software"), to reproduce and distribute the
|
||||
Font Software, including without limitation the rights to use, copy, merge,
|
||||
publish, distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall
|
||||
be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular
|
||||
the designs of glyphs or characters in the Fonts may be modified and
|
||||
additional glyphs or characters may be added to the Fonts, only if the fonts
|
||||
are renamed to names not containing either the words "Bitstream" or the word
|
||||
"Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font
|
||||
Software that has been modified and is distributed under the "Bitstream
|
||||
Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no
|
||||
copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
||||
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
|
||||
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
|
||||
FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome
|
||||
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||
otherwise to promote the sale, use or other dealings in this Font Software
|
||||
without prior written authorization from the Gnome Foundation or Bitstream
|
||||
Inc., respectively. For further information, contact: fonts at gnome dot
|
||||
org.
|
||||
|
||||
Arev Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the fonts accompanying this license ("Fonts") and
|
||||
associated documentation files (the "Font Software"), to reproduce
|
||||
and distribute the modifications to the Bitstream Vera Font Software,
|
||||
including without limitation the rights to use, copy, merge, publish,
|
||||
distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be included in all copies of one or more of the Font Software
|
||||
typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in
|
||||
particular the designs of glyphs or characters in the Fonts may be
|
||||
modified and additional glyphs or characters may be added to the
|
||||
Fonts, only if the fonts are renamed to names not containing either
|
||||
the words "Tavmjong Bah" or the word "Arev".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts
|
||||
or Font Software that has been modified and is distributed under the
|
||||
"Tavmjong Bah Arev" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy of one or more of the Font Software typefaces may be sold by
|
||||
itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
|
||||
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of Tavmjong Bah shall not
|
||||
be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in this Font Software without prior written authorization
|
||||
from Tavmjong Bah. For further information, contact: tavmjong @ free
|
||||
. fr.
|
||||
|
||||
$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
|
@ -1,8 +1,8 @@
|
||||
|
||||
#include "columnbase.hpp"
|
||||
|
||||
CSMWorld::ColumnBase::ColumnBase (const std::string& title, int flags)
|
||||
: mTitle (title), mFlags (flags)
|
||||
CSMWorld::ColumnBase::ColumnBase (const std::string& title, Display displayType, int flags)
|
||||
: mTitle (title), mDisplayType (displayType), mFlags (flags)
|
||||
{}
|
||||
|
||||
CSMWorld::ColumnBase::~ColumnBase() {}
|
||||
|
@ -14,7 +14,8 @@ namespace CSMWorld
|
||||
{
|
||||
enum Roles
|
||||
{
|
||||
Role_Flags = Qt::UserRole
|
||||
Role_Flags = Qt::UserRole,
|
||||
Role_Display = Qt::UserRole+1
|
||||
};
|
||||
|
||||
enum Flags
|
||||
@ -23,10 +24,18 @@ namespace CSMWorld
|
||||
Flag_Dialogue = 2 // column should be displayed in dialogue view
|
||||
};
|
||||
|
||||
enum Display
|
||||
{
|
||||
Display_String,
|
||||
Display_Integer,
|
||||
Display_Float
|
||||
};
|
||||
|
||||
std::string mTitle;
|
||||
int mFlags;
|
||||
Display mDisplayType;
|
||||
|
||||
ColumnBase (const std::string& title, int flag);
|
||||
ColumnBase (const std::string& title, Display displayType, int flag);
|
||||
|
||||
virtual ~ColumnBase();
|
||||
|
||||
@ -34,6 +43,7 @@ namespace CSMWorld
|
||||
|
||||
virtual bool isUserEditable() const;
|
||||
///< Can this column be edited directly by the user?
|
||||
|
||||
};
|
||||
|
||||
template<typename ESXRecordT>
|
||||
@ -42,8 +52,8 @@ namespace CSMWorld
|
||||
std::string mTitle;
|
||||
int mFlags;
|
||||
|
||||
Column (const std::string& title, int flags = Flag_Table | Flag_Dialogue)
|
||||
: ColumnBase (title, flags) {}
|
||||
Column (const std::string& title, Display displayType, int flags = Flag_Table | Flag_Dialogue)
|
||||
: ColumnBase (title, displayType, flags) {}
|
||||
|
||||
virtual QVariant get (const Record<ESXRecordT>& record) const = 0;
|
||||
|
||||
|
@ -8,7 +8,7 @@ namespace CSMWorld
|
||||
template<typename ESXRecordT>
|
||||
struct FloatValueColumn : public Column<ESXRecordT>
|
||||
{
|
||||
FloatValueColumn() : Column<ESXRecordT> ("Value") {}
|
||||
FloatValueColumn() : Column<ESXRecordT> ("Value", ColumnBase::Display_Float) {}
|
||||
|
||||
virtual QVariant get (const Record<ESXRecordT>& record) const
|
||||
{
|
||||
@ -31,7 +31,7 @@ namespace CSMWorld
|
||||
template<typename ESXRecordT>
|
||||
struct StringIdColumn : public Column<ESXRecordT>
|
||||
{
|
||||
StringIdColumn() : Column<ESXRecordT> ("ID") {}
|
||||
StringIdColumn() : Column<ESXRecordT> ("ID", ColumnBase::Display_String) {}
|
||||
|
||||
virtual QVariant get (const Record<ESXRecordT>& record) const
|
||||
{
|
||||
@ -47,7 +47,7 @@ namespace CSMWorld
|
||||
template<typename ESXRecordT>
|
||||
struct RecordStateColumn : public Column<ESXRecordT>
|
||||
{
|
||||
RecordStateColumn() : Column<ESXRecordT> ("*") {}
|
||||
RecordStateColumn() : Column<ESXRecordT> ("*", ColumnBase::Display_Integer) {}
|
||||
|
||||
virtual QVariant get (const Record<ESXRecordT>& record) const
|
||||
{
|
||||
@ -78,7 +78,8 @@ namespace CSMWorld
|
||||
{
|
||||
int mType;
|
||||
|
||||
FixedRecordTypeColumn (int type) : Column<ESXRecordT> ("Type", 0), mType (type) {}
|
||||
FixedRecordTypeColumn (int type)
|
||||
: Column<ESXRecordT> ("Type", ColumnBase::Display_Integer, 0), mType (type) {}
|
||||
|
||||
virtual QVariant get (const Record<ESXRecordT>& record) const
|
||||
{
|
||||
|
@ -51,6 +51,9 @@ QVariant CSMWorld::IdTable::headerData (int section, Qt::Orientation orientation
|
||||
if (role==ColumnBase::Role_Flags)
|
||||
return mIdCollection->getColumn (section).mFlags;
|
||||
|
||||
if (role==ColumnBase::Role_Display)
|
||||
return mIdCollection->getColumn (section).mDisplayType;
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,13 @@
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QAbstractTableModel>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QSpinBox>
|
||||
#include <QLineEdit>
|
||||
#include <QDataWidgetMapper>
|
||||
|
||||
#include "../../model/world/columnbase.hpp"
|
||||
#include "../../model/world/idtable.hpp"
|
||||
|
||||
CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document,
|
||||
bool createAndDelete)
|
||||
@ -23,6 +28,9 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM
|
||||
|
||||
int columns = model->columnCount();
|
||||
|
||||
mWidgetMapper = new QDataWidgetMapper (this);
|
||||
mWidgetMapper->setModel (model);
|
||||
|
||||
for (int i=0; i<columns; ++i)
|
||||
{
|
||||
int flags = model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt();
|
||||
@ -30,8 +38,54 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM
|
||||
if (flags & CSMWorld::ColumnBase::Flag_Dialogue)
|
||||
{
|
||||
layout->addWidget (new QLabel (model->headerData (i, Qt::Horizontal).toString()), i, 0);
|
||||
|
||||
CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display>
|
||||
(model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
|
||||
|
||||
QWidget *widget = 0;
|
||||
|
||||
if (model->flags (model->index (0, i)) & Qt::ItemIsEditable)
|
||||
{
|
||||
switch (display)
|
||||
{
|
||||
case CSMWorld::ColumnBase::Display_String:
|
||||
|
||||
layout->addWidget (widget = new QLineEdit, i, 1);
|
||||
break;
|
||||
|
||||
case CSMWorld::ColumnBase::Display_Integer:
|
||||
|
||||
/// \todo configure widget properly (range)
|
||||
layout->addWidget (widget = new QSpinBox, i, 1);
|
||||
break;
|
||||
|
||||
case CSMWorld::ColumnBase::Display_Float:
|
||||
|
||||
/// \todo configure widget properly (range, format?)
|
||||
layout->addWidget (widget = new QDoubleSpinBox, i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (display)
|
||||
{
|
||||
case CSMWorld::ColumnBase::Display_String:
|
||||
case CSMWorld::ColumnBase::Display_Integer:
|
||||
case CSMWorld::ColumnBase::Display_Float:
|
||||
|
||||
layout->addWidget (widget = new QLabel, i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (widget)
|
||||
mWidgetMapper->addMapping (widget, i);
|
||||
}
|
||||
}
|
||||
|
||||
mWidgetMapper->setCurrentModelIndex (
|
||||
dynamic_cast<CSMWorld::IdTable&> (*model).getModelIndex (id.getId(), 0));
|
||||
}
|
||||
|
||||
void CSVWorld::DialogueSubView::setEditLock (bool locked)
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "../doc/subview.hpp"
|
||||
|
||||
class QDataWidgetMapper;
|
||||
|
||||
namespace CSMDoc
|
||||
{
|
||||
class Document;
|
||||
@ -12,6 +14,7 @@ namespace CSVWorld
|
||||
{
|
||||
class DialogueSubView : public CSVDoc::SubView
|
||||
{
|
||||
QDataWidgetMapper *mWidgetMapper;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -21,5 +21,6 @@ void CSVWorld::TableSubView::setEditLock (bool locked)
|
||||
|
||||
void CSVWorld::TableSubView::rowActivated (const QModelIndex& index)
|
||||
{
|
||||
focusId (mTable->getUniversalId (index.row()));
|
||||
/// \todo re-enable, after dialogue sub views have been fixed up
|
||||
// focusId (mTable->getUniversalId (index.row()));
|
||||
}
|
@ -84,7 +84,7 @@ namespace MWBase
|
||||
///< Start playing music from the selected folder
|
||||
/// \param name of the folder that contains the playlist
|
||||
|
||||
virtual void say(MWWorld::Ptr reference, const std::string& filename) = 0;
|
||||
virtual void say(const MWWorld::Ptr &reference, const std::string& filename) = 0;
|
||||
///< Make an actor say some text.
|
||||
/// \param filename name of a sound file in "Sound/" in the data directory.
|
||||
|
||||
@ -92,10 +92,10 @@ namespace MWBase
|
||||
///< Say some text, without an actor ref
|
||||
/// \param filename name of a sound file in "Sound/" in the data directory.
|
||||
|
||||
virtual bool sayDone(MWWorld::Ptr reference=MWWorld::Ptr()) const = 0;
|
||||
virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const = 0;
|
||||
///< Is actor not speaking?
|
||||
|
||||
virtual void stopSay(MWWorld::Ptr reference=MWWorld::Ptr()) = 0;
|
||||
virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()) = 0;
|
||||
///< Stop an actor speaking
|
||||
|
||||
virtual SoundPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0;
|
||||
@ -105,14 +105,14 @@ namespace MWBase
|
||||
PlayMode mode=Play_Normal) = 0;
|
||||
///< Play a sound, independently of 3D-position
|
||||
|
||||
virtual SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId,
|
||||
virtual SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId,
|
||||
float volume, float pitch, PlayMode mode=Play_Normal) = 0;
|
||||
///< Play a sound from an object
|
||||
|
||||
virtual void stopSound3D(MWWorld::Ptr reference, const std::string& soundId) = 0;
|
||||
virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId) = 0;
|
||||
///< Stop the given object from playing the given sound,
|
||||
|
||||
virtual void stopSound3D(MWWorld::Ptr reference) = 0;
|
||||
virtual void stopSound3D(const MWWorld::Ptr &reference) = 0;
|
||||
///< Stop the given object from playing all sounds.
|
||||
|
||||
virtual void stopSound(const MWWorld::CellStore *cell) = 0;
|
||||
@ -121,7 +121,7 @@ namespace MWBase
|
||||
virtual void stopSound(const std::string& soundId) = 0;
|
||||
///< Stop a non-3d looping sound
|
||||
|
||||
virtual bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const = 0;
|
||||
virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const = 0;
|
||||
///< Is the given sound currently playing on the given object?
|
||||
|
||||
virtual void pauseSounds(int types=Play_TypeMask) = 0;
|
||||
|
@ -45,6 +45,7 @@ namespace MWWorld
|
||||
class Ptr;
|
||||
class TimeStamp;
|
||||
class ESMStore;
|
||||
class RefData;
|
||||
}
|
||||
|
||||
namespace MWBase
|
||||
@ -138,6 +139,9 @@ namespace MWBase
|
||||
|
||||
virtual std::string getCurrentCellName() const = 0;
|
||||
|
||||
virtual void removeRefScript (MWWorld::RefData *ref) = 0;
|
||||
//< Remove the script attached to ref from mLocalScripts
|
||||
|
||||
virtual MWWorld::Ptr getPtr (const std::string& name, bool activeOnly) = 0;
|
||||
///< Return a pointer to a liveCellRef with the given name.
|
||||
/// \param activeOnly do non search inactive cells.
|
||||
|
@ -100,14 +100,15 @@ namespace MWClass
|
||||
}
|
||||
else
|
||||
{
|
||||
/// \todo do something with mNpdt12 maybe:p
|
||||
for (int i=0; i<8; ++i)
|
||||
data->mCreatureStats.getAttribute (i).set (10);
|
||||
|
||||
for (int i=0; i<3; ++i)
|
||||
data->mCreatureStats.setDynamic (i, 10);
|
||||
|
||||
data->mCreatureStats.setLevel (1);
|
||||
data->mCreatureStats.setLevel(ref->mBase->mNpdt12.mLevel);
|
||||
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
|
||||
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
|
||||
}
|
||||
|
||||
data->mCreatureStats.setAiSetting (0, ref->mBase->mAiData.mHello);
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/journal.hpp"
|
||||
#include "../mwbase/scriptmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
@ -122,15 +123,9 @@ namespace MWDialogue
|
||||
|
||||
MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor);
|
||||
mTalkedTo = creatureStats.hasTalkedToPlayer();
|
||||
creatureStats.talkedToPlayer();
|
||||
|
||||
mActorKnownTopics.clear();
|
||||
|
||||
//initialise the GUI
|
||||
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue);
|
||||
MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
|
||||
win->startDialogue(actor, MWWorld::Class::get (actor).getName (actor));
|
||||
|
||||
//setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI
|
||||
updateTopics();
|
||||
|
||||
@ -144,8 +139,16 @@ namespace MWDialogue
|
||||
{
|
||||
if(it->mType == ESM::Dialogue::Greeting)
|
||||
{
|
||||
if (const ESM::DialInfo *info = filter.search (*it))
|
||||
// Search a response (we do not accept a fallback to "Info refusal" here)
|
||||
if (const ESM::DialInfo *info = filter.search (*it, false))
|
||||
{
|
||||
//initialise the GUI
|
||||
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue);
|
||||
MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
|
||||
win->startDialogue(actor, MWWorld::Class::get (actor).getName (actor));
|
||||
|
||||
creatureStats.talkedToPlayer();
|
||||
|
||||
if (!info->mSound.empty())
|
||||
{
|
||||
// TODO play sound
|
||||
@ -246,12 +249,12 @@ namespace MWDialogue
|
||||
|
||||
const ESM::Dialogue& dialogue = *dialogues.find (topic);
|
||||
|
||||
if (const ESM::DialInfo *info = filter.search (dialogue))
|
||||
MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
|
||||
|
||||
if (const ESM::DialInfo *info = filter.search (dialogue, true))
|
||||
{
|
||||
parseText (info->mResponse);
|
||||
|
||||
MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
|
||||
|
||||
if (dialogue.mType==ESM::Dialogue::Persuasion)
|
||||
{
|
||||
std::string modifiedTopic = "s" + topic;
|
||||
@ -268,12 +271,20 @@ namespace MWDialogue
|
||||
|
||||
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
|
||||
win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext));
|
||||
MWBase::Environment::get().getJournal()->addTopic (topic, info->mId);
|
||||
|
||||
executeScript (info->mResultScript);
|
||||
|
||||
mLastTopic = topic;
|
||||
mLastDialogue = *info;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no response found, print a fallback text
|
||||
win->addTitle (topic);
|
||||
win->addText ("…");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void DialogueManager::updateTopics()
|
||||
@ -292,7 +303,7 @@ namespace MWDialogue
|
||||
{
|
||||
if (iter->mType == ESM::Dialogue::Topic)
|
||||
{
|
||||
if (filter.search (*iter))
|
||||
if (filter.responseAvailable (*iter))
|
||||
{
|
||||
std::string lower = Misc::StringUtils::lowerCase(iter->mId);
|
||||
mActorKnownTopics.push_back (lower);
|
||||
@ -381,6 +392,10 @@ namespace MWDialogue
|
||||
|
||||
void DialogueManager::goodbyeSelected()
|
||||
{
|
||||
// Do not close the dialogue window if the player has to answer a question
|
||||
if (mIsInChoice)
|
||||
return;
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
|
||||
|
||||
// Apply disposition change to NPC's base disposition
|
||||
@ -405,7 +420,7 @@ namespace MWDialogue
|
||||
{
|
||||
Filter filter (mActor, mChoice, mTalkedTo);
|
||||
|
||||
if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic]))
|
||||
if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic], true))
|
||||
{
|
||||
mChoiceMap.clear();
|
||||
mChoice = -1;
|
||||
@ -415,6 +430,7 @@ namespace MWDialogue
|
||||
|
||||
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
|
||||
MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (Interpreter::fixDefinesDialog(text, interpreterContext));
|
||||
MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId);
|
||||
executeScript (info->mResultScript);
|
||||
mLastTopic = mLastTopic;
|
||||
mLastDialogue = *info;
|
||||
|
@ -19,12 +19,19 @@
|
||||
|
||||
bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const
|
||||
{
|
||||
bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name());
|
||||
|
||||
// actor id
|
||||
if (!info.mActor.empty())
|
||||
{
|
||||
if ( Misc::StringUtils::lowerCase (info.mActor)!=MWWorld::Class::get (mActor).getId (mActor))
|
||||
return false;
|
||||
|
||||
bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name());
|
||||
}
|
||||
else if (isCreature)
|
||||
{
|
||||
// Creatures must not have topics aside of those specific to their id
|
||||
return false;
|
||||
}
|
||||
|
||||
// NPC race
|
||||
if (!info.mRace.empty())
|
||||
@ -114,6 +121,18 @@ bool MWDialogue::Filter::testSelectStructs (const ESM::DialInfo& info) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info) const
|
||||
{
|
||||
bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name());
|
||||
|
||||
if (isCreature)
|
||||
return true;
|
||||
|
||||
int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor);
|
||||
|
||||
return actorDisposition >= info.mData.mDisposition;
|
||||
}
|
||||
|
||||
bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const
|
||||
{
|
||||
if (select.isNpcOnly() && mActor.getTypeName()!=typeid (ESM::NPC).name())
|
||||
@ -155,7 +174,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
|
||||
int i = 0;
|
||||
|
||||
for (; i<static_cast<int> (script->mVarNames.size()); ++i)
|
||||
if (script->mVarNames[i]==name)
|
||||
if (Misc::StringUtils::lowerCase(script->mVarNames[i]) == name)
|
||||
break;
|
||||
|
||||
if (i>=static_cast<int> (script->mVarNames.size()))
|
||||
@ -540,18 +559,50 @@ MWDialogue::Filter::Filter (const MWWorld::Ptr& actor, int choice, bool talkedTo
|
||||
: mActor (actor), mChoice (choice), mTalkedToPlayer (talkedToPlayer)
|
||||
{}
|
||||
|
||||
bool MWDialogue::Filter::operator() (const ESM::DialInfo& info) const
|
||||
const ESM::DialInfo *MWDialogue::Filter::search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const
|
||||
{
|
||||
return testActor (info) && testPlayer (info) && testSelectStructs (info);
|
||||
}
|
||||
bool infoRefusal = false;
|
||||
|
||||
const ESM::DialInfo *MWDialogue::Filter::search (const ESM::Dialogue& dialogue) const
|
||||
{
|
||||
// Iterate over topic responses to find a matching one
|
||||
for (std::vector<ESM::DialInfo>::const_iterator iter = dialogue.mInfo.begin();
|
||||
iter!=dialogue.mInfo.end(); ++iter)
|
||||
if ((*this) (*iter))
|
||||
return &*iter;
|
||||
{
|
||||
if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter))
|
||||
{
|
||||
if (testDisposition (*iter))
|
||||
return &*iter;
|
||||
else
|
||||
infoRefusal = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (infoRefusal && fallbackToInfoRefusal)
|
||||
{
|
||||
// No response is valid because of low NPC disposition,
|
||||
// search a response in the topic "Info Refusal"
|
||||
|
||||
const MWWorld::Store<ESM::Dialogue> &dialogues =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
|
||||
|
||||
const ESM::Dialogue& infoRefusalDialogue = *dialogues.find ("Info Refusal");
|
||||
|
||||
for (std::vector<ESM::DialInfo>::const_iterator iter = infoRefusalDialogue.mInfo.begin();
|
||||
iter!=infoRefusalDialogue.mInfo.end(); ++iter)
|
||||
if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter) && testDisposition(*iter))
|
||||
return &*iter;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool MWDialogue::Filter::responseAvailable (const ESM::Dialogue& dialogue) const
|
||||
{
|
||||
for (std::vector<ESM::DialInfo>::const_iterator iter = dialogue.mInfo.begin();
|
||||
iter!=dialogue.mInfo.end(); ++iter)
|
||||
{
|
||||
if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -18,40 +18,45 @@ namespace MWDialogue
|
||||
MWWorld::Ptr mActor;
|
||||
int mChoice;
|
||||
bool mTalkedToPlayer;
|
||||
|
||||
|
||||
bool testActor (const ESM::DialInfo& info) const;
|
||||
///< Is this the right actor for this \a info?
|
||||
|
||||
|
||||
bool testPlayer (const ESM::DialInfo& info) const;
|
||||
///< Do the player and the cell the player is currently in match \a info?
|
||||
|
||||
|
||||
bool testSelectStructs (const ESM::DialInfo& info) const;
|
||||
///< Are all select structs matching?
|
||||
|
||||
|
||||
bool testDisposition (const ESM::DialInfo& info) const;
|
||||
///< Is the actor disposition toward the player high enough?
|
||||
|
||||
bool testSelectStruct (const SelectWrapper& select) const;
|
||||
|
||||
|
||||
bool testSelectStructNumeric (const SelectWrapper& select) const;
|
||||
|
||||
|
||||
int getSelectStructInteger (const SelectWrapper& select) const;
|
||||
|
||||
|
||||
bool getSelectStructBoolean (const SelectWrapper& select) const;
|
||||
|
||||
|
||||
int getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const;
|
||||
|
||||
|
||||
bool hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, const std::string& factionId,
|
||||
int rank) const;
|
||||
|
||||
bool hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, const std::string& factionId,
|
||||
int rank) const;
|
||||
|
||||
public:
|
||||
|
||||
Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer);
|
||||
|
||||
bool operator() (const ESM::DialInfo& info) const;
|
||||
///< \return does the dialogue match?
|
||||
|
||||
const ESM::DialInfo *search (const ESM::Dialogue& dialogue) const;
|
||||
public:
|
||||
|
||||
Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer);
|
||||
|
||||
const ESM::DialInfo *search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const;
|
||||
///< Get a matching response for the requested dialogue.
|
||||
/// Redirect to "Info Refusal" topic if a response fulfills all conditions but disposition.
|
||||
|
||||
bool responseAvailable (const ESM::Dialogue& dialogue) const;
|
||||
///< Does a matching response exist? (disposition is ignored for this check)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -61,8 +61,8 @@ namespace MWDialogue
|
||||
StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index)
|
||||
{
|
||||
int day = MWBase::Environment::get().getWorld()->getGlobalVariable ("dayspassed").mLong;
|
||||
int month = MWBase::Environment::get().getWorld()->getGlobalVariable ("day").mLong;
|
||||
int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalVariable ("month").mLong;
|
||||
int month = MWBase::Environment::get().getWorld()->getGlobalVariable ("month").mLong;
|
||||
int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalVariable ("day").mLong;
|
||||
|
||||
return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth);
|
||||
}
|
||||
|
@ -31,6 +31,12 @@ namespace MWDialogue
|
||||
|
||||
void Journal::addEntry (const std::string& id, int index)
|
||||
{
|
||||
// bail out of we already have heard this...
|
||||
std::string infoId = JournalEntry::idFromIndex (id, index);
|
||||
for (TEntryIter i = mJournal.begin (); i != mJournal.end (); ++i)
|
||||
if (i->mTopic == id && i->mInfoId == infoId)
|
||||
return;
|
||||
|
||||
StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index);
|
||||
|
||||
mJournal.push_back (entry);
|
||||
|
@ -27,17 +27,17 @@ namespace MWDialogue
|
||||
mEntries.push_back (entry.mInfoId);
|
||||
}
|
||||
|
||||
Topic::TEntryIter Topic::begin()
|
||||
Topic::TEntryIter Topic::begin() const
|
||||
{
|
||||
return mEntries.begin();
|
||||
}
|
||||
|
||||
Topic::TEntryIter Topic::end()
|
||||
Topic::TEntryIter Topic::end() const
|
||||
{
|
||||
return mEntries.end();
|
||||
}
|
||||
|
||||
JournalEntry Topic::getEntry (const std::string& infoId)
|
||||
JournalEntry Topic::getEntry (const std::string& infoId) const
|
||||
{
|
||||
return JournalEntry (mTopic, infoId);
|
||||
}
|
||||
|
@ -34,13 +34,15 @@ namespace MWDialogue
|
||||
///
|
||||
/// \note Redundant entries are ignored.
|
||||
|
||||
TEntryIter begin();
|
||||
std::string const & getName () const { return mTopic; }
|
||||
|
||||
TEntryIter begin() const;
|
||||
///< Iterator pointing to the begin of the journal for this topic.
|
||||
|
||||
TEntryIter end();
|
||||
TEntryIter end() const;
|
||||
///< Iterator pointing past the end of the journal for this topic.
|
||||
|
||||
JournalEntry getEntry (const std::string& infoId);
|
||||
JournalEntry getEntry (const std::string& infoId) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -490,7 +490,7 @@ void DialogueWindow::onReferenceUnavailable()
|
||||
|
||||
void DialogueWindow::onFrame()
|
||||
{
|
||||
if(mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name())
|
||||
if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name())
|
||||
{
|
||||
int disp = std::max(0, std::min(100,
|
||||
MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)
|
||||
|
@ -6,41 +6,42 @@
|
||||
#include "renderconst.hpp"
|
||||
|
||||
|
||||
using namespace Ogre;
|
||||
using namespace MWRender;
|
||||
using namespace NifOgre;
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
Actors::~Actors(){
|
||||
|
||||
std::map<MWWorld::Ptr, Animation*>::iterator it = mAllActors.begin();
|
||||
for (; it != mAllActors.end(); ++it) {
|
||||
PtrAnimationMap::iterator it = mAllActors.begin();
|
||||
for(;it != mAllActors.end();++it)
|
||||
{
|
||||
delete it->second;
|
||||
it->second = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void Actors::setMwRoot(Ogre::SceneNode* root){
|
||||
mMwRoot = root;
|
||||
}
|
||||
void Actors::insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv){
|
||||
void Actors::setMwRoot(Ogre::SceneNode* root)
|
||||
{ mMwRoot = root; }
|
||||
|
||||
void Actors::insertNPC(const MWWorld::Ptr &ptr, MWWorld::InventoryStore &inv)
|
||||
{
|
||||
insertBegin(ptr, true, true);
|
||||
NpcAnimation* anim = new MWRender::NpcAnimation(ptr, ptr.getRefData ().getBaseNode (), inv, RV_Actors);
|
||||
|
||||
mAllActors[ptr] = anim;
|
||||
}
|
||||
void Actors::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_){
|
||||
|
||||
void Actors::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_)
|
||||
{
|
||||
Ogre::SceneNode* cellnode;
|
||||
if(mCellSceneNodes.find(ptr.getCell()) == mCellSceneNodes.end())
|
||||
CellSceneNodeMap::const_iterator celliter = mCellSceneNodes.find(ptr.getCell());
|
||||
if(celliter != mCellSceneNodes.end())
|
||||
cellnode = celliter->second;
|
||||
else
|
||||
{
|
||||
//Create the scenenode and put it in the map
|
||||
cellnode = mMwRoot->createChildSceneNode();
|
||||
mCellSceneNodes[ptr.getCell()] = cellnode;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellnode = mCellSceneNodes[ptr.getCell()];
|
||||
}
|
||||
|
||||
Ogre::SceneNode* insert = cellnode->createChildSceneNode();
|
||||
const float *f = ptr.getRefData().getPosition().pos;
|
||||
@ -51,13 +52,13 @@ void Actors::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_){
|
||||
f = ptr.getCellRef().mPos.rot;
|
||||
|
||||
// Rotate around X axis
|
||||
Quaternion xr(Radian(-f[0]), Vector3::UNIT_X);
|
||||
Ogre::Quaternion xr(Ogre::Radian(-f[0]), Ogre::Vector3::UNIT_X);
|
||||
|
||||
// Rotate around Y axis
|
||||
Quaternion yr(Radian(-f[1]), Vector3::UNIT_Y);
|
||||
Ogre::Quaternion yr(Ogre::Radian(-f[1]), Ogre::Vector3::UNIT_Y);
|
||||
|
||||
// Rotate around Z axis
|
||||
Quaternion zr(Radian(-f[2]), Vector3::UNIT_Z);
|
||||
Ogre::Quaternion zr(Ogre::Radian(-f[2]), Ogre::Vector3::UNIT_Z);
|
||||
|
||||
// Rotates first around z, then y, then x
|
||||
insert->setOrientation(xr*yr*zr);
|
||||
@ -71,30 +72,30 @@ void Actors::insertCreature (const MWWorld::Ptr& ptr){
|
||||
|
||||
insertBegin(ptr, true, true);
|
||||
CreatureAnimation* anim = new MWRender::CreatureAnimation(ptr);
|
||||
//mAllActors.insert(std::pair<MWWorld::Ptr, Animation*>(ptr,anim));
|
||||
|
||||
delete mAllActors[ptr];
|
||||
mAllActors[ptr] = anim;
|
||||
//mAllActors.push_back(&anim);*/
|
||||
}
|
||||
|
||||
bool Actors::deleteObject (const MWWorld::Ptr& ptr)
|
||||
{
|
||||
delete mAllActors[ptr];
|
||||
mAllActors.erase(ptr);
|
||||
if (Ogre::SceneNode *base = ptr.getRefData().getBaseNode())
|
||||
delete mAllActors[ptr];
|
||||
mAllActors.erase(ptr);
|
||||
|
||||
if(Ogre::SceneNode *base=ptr.getRefData().getBaseNode())
|
||||
{
|
||||
|
||||
Ogre::SceneNode *parent = base->getParentSceneNode();
|
||||
|
||||
for (std::map<MWWorld::Ptr::CellStore *, Ogre::SceneNode *>::const_iterator iter (
|
||||
mCellSceneNodes.begin()); iter!=mCellSceneNodes.end(); ++iter)
|
||||
if (iter->second==parent)
|
||||
CellSceneNodeMap::const_iterator iter(mCellSceneNodes.begin());
|
||||
for(;iter != mCellSceneNodes.end();++iter)
|
||||
{
|
||||
if(iter->second == parent)
|
||||
{
|
||||
base->removeAndDestroyAllChildren();
|
||||
mRend.getScene()->destroySceneNode (base);
|
||||
ptr.getRefData().setBaseNode (0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -102,57 +103,70 @@ bool Actors::deleteObject (const MWWorld::Ptr& ptr)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Actors::removeCell(MWWorld::Ptr::CellStore* store){
|
||||
if(mCellSceneNodes.find(store) != mCellSceneNodes.end())
|
||||
void Actors::removeCell(MWWorld::Ptr::CellStore* store)
|
||||
{
|
||||
for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end();)
|
||||
{
|
||||
Ogre::SceneNode* base = mCellSceneNodes[store];
|
||||
base->removeAndDestroyAllChildren();
|
||||
mCellSceneNodes.erase(store);
|
||||
mRend.getScene()->destroySceneNode(base);
|
||||
base = 0;
|
||||
}
|
||||
for(std::map<MWWorld::Ptr, Animation*>::iterator iter = mAllActors.begin(); iter != mAllActors.end(); )
|
||||
{
|
||||
if(iter->first.getCell() == store){
|
||||
if(iter->first.getCell() == store)
|
||||
{
|
||||
delete iter->second;
|
||||
mAllActors.erase(iter++);
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
CellSceneNodeMap::iterator celliter = mCellSceneNodes.find(store);
|
||||
if(celliter != mCellSceneNodes.end())
|
||||
{
|
||||
Ogre::SceneNode *base = celliter->second;
|
||||
base->removeAndDestroyAllChildren();
|
||||
mRend.getScene()->destroySceneNode(base);
|
||||
mCellSceneNodes.erase(celliter);
|
||||
}
|
||||
}
|
||||
|
||||
void Actors::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number){
|
||||
if(mAllActors.find(ptr) != mAllActors.end())
|
||||
mAllActors[ptr]->playGroup(groupName, mode, number);
|
||||
void Actors::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number)
|
||||
{
|
||||
PtrAnimationMap::const_iterator iter = mAllActors.find(ptr);
|
||||
if(iter != mAllActors.end())
|
||||
iter->second->playGroup(groupName, mode, number);
|
||||
}
|
||||
void Actors::skipAnimation (const MWWorld::Ptr& ptr){
|
||||
if(mAllActors.find(ptr) != mAllActors.end())
|
||||
mAllActors[ptr]->skipAnim();
|
||||
void Actors::skipAnimation (const MWWorld::Ptr& ptr)
|
||||
{
|
||||
PtrAnimationMap::const_iterator iter = mAllActors.find(ptr);
|
||||
if(iter != mAllActors.end())
|
||||
iter->second->skipAnim();
|
||||
}
|
||||
void Actors::update (float duration){
|
||||
for(std::map<MWWorld::Ptr, Animation*>::iterator iter = mAllActors.begin(); iter != mAllActors.end(); iter++)
|
||||
void Actors::update (float duration)
|
||||
{
|
||||
for(PtrAnimationMap::const_iterator iter = mAllActors.begin();iter != mAllActors.end();iter++)
|
||||
iter->second->runAnimation(duration);
|
||||
}
|
||||
|
||||
void
|
||||
Actors::updateObjectCell(const MWWorld::Ptr &ptr)
|
||||
void Actors::updateObjectCell(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
Ogre::SceneNode *node;
|
||||
MWWorld::CellStore *newCell = ptr.getCell();
|
||||
|
||||
if(mCellSceneNodes.find(newCell) == mCellSceneNodes.end()) {
|
||||
CellSceneNodeMap::const_iterator celliter = mCellSceneNodes.find(newCell);
|
||||
if(celliter != mCellSceneNodes.end())
|
||||
node = celliter->second;
|
||||
else
|
||||
{
|
||||
node = mMwRoot->createChildSceneNode();
|
||||
mCellSceneNodes[newCell] = node;
|
||||
} else {
|
||||
node = mCellSceneNodes[newCell];
|
||||
}
|
||||
node->addChild(ptr.getRefData().getBaseNode());
|
||||
if (mAllActors.find(ptr) != mAllActors.end()) {
|
||||
|
||||
PtrAnimationMap::iterator iter = mAllActors.find(ptr);
|
||||
if(iter != mAllActors.end())
|
||||
{
|
||||
/// \note Update key (Ptr's are compared only with refdata so mCell
|
||||
/// on key is outdated), maybe redundant
|
||||
Animation *anim = mAllActors[ptr];
|
||||
mAllActors.erase(ptr);
|
||||
Animation *anim = iter->second;
|
||||
mAllActors.erase(iter);
|
||||
mAllActors[ptr] = anim;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,18 +10,22 @@ namespace MWWorld
|
||||
class CellStore;
|
||||
}
|
||||
|
||||
namespace MWRender{
|
||||
class Actors{
|
||||
namespace MWRender
|
||||
{
|
||||
class Actors
|
||||
{
|
||||
typedef std::map<MWWorld::CellStore*,Ogre::SceneNode*> CellSceneNodeMap;
|
||||
typedef std::map<MWWorld::Ptr,Animation*> PtrAnimationMap;
|
||||
|
||||
OEngine::Render::OgreRenderer &mRend;
|
||||
std::map<MWWorld::CellStore *, Ogre::SceneNode *> mCellSceneNodes;
|
||||
Ogre::SceneNode* mMwRoot;
|
||||
std::map<MWWorld::Ptr, Animation*> mAllActors;
|
||||
CellSceneNodeMap mCellSceneNodes;
|
||||
PtrAnimationMap mAllActors;
|
||||
|
||||
|
||||
|
||||
public:
|
||||
public:
|
||||
Actors(OEngine::Render::OgreRenderer& _rend): mRend(_rend) {}
|
||||
~Actors();
|
||||
|
||||
void setMwRoot(Ogre::SceneNode* root);
|
||||
void insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_);
|
||||
void insertCreature (const MWWorld::Ptr& ptr);
|
||||
|
@ -181,10 +181,14 @@ namespace MWScript
|
||||
runtime.pop();
|
||||
|
||||
std::vector<int> idleList;
|
||||
for (unsigned int i=0; i<arg0; ++i) {
|
||||
idleList.push_back (0); // why MW, why?
|
||||
|
||||
for (int i=2; i<10 && arg0; ++i)
|
||||
{
|
||||
Interpreter::Type_Integer idleValue = runtime[0].mFloat;
|
||||
idleList.push_back(idleValue);
|
||||
runtime.pop();
|
||||
--arg0;
|
||||
}
|
||||
|
||||
// discard additional arguments (reset), because we have no idea what they mean.
|
||||
|
@ -13,7 +13,7 @@ namespace Interpreter
|
||||
|
||||
namespace MWScript
|
||||
{
|
||||
/// \brief Temporaty script functionality limited to the console
|
||||
/// \brief Temporary script functionality limited to the console
|
||||
namespace User
|
||||
{
|
||||
void registerExtensions (Compiler::Extensions& extensions);
|
||||
|
@ -160,7 +160,7 @@ namespace MWSound
|
||||
return volume;
|
||||
}
|
||||
|
||||
bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const
|
||||
bool SoundManager::isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const
|
||||
{
|
||||
SoundMap::const_iterator snditer = mActiveSounds.begin();
|
||||
while(snditer != mActiveSounds.end())
|
||||
@ -229,7 +229,7 @@ namespace MWSound
|
||||
startRandomTitle();
|
||||
}
|
||||
|
||||
void SoundManager::say(MWWorld::Ptr ptr, const std::string& filename)
|
||||
void SoundManager::say(const MWWorld::Ptr &ptr, const std::string& filename)
|
||||
{
|
||||
if(!mOutput->isInitialized())
|
||||
return;
|
||||
@ -269,12 +269,12 @@ namespace MWSound
|
||||
}
|
||||
}
|
||||
|
||||
bool SoundManager::sayDone(MWWorld::Ptr ptr) const
|
||||
bool SoundManager::sayDone(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
return !isPlaying(ptr, "_say_sound");
|
||||
}
|
||||
|
||||
void SoundManager::stopSay(MWWorld::Ptr ptr)
|
||||
void SoundManager::stopSay(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
SoundMap::iterator snditer = mActiveSounds.begin();
|
||||
while(snditer != mActiveSounds.end())
|
||||
@ -328,7 +328,7 @@ namespace MWSound
|
||||
return sound;
|
||||
}
|
||||
|
||||
MWBase::SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId,
|
||||
MWBase::SoundPtr SoundManager::playSound3D(const MWWorld::Ptr &ptr, const std::string& soundId,
|
||||
float volume, float pitch, PlayMode mode)
|
||||
{
|
||||
MWBase::SoundPtr sound;
|
||||
@ -356,7 +356,7 @@ namespace MWSound
|
||||
return sound;
|
||||
}
|
||||
|
||||
void SoundManager::stopSound3D(MWWorld::Ptr ptr, const std::string& soundId)
|
||||
void SoundManager::stopSound3D(const MWWorld::Ptr &ptr, const std::string& soundId)
|
||||
{
|
||||
SoundMap::iterator snditer = mActiveSounds.begin();
|
||||
while(snditer != mActiveSounds.end())
|
||||
@ -371,7 +371,7 @@ namespace MWSound
|
||||
}
|
||||
}
|
||||
|
||||
void SoundManager::stopSound3D(MWWorld::Ptr ptr)
|
||||
void SoundManager::stopSound3D(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
SoundMap::iterator snditer = mActiveSounds.begin();
|
||||
while(snditer != mActiveSounds.end())
|
||||
@ -418,7 +418,7 @@ namespace MWSound
|
||||
}
|
||||
}
|
||||
|
||||
bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const
|
||||
bool SoundManager::getSoundPlaying(const MWWorld::Ptr &ptr, const std::string& soundId) const
|
||||
{
|
||||
return isPlaying(ptr, soundId);
|
||||
}
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class Sound_Output;
|
||||
@ -57,7 +55,7 @@ namespace MWSound
|
||||
std::string lookup(const std::string &soundId,
|
||||
float &volume, float &min, float &max);
|
||||
void streamMusicFull(const std::string& filename);
|
||||
bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const;
|
||||
bool isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const;
|
||||
void updateSounds(float duration);
|
||||
void updateRegionSound(float duration);
|
||||
|
||||
@ -93,7 +91,7 @@ namespace MWSound
|
||||
///< Start playing music from the selected folder
|
||||
/// \param name of the folder that contains the playlist
|
||||
|
||||
virtual void say(MWWorld::Ptr reference, const std::string& filename);
|
||||
virtual void say(const MWWorld::Ptr &reference, const std::string& filename);
|
||||
///< Make an actor say some text.
|
||||
/// \param filename name of a sound file in "Sound/" in the data directory.
|
||||
|
||||
@ -101,10 +99,10 @@ namespace MWSound
|
||||
///< Say some text, without an actor ref
|
||||
/// \param filename name of a sound file in "Sound/" in the data directory.
|
||||
|
||||
virtual bool sayDone(MWWorld::Ptr reference=MWWorld::Ptr()) const;
|
||||
virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const;
|
||||
///< Is actor not speaking?
|
||||
|
||||
virtual void stopSay(MWWorld::Ptr reference=MWWorld::Ptr());
|
||||
virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr());
|
||||
///< Stop an actor speaking
|
||||
|
||||
virtual MWBase::SoundPtr playTrack(const DecoderPtr& decoder, PlayType type);
|
||||
@ -113,14 +111,14 @@ namespace MWSound
|
||||
virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayMode mode=Play_Normal);
|
||||
///< Play a sound, independently of 3D-position
|
||||
|
||||
virtual MWBase::SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId,
|
||||
virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId,
|
||||
float volume, float pitch, PlayMode mode=Play_Normal);
|
||||
///< Play a sound from an object
|
||||
|
||||
virtual void stopSound3D(MWWorld::Ptr reference, const std::string& soundId);
|
||||
virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId);
|
||||
///< Stop the given object from playing the given sound,
|
||||
|
||||
virtual void stopSound3D(MWWorld::Ptr reference);
|
||||
virtual void stopSound3D(const MWWorld::Ptr &reference);
|
||||
///< Stop the given object from playing all sounds.
|
||||
|
||||
virtual void stopSound(const MWWorld::CellStore *cell);
|
||||
@ -129,7 +127,7 @@ namespace MWSound
|
||||
virtual void stopSound(const std::string& soundId);
|
||||
///< Stop a non-3d looping sound
|
||||
|
||||
virtual bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const;
|
||||
virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const;
|
||||
///< Is the given sound currently playing on the given object?
|
||||
|
||||
virtual void pauseSounds(int types=Play_TypeMask);
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "manualref.hpp"
|
||||
#include "refdata.hpp"
|
||||
#include "class.hpp"
|
||||
#include "localscripts.hpp"
|
||||
#include "player.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -71,6 +73,31 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
|
||||
}
|
||||
|
||||
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& ptr)
|
||||
{
|
||||
MWWorld::ContainerStoreIterator it = addImp(ptr);
|
||||
MWWorld::Ptr item = *it;
|
||||
|
||||
std::string script = MWWorld::Class::get(item).getScript(item);
|
||||
if(script != "")
|
||||
{
|
||||
CellStore *cell;
|
||||
|
||||
Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
|
||||
// Items in players inventory have cell set to 0, so their scripts will never be removed
|
||||
if(&(MWWorld::Class::get (player).getContainerStore (player)) == this)
|
||||
cell = 0;
|
||||
else
|
||||
cell = player.getCell();
|
||||
|
||||
item.mCell = cell;
|
||||
item.mContainerStore = 0;
|
||||
MWBase::Environment::get().getWorld()->getLocalScripts().add(script, item);
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr)
|
||||
{
|
||||
int type = getType(ptr);
|
||||
|
||||
@ -162,7 +189,7 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const MWWor
|
||||
}
|
||||
|
||||
ref.getPtr().getRefData().setCount (std::abs(iter->mCount)); /// \todo implement item restocking (indicated by negative count)
|
||||
add (ref.getPtr());
|
||||
addImp (ref.getPtr());
|
||||
}
|
||||
|
||||
flagAsModified();
|
||||
|
@ -52,6 +52,7 @@ namespace MWWorld
|
||||
int mStateId;
|
||||
mutable float mCachedWeight;
|
||||
mutable bool mWeightUpToDate;
|
||||
ContainerStoreIterator addImp (const Ptr& ptr);
|
||||
|
||||
public:
|
||||
|
||||
|
@ -3,6 +3,10 @@
|
||||
#include "esmstore.hpp"
|
||||
#include "cellstore.hpp"
|
||||
|
||||
#include "class.hpp"
|
||||
#include "containerstore.hpp"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
@ -19,6 +23,32 @@ namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds scripts for items in containers (containers/npcs/creatures)
|
||||
template<typename T>
|
||||
void listCellScriptsCont (MWWorld::LocalScripts& localScripts,
|
||||
MWWorld::CellRefList<T>& cellRefList, MWWorld::Ptr::CellStore *cell)
|
||||
{
|
||||
for (typename MWWorld::CellRefList<T>::List::iterator iter (
|
||||
cellRefList.mList.begin());
|
||||
iter!=cellRefList.mList.end(); ++iter)
|
||||
{
|
||||
|
||||
MWWorld::Ptr containerPtr (&*iter, cell);
|
||||
|
||||
MWWorld::ContainerStore& container = MWWorld::Class::get(containerPtr).getContainerStore(containerPtr);
|
||||
for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3)
|
||||
{
|
||||
std::string script = MWWorld::Class::get(*it3).getScript(*it3);
|
||||
if(script != "")
|
||||
{
|
||||
MWWorld::Ptr item = *it3;
|
||||
item.mCell = cell;
|
||||
localScripts.add (script, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) {}
|
||||
@ -78,13 +108,16 @@ void MWWorld::LocalScripts::addCell (Ptr::CellStore *cell)
|
||||
listCellScripts (*this, cell->mBooks, cell);
|
||||
listCellScripts (*this, cell->mClothes, cell);
|
||||
listCellScripts (*this, cell->mContainers, cell);
|
||||
listCellScriptsCont (*this, cell->mContainers, cell);
|
||||
listCellScripts (*this, cell->mCreatures, cell);
|
||||
listCellScriptsCont (*this, cell->mCreatures, cell);
|
||||
listCellScripts (*this, cell->mDoors, cell);
|
||||
listCellScripts (*this, cell->mIngreds, cell);
|
||||
listCellScripts (*this, cell->mLights, cell);
|
||||
listCellScripts (*this, cell->mLockpicks, cell);
|
||||
listCellScripts (*this, cell->mMiscItems, cell);
|
||||
listCellScripts (*this, cell->mNpcs, cell);
|
||||
listCellScriptsCont (*this, cell->mNpcs, cell);
|
||||
listCellScripts (*this, cell->mProbes, cell);
|
||||
listCellScripts (*this, cell->mRepairs, cell);
|
||||
listCellScripts (*this, cell->mWeapons, cell);
|
||||
@ -101,7 +134,7 @@ void MWWorld::LocalScripts::clearCell (Ptr::CellStore *cell)
|
||||
|
||||
while (iter!=mScripts.end())
|
||||
{
|
||||
if (iter->second.getCell()==cell)
|
||||
if (iter->second.mCell==cell)
|
||||
{
|
||||
if (iter==mIter)
|
||||
++mIter;
|
||||
@ -113,6 +146,20 @@ void MWWorld::LocalScripts::clearCell (Ptr::CellStore *cell)
|
||||
}
|
||||
}
|
||||
|
||||
void MWWorld::LocalScripts::remove (RefData *ref)
|
||||
{
|
||||
for (std::list<std::pair<std::string, Ptr> >::iterator iter = mScripts.begin();
|
||||
iter!=mScripts.end(); ++iter)
|
||||
if (&(iter->second.getRefData()) == ref)
|
||||
{
|
||||
if (iter==mIter)
|
||||
++mIter;
|
||||
|
||||
mScripts.erase (iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MWWorld::LocalScripts::remove (const Ptr& ptr)
|
||||
{
|
||||
for (std::list<std::pair<std::string, Ptr> >::iterator iter = mScripts.begin();
|
||||
|
@ -10,6 +10,7 @@ namespace MWWorld
|
||||
{
|
||||
struct ESMStore;
|
||||
class CellStore;
|
||||
class RefData;
|
||||
|
||||
/// \brief List of active local scripts
|
||||
class LocalScripts
|
||||
@ -47,6 +48,8 @@ namespace MWWorld
|
||||
|
||||
void clearCell (CellStore *cell);
|
||||
///< Remove all scripts belonging to \a cell.
|
||||
|
||||
void remove (RefData *ref);
|
||||
|
||||
void remove (const Ptr& ptr);
|
||||
///< Remove script for given reference (ignored if reference does not have a scirpt listed).
|
||||
|
@ -74,7 +74,7 @@ namespace MWWorld
|
||||
|
||||
bool isInCell() const
|
||||
{
|
||||
return (mCell != 0);
|
||||
return (mContainerStore == 0);
|
||||
}
|
||||
|
||||
void setContainerStore (ContainerStore *store);
|
||||
|
@ -6,6 +6,9 @@
|
||||
#include "customdata.hpp"
|
||||
#include "cellstore.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
void RefData::copy (const RefData& refData)
|
||||
@ -107,6 +110,9 @@ namespace MWWorld
|
||||
|
||||
void RefData::setCount (int count)
|
||||
{
|
||||
if(count == 0)
|
||||
MWBase::Environment::get().getWorld()->removeRefScript(this);
|
||||
|
||||
mCount = count;
|
||||
}
|
||||
|
||||
|
@ -341,6 +341,11 @@ namespace MWWorld
|
||||
return name;
|
||||
}
|
||||
|
||||
void World::removeRefScript (MWWorld::RefData *ref)
|
||||
{
|
||||
mLocalScripts.remove (ref);
|
||||
}
|
||||
|
||||
Ptr World::getPtr (const std::string& name, bool activeOnly)
|
||||
{
|
||||
// the player is always in an active cell.
|
||||
@ -396,23 +401,62 @@ namespace MWWorld
|
||||
return MWWorld::Ptr();
|
||||
}
|
||||
|
||||
void World::addContainerScripts(const Ptr& reference, Ptr::CellStore * cell)
|
||||
{
|
||||
if( reference.getTypeName()==typeid (ESM::Container).name() ||
|
||||
reference.getTypeName()==typeid (ESM::NPC).name() ||
|
||||
reference.getTypeName()==typeid (ESM::Creature).name())
|
||||
{
|
||||
MWWorld::ContainerStore& container = MWWorld::Class::get(reference).getContainerStore(reference);
|
||||
for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
|
||||
{
|
||||
std::string script = MWWorld::Class::get(*it).getScript(*it);
|
||||
if(script != "")
|
||||
{
|
||||
MWWorld::Ptr item = *it;
|
||||
item.mCell = cell;
|
||||
mLocalScripts.add (script, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void World::enable (const Ptr& reference)
|
||||
{
|
||||
if (!reference.getRefData().isEnabled())
|
||||
{
|
||||
reference.getRefData().enable();
|
||||
|
||||
|
||||
if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
|
||||
mWorldScene->addObjectToScene (reference);
|
||||
}
|
||||
}
|
||||
|
||||
void World::removeContainerScripts(const Ptr& reference)
|
||||
{
|
||||
if( reference.getTypeName()==typeid (ESM::Container).name() ||
|
||||
reference.getTypeName()==typeid (ESM::NPC).name() ||
|
||||
reference.getTypeName()==typeid (ESM::Creature).name())
|
||||
{
|
||||
MWWorld::ContainerStore& container = MWWorld::Class::get(reference).getContainerStore(reference);
|
||||
for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
|
||||
{
|
||||
std::string script = MWWorld::Class::get(*it).getScript(*it);
|
||||
if(script != "")
|
||||
{
|
||||
MWWorld::Ptr item = *it;
|
||||
mLocalScripts.remove (item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void World::disable (const Ptr& reference)
|
||||
{
|
||||
if (reference.getRefData().isEnabled())
|
||||
{
|
||||
reference.getRefData().disable();
|
||||
|
||||
|
||||
if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
|
||||
mWorldScene->removeObjectFromScene (reference);
|
||||
}
|
||||
@ -635,6 +679,7 @@ namespace MWWorld
|
||||
{
|
||||
mWorldScene->removeObjectFromScene (ptr);
|
||||
mLocalScripts.remove (ptr);
|
||||
removeContainerScripts (ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -648,6 +693,8 @@ namespace MWWorld
|
||||
CellStore *currCell = ptr.getCell();
|
||||
bool isPlayer = ptr == mPlayer->getPlayer();
|
||||
bool haveToMove = mWorldScene->isCellActive(*currCell) || isPlayer;
|
||||
|
||||
removeContainerScripts(ptr);
|
||||
|
||||
if (*currCell != newCell)
|
||||
{
|
||||
@ -675,6 +722,8 @@ namespace MWWorld
|
||||
MWWorld::Ptr copy =
|
||||
MWWorld::Class::get(ptr).copyToCell(ptr, newCell);
|
||||
|
||||
addContainerScripts(copy, &newCell);
|
||||
|
||||
mRendering->moveObjectToCell(copy, vec, currCell);
|
||||
|
||||
if (MWWorld::Class::get(ptr).isActor())
|
||||
@ -1283,6 +1332,7 @@ namespace MWWorld
|
||||
if (!script.empty()) {
|
||||
mLocalScripts.add(script, dropped);
|
||||
}
|
||||
addContainerScripts(dropped, &cell);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,9 @@ namespace MWWorld
|
||||
float getNpcActivationDistance ();
|
||||
float getObjectActivationDistance ();
|
||||
|
||||
void removeContainerScripts(const Ptr& reference);
|
||||
void addContainerScripts(const Ptr& reference, Ptr::CellStore* cell);
|
||||
|
||||
public:
|
||||
|
||||
World (OEngine::Render::OgreRenderer& renderer,
|
||||
@ -172,6 +175,9 @@ namespace MWWorld
|
||||
virtual std::vector<std::string> getGlobals () const;
|
||||
|
||||
virtual std::string getCurrentCellName () const;
|
||||
|
||||
virtual void removeRefScript (MWWorld::RefData *ref);
|
||||
//< Remove the script attached to ref from mLocalScripts
|
||||
|
||||
virtual Ptr getPtr (const std::string& name, bool activeOnly);
|
||||
///< Return a pointer to a liveCellRef with the given name.
|
||||
|
@ -31,150 +31,30 @@
|
||||
|
||||
#include "../files/constrainedfiledatastream.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using namespace Ogre;
|
||||
using namespace Bsa;
|
||||
|
||||
struct PathPatternMatcher
|
||||
{
|
||||
PathPatternMatcher (char const * pattern) : pattern (pattern)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator () (char const * input)
|
||||
{
|
||||
char const * p = pattern;
|
||||
char const * i = input;
|
||||
|
||||
while (*p && *i)
|
||||
{
|
||||
if (*p == '*')
|
||||
{
|
||||
++p;
|
||||
|
||||
while (*i && *i != *p && *i != '/' && *i != '\\')
|
||||
++i;
|
||||
}
|
||||
else
|
||||
if (*p == '?')
|
||||
{
|
||||
if (*i == '/' || *i == '\\')
|
||||
break;
|
||||
|
||||
++i, ++p;
|
||||
}
|
||||
if (*p == '/' || *p == '\\')
|
||||
{
|
||||
if (*i != '/' && *i != '\\')
|
||||
break;
|
||||
|
||||
++i, ++p;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*i != *p)
|
||||
break;
|
||||
|
||||
++i, ++p;
|
||||
}
|
||||
}
|
||||
|
||||
return *p == 0 && *i == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
char const * pattern;
|
||||
};
|
||||
|
||||
struct FileNameGatherer
|
||||
{
|
||||
StringVectorPtr ptr;
|
||||
|
||||
FileNameGatherer (StringVectorPtr ptr) : ptr (ptr)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () (std::string const & filename) const
|
||||
{
|
||||
ptr->push_back (filename);
|
||||
}
|
||||
};
|
||||
|
||||
struct FileInfoGatherer
|
||||
{
|
||||
Archive const * archive;
|
||||
FileInfoListPtr ptr;
|
||||
|
||||
FileInfoGatherer (Archive const * archive, FileInfoListPtr ptr) :
|
||||
archive (archive), ptr (ptr)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () (std::string filename) const
|
||||
{
|
||||
FileInfo fi;
|
||||
|
||||
std::size_t pt = filename.rfind ('/');
|
||||
if (pt == std::string::npos)
|
||||
pt = 0;
|
||||
|
||||
fi.archive = const_cast <Archive *> (archive);
|
||||
fi.path = filename.substr (0, pt);
|
||||
fi.filename = filename.substr (pt);
|
||||
fi.compressedSize = fi.uncompressedSize = 0;
|
||||
|
||||
ptr->push_back(fi);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename file_iterator, typename filename_extractor, typename match_handler>
|
||||
void matchFiles (bool recursive, std::string const & pattern, file_iterator begin, file_iterator end, filename_extractor filenameExtractor, match_handler matchHandler)
|
||||
{
|
||||
if (recursive && pattern == "*")
|
||||
{
|
||||
for (file_iterator i = begin; i != end; ++i)
|
||||
matchHandler (filenameExtractor (*i));
|
||||
}
|
||||
else
|
||||
{
|
||||
PathPatternMatcher matcher (pattern.c_str ());
|
||||
|
||||
if (recursive)
|
||||
{
|
||||
for (file_iterator i = begin; i != end; ++i)
|
||||
{
|
||||
char const * filename = filenameExtractor (*i);
|
||||
char const * matchable_part = filename;
|
||||
|
||||
for (char const * j = matchable_part; *j; ++j)
|
||||
{
|
||||
if (*j == '/' || *j == '\\')
|
||||
matchable_part = j + 1;
|
||||
}
|
||||
|
||||
if (matcher (matchable_part))
|
||||
matchHandler (filename);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (file_iterator i = begin; i != end; ++i)
|
||||
{
|
||||
char const * filename = filenameExtractor (*i);
|
||||
|
||||
if (matcher (filename))
|
||||
matchHandler (filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool fsstrict = false;
|
||||
|
||||
static char strict_normalize_char(char ch)
|
||||
{
|
||||
return ch == '\\' ? '/' : ch;
|
||||
}
|
||||
|
||||
static char nonstrict_normalize_char(char ch)
|
||||
{
|
||||
return ch == '\\' ? '/' : std::tolower(ch);
|
||||
}
|
||||
|
||||
template<typename T1, typename T2>
|
||||
static std::string normalize_path(T1 begin, T2 end)
|
||||
{
|
||||
std::string normalized;
|
||||
normalized.reserve(std::distance(begin, end));
|
||||
char (*normalize_char)(char) = fsstrict ? &strict_normalize_char : &nonstrict_normalize_char;
|
||||
std::transform(begin, end, std::back_inserter(normalized), normalize_char);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/// An OGRE Archive wrapping a BSAFile archive
|
||||
class DirArchive: public Ogre::Archive
|
||||
{
|
||||
@ -182,37 +62,12 @@ class DirArchive: public Ogre::Archive
|
||||
|
||||
index mIndex;
|
||||
|
||||
static char strict_normalize_char(char ch)
|
||||
{
|
||||
return ch == '\\' ? '/' : ch;
|
||||
}
|
||||
|
||||
static char nonstrict_normalize_char(char ch)
|
||||
{
|
||||
return ch == '\\' ? '/' : std::tolower (ch);
|
||||
}
|
||||
|
||||
static std::string normalize_path (std::string::const_iterator begin, std::string::const_iterator end)
|
||||
{
|
||||
std::string normalized;
|
||||
normalized.reserve (end-begin);
|
||||
char (*normalize_char) (char) = fsstrict ? &strict_normalize_char : &nonstrict_normalize_char;
|
||||
std::transform (begin, end, std::back_inserter (normalized), normalize_char);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
index::const_iterator lookup_filename (std::string const & filename) const
|
||||
{
|
||||
std::string normalized = normalize_path (filename.begin (), filename.end ());
|
||||
|
||||
return mIndex.find (normalized);
|
||||
}
|
||||
|
||||
static char const * extractFilename (index::value_type const & entry)
|
||||
{
|
||||
return entry.first.c_str ();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
DirArchive(const String& name)
|
||||
@ -273,15 +128,20 @@ public:
|
||||
StringVectorPtr find(const String& pattern, bool recursive = true,
|
||||
bool dirs = false)
|
||||
{
|
||||
std::string normalizedPattern = normalize_path (pattern.begin (), pattern.end ());
|
||||
std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
|
||||
StringVectorPtr ptr = StringVectorPtr(new StringVector());
|
||||
matchFiles (recursive, normalizedPattern, mIndex.begin (), mIndex.end (), extractFilename, FileNameGatherer (ptr));
|
||||
for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();iter++)
|
||||
{
|
||||
if(Ogre::StringUtil::match(iter->first, normalizedPattern) ||
|
||||
(recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern)))
|
||||
ptr->push_back(iter->first);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool exists(const String& filename)
|
||||
{
|
||||
return lookup_filename (filename) != mIndex.end ();
|
||||
return lookup_filename(filename) != mIndex.end ();
|
||||
}
|
||||
|
||||
time_t getModifiedTime(const String&) { return 0; }
|
||||
@ -289,21 +149,44 @@ public:
|
||||
FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
|
||||
bool dirs = false) const
|
||||
{
|
||||
std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
|
||||
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
|
||||
FileInfoGatherer gatherer (this, ptr);
|
||||
|
||||
std::string normalizedPattern = normalize_path (pattern.begin (), pattern.end ());
|
||||
|
||||
index::const_iterator i = mIndex.find (normalizedPattern);
|
||||
|
||||
if (i != mIndex.end ())
|
||||
index::const_iterator i = mIndex.find(normalizedPattern);
|
||||
if(i != mIndex.end())
|
||||
{
|
||||
gatherer (i->first);
|
||||
std::string::size_type pt = i->first.rfind('/');
|
||||
if(pt == std::string::npos)
|
||||
pt = 0;
|
||||
|
||||
FileInfo fi;
|
||||
fi.archive = const_cast<DirArchive*>(this);
|
||||
fi.path = i->first.substr(0, pt);
|
||||
fi.filename = i->first.substr((i->first[pt]=='/') ? pt+1 : pt);
|
||||
fi.compressedSize = fi.uncompressedSize = 0;
|
||||
|
||||
ptr->push_back(fi);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();iter++)
|
||||
{
|
||||
if(Ogre::StringUtil::match(iter->first, normalizedPattern) ||
|
||||
(recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern)))
|
||||
{
|
||||
std::string::size_type pt = iter->first.rfind('/');
|
||||
if(pt == std::string::npos)
|
||||
pt = 0;
|
||||
|
||||
matchFiles (recursive, normalizedPattern, mIndex.begin (), mIndex.end (), extractFilename, gatherer);
|
||||
FileInfo fi;
|
||||
fi.archive = const_cast<DirArchive*>(this);
|
||||
fi.path = iter->first.substr(0, pt);
|
||||
fi.filename = iter->first.substr((iter->first[pt]=='/') ? pt+1 : pt);
|
||||
fi.compressedSize = fi.uncompressedSize = 0;
|
||||
|
||||
ptr->push_back(fi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
@ -312,9 +195,9 @@ public:
|
||||
|
||||
class BSAArchive : public Archive
|
||||
{
|
||||
BSAFile arc;
|
||||
Bsa::BSAFile arc;
|
||||
|
||||
static char const * extractFilename (BSAFile::FileStruct const & entry)
|
||||
static const char *extractFilename(const Bsa::BSAFile::FileStruct &entry)
|
||||
{
|
||||
return entry.name;
|
||||
}
|
||||
@ -330,13 +213,13 @@ public:
|
||||
void load() {}
|
||||
void unload() {}
|
||||
|
||||
DataStreamPtr open(const String& filename, bool recursive = true) const
|
||||
DataStreamPtr open(const String& filename, bool readonly = true) const
|
||||
{
|
||||
// Get a non-const reference to arc. This is a hack and it's all
|
||||
// OGRE's fault. You should NOT expect an open() command not to
|
||||
// have any side effects on the archive, and hence this function
|
||||
// should not have been declared const in the first place.
|
||||
BSAFile *narc = const_cast<BSAFile*>(&arc);
|
||||
Bsa::BSAFile *narc = const_cast<Bsa::BSAFile*>(&arc);
|
||||
|
||||
// Open the file
|
||||
return narc->getFile(filename.c_str());
|
||||
@ -360,32 +243,51 @@ public:
|
||||
return findFileInfo ("*", recursive, dirs);
|
||||
}
|
||||
|
||||
// After load() is called, find("*") is called once. It doesn't seem
|
||||
// to matter that we return an empty list, exists() gets called on
|
||||
// the correct files anyway.
|
||||
StringVectorPtr find(const String& pattern, bool recursive = true,
|
||||
bool dirs = false)
|
||||
{
|
||||
StringVectorPtr find(const String& pattern, bool recursive = true,
|
||||
bool dirs = false)
|
||||
{
|
||||
std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
|
||||
const Bsa::BSAFile::FileList &filelist = arc.getList();
|
||||
StringVectorPtr ptr = StringVectorPtr(new StringVector());
|
||||
matchFiles (recursive, pattern, arc.getList ().begin (), arc.getList ().end (), extractFilename, FileNameGatherer (ptr));
|
||||
for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();iter++)
|
||||
{
|
||||
std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name));
|
||||
if(Ogre::StringUtil::match(ent, normalizedPattern) ||
|
||||
(recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern)))
|
||||
ptr->push_back(iter->name);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Gets called once for each of the ogre formats, *.program,
|
||||
*.material etc. We ignore all these.
|
||||
FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
|
||||
bool dirs = false) const
|
||||
{
|
||||
std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
|
||||
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
|
||||
const Bsa::BSAFile::FileList &filelist = arc.getList();
|
||||
|
||||
However, it's also called by MyGUI to find individual textures,
|
||||
and we can't ignore these since many of the GUI textures are
|
||||
located in BSAs. So instead we channel it through exists() and
|
||||
set up a single-element result list if the file is found.
|
||||
*/
|
||||
FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
|
||||
bool dirs = false) const
|
||||
{
|
||||
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
|
||||
matchFiles (recursive, pattern, arc.getList ().begin (), arc.getList ().end (), extractFilename, FileInfoGatherer (this, ptr));
|
||||
return ptr;
|
||||
}
|
||||
for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();iter++)
|
||||
{
|
||||
std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name));
|
||||
if(Ogre::StringUtil::match(ent, normalizedPattern) ||
|
||||
(recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern)))
|
||||
{
|
||||
std::string::size_type pt = ent.rfind('/');
|
||||
if(pt == std::string::npos)
|
||||
pt = 0;
|
||||
|
||||
FileInfo fi;
|
||||
fi.archive = const_cast<BSAArchive*>(this);
|
||||
fi.path = std::string(iter->name, pt);
|
||||
fi.filename = std::string(iter->name + ((ent[pt]=='/') ? pt+1 : pt));
|
||||
fi.compressedSize = fi.uncompressedSize = iter->fileSize;
|
||||
|
||||
ptr->push_back(fi);
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
// An archive factory for BSA archives
|
||||
@ -450,7 +352,6 @@ static void insertDirFactory()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Bsa
|
||||
{
|
||||
|
@ -118,5 +118,5 @@ for the open-source EB Garamond fontface.
|
||||
Thanks to Dongle,
|
||||
for his Daedric fontface, see Daedric Font License.txt for his license terms.
|
||||
|
||||
Thanks to Bitstream Inc.
|
||||
for their Bitstream Vera fontface, see Bitstream Vera License.txt for their license terms.
|
||||
Thanks to DejaVu team,
|
||||
for their DejaVuLGCSansMono fontface, see DejaVu Font License.txt for their license terms.
|
||||
|
@ -80,7 +80,7 @@ set(MYGUI_FILES
|
||||
openmw_travel_window.layout
|
||||
openmw_persuasion_dialog.layout
|
||||
smallbars.png
|
||||
VeraMono.ttf
|
||||
DejaVuLGCSansMono.ttf
|
||||
markers.png
|
||||
)
|
||||
|
||||
|
BIN
files/mygui/DejaVuLGCSansMono.ttf
Normal file
BIN
files/mygui/DejaVuLGCSansMono.ttf
Normal file
Binary file not shown.
Binary file not shown.
@ -11,6 +11,7 @@
|
||||
<Code range="33 126"/>
|
||||
<Code range="192 382"/> <!-- Central and Eastern European languages glyphs -->
|
||||
<Code range="1025 1105"/>
|
||||
<Code range="2026"/> <!-- Ellipsis -->
|
||||
<Code range="8470"/>
|
||||
<Code range="8211"/> <!-- Minus -->
|
||||
<Code range="8216 8217"/> <!-- Single quotes -->
|
||||
@ -35,7 +36,7 @@
|
||||
</Resource>
|
||||
|
||||
<Resource type="ResourceTrueTypeFont" name="MonoFont">
|
||||
<Property key="Source" value="VeraMono.ttf"/>
|
||||
<Property key="Source" value="DejaVuLGCSansMono.ttf"/>
|
||||
<Property key="Size" value="18"/>
|
||||
<Property key="Resolution" value="50"/>
|
||||
<Property key="Antialias" value="false"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user