mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-27 03:35:27 +00:00
Merge branch 'dialogue' of https://github.com/zinnschlag/openmw.git into DialogueSystem
This commit is contained in:
commit
5f3ebcbb7c
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,3 +7,6 @@ Docs/mainpage.hpp
|
||||
CMakeFiles
|
||||
*/CMakeFiles
|
||||
CMakeCache.txt
|
||||
Makefile
|
||||
makefile
|
||||
data
|
||||
|
@ -1,5 +0,0 @@
|
||||
esmtool_cmd.c: esmtool.ggo
|
||||
gengetopt < esmtool.ggo
|
||||
|
||||
clean:
|
||||
rm esmtool_cmd.c esmtool_cmd.h
|
@ -1,22 +1,20 @@
|
||||
project(OpenMW)
|
||||
|
||||
# config file
|
||||
|
||||
configure_file ("${OpenMW_SOURCE_DIR}/config.hpp.cmake" "${OpenMW_SOURCE_DIR}/config.hpp")
|
||||
configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/config.hpp")
|
||||
|
||||
# local files
|
||||
|
||||
set(GAME
|
||||
main.cpp
|
||||
engine.cpp
|
||||
)
|
||||
)
|
||||
set(GAME_HEADER
|
||||
engine.hpp
|
||||
config.hpp)
|
||||
config.hpp
|
||||
)
|
||||
source_group(game FILES ${GAME} ${GAME_HEADER})
|
||||
|
||||
add_openmw_dir (mwrender
|
||||
renderingmanager debugging sky player animation npcanimation creatureanimation actors objects renderinginterface
|
||||
renderingmanager debugging sky player animation npcanimation creatureanimation actors objects renderinginterface
|
||||
)
|
||||
|
||||
add_openmw_dir (mwinput
|
||||
@ -25,7 +23,7 @@ add_openmw_dir (mwinput
|
||||
|
||||
add_openmw_dir (mwgui
|
||||
layouts text_input widgets race class birth review window_manager console dialogue
|
||||
dialogue_history window_base stats_window messagebox journalwindow
|
||||
dialogue_history window_base stats_window messagebox journalwindow charactercreation
|
||||
)
|
||||
|
||||
add_openmw_dir (mwdialogue
|
||||
@ -46,7 +44,7 @@ add_openmw_dir (mwsound
|
||||
add_openmw_dir (mwworld
|
||||
refdata world physicssystem scene environment globals class action nullaction actionteleport
|
||||
containerstore actiontalk actiontake containerstore manualref containerutil player cellfunctors
|
||||
cells localscripts
|
||||
cells localscripts customdata
|
||||
)
|
||||
|
||||
add_openmw_dir (mwclass
|
||||
@ -61,13 +59,11 @@ add_openmw_dir (mwmechanics
|
||||
# Main executable
|
||||
add_executable(openmw
|
||||
${OPENMW_LIBS} ${OPENMW_LIBS_HEADER}
|
||||
${CONPONENT_FILES}
|
||||
${COMPONENT_FILES}
|
||||
${OPENMW_FILES}
|
||||
${GAME} ${GAME_HEADER}
|
||||
${APPLE_BUNDLE_RESOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries (openmw components)
|
||||
)
|
||||
|
||||
# Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING
|
||||
# when we change the backend.
|
||||
@ -75,26 +71,27 @@ include_directories(${SOUND_INPUT_INCLUDES} ${BULLET_INCLUDE_DIRS})
|
||||
add_definitions(${SOUND_DEFINE})
|
||||
|
||||
target_link_libraries(openmw
|
||||
${OGRE_LIBRARIES}
|
||||
${OIS_LIBRARIES}
|
||||
${Boost_LIBRARIES}
|
||||
${OPENAL_LIBRARY}
|
||||
${SOUND_INPUT_LIBRARY}
|
||||
${BULLET_LIBRARIES}
|
||||
caelum
|
||||
MyGUIEngine
|
||||
MyGUIOgrePlatform
|
||||
${OGRE_LIBRARIES}
|
||||
${OIS_LIBRARIES}
|
||||
${Boost_LIBRARIES}
|
||||
${OPENAL_LIBRARY}
|
||||
${SOUND_INPUT_LIBRARY}
|
||||
${BULLET_LIBRARIES}
|
||||
caelum
|
||||
components
|
||||
MyGUIEngine
|
||||
MyGUIOgrePlatform
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
if(APPLE)
|
||||
find_library(CARBON_FRAMEWORK Carbon)
|
||||
target_link_libraries(openmw ${CARBON_FRAMEWORK})
|
||||
install(TARGETS openmw
|
||||
BUNDLE DESTINATION .
|
||||
RUNTIME DESTINATION ../MacOS
|
||||
COMPONENT Runtime)
|
||||
endif (APPLE)
|
||||
endif(APPLE)
|
||||
|
||||
if(DPKG_PROGRAM)
|
||||
INSTALL(TARGETS openmw RUNTIME DESTINATION games COMPONENT openmw)
|
||||
endif()
|
||||
endif(DPKG_PROGRAM)
|
||||
|
@ -35,8 +35,6 @@
|
||||
#include "mwsound/soundmanager.hpp"
|
||||
|
||||
#include "mwworld/world.hpp"
|
||||
#include "mwworld/ptr.hpp"
|
||||
#include "mwworld/environment.hpp"
|
||||
#include "mwworld/class.hpp"
|
||||
#include "mwworld/player.hpp"
|
||||
|
||||
@ -147,7 +145,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt)
|
||||
mEnvironment.mWorld->advanceTime (
|
||||
mEnvironment.mFrameDuration*mEnvironment.mWorld->getTimeScaleFactor()/3600);
|
||||
|
||||
|
||||
|
||||
if (changed) // keep change flag for another frame, if cell changed happend in local script
|
||||
mEnvironment.mWorld->markCellAsUnchanged();
|
||||
|
||||
@ -158,6 +156,9 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt)
|
||||
if (mEnvironment.mWindowManager->getMode()==MWGui::GM_Game)
|
||||
mEnvironment.mWorld->doPhysics (movement, mEnvironment.mFrameDuration);
|
||||
|
||||
// update world
|
||||
mEnvironment.mWorld->update (evt.timeSinceLastFrame);
|
||||
|
||||
// report focus object (for debugging)
|
||||
if (mReportFocus)
|
||||
updateFocusReport (mEnvironment.mFrameDuration);
|
||||
@ -172,7 +173,6 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt)
|
||||
|
||||
OMW::Engine::Engine(Cfg::ConfigurationManager& configurationManager)
|
||||
: mOgre (0)
|
||||
, mPhysicEngine (0)
|
||||
, mFpsLevel(0)
|
||||
, mDebug (false)
|
||||
, mVerboseScripts (false)
|
||||
@ -183,7 +183,6 @@ OMW::Engine::Engine(Cfg::ConfigurationManager& configurationManager)
|
||||
, mFocusTDiff (0)
|
||||
, mScriptManager (0)
|
||||
, mScriptContext (0)
|
||||
, mGuiManager (0)
|
||||
, mFSStrict (false)
|
||||
, mCfgMgr(configurationManager)
|
||||
{
|
||||
@ -193,7 +192,6 @@ OMW::Engine::Engine(Cfg::ConfigurationManager& configurationManager)
|
||||
|
||||
OMW::Engine::~Engine()
|
||||
{
|
||||
delete mGuiManager;
|
||||
delete mEnvironment.mWorld;
|
||||
delete mEnvironment.mSoundManager;
|
||||
delete mEnvironment.mGlobalScripts;
|
||||
@ -202,7 +200,6 @@ OMW::Engine::~Engine()
|
||||
delete mEnvironment.mJournal;
|
||||
delete mScriptManager;
|
||||
delete mScriptContext;
|
||||
delete mPhysicEngine;
|
||||
delete mOgre;
|
||||
}
|
||||
|
||||
@ -330,26 +327,15 @@ void OMW::Engine::go()
|
||||
|
||||
loadBSA();
|
||||
|
||||
/// \todo move this into the physics manager
|
||||
// Create physics. shapeLoader is deleted by the physic engine
|
||||
NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader();
|
||||
mPhysicEngine = new OEngine::Physic::PhysicEngine(shapeLoader);
|
||||
|
||||
// Create the world
|
||||
mEnvironment.mWorld = new MWWorld::World (*mOgre, mPhysicEngine, mFileCollections, mMaster,
|
||||
mEnvironment.mWorld = new MWWorld::World (*mOgre, mFileCollections, mMaster,
|
||||
mResDir, mNewGame, mEnvironment, mEncoding);
|
||||
|
||||
/// \todo move this into the GUI manager (a.k.a WindowManager)
|
||||
// Set up the GUI system
|
||||
mGuiManager = new OEngine::GUI::MyGUIManager(mOgre->getWindow(), mOgre->getScene(), false,
|
||||
mCfgMgr.getLogPath().string() + std::string("/"));
|
||||
|
||||
|
||||
// Create window manager - this manages all the MW-specific GUI windows
|
||||
MWScript::registerExtensions (mExtensions);
|
||||
|
||||
mEnvironment.mWindowManager = new MWGui::WindowManager(mGuiManager->getGui(), mEnvironment,
|
||||
mExtensions, mFpsLevel, mNewGame);
|
||||
mEnvironment.mWindowManager = new MWGui::WindowManager(mEnvironment,
|
||||
mExtensions, mFpsLevel, mNewGame, mOgre, mCfgMgr.getLogPath().string() + std::string("/"));
|
||||
|
||||
// Create sound system
|
||||
mEnvironment.mSoundManager = new MWSound::SoundManager(mOgre->getRoot(),
|
||||
|
@ -7,8 +7,6 @@
|
||||
|
||||
#include <OgreFrameListener.h>
|
||||
|
||||
#include <openengine/bullet/physic.hpp>
|
||||
|
||||
#include <components/compiler/extensions.hpp>
|
||||
#include <components/files/collections.hpp>
|
||||
#include <components/cfg/configurationmanager.hpp>
|
||||
@ -63,7 +61,6 @@ namespace OMW
|
||||
boost::filesystem::path mDataDir;
|
||||
boost::filesystem::path mResDir;
|
||||
OEngine::Render::OgreRenderer *mOgre;
|
||||
OEngine::Physic::PhysicEngine* mPhysicEngine;
|
||||
std::string mCellName;
|
||||
std::string mMaster;
|
||||
int mFpsLevel;
|
||||
@ -80,7 +77,7 @@ namespace OMW
|
||||
MWScript::ScriptManager *mScriptManager;
|
||||
Compiler::Extensions mExtensions;
|
||||
Compiler::Context *mScriptContext;
|
||||
OEngine::GUI::MyGUIManager *mGuiManager;
|
||||
|
||||
|
||||
Files::Collections mFileCollections;
|
||||
bool mFSStrict;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
@ -20,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_APPARATUS_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
@ -20,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_ARMOR_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
@ -20,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_BOOK_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
@ -20,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_CLOTHING_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -6,9 +6,41 @@
|
||||
#include <components/esm_store/cell_store.hpp>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/customdata.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CustomData : public MWWorld::CustomData
|
||||
{
|
||||
MWWorld::ContainerStore<MWWorld::RefData> mContainerStore;
|
||||
|
||||
virtual MWWorld::CustomData *clone() const;
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CustomData::clone() const
|
||||
{
|
||||
return new CustomData (*this);
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
void Container::ensureCustomData (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::auto_ptr<CustomData> data (new CustomData);
|
||||
|
||||
// \todo add initial container content
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
}
|
||||
}
|
||||
|
||||
void Container::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
|
||||
{
|
||||
ESMS::LiveCellRef<ESM::Container, MWWorld::RefData> *ref =
|
||||
@ -16,7 +48,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
@ -50,17 +82,9 @@ namespace MWClass
|
||||
MWWorld::ContainerStore<MWWorld::RefData>& Container::getContainerStore (const MWWorld::Ptr& ptr)
|
||||
const
|
||||
{
|
||||
if (!ptr.getRefData().getContainerStore().get())
|
||||
{
|
||||
boost::shared_ptr<MWWorld::ContainerStore<MWWorld::RefData> > store (
|
||||
new MWWorld::ContainerStore<MWWorld::RefData>);
|
||||
ensureCustomData (ptr);
|
||||
|
||||
// TODO add initial content
|
||||
|
||||
ptr.getRefData().getContainerStore() = store;
|
||||
}
|
||||
|
||||
return *ptr.getRefData().getContainerStore();
|
||||
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
|
||||
}
|
||||
|
||||
std::string Container::getScript (const MWWorld::Ptr& ptr) const
|
||||
|
@ -2,12 +2,13 @@
|
||||
#define GAME_MWCLASS_CONTAINER_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
class Container : public MWWorld::Class
|
||||
{
|
||||
void ensureCustomData (const MWWorld::Ptr& ptr) const;
|
||||
|
||||
public:
|
||||
|
||||
virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
|
||||
|
@ -4,16 +4,62 @@
|
||||
#include <components/esm/loadcrea.hpp>
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/mechanicsmanager.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontalk.hpp"
|
||||
#include "../mwworld/environment.hpp"
|
||||
#include "../mwworld/customdata.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CustomData : public MWWorld::CustomData
|
||||
{
|
||||
MWMechanics::CreatureStats mCreatureStats;
|
||||
MWWorld::ContainerStore<MWWorld::RefData> mContainerStore;
|
||||
|
||||
#include "../mwmechanics/mechanicsmanager.hpp"
|
||||
virtual MWWorld::CustomData *clone() const;
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CustomData::clone() const
|
||||
{
|
||||
return new CustomData (*this);
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::auto_ptr<CustomData> data (new CustomData);
|
||||
|
||||
ESMS::LiveCellRef<ESM::Creature, MWWorld::RefData> *ref = ptr.get<ESM::Creature>();
|
||||
|
||||
// creature stats
|
||||
data->mCreatureStats.mAttributes[0].set (ref->base->data.strength);
|
||||
data->mCreatureStats.mAttributes[1].set (ref->base->data.intelligence);
|
||||
data->mCreatureStats.mAttributes[2].set (ref->base->data.willpower);
|
||||
data->mCreatureStats.mAttributes[3].set (ref->base->data.agility);
|
||||
data->mCreatureStats.mAttributes[4].set (ref->base->data.speed);
|
||||
data->mCreatureStats.mAttributes[5].set (ref->base->data.endurance);
|
||||
data->mCreatureStats.mAttributes[6].set (ref->base->data.personality);
|
||||
data->mCreatureStats.mAttributes[7].set (ref->base->data.luck);
|
||||
data->mCreatureStats.mDynamic[0].set (ref->base->data.health);
|
||||
data->mCreatureStats.mDynamic[1].set (ref->base->data.mana);
|
||||
data->mCreatureStats.mDynamic[2].set (ref->base->data.fatigue);
|
||||
|
||||
data->mCreatureStats.mLevel = ref->base->data.level;
|
||||
|
||||
// \todo add initial container content
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Creature::getId (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
ESMS::LiveCellRef<ESM::Creature, MWWorld::RefData> *ref =
|
||||
@ -24,18 +70,8 @@ namespace MWClass
|
||||
|
||||
void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
|
||||
{
|
||||
|
||||
/*ESMS::LiveCellRef<ESM::Creature, MWWorld::RefData> *ref =
|
||||
ptr.get<ESM::Creature>();
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
if (!model.empty())
|
||||
{*/
|
||||
MWRender::Actors& actors = renderingInterface.getActors();
|
||||
actors.insertCreature(ptr);
|
||||
|
||||
MWRender::Actors& actors = renderingInterface.getActors();
|
||||
actors.insertCreature(ptr);
|
||||
}
|
||||
|
||||
void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics, MWWorld::Environment& environment) const
|
||||
@ -49,7 +85,6 @@ namespace MWClass
|
||||
if(!model.empty()){
|
||||
physics.insertActorPhysics(ptr, "meshes\\" + model);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Creature::enable (const MWWorld::Ptr& ptr, MWWorld::Environment& environment) const
|
||||
@ -72,31 +107,9 @@ namespace MWClass
|
||||
|
||||
MWMechanics::CreatureStats& Creature::getCreatureStats (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getCreatureStats().get())
|
||||
{
|
||||
boost::shared_ptr<MWMechanics::CreatureStats> stats (
|
||||
new MWMechanics::CreatureStats);
|
||||
ensureCustomData (ptr);
|
||||
|
||||
ESMS::LiveCellRef<ESM::Creature, MWWorld::RefData> *ref = ptr.get<ESM::Creature>();
|
||||
|
||||
stats->mAttributes[0].set (ref->base->data.strength);
|
||||
stats->mAttributes[1].set (ref->base->data.intelligence);
|
||||
stats->mAttributes[2].set (ref->base->data.willpower);
|
||||
stats->mAttributes[3].set (ref->base->data.agility);
|
||||
stats->mAttributes[4].set (ref->base->data.speed);
|
||||
stats->mAttributes[5].set (ref->base->data.endurance);
|
||||
stats->mAttributes[6].set (ref->base->data.personality);
|
||||
stats->mAttributes[7].set (ref->base->data.luck);
|
||||
stats->mDynamic[0].set (ref->base->data.health);
|
||||
stats->mDynamic[1].set (ref->base->data.mana);
|
||||
stats->mDynamic[2].set (ref->base->data.fatigue);
|
||||
|
||||
stats->mLevel = ref->base->data.level;
|
||||
|
||||
ptr.getRefData().getCreatureStats() = stats;
|
||||
}
|
||||
|
||||
return *ptr.getRefData().getCreatureStats();
|
||||
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mCreatureStats;
|
||||
}
|
||||
|
||||
boost::shared_ptr<MWWorld::Action> Creature::activate (const MWWorld::Ptr& ptr,
|
||||
@ -108,17 +121,9 @@ namespace MWClass
|
||||
MWWorld::ContainerStore<MWWorld::RefData>& Creature::getContainerStore (const MWWorld::Ptr& ptr)
|
||||
const
|
||||
{
|
||||
if (!ptr.getRefData().getContainerStore().get())
|
||||
{
|
||||
boost::shared_ptr<MWWorld::ContainerStore<MWWorld::RefData> > store (
|
||||
new MWWorld::ContainerStore<MWWorld::RefData>);
|
||||
ensureCustomData (ptr);
|
||||
|
||||
// TODO add initial content
|
||||
|
||||
ptr.getRefData().getContainerStore() = store;
|
||||
}
|
||||
|
||||
return *ptr.getRefData().getContainerStore();
|
||||
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
|
||||
}
|
||||
|
||||
std::string Creature::getScript (const MWWorld::Ptr& ptr) const
|
||||
|
@ -10,6 +10,8 @@ namespace MWClass
|
||||
{
|
||||
class Creature : public MWWorld::Class
|
||||
{
|
||||
void ensureCustomData (const MWWorld::Ptr& ptr) const;
|
||||
|
||||
public:
|
||||
|
||||
virtual std::string getId (const MWWorld::Ptr& ptr) const;
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
void Door::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
|
||||
@ -25,7 +23,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
@ -39,13 +37,11 @@ namespace MWClass
|
||||
ESMS::LiveCellRef<ESM::Door, MWWorld::RefData> *ref =
|
||||
ptr.get<ESM::Door>();
|
||||
|
||||
|
||||
const std::string &model = ref->base->model;
|
||||
assert (ref->base != NULL);
|
||||
if(!model.empty()){
|
||||
physics.insertObjectPhysics(ptr, "meshes\\" + model);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string Door::getName (const MWWorld::Ptr& ptr) const
|
||||
@ -86,7 +82,7 @@ namespace MWClass
|
||||
}
|
||||
else
|
||||
{
|
||||
// another NPC or a create is using the door
|
||||
// another NPC or a creature is using the door
|
||||
// TODO return action for teleporting other NPC/creature
|
||||
return boost::shared_ptr<MWWorld::Action> (new MWWorld::NullAction);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_DOOR_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
@ -20,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
@ -34,14 +35,11 @@ namespace MWClass
|
||||
ESMS::LiveCellRef<ESM::Ingredient, MWWorld::RefData> *ref =
|
||||
ptr.get<ESM::Ingredient>();
|
||||
|
||||
|
||||
|
||||
const std::string &model = ref->base->model;
|
||||
assert (ref->base != NULL);
|
||||
if(!model.empty()){
|
||||
physics.insertObjectPhysics(ptr, "meshes\\" + model);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string Ingredient::getName (const MWWorld::Ptr& ptr) const
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_INGREDIENT_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
#include "../mwsound/soundmanager.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
namespace MWClass
|
||||
@ -23,7 +25,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_LIGHT_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
namespace MWClass
|
||||
@ -19,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_LOCKPICK_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
namespace MWClass
|
||||
@ -19,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_MISC_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -1,27 +1,88 @@
|
||||
|
||||
#include "npc.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <OgreSceneNode.h>
|
||||
|
||||
#include <components/esm/loadnpc.hpp>
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwmechanics/mechanicsmanager.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontalk.hpp"
|
||||
#include "../mwworld/environment.hpp"
|
||||
#include "../mwworld/world.hpp"
|
||||
|
||||
#include "../mwmechanics/mechanicsmanager.hpp"
|
||||
#include <OgreSceneNode.h>
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/customdata.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
const Ogre::Radian kOgrePi (Ogre::Math::PI);
|
||||
const Ogre::Radian kOgrePiOverTwo (Ogre::Math::PI / Ogre::Real(2.0));
|
||||
|
||||
struct CustomData : public MWWorld::CustomData
|
||||
{
|
||||
MWMechanics::NpcStats mNpcStats;
|
||||
MWMechanics::CreatureStats mCreatureStats;
|
||||
MWMechanics::Movement mMovement;
|
||||
MWWorld::ContainerStore<MWWorld::RefData> mContainerStore;
|
||||
|
||||
virtual MWWorld::CustomData *clone() const;
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CustomData::clone() const
|
||||
{
|
||||
return new CustomData (*this);
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::auto_ptr<CustomData> data (new CustomData);
|
||||
|
||||
ESMS::LiveCellRef<ESM::NPC, MWWorld::RefData> *ref = ptr.get<ESM::NPC>();
|
||||
|
||||
// NPC stats
|
||||
if (!ref->base->faction.empty())
|
||||
{
|
||||
// TODO research how initial rank is stored. The information in loadnpc.hpp are at
|
||||
// best very unclear.
|
||||
data->mNpcStats.mFactionRank[ref->base->faction] = 0;
|
||||
}
|
||||
|
||||
for (int i=0; i<27; ++i)
|
||||
data->mNpcStats.mSkill[i].setBase (ref->base->npdt52.skills[i]);
|
||||
|
||||
// creature stats
|
||||
data->mCreatureStats.mAttributes[0].set (ref->base->npdt52.strength);
|
||||
data->mCreatureStats.mAttributes[1].set (ref->base->npdt52.intelligence);
|
||||
data->mCreatureStats.mAttributes[2].set (ref->base->npdt52.willpower);
|
||||
data->mCreatureStats.mAttributes[3].set (ref->base->npdt52.agility);
|
||||
data->mCreatureStats.mAttributes[4].set (ref->base->npdt52.speed);
|
||||
data->mCreatureStats.mAttributes[5].set (ref->base->npdt52.endurance);
|
||||
data->mCreatureStats.mAttributes[6].set (ref->base->npdt52.personality);
|
||||
data->mCreatureStats.mAttributes[7].set (ref->base->npdt52.luck);
|
||||
data->mCreatureStats.mDynamic[0].set (ref->base->npdt52.health);
|
||||
data->mCreatureStats.mDynamic[1].set (ref->base->npdt52.mana);
|
||||
data->mCreatureStats.mDynamic[2].set (ref->base->npdt52.fatigue);
|
||||
|
||||
data->mCreatureStats.mLevel = ref->base->npdt52.level;
|
||||
|
||||
// \todo add initial container content
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Npc::getId (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
ESMS::LiveCellRef<ESM::NPC, MWWorld::RefData> *ref =
|
||||
@ -77,56 +138,16 @@ namespace MWClass
|
||||
|
||||
MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getCreatureStats().get())
|
||||
{
|
||||
boost::shared_ptr<MWMechanics::CreatureStats> stats (
|
||||
new MWMechanics::CreatureStats);
|
||||
ensureCustomData (ptr);
|
||||
|
||||
ESMS::LiveCellRef<ESM::NPC, MWWorld::RefData> *ref = ptr.get<ESM::NPC>();
|
||||
|
||||
stats->mAttributes[0].set (ref->base->npdt52.strength);
|
||||
stats->mAttributes[1].set (ref->base->npdt52.intelligence);
|
||||
stats->mAttributes[2].set (ref->base->npdt52.willpower);
|
||||
stats->mAttributes[3].set (ref->base->npdt52.agility);
|
||||
stats->mAttributes[4].set (ref->base->npdt52.speed);
|
||||
stats->mAttributes[5].set (ref->base->npdt52.endurance);
|
||||
stats->mAttributes[6].set (ref->base->npdt52.personality);
|
||||
stats->mAttributes[7].set (ref->base->npdt52.luck);
|
||||
stats->mDynamic[0].set (ref->base->npdt52.health);
|
||||
stats->mDynamic[1].set (ref->base->npdt52.mana);
|
||||
stats->mDynamic[2].set (ref->base->npdt52.fatigue);
|
||||
|
||||
stats->mLevel = ref->base->npdt52.level;
|
||||
|
||||
ptr.getRefData().getCreatureStats() = stats;
|
||||
}
|
||||
|
||||
return *ptr.getRefData().getCreatureStats();
|
||||
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mCreatureStats;
|
||||
}
|
||||
|
||||
MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getNpcStats().get())
|
||||
{
|
||||
boost::shared_ptr<MWMechanics::NpcStats> stats (
|
||||
new MWMechanics::NpcStats);
|
||||
ensureCustomData (ptr);
|
||||
|
||||
ESMS::LiveCellRef<ESM::NPC, MWWorld::RefData> *ref = ptr.get<ESM::NPC>();
|
||||
|
||||
if (!ref->base->faction.empty())
|
||||
{
|
||||
// TODO research how initial rank is stored. The information in loadnpc.hpp are at
|
||||
// best very unclear.
|
||||
stats->mFactionRank[ref->base->faction] = 0;
|
||||
}
|
||||
|
||||
for (int i=0; i<27; ++i)
|
||||
stats->mSkill[i].setBase (ref->base->npdt52.skills[i]);
|
||||
|
||||
ptr.getRefData().getNpcStats() = stats;
|
||||
}
|
||||
|
||||
return *ptr.getRefData().getNpcStats();
|
||||
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
|
||||
}
|
||||
|
||||
boost::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
|
||||
@ -138,17 +159,9 @@ namespace MWClass
|
||||
MWWorld::ContainerStore<MWWorld::RefData>& Npc::getContainerStore (const MWWorld::Ptr& ptr)
|
||||
const
|
||||
{
|
||||
if (!ptr.getRefData().getContainerStore().get())
|
||||
{
|
||||
boost::shared_ptr<MWWorld::ContainerStore<MWWorld::RefData> > store (
|
||||
new MWWorld::ContainerStore<MWWorld::RefData>);
|
||||
ensureCustomData (ptr);
|
||||
|
||||
// TODO add initial content
|
||||
|
||||
ptr.getRefData().getContainerStore() = store;
|
||||
}
|
||||
|
||||
return *ptr.getRefData().getContainerStore();
|
||||
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
|
||||
}
|
||||
|
||||
std::string Npc::getScript (const MWWorld::Ptr& ptr) const
|
||||
@ -239,29 +252,20 @@ namespace MWClass
|
||||
|
||||
MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getMovement().get())
|
||||
{
|
||||
boost::shared_ptr<MWMechanics::Movement> movement (
|
||||
new MWMechanics::Movement);
|
||||
ensureCustomData (ptr);
|
||||
|
||||
ptr.getRefData().getMovement() = movement;
|
||||
}
|
||||
|
||||
return *ptr.getRefData().getMovement();
|
||||
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mMovement;
|
||||
}
|
||||
|
||||
Ogre::Vector3 Npc::getMovementVector (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
Ogre::Vector3 vector (0, 0, 0);
|
||||
|
||||
if (ptr.getRefData().getMovement().get())
|
||||
{
|
||||
vector.x = - ptr.getRefData().getMovement()->mLeftRight * 200;
|
||||
vector.y = ptr.getRefData().getMovement()->mForwardBackward * 200;
|
||||
vector.x = - getMovementSettings (ptr).mLeftRight * 200;
|
||||
vector.y = getMovementSettings (ptr).mForwardBackward * 200;
|
||||
|
||||
if (getStance (ptr, Run, false))
|
||||
vector *= 2;
|
||||
}
|
||||
if (getStance (ptr, Run, false))
|
||||
vector *= 2;
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
@ -3,11 +3,12 @@
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
class Npc : public MWWorld::Class
|
||||
{
|
||||
void ensureCustomData (const MWWorld::Ptr& ptr) const;
|
||||
|
||||
public:
|
||||
|
||||
virtual std::string getId (const MWWorld::Ptr& ptr) const;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
@ -20,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_POTION_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
class Probe : public MWWorld::Class
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
namespace MWClass
|
||||
@ -19,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_REPAIR_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
@ -15,7 +16,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_STATIC_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontake.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
#include "containerutil.hpp"
|
||||
|
||||
@ -20,7 +21,7 @@ namespace MWClass
|
||||
|
||||
assert (ref->base != NULL);
|
||||
const std::string &model = ref->base->model;
|
||||
|
||||
|
||||
if (!model.empty())
|
||||
{
|
||||
MWRender::Objects& objects = renderingInterface.getObjects();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define GAME_MWCLASS_WEAPON_H
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
|
@ -209,7 +209,7 @@ namespace MWDialogue
|
||||
for(std::deque<MWDialogue::StampedJournalEntry>::const_iterator it = mEnvironment.mJournal->begin();it!=mEnvironment.mJournal->end();it++)
|
||||
{
|
||||
|
||||
if(it->mTopic == name) isInJournal = true;
|
||||
if(it->mTopic == name) isInJournal = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -239,7 +239,7 @@ namespace MWDialogue
|
||||
{
|
||||
ESMS::LiveCellRef<ESM::NPC, MWWorld::RefData>* npc = actor.get<ESM::NPC>();
|
||||
int isFaction = int(toLower(npc->base->faction) == toLower(name));
|
||||
if(selectCompare<int,int>(comp,!isFaction,select.i))
|
||||
if(selectCompare<int,int>(comp,!isFaction,select.i))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@ -253,7 +253,7 @@ namespace MWDialogue
|
||||
{
|
||||
ESMS::LiveCellRef<ESM::NPC, MWWorld::RefData>* npc = actor.get<ESM::NPC>();
|
||||
int isClass = int(toLower(npc->base->cls) == toLower(name));
|
||||
if(selectCompare<int,int>(comp,!isClass,select.i))
|
||||
if(selectCompare<int,int>(comp,!isClass,select.i))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@ -268,7 +268,7 @@ namespace MWDialogue
|
||||
ESMS::LiveCellRef<ESM::NPC, MWWorld::RefData>* npc = actor.get<ESM::NPC>();
|
||||
int isRace = int(toLower(npc->base->race) == toLower(name));
|
||||
//std::cout << "isRace"<<isRace; mEnvironment.mWorld
|
||||
if(selectCompare<int,int>(comp,!isRace,select.i))
|
||||
if(selectCompare<int,int>(comp,!isRace,select.i))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@ -462,7 +462,7 @@ namespace MWDialogue
|
||||
for(ESMS::RecListT<ESM::Dialogue>::MapType::iterator it = dialogueList.begin(); it!=dialogueList.end();it++)
|
||||
{
|
||||
ESM::Dialogue ndialogue = it->second;
|
||||
if(ndialogue.type == ESM::Dialogue::Type::Topic)
|
||||
if(ndialogue.type == ESM::Dialogue::Topic)
|
||||
{
|
||||
for (std::vector<ESM::DialInfo>::const_iterator iter (it->second.mInfo.begin());
|
||||
iter!=it->second.mInfo.end(); ++iter)
|
||||
@ -488,7 +488,7 @@ namespace MWDialogue
|
||||
for(ESMS::RecListT<ESM::Dialogue>::MapType::iterator it = dialogueList.begin(); it!=dialogueList.end();it++)
|
||||
{
|
||||
ESM::Dialogue ndialogue = it->second;
|
||||
if(ndialogue.type == ESM::Dialogue::Type::Greeting)
|
||||
if(ndialogue.type == ESM::Dialogue::Greeting)
|
||||
{
|
||||
if (greetingFound) break;
|
||||
for (std::vector<ESM::DialInfo>::const_iterator iter (it->second.mInfo.begin());
|
||||
|
629
apps/openmw/mwgui/charactercreation.cpp
Normal file
629
apps/openmw/mwgui/charactercreation.cpp
Normal file
@ -0,0 +1,629 @@
|
||||
#include "charactercreation.hpp"
|
||||
|
||||
#include "text_input.hpp"
|
||||
#include "race.hpp"
|
||||
#include "class.hpp"
|
||||
#include "birth.hpp"
|
||||
#include "review.hpp"
|
||||
#include "dialogue.hpp"
|
||||
#include "mode.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Step
|
||||
{
|
||||
const char* mText;
|
||||
const char* mButtons[3];
|
||||
ESM::Class::Specialization mSpecializations[3]; // The specialization for each answer
|
||||
};
|
||||
|
||||
static boost::array<Step, 10> sGenerateClassSteps = { {
|
||||
// Question 1
|
||||
{"On a clear day you chance upon a strange animal, its legs trapped in a hunter's clawsnare. Judging from the bleeding, it will not survive long.",
|
||||
{"Draw your dagger, mercifully endings its life with a single thrust.",
|
||||
"Use herbs from your pack to put it to sleep.",
|
||||
"Do not interfere in the natural evolution of events, but rather take the opportunity to learn more about a strange animal that you have never seen before."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 2
|
||||
{"One Summer afternoon your father gives you a choice of chores.",
|
||||
{"Work in the forge with him casting iron for a new plow.",
|
||||
"Gather herbs for your mother who is preparing dinner.",
|
||||
"Go catch fish at the stream using a net and line."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 3
|
||||
{"Your cousin has given you a very embarrassing nickname and, even worse, likes to call you it in front of your friends. You asked him to stop, but he finds it very amusing to watch you blush.",
|
||||
{"Beat up your cousin, then tell him that if he ever calls you that nickname again, you will bloody him worse than this time.",
|
||||
"Make up a story that makes your nickname a badge of honor instead of something humiliating.",
|
||||
"Make up an even more embarrassing nickname for him and use it constantly until he learns his lesson."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 4
|
||||
{"There is a lot of heated discussion at the local tavern over a grouped of people called 'Telepaths'. They have been hired by certain City-State kings. Rumor has it these Telepaths read a person's mind and tell their lord whether a follower is telling the truth or not.",
|
||||
{"This is a terrible practice. A person's thoughts are his own and no one, not even a king, has the right to make such an invasion into another human's mind.",
|
||||
"Loyal followers to the king have nothing to fear from a Telepath. It is important to have a method of finding assassins and spies before it is too late.",
|
||||
"In these times, it is a necessary evil. Although you do not necessarily like the idea, a Telepath could have certain advantages during a time of war or in finding someone innocent of a crime."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 5
|
||||
{"Your mother sends you to the market with a list of goods to buy. After you finish you find that by mistake a shopkeeper has given you too much money back in exchange for one of the items.",
|
||||
{"Return to the store and give the shopkeeper his hard-earned money, explaining to him the mistake?",
|
||||
"Decide to put the extra money to good use and purchase items that would help your family?",
|
||||
"Pocket the extra money, knowing that shopkeepers in general tend to overcharge customers anyway?"},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 6
|
||||
{"While in the market place you witness a thief cut a purse from a noble. Even as he does so, the noble notices and calls for the city guards. In his haste to get away, the thief drops the purse near you. Surprisingly no one seems to notice the bag of coins at your feet.",
|
||||
{"Pick up the bag and signal to the guard, knowing that the only honorable thing to do is return the money to its rightful owner.",
|
||||
"Leave the bag there, knowing that it is better not to get involved.",
|
||||
"Pick up the bag and pocket it, knowing that the extra windfall will help your family in times of trouble."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 7
|
||||
{"Your father sends you on a task which you loathe, cleaning the stables. On the way there, pitchfork in hand, you run into your friend from the homestead near your own. He offers to do it for you, in return for a future favor of his choosing.",
|
||||
{"Decline his offer, knowing that your father expects you to do the work, and it is better not to be in debt.",
|
||||
"Ask him to help you, knowing that two people can do the job faster than one, and agree to help him with one task of his choosing in the future.",
|
||||
"Accept his offer, reasoning that as long as the stables are cleaned, it matters not who does the cleaning."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 8
|
||||
{"Your mother asks you to help fix the stove. While you are working, a very hot pipe slips its mooring and falls towards her.",
|
||||
{"Position yourself between the pipe and your mother.",
|
||||
"Grab the hot pipe and try to push it away.",
|
||||
"Push your mother out of the way."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 9
|
||||
{"While in town the baker gives you a sweetroll. Delighted, you take it into an alley to enjoy only to be intercepted by a gang of three other kids your age. The leader demands the sweetroll, or else he and his friends will beat you and take it.",
|
||||
{"Drop the sweetroll and step on it, then get ready for the fight.",
|
||||
"Give him the sweetroll now without argument, knowing that later this afternoon you will have all your friends with you and can come and take whatever he owes you.",
|
||||
"Act like you're going to give him the sweetroll, but at the last minute throw it in the air, hoping that they'll pay attention to it long enough for you to get a shot in on the leader."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 10
|
||||
{"Entering town you find that you are witness to a very well-dressed man running from a crowd. He screams to you for help. The crowd behind him seem very angry.",
|
||||
{"Rush to the town's aid immediately, despite your lack of knowledge of the circumstances.",
|
||||
"Stand aside and allow the man and the mob to pass, realizing it is probably best not to get involved.",
|
||||
"Rush to the man's aid immediately, despite your lack of knowledge of the circumstances."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
using namespace MWGui;
|
||||
|
||||
CharacterCreation::CharacterCreation(WindowManager* _wm, MWWorld::Environment* _environment)
|
||||
: mNameDialog(0)
|
||||
, mRaceDialog(0)
|
||||
, mDialogueWindow(0)
|
||||
, mClassChoiceDialog(0)
|
||||
, mGenerateClassQuestionDialog(0)
|
||||
, mGenerateClassResultDialog(0)
|
||||
, mPickClassDialog(0)
|
||||
, mCreateClassDialog(0)
|
||||
, mBirthSignDialog(0)
|
||||
, mReviewDialog(0)
|
||||
, mWM(_wm)
|
||||
, mEnvironment(_environment)
|
||||
{
|
||||
mCreationStage = CSE_NotStarted;
|
||||
}
|
||||
|
||||
void CharacterCreation::spawnDialog(const char id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case GM_Name:
|
||||
if(mNameDialog)
|
||||
mWM->removeDialog(mNameDialog);
|
||||
mNameDialog = new TextInputDialog(*mWM);
|
||||
mNameDialog->setTextLabel(mWM->getGameSettingString("sName", "Name"));
|
||||
mNameDialog->setTextInput(mPlayerName);
|
||||
mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen);
|
||||
mNameDialog->eventDone = MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone);
|
||||
mNameDialog->open();
|
||||
break;
|
||||
|
||||
case GM_Race:
|
||||
if (mRaceDialog)
|
||||
mWM->removeDialog(mRaceDialog);
|
||||
mRaceDialog = new RaceDialog(*mWM);
|
||||
mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen);
|
||||
mRaceDialog->setRaceId(mPlayerRaceId);
|
||||
mRaceDialog->eventDone = MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone);
|
||||
mRaceDialog->eventBack = MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack);
|
||||
mRaceDialog->open();
|
||||
break;
|
||||
|
||||
case GM_Class:
|
||||
if (mClassChoiceDialog)
|
||||
mWM->removeDialog(mClassChoiceDialog);
|
||||
mClassChoiceDialog = new ClassChoiceDialog(*mWM);
|
||||
mClassChoiceDialog->eventButtonSelected = MyGUI::newDelegate(this, &CharacterCreation::onClassChoice);
|
||||
mClassChoiceDialog->open();
|
||||
break;
|
||||
|
||||
case GM_ClassPick:
|
||||
if (mPickClassDialog)
|
||||
mWM->removeDialog(mPickClassDialog);
|
||||
mPickClassDialog = new PickClassDialog(*mWM);
|
||||
mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
|
||||
mPickClassDialog->setClassId(mPlayerClass.name);
|
||||
mPickClassDialog->eventDone = MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone);
|
||||
mPickClassDialog->eventBack = MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack);
|
||||
mPickClassDialog->open();
|
||||
break;
|
||||
|
||||
case GM_Birth:
|
||||
if (mBirthSignDialog)
|
||||
mWM->removeDialog(mBirthSignDialog);
|
||||
mBirthSignDialog = new BirthDialog(*mWM);
|
||||
mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen);
|
||||
mBirthSignDialog->setBirthId(mPlayerBirthSignId);
|
||||
mBirthSignDialog->eventDone = MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone);
|
||||
mBirthSignDialog->eventBack = MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack);
|
||||
mBirthSignDialog->open();
|
||||
break;
|
||||
|
||||
case GM_ClassCreate:
|
||||
if (mCreateClassDialog)
|
||||
mWM->removeDialog(mCreateClassDialog);
|
||||
mCreateClassDialog = new CreateClassDialog(*mWM);
|
||||
mCreateClassDialog->eventDone = MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone);
|
||||
mCreateClassDialog->eventBack = MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack);
|
||||
mCreateClassDialog->open();
|
||||
break;
|
||||
case GM_ClassGenerate:
|
||||
mGenerateClassStep = 0;
|
||||
mGenerateClass = "";
|
||||
mGenerateClassSpecializations[0] = 0;
|
||||
mGenerateClassSpecializations[1] = 0;
|
||||
mGenerateClassSpecializations[2] = 0;
|
||||
showClassQuestionDialog();
|
||||
break;
|
||||
case GM_Review:
|
||||
if (mReviewDialog)
|
||||
mWM->removeDialog(mReviewDialog);
|
||||
mReviewDialog = new ReviewDialog(*mWM);
|
||||
mReviewDialog->setPlayerName(mPlayerName);
|
||||
mReviewDialog->setRace(mPlayerRaceId);
|
||||
mReviewDialog->setClass(mPlayerClass);
|
||||
mReviewDialog->setBirthSign(mPlayerBirthSignId);
|
||||
|
||||
mReviewDialog->setHealth(mPlayerHealth);
|
||||
mReviewDialog->setMagicka(mPlayerMagicka);
|
||||
mReviewDialog->setFatigue(mPlayerFatigue);
|
||||
|
||||
{
|
||||
std::map<ESM::Attribute::AttributeID, MWMechanics::Stat<int> >::iterator end = mPlayerAttributes.end();
|
||||
for (std::map<ESM::Attribute::AttributeID, MWMechanics::Stat<int> >::iterator it = mPlayerAttributes.begin(); it != end; ++it)
|
||||
{
|
||||
mReviewDialog->setAttribute(it->first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::map<ESM::Skill::SkillEnum, MWMechanics::Stat<float> >::iterator end = mPlayerSkillValues.end();
|
||||
for (std::map<ESM::Skill::SkillEnum, MWMechanics::Stat<float> >::iterator it = mPlayerSkillValues.begin(); it != end; ++it)
|
||||
{
|
||||
mReviewDialog->setSkillValue(it->first, it->second);
|
||||
}
|
||||
mReviewDialog->configureSkills(mPlayerMajorSkills, mPlayerMinorSkills);
|
||||
}
|
||||
|
||||
mReviewDialog->eventDone = MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone);
|
||||
mReviewDialog->eventBack = MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack);
|
||||
mReviewDialog->eventActivateDialog = MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog);
|
||||
mReviewDialog->open();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterCreation::setPlayerHealth (const MWMechanics::DynamicStat<int>& value)
|
||||
{
|
||||
mPlayerHealth = value;
|
||||
}
|
||||
|
||||
void CharacterCreation::setPlayerMagicka (const MWMechanics::DynamicStat<int>& value)
|
||||
{
|
||||
mPlayerMagicka = value;
|
||||
}
|
||||
|
||||
void CharacterCreation::setPlayerFatigue (const MWMechanics::DynamicStat<int>& value)
|
||||
{
|
||||
mPlayerFatigue = value;
|
||||
}
|
||||
|
||||
void CharacterCreation::onReviewDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (mReviewDialog)
|
||||
mWM->removeDialog(mReviewDialog);
|
||||
|
||||
mWM->setGuiMode(GM_Game);
|
||||
}
|
||||
|
||||
void CharacterCreation::onReviewDialogBack()
|
||||
{
|
||||
if (mReviewDialog)
|
||||
mWM->removeDialog(mReviewDialog);
|
||||
|
||||
mWM->setGuiMode(GM_Birth);
|
||||
}
|
||||
|
||||
void CharacterCreation::onReviewActivateDialog(int parDialog)
|
||||
{
|
||||
if (mReviewDialog)
|
||||
mWM->removeDialog(mReviewDialog);
|
||||
mCreationStage = CSE_ReviewNext;
|
||||
|
||||
switch(parDialog)
|
||||
{
|
||||
case ReviewDialog::NAME_DIALOG:
|
||||
mWM->setGuiMode(GM_Name);
|
||||
break;
|
||||
case ReviewDialog::RACE_DIALOG:
|
||||
mWM->setGuiMode(GM_Race);
|
||||
break;
|
||||
case ReviewDialog::CLASS_DIALOG:
|
||||
mWM->setGuiMode(GM_Class);
|
||||
break;
|
||||
case ReviewDialog::BIRTHSIGN_DIALOG:
|
||||
mWM->setGuiMode(GM_Birth);
|
||||
};
|
||||
}
|
||||
|
||||
void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (mPickClassDialog)
|
||||
{
|
||||
const std::string &classId = mPickClassDialog->getClassId();
|
||||
if (!classId.empty())
|
||||
mEnvironment->mMechanicsManager->setPlayerClass(classId);
|
||||
const ESM::Class *klass = mEnvironment->mWorld->getStore().classes.find(classId);
|
||||
if (klass)
|
||||
{
|
||||
mPlayerClass = *klass;
|
||||
mWM->setPlayerClass(mPlayerClass);
|
||||
}
|
||||
mWM->removeDialog(mPickClassDialog);
|
||||
}
|
||||
|
||||
//TODO This bit gets repeated a few times; wrap it in a function
|
||||
if (mCreationStage == CSE_ReviewNext)
|
||||
mWM->setGuiMode(GM_Review);
|
||||
else if (mCreationStage >= CSE_ClassChosen)
|
||||
mWM->setGuiMode(GM_Birth);
|
||||
else
|
||||
{
|
||||
mCreationStage = CSE_ClassChosen;
|
||||
mWM->setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterCreation::onPickClassDialogBack()
|
||||
{
|
||||
if (mPickClassDialog)
|
||||
{
|
||||
const std::string classId = mPickClassDialog->getClassId();
|
||||
if (!classId.empty())
|
||||
mEnvironment->mMechanicsManager->setPlayerClass(classId);
|
||||
mWM->removeDialog(mPickClassDialog);
|
||||
}
|
||||
|
||||
mWM->setGuiMode(GM_Class);
|
||||
}
|
||||
|
||||
void CharacterCreation::onClassChoice(int _index)
|
||||
{
|
||||
if (mClassChoiceDialog)
|
||||
{
|
||||
mWM->removeDialog(mClassChoiceDialog);
|
||||
}
|
||||
|
||||
switch(_index)
|
||||
{
|
||||
case ClassChoiceDialog::Class_Generate:
|
||||
mWM->setGuiMode(GM_ClassGenerate);
|
||||
break;
|
||||
case ClassChoiceDialog::Class_Pick:
|
||||
mWM->setGuiMode(GM_ClassPick);
|
||||
break;
|
||||
case ClassChoiceDialog::Class_Create:
|
||||
mWM->setGuiMode(GM_ClassCreate);
|
||||
break;
|
||||
case ClassChoiceDialog::Class_Back:
|
||||
mWM->setGuiMode(GM_Race);
|
||||
break;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
void CharacterCreation::onNameDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (mNameDialog)
|
||||
{
|
||||
mPlayerName = mNameDialog->getTextInput();
|
||||
mWM->setValue("name", mPlayerName);
|
||||
mEnvironment->mMechanicsManager->setPlayerName(mPlayerName);
|
||||
mWM->removeDialog(mNameDialog);
|
||||
}
|
||||
|
||||
if (mCreationStage == CSE_ReviewNext)
|
||||
mWM->setGuiMode(GM_Review);
|
||||
else if (mCreationStage >= CSE_NameChosen)
|
||||
mWM->setGuiMode(GM_Race);
|
||||
else
|
||||
{
|
||||
mCreationStage = CSE_NameChosen;
|
||||
mWM->setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterCreation::onRaceDialogBack()
|
||||
{
|
||||
if (mRaceDialog)
|
||||
{
|
||||
mPlayerRaceId = mRaceDialog->getRaceId();
|
||||
if (!mPlayerRaceId.empty())
|
||||
mEnvironment->mMechanicsManager->setPlayerRace(mPlayerRaceId, mRaceDialog->getGender() == RaceDialog::GM_Male);
|
||||
mWM->removeDialog(mRaceDialog);
|
||||
}
|
||||
|
||||
mWM->setGuiMode(GM_Name);
|
||||
}
|
||||
|
||||
void CharacterCreation::onRaceDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (mRaceDialog)
|
||||
{
|
||||
mPlayerRaceId = mRaceDialog->getRaceId();
|
||||
mWM->setValue("race", mPlayerRaceId);
|
||||
if (!mPlayerRaceId.empty())
|
||||
mEnvironment->mMechanicsManager->setPlayerRace(mPlayerRaceId, mRaceDialog->getGender() == RaceDialog::GM_Male);
|
||||
mWM->removeDialog(mRaceDialog);
|
||||
}
|
||||
|
||||
if (mCreationStage == CSE_ReviewNext)
|
||||
mWM->setGuiMode(GM_Review);
|
||||
else if(mCreationStage >= CSE_RaceChosen)
|
||||
mWM->setGuiMode(GM_Class);
|
||||
else
|
||||
{
|
||||
mCreationStage = CSE_RaceChosen;
|
||||
mWM->setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (mBirthSignDialog)
|
||||
{
|
||||
mPlayerBirthSignId = mBirthSignDialog->getBirthId();
|
||||
mWM->setBirthSign(mPlayerBirthSignId);
|
||||
if (!mPlayerBirthSignId.empty())
|
||||
mEnvironment->mMechanicsManager->setPlayerBirthsign(mPlayerBirthSignId);
|
||||
mWM->removeDialog(mBirthSignDialog);
|
||||
}
|
||||
|
||||
if (mCreationStage >= CSE_BirthSignChosen)
|
||||
mWM->setGuiMode(GM_Review);
|
||||
else
|
||||
{
|
||||
mCreationStage = CSE_BirthSignChosen;
|
||||
mWM->setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterCreation::onBirthSignDialogBack()
|
||||
{
|
||||
if (mBirthSignDialog)
|
||||
{
|
||||
mEnvironment->mMechanicsManager->setPlayerBirthsign(mBirthSignDialog->getBirthId());
|
||||
mWM->removeDialog(mBirthSignDialog);
|
||||
}
|
||||
|
||||
mWM->setGuiMode(GM_Class);
|
||||
}
|
||||
|
||||
void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (mCreateClassDialog)
|
||||
{
|
||||
ESM::Class klass;
|
||||
klass.name = mCreateClassDialog->getName();
|
||||
klass.description = mCreateClassDialog->getDescription();
|
||||
klass.data.specialization = mCreateClassDialog->getSpecializationId();
|
||||
klass.data.isPlayable = 0x1;
|
||||
|
||||
std::vector<int> attributes = mCreateClassDialog->getFavoriteAttributes();
|
||||
assert(attributes.size() == 2);
|
||||
klass.data.attribute[0] = attributes[0];
|
||||
klass.data.attribute[1] = attributes[1];
|
||||
|
||||
std::vector<ESM::Skill::SkillEnum> majorSkills = mCreateClassDialog->getMajorSkills();
|
||||
std::vector<ESM::Skill::SkillEnum> minorSkills = mCreateClassDialog->getMinorSkills();
|
||||
assert(majorSkills.size() >= sizeof(klass.data.skills)/sizeof(klass.data.skills[0]));
|
||||
assert(minorSkills.size() >= sizeof(klass.data.skills)/sizeof(klass.data.skills[0]));
|
||||
for (size_t i = 0; i < sizeof(klass.data.skills)/sizeof(klass.data.skills[0]); ++i)
|
||||
{
|
||||
klass.data.skills[i][1] = majorSkills[i];
|
||||
klass.data.skills[i][0] = minorSkills[i];
|
||||
}
|
||||
mEnvironment->mMechanicsManager->setPlayerClass(klass);
|
||||
mPlayerClass = klass;
|
||||
mWM->setPlayerClass(klass);
|
||||
|
||||
mWM->removeDialog(mCreateClassDialog);
|
||||
}
|
||||
|
||||
if (mCreationStage == CSE_ReviewNext)
|
||||
mWM->setGuiMode(GM_Review);
|
||||
else if (mCreationStage >= CSE_ClassChosen)
|
||||
mWM->setGuiMode(GM_Birth);
|
||||
else
|
||||
{
|
||||
mCreationStage = CSE_ClassChosen;
|
||||
mWM->setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterCreation::onCreateClassDialogBack()
|
||||
{
|
||||
if (mCreateClassDialog)
|
||||
mWM->removeDialog(mCreateClassDialog);
|
||||
|
||||
mWM->setGuiMode(GM_Class);
|
||||
}
|
||||
|
||||
void CharacterCreation::onClassQuestionChosen(int _index)
|
||||
{
|
||||
if (mGenerateClassQuestionDialog)
|
||||
mWM->removeDialog(mGenerateClassQuestionDialog);
|
||||
if (_index < 0 || _index >= 3)
|
||||
{
|
||||
mWM->setGuiMode(GM_Class);
|
||||
return;
|
||||
}
|
||||
|
||||
ESM::Class::Specialization specialization = sGenerateClassSteps[mGenerateClassStep].mSpecializations[_index];
|
||||
if (specialization == ESM::Class::Stealth)
|
||||
++mGenerateClassSpecializations[0];
|
||||
else if (specialization == ESM::Class::Combat)
|
||||
++mGenerateClassSpecializations[1];
|
||||
else if (specialization == ESM::Class::Magic)
|
||||
++mGenerateClassSpecializations[2];
|
||||
++mGenerateClassStep;
|
||||
showClassQuestionDialog();
|
||||
}
|
||||
|
||||
void CharacterCreation::showClassQuestionDialog()
|
||||
{
|
||||
if (mGenerateClassStep == sGenerateClassSteps.size())
|
||||
{
|
||||
static boost::array<ClassPoint, 23> classes = { {
|
||||
{"Acrobat", {6, 2, 2}},
|
||||
{"Agent", {6, 1, 3}},
|
||||
{"Archer", {3, 5, 2}},
|
||||
{"Archer", {5, 5, 0}},
|
||||
{"Assassin", {6, 3, 1}},
|
||||
{"Barbarian", {3, 6, 1}},
|
||||
{"Bard", {3, 3, 3}},
|
||||
{"Battlemage", {1, 3, 6}},
|
||||
{"Crusader", {1, 6, 3}},
|
||||
{"Healer", {3, 1, 6}},
|
||||
{"Knight", {2, 6, 2}},
|
||||
{"Monk", {5, 3, 2}},
|
||||
{"Nightblade", {4, 2, 4}},
|
||||
{"Pilgrim", {5, 2, 3}},
|
||||
{"Rogue", {3, 4, 3}},
|
||||
{"Rogue", {4, 4, 2}},
|
||||
{"Rogue", {5, 4, 1}},
|
||||
{"Scout", {2, 5, 3}},
|
||||
{"Sorcerer", {2, 2, 6}},
|
||||
{"Spellsword", {2, 4, 4}},
|
||||
{"Spellsword", {5, 1, 4}},
|
||||
{"Witchhunter", {2, 3, 5}},
|
||||
{"Witchhunter", {5, 0, 5}}
|
||||
} };
|
||||
|
||||
int match = -1;
|
||||
for (unsigned i = 0; i < classes.size(); ++i)
|
||||
{
|
||||
if (mGenerateClassSpecializations[0] == classes[i].points[0] &&
|
||||
mGenerateClassSpecializations[1] == classes[i].points[1] &&
|
||||
mGenerateClassSpecializations[2] == classes[i].points[2])
|
||||
{
|
||||
match = i;
|
||||
mGenerateClass = classes[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match == -1)
|
||||
{
|
||||
if (mGenerateClassSpecializations[0] >= 7)
|
||||
mGenerateClass = "Thief";
|
||||
else if (mGenerateClassSpecializations[1] >= 7)
|
||||
mGenerateClass = "Warrior";
|
||||
else if (mGenerateClassSpecializations[2] >= 7)
|
||||
mGenerateClass = "Mage";
|
||||
else
|
||||
{
|
||||
std::cerr << "Failed to deduce class from chosen answers in generate class dialog" << std::endl;
|
||||
mGenerateClass = "Thief";
|
||||
}
|
||||
}
|
||||
|
||||
if (mGenerateClassResultDialog)
|
||||
mWM->removeDialog(mGenerateClassResultDialog);
|
||||
mGenerateClassResultDialog = new GenerateClassResultDialog(*mWM);
|
||||
mGenerateClassResultDialog->setClassId(mGenerateClass);
|
||||
mGenerateClassResultDialog->eventBack = MyGUI::newDelegate(this, &CharacterCreation::onGenerateClassBack);
|
||||
mGenerateClassResultDialog->eventDone = MyGUI::newDelegate(this, &CharacterCreation::onGenerateClassDone);
|
||||
mGenerateClassResultDialog->open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGenerateClassStep > sGenerateClassSteps.size())
|
||||
{
|
||||
mWM->setGuiMode(GM_Class);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGenerateClassQuestionDialog)
|
||||
mWM->removeDialog(mGenerateClassQuestionDialog);
|
||||
mGenerateClassQuestionDialog = new InfoBoxDialog(*mWM);
|
||||
|
||||
InfoBoxDialog::ButtonList buttons;
|
||||
mGenerateClassQuestionDialog->setText(sGenerateClassSteps[mGenerateClassStep].mText);
|
||||
buttons.push_back(sGenerateClassSteps[mGenerateClassStep].mButtons[0]);
|
||||
buttons.push_back(sGenerateClassSteps[mGenerateClassStep].mButtons[1]);
|
||||
buttons.push_back(sGenerateClassSteps[mGenerateClassStep].mButtons[2]);
|
||||
mGenerateClassQuestionDialog->setButtons(buttons);
|
||||
mGenerateClassQuestionDialog->eventButtonSelected = MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen);
|
||||
mGenerateClassQuestionDialog->open();
|
||||
}
|
||||
|
||||
void CharacterCreation::onGenerateClassBack()
|
||||
{
|
||||
if(mCreationStage < CSE_ClassChosen)
|
||||
mCreationStage = CSE_ClassChosen;
|
||||
|
||||
if (mGenerateClassResultDialog)
|
||||
mWM->removeDialog(mGenerateClassResultDialog);
|
||||
mEnvironment->mMechanicsManager->setPlayerClass(mGenerateClass);
|
||||
|
||||
mWM->setGuiMode(GM_Class);
|
||||
}
|
||||
|
||||
void CharacterCreation::onGenerateClassDone(WindowBase* parWindow)
|
||||
{
|
||||
if (mGenerateClassResultDialog)
|
||||
mWM->removeDialog(mGenerateClassResultDialog);
|
||||
mEnvironment->mMechanicsManager->setPlayerClass(mGenerateClass);
|
||||
|
||||
if (mCreationStage == CSE_ReviewNext)
|
||||
mWM->setGuiMode(GM_Review);
|
||||
else if (mCreationStage >= CSE_ClassChosen)
|
||||
mWM->setGuiMode(GM_Birth);
|
||||
else
|
||||
{
|
||||
mCreationStage = CSE_ClassChosen;
|
||||
mWM->setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
CharacterCreation::~CharacterCreation()
|
||||
{
|
||||
delete mNameDialog;
|
||||
delete mRaceDialog;
|
||||
delete mDialogueWindow;
|
||||
delete mClassChoiceDialog;
|
||||
delete mGenerateClassQuestionDialog;
|
||||
delete mGenerateClassResultDialog;
|
||||
delete mPickClassDialog;
|
||||
delete mCreateClassDialog;
|
||||
delete mBirthSignDialog;
|
||||
delete mReviewDialog;
|
||||
}
|
120
apps/openmw/mwgui/charactercreation.hpp
Normal file
120
apps/openmw/mwgui/charactercreation.hpp
Normal file
@ -0,0 +1,120 @@
|
||||
#ifndef CHARACTER_CREATION_HPP
|
||||
#define CHARACTER_CREATION_HPP
|
||||
|
||||
#include "window_manager.hpp"
|
||||
|
||||
#include "../mwmechanics/mechanicsmanager.hpp"
|
||||
#include "../mwmechanics/stat.hpp"
|
||||
#include "../mwworld/world.hpp"
|
||||
#include <components/esm_store/store.hpp>
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
class WindowBase;
|
||||
|
||||
class TextInputDialog;
|
||||
class InfoBoxDialog;
|
||||
class RaceDialog;
|
||||
class DialogueWindow;
|
||||
class ClassChoiceDialog;
|
||||
class GenerateClassResultDialog;
|
||||
class PickClassDialog;
|
||||
class CreateClassDialog;
|
||||
class BirthDialog;
|
||||
class ReviewDialog;
|
||||
class MessageBoxManager;
|
||||
|
||||
class CharacterCreation
|
||||
{
|
||||
public:
|
||||
typedef std::vector<int> SkillList;
|
||||
|
||||
CharacterCreation(WindowManager* _wm, MWWorld::Environment* _environment);
|
||||
~CharacterCreation();
|
||||
|
||||
//Show a dialog
|
||||
void spawnDialog(const char id);
|
||||
|
||||
void setPlayerHealth (const MWMechanics::DynamicStat<int>& value);
|
||||
|
||||
void setPlayerMagicka (const MWMechanics::DynamicStat<int>& value);
|
||||
|
||||
void setPlayerFatigue (const MWMechanics::DynamicStat<int>& value);
|
||||
|
||||
private:
|
||||
//Dialogs
|
||||
TextInputDialog* mNameDialog;
|
||||
RaceDialog* mRaceDialog;
|
||||
DialogueWindow* mDialogueWindow;
|
||||
ClassChoiceDialog* mClassChoiceDialog;
|
||||
InfoBoxDialog* mGenerateClassQuestionDialog;
|
||||
GenerateClassResultDialog* mGenerateClassResultDialog;
|
||||
PickClassDialog* mPickClassDialog;
|
||||
CreateClassDialog* mCreateClassDialog;
|
||||
BirthDialog* mBirthSignDialog;
|
||||
ReviewDialog* mReviewDialog;
|
||||
|
||||
WindowManager* mWM;
|
||||
MWWorld::Environment* mEnvironment;
|
||||
|
||||
//Player data
|
||||
std::string mPlayerName;
|
||||
std::string mPlayerRaceId;
|
||||
std::string mPlayerBirthSignId;
|
||||
ESM::Class mPlayerClass;
|
||||
std::map<ESM::Attribute::AttributeID, MWMechanics::Stat<int> > mPlayerAttributes;
|
||||
SkillList mPlayerMajorSkills, mPlayerMinorSkills;
|
||||
std::map<ESM::Skill::SkillEnum, MWMechanics::Stat<float> > mPlayerSkillValues;
|
||||
MWMechanics::DynamicStat<int> mPlayerHealth;
|
||||
MWMechanics::DynamicStat<int> mPlayerMagicka;
|
||||
MWMechanics::DynamicStat<int> mPlayerFatigue;
|
||||
|
||||
//Class generation vars
|
||||
unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog
|
||||
unsigned mGenerateClassSpecializations[3]; // A counter for each specialization which is increased when an answer is chosen
|
||||
std::string mGenerateClass; // In order: Stealth, Combat, Magic
|
||||
|
||||
////Dialog events
|
||||
//Name dialog
|
||||
void onNameDialogDone(WindowBase* parWindow);
|
||||
|
||||
//Race dialog
|
||||
void onRaceDialogDone(WindowBase* parWindow);
|
||||
void onRaceDialogBack();
|
||||
|
||||
//Class dialogs
|
||||
void onClassChoice(int _index);
|
||||
void onPickClassDialogDone(WindowBase* parWindow);
|
||||
void onPickClassDialogBack();
|
||||
void onCreateClassDialogDone(WindowBase* parWindow);
|
||||
void onCreateClassDialogBack();
|
||||
void showClassQuestionDialog();
|
||||
void onClassQuestionChosen(int _index);
|
||||
void onGenerateClassBack();
|
||||
void onGenerateClassDone(WindowBase* parWindow);
|
||||
|
||||
//Birthsign dialog
|
||||
void onBirthSignDialogDone(WindowBase* parWindow);
|
||||
void onBirthSignDialogBack();
|
||||
|
||||
//Review dialog
|
||||
void onReviewDialogDone(WindowBase* parWindow);
|
||||
void onReviewDialogBack();
|
||||
void onReviewActivateDialog(int parDialog);
|
||||
|
||||
enum CSE //Creation Stage Enum
|
||||
{
|
||||
CSE_NotStarted,
|
||||
CSE_NameChosen,
|
||||
CSE_RaceChosen,
|
||||
CSE_ClassChosen,
|
||||
CSE_BirthSignChosen,
|
||||
CSE_ReviewNext
|
||||
};
|
||||
|
||||
CSE mCreationStage; // Which state the character creating is in, controls back/next/ok buttons
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -1,9 +1,6 @@
|
||||
#include "window_manager.hpp"
|
||||
#include "layouts.hpp"
|
||||
#include "text_input.hpp"
|
||||
#include "race.hpp"
|
||||
#include "class.hpp"
|
||||
#include "birth.hpp"
|
||||
#include "review.hpp"
|
||||
#include "dialogue.hpp"
|
||||
#include "dialogue_history.hpp"
|
||||
@ -15,6 +12,7 @@
|
||||
|
||||
#include "console.hpp"
|
||||
#include "journalwindow.hpp"
|
||||
#include "charactercreation.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
@ -22,20 +20,10 @@
|
||||
|
||||
using namespace MWGui;
|
||||
|
||||
WindowManager::WindowManager(MyGUI::Gui *_gui, MWWorld::Environment& environment,
|
||||
const Compiler::Extensions& extensions, int fpsLevel, bool newGame)
|
||||
WindowManager::WindowManager(MWWorld::Environment& environment,
|
||||
const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string logpath)
|
||||
: environment(environment)
|
||||
, nameDialog(nullptr)
|
||||
, raceDialog(nullptr)
|
||||
, dialogueWindow(nullptr)
|
||||
, classChoiceDialog(nullptr)
|
||||
, generateClassQuestionDialog(nullptr)
|
||||
, generateClassResultDialog(nullptr)
|
||||
, pickClassDialog(nullptr)
|
||||
, createClassDialog(nullptr)
|
||||
, birthSignDialog(nullptr)
|
||||
, reviewDialog(nullptr)
|
||||
, gui(_gui)
|
||||
, mode(GM_Game)
|
||||
, nextMode(GM_Game)
|
||||
, needModeChange(false)
|
||||
@ -44,7 +32,9 @@ WindowManager::WindowManager(MyGUI::Gui *_gui, MWWorld::Environment& environment
|
||||
{
|
||||
showFPSLevel = fpsLevel;
|
||||
|
||||
creationStage = NotStarted;
|
||||
// Set up the GUI system
|
||||
mGuiManager = new OEngine::GUI::MyGUIManager(mOgre->getWindow(), mOgre->getScene(), false, logpath);
|
||||
gui = mGuiManager->getGui();
|
||||
|
||||
//Register own widgets with MyGUI
|
||||
MyGUI::FactoryManager::getInstance().registerFactory<DialogeHistory>("Widget");
|
||||
@ -58,9 +48,6 @@ WindowManager::WindowManager(MyGUI::Gui *_gui, MWWorld::Environment& environment
|
||||
menu = new MainMenu(w,h);
|
||||
map = new MapWindow();
|
||||
stats = new StatsWindow(*this);
|
||||
#if 0
|
||||
inventory = new InventoryWindow ();
|
||||
#endif
|
||||
console = new Console(w,h, environment, extensions);
|
||||
mJournal = new JournalWindow(*this);
|
||||
mMessageBoxManager = new MessageBoxManager(this);
|
||||
@ -69,6 +56,8 @@ WindowManager::WindowManager(MyGUI::Gui *_gui, MWWorld::Environment& environment
|
||||
// The HUD is always on
|
||||
hud->setVisible(true);
|
||||
|
||||
mCharGen = new CharacterCreation(this, &environment);
|
||||
|
||||
// Setup player stats
|
||||
for (int i = 0; i < ESM::Attribute::Length; ++i)
|
||||
{
|
||||
@ -92,6 +81,7 @@ WindowManager::WindowManager(MyGUI::Gui *_gui, MWWorld::Environment& environment
|
||||
|
||||
WindowManager::~WindowManager()
|
||||
{
|
||||
delete mGuiManager;
|
||||
delete console;
|
||||
delete mMessageBoxManager;
|
||||
delete hud;
|
||||
@ -99,20 +89,9 @@ WindowManager::~WindowManager()
|
||||
delete menu;
|
||||
delete stats;
|
||||
delete mJournal;
|
||||
#if 0
|
||||
delete inventory;
|
||||
#endif
|
||||
|
||||
delete nameDialog;
|
||||
delete raceDialog;
|
||||
delete dialogueWindow;
|
||||
delete classChoiceDialog;
|
||||
delete generateClassQuestionDialog;
|
||||
delete generateClassResultDialog;
|
||||
delete pickClassDialog;
|
||||
delete createClassDialog;
|
||||
delete birthSignDialog;
|
||||
delete reviewDialog;
|
||||
|
||||
delete mCharGen;
|
||||
|
||||
cleanupGarbage();
|
||||
}
|
||||
@ -169,9 +148,6 @@ void WindowManager::updateVisible()
|
||||
map->setVisible(false);
|
||||
menu->setVisible(false);
|
||||
stats->setVisible(false);
|
||||
#if 0
|
||||
inventory->setVisible(false);
|
||||
#endif
|
||||
console->disable();
|
||||
mJournal->setVisible(false);
|
||||
dialogueWindow->setVisible(false);
|
||||
@ -180,7 +156,7 @@ void WindowManager::updateVisible()
|
||||
gui->setVisiblePointer(isGuiMode());
|
||||
|
||||
// If in game mode, don't show anything.
|
||||
if(mode == GM_Game)
|
||||
if(mode == GM_Game) //Use a switch/case structure
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -198,126 +174,10 @@ void WindowManager::updateVisible()
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == GM_Name)
|
||||
//There must be a more elegant solution
|
||||
if (mode == GM_Name || mode == GM_Race || mode == GM_Class || mode == GM_ClassPick || mode == GM_ClassCreate || mode == GM_Birth || mode == GM_ClassGenerate || mode == GM_Review)
|
||||
{
|
||||
if (nameDialog)
|
||||
removeDialog(nameDialog);
|
||||
nameDialog = new TextInputDialog(*this);
|
||||
std::string sName = getGameSettingString("sName", "Name");
|
||||
nameDialog->setTextLabel(sName);
|
||||
nameDialog->setTextInput(playerName);
|
||||
nameDialog->setNextButtonShow(creationStage >= NameChosen);
|
||||
nameDialog->eventDone = MyGUI::newDelegate(this, &WindowManager::onNameDialogDone);
|
||||
nameDialog->open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == GM_Race)
|
||||
{
|
||||
if (raceDialog)
|
||||
removeDialog(raceDialog);
|
||||
raceDialog = new RaceDialog(*this);
|
||||
raceDialog->setNextButtonShow(creationStage >= RaceChosen);
|
||||
raceDialog->setRaceId(playerRaceId);
|
||||
raceDialog->eventDone = MyGUI::newDelegate(this, &WindowManager::onRaceDialogDone);
|
||||
raceDialog->eventBack = MyGUI::newDelegate(this, &WindowManager::onRaceDialogBack);
|
||||
raceDialog->open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == GM_Class)
|
||||
{
|
||||
if (classChoiceDialog)
|
||||
removeDialog(classChoiceDialog);
|
||||
classChoiceDialog = new ClassChoiceDialog(*this);
|
||||
classChoiceDialog->eventButtonSelected = MyGUI::newDelegate(this, &WindowManager::onClassChoice);
|
||||
classChoiceDialog->open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == GM_ClassGenerate)
|
||||
{
|
||||
generateClassStep = 0;
|
||||
generateClass = "";
|
||||
generateClassSpecializations[0] = 0;
|
||||
generateClassSpecializations[1] = 0;
|
||||
generateClassSpecializations[2] = 0;
|
||||
showClassQuestionDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == GM_ClassPick)
|
||||
{
|
||||
if (pickClassDialog)
|
||||
removeDialog(pickClassDialog);
|
||||
pickClassDialog = new PickClassDialog(*this);
|
||||
pickClassDialog->setNextButtonShow(creationStage >= ClassChosen);
|
||||
pickClassDialog->setClassId(playerClass.name);
|
||||
pickClassDialog->eventDone = MyGUI::newDelegate(this, &WindowManager::onPickClassDialogDone);
|
||||
pickClassDialog->eventBack = MyGUI::newDelegate(this, &WindowManager::onPickClassDialogBack);
|
||||
pickClassDialog->open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == GM_ClassCreate)
|
||||
{
|
||||
if (createClassDialog)
|
||||
removeDialog(createClassDialog);
|
||||
createClassDialog = new CreateClassDialog(*this);
|
||||
createClassDialog->eventDone = MyGUI::newDelegate(this, &WindowManager::onCreateClassDialogDone);
|
||||
createClassDialog->eventBack = MyGUI::newDelegate(this, &WindowManager::onCreateClassDialogBack);
|
||||
createClassDialog->open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == GM_Birth)
|
||||
{
|
||||
if (birthSignDialog)
|
||||
removeDialog(birthSignDialog);
|
||||
birthSignDialog = new BirthDialog(*this);
|
||||
birthSignDialog->setNextButtonShow(creationStage >= BirthSignChosen);
|
||||
birthSignDialog->setBirthId(playerBirthSignId);
|
||||
birthSignDialog->eventDone = MyGUI::newDelegate(this, &WindowManager::onBirthSignDialogDone);
|
||||
birthSignDialog->eventBack = MyGUI::newDelegate(this, &WindowManager::onBirthSignDialogBack);
|
||||
birthSignDialog->open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == GM_Review)
|
||||
{
|
||||
if (reviewDialog)
|
||||
removeDialog(reviewDialog);
|
||||
reviewDialog = new ReviewDialog(*this);
|
||||
reviewDialog->setPlayerName(playerName);
|
||||
reviewDialog->setRace(playerRaceId);
|
||||
reviewDialog->setClass(playerClass);
|
||||
reviewDialog->setBirthSign(playerBirthSignId);
|
||||
|
||||
reviewDialog->setHealth(playerHealth);
|
||||
reviewDialog->setMagicka(playerMagicka);
|
||||
reviewDialog->setFatigue(playerFatigue);
|
||||
|
||||
{
|
||||
std::map<ESM::Attribute::AttributeID, MWMechanics::Stat<int> >::iterator end = playerAttributes.end();
|
||||
for (std::map<ESM::Attribute::AttributeID, MWMechanics::Stat<int> >::iterator it = playerAttributes.begin(); it != end; ++it)
|
||||
{
|
||||
reviewDialog->setAttribute(it->first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::map<ESM::Skill::SkillEnum, MWMechanics::Stat<float> >::iterator end = playerSkillValues.end();
|
||||
for (std::map<ESM::Skill::SkillEnum, MWMechanics::Stat<float> >::iterator it = playerSkillValues.begin(); it != end; ++it)
|
||||
{
|
||||
reviewDialog->setSkillValue(it->first, it->second);
|
||||
}
|
||||
reviewDialog->configureSkills(playerMajorSkills, playerMinorSkills);
|
||||
}
|
||||
|
||||
reviewDialog->eventDone = MyGUI::newDelegate(this, &WindowManager::onReviewDialogDone);
|
||||
reviewDialog->eventBack = MyGUI::newDelegate(this, &WindowManager::onReviewDialogBack);
|
||||
reviewDialog->eventActivateDialog = MyGUI::newDelegate(this, &WindowManager::onReviewActivateDialog);
|
||||
reviewDialog->open();
|
||||
mCharGen->spawnDialog(mode);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -332,9 +192,6 @@ void WindowManager::updateVisible()
|
||||
// Show the windows we want
|
||||
map -> setVisible( (eff & GW_Map) != 0 );
|
||||
stats -> setVisible( (eff & GW_Stats) != 0 );
|
||||
#if 0
|
||||
// inventory -> setVisible( eff & GW_Inventory );
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@ -359,7 +216,6 @@ void WindowManager::updateVisible()
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Unsupported mode, switch back to game
|
||||
// Note: The call will eventually end up this method again but
|
||||
// will stop at the check if(mode == GM_Game) above.
|
||||
@ -407,13 +263,34 @@ void WindowManager::setValue (const std::string& id, const MWMechanics::DynamicS
|
||||
stats->setValue (id, value);
|
||||
hud->setValue (id, value);
|
||||
if (id == "HBar")
|
||||
{
|
||||
playerHealth = value;
|
||||
mCharGen->setPlayerHealth (value);
|
||||
}
|
||||
else if (id == "MBar")
|
||||
{
|
||||
playerMagicka = value;
|
||||
mCharGen->setPlayerMagicka (value);
|
||||
}
|
||||
else if (id == "FBar")
|
||||
{
|
||||
playerFatigue = value;
|
||||
mCharGen->setPlayerFatigue (value);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
MWMechanics::DynamicStat<int> WindowManager::getValue(const std::string& id)
|
||||
{
|
||||
if(id == "HBar")
|
||||
return playerHealth;
|
||||
else if (id == "MBar")
|
||||
return playerMagicka;
|
||||
else if (id == "FBar")
|
||||
return playerFatigue;
|
||||
}
|
||||
#endif
|
||||
|
||||
void WindowManager::setValue (const std::string& id, const std::string& value)
|
||||
{
|
||||
stats->setValue (id, value);
|
||||
@ -503,49 +380,6 @@ const std::string &WindowManager::getGameSettingString(const std::string &id, co
|
||||
return default_;
|
||||
}
|
||||
|
||||
void WindowManager::onNameDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (nameDialog)
|
||||
{
|
||||
playerName = nameDialog->getTextInput();
|
||||
environment.mMechanicsManager->setPlayerName(playerName);
|
||||
removeDialog(nameDialog);
|
||||
}
|
||||
|
||||
// Go to next dialog if name was previously chosen
|
||||
if (creationStage == ReviewNext)
|
||||
setGuiMode(GM_Review);
|
||||
else if (creationStage >= NameChosen)
|
||||
setGuiMode(GM_Race);
|
||||
else
|
||||
{
|
||||
creationStage = NameChosen;
|
||||
setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::onRaceDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (raceDialog)
|
||||
{
|
||||
playerRaceId = raceDialog->getRaceId();
|
||||
if (!playerRaceId.empty())
|
||||
environment.mMechanicsManager->setPlayerRace(playerRaceId, raceDialog->getGender() == RaceDialog::GM_Male);
|
||||
removeDialog(raceDialog);
|
||||
}
|
||||
|
||||
// Go to next dialog if race was previously chosen
|
||||
if (creationStage == ReviewNext)
|
||||
setGuiMode(GM_Review);
|
||||
else if(creationStage >= RaceChosen)
|
||||
setGuiMode(GM_Class);
|
||||
else
|
||||
{
|
||||
creationStage = RaceChosen;
|
||||
setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::onDialogueWindowBye()
|
||||
{
|
||||
if (dialogueWindow)
|
||||
@ -557,434 +391,11 @@ void WindowManager::onDialogueWindowBye()
|
||||
setGuiMode(GM_Game);
|
||||
}
|
||||
|
||||
void WindowManager::onRaceDialogBack()
|
||||
{
|
||||
if (raceDialog)
|
||||
{
|
||||
playerRaceId = raceDialog->getRaceId();
|
||||
if (!playerRaceId.empty())
|
||||
environment.mMechanicsManager->setPlayerRace(playerRaceId, raceDialog->getGender() == RaceDialog::GM_Male);
|
||||
removeDialog(raceDialog);
|
||||
}
|
||||
|
||||
setGuiMode(GM_Name);
|
||||
}
|
||||
|
||||
void WindowManager::onClassChoice(int _index)
|
||||
{
|
||||
if (classChoiceDialog)
|
||||
{
|
||||
removeDialog(classChoiceDialog);
|
||||
}
|
||||
|
||||
switch(_index)
|
||||
{
|
||||
case ClassChoiceDialog::Class_Generate:
|
||||
setGuiMode(GM_ClassGenerate);
|
||||
break;
|
||||
case ClassChoiceDialog::Class_Pick:
|
||||
setGuiMode(GM_ClassPick);
|
||||
break;
|
||||
case ClassChoiceDialog::Class_Create:
|
||||
setGuiMode(GM_ClassCreate);
|
||||
break;
|
||||
case ClassChoiceDialog::Class_Back:
|
||||
setGuiMode(GM_Race);
|
||||
break;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
void WindowManager::onFrame (float frameDuration)
|
||||
{
|
||||
mMessageBoxManager->onFrame(frameDuration);
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
struct Step
|
||||
{
|
||||
const char* text;
|
||||
const char* buttons[3];
|
||||
// The specialization for each answer
|
||||
ESM::Class::Specialization specializations[3];
|
||||
};
|
||||
|
||||
static boost::array<Step, 10> generateClassSteps = { {
|
||||
// Question 1
|
||||
{"On a clear day you chance upon a strange animal, its legs trapped in a hunter's clawsnare. Judging from the bleeding, it will not survive long.",
|
||||
{"Draw your dagger, mercifully endings its life with a single thrust.",
|
||||
"Use herbs from your pack to put it to sleep.",
|
||||
"Do not interfere in the natural evolution of events, but rather take the opportunity to learn more about a strange animal that you have never seen before."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 2
|
||||
{"One Summer afternoon your father gives you a choice of chores.",
|
||||
{"Work in the forge with him casting iron for a new plow.",
|
||||
"Gather herbs for your mother who is preparing dinner.",
|
||||
"Go catch fish at the stream using a net and line."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 3
|
||||
{"Your cousin has given you a very embarrassing nickname and, even worse, likes to call you it in front of your friends. You asked him to stop, but he finds it very amusing to watch you blush.",
|
||||
{"Beat up your cousin, then tell him that if he ever calls you that nickname again, you will bloody him worse than this time.",
|
||||
"Make up a story that makes your nickname a badge of honor instead of something humiliating.",
|
||||
"Make up an even more embarrassing nickname for him and use it constantly until he learns his lesson."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 4
|
||||
{"There is a lot of heated discussion at the local tavern over a grouped of people called 'Telepaths'. They have been hired by certain City-State kings. Rumor has it these Telepaths read a person's mind and tell their lord whether a follower is telling the truth or not.",
|
||||
{"This is a terrible practice. A person's thoughts are his own and no one, not even a king, has the right to make such an invasion into another human's mind.",
|
||||
"Loyal followers to the king have nothing to fear from a Telepath. It is important to have a method of finding assassins and spies before it is too late.",
|
||||
"In these times, it is a necessary evil. Although you do not necessarily like the idea, a Telepath could have certain advantages during a time of war or in finding someone innocent of a crime."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 5
|
||||
{"Your mother sends you to the market with a list of goods to buy. After you finish you find that by mistake a shopkeeper has given you too much money back in exchange for one of the items.",
|
||||
{"Return to the store and give the shopkeeper his hard-earned money, explaining to him the mistake?",
|
||||
"Decide to put the extra money to good use and purchase items that would help your family?",
|
||||
"Pocket the extra money, knowing that shopkeepers in general tend to overcharge customers anyway?"},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 6
|
||||
{"While in the market place you witness a thief cut a purse from a noble. Even as he does so, the noble notices and calls for the city guards. In his haste to get away, the thief drops the purse near you. Surprisingly no one seems to notice the bag of coins at your feet.",
|
||||
{"Pick up the bag and signal to the guard, knowing that the only honorable thing to do is return the money to its rightful owner.",
|
||||
"Leave the bag there, knowing that it is better not to get involved.",
|
||||
"Pick up the bag and pocket it, knowing that the extra windfall will help your family in times of trouble."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 7
|
||||
{"Your father sends you on a task which you loathe, cleaning the stables. On the way there, pitchfork in hand, you run into your friend from the homestead near your own. He offers to do it for you, in return for a future favor of his choosing.",
|
||||
{"Decline his offer, knowing that your father expects you to do the work, and it is better not to be in debt.",
|
||||
"Ask him to help you, knowing that two people can do the job faster than one, and agree to help him with one task of his choosing in the future.",
|
||||
"Accept his offer, reasoning that as long as the stables are cleaned, it matters not who does the cleaning."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 8
|
||||
{"Your mother asks you to help fix the stove. While you are working, a very hot pipe slips its mooring and falls towards her.",
|
||||
{"Position yourself between the pipe and your mother.",
|
||||
"Grab the hot pipe and try to push it away.",
|
||||
"Push your mother out of the way."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 9
|
||||
{"While in town the baker gives you a sweetroll. Delighted, you take it into an alley to enjoy only to be intercepted by a gang of three other kids your age. The leader demands the sweetroll, or else he and his friends will beat you and take it.",
|
||||
{"Drop the sweetroll and step on it, then get ready for the fight.",
|
||||
"Give him the sweetroll now without argument, knowing that later this afternoon you will have all your friends with you and can come and take whatever he owes you.",
|
||||
"Act like you're going to give him the sweetroll, but at the last minute throw it in the air, hoping that they'll pay attention to it long enough for you to get a shot in on the leader."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
},
|
||||
// Question 10
|
||||
{"Entering town you find that you are witness to a very well-dressed man running from a crowd. He screams to you for help. The crowd behind him seem very angry.",
|
||||
{"Rush to the town's aid immediately, despite your lack of knowledge of the circumstances.",
|
||||
"Stand aside and allow the man and the mob to pass, realizing it is probably best not to get involved.",
|
||||
"Rush to the man's aid immediately, despite your lack of knowledge of the circumstances."},
|
||||
{ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
void WindowManager::showClassQuestionDialog()
|
||||
{
|
||||
if (generateClassStep == generateClassSteps.size())
|
||||
{
|
||||
|
||||
static boost::array<ClassPoint, 23> classes = { {
|
||||
{"Acrobat", {6, 2, 2}},
|
||||
{"Agent", {6, 1, 3}},
|
||||
{"Archer", {3, 5, 2}},
|
||||
{"Archer", {5, 5, 0}},
|
||||
{"Assassin", {6, 3, 1}},
|
||||
{"Barbarian", {3, 6, 1}},
|
||||
{"Bard", {3, 3, 3}},
|
||||
{"Battlemage", {1, 3, 6}},
|
||||
{"Crusader", {1, 6, 3}},
|
||||
{"Healer", {3, 1, 6}},
|
||||
{"Knight", {2, 6, 2}},
|
||||
{"Monk", {5, 3, 2}},
|
||||
{"Nightblade", {4, 2, 4}},
|
||||
{"Pilgrim", {5, 2, 3}},
|
||||
{"Rogue", {3, 4, 3}},
|
||||
{"Rogue", {4, 4, 2}},
|
||||
{"Rogue", {5, 4, 1}},
|
||||
{"Scout", {2, 5, 3}},
|
||||
{"Sorcerer", {2, 2, 6}},
|
||||
{"Spellsword", {2, 4, 4}},
|
||||
{"Spellsword", {5, 1, 4}},
|
||||
{"Witchhunter", {2, 3, 5}},
|
||||
{"Witchhunter", {5, 0, 5}}
|
||||
} };
|
||||
|
||||
int match = -1;
|
||||
for (unsigned i = 0; i < classes.size(); ++i)
|
||||
{
|
||||
if (generateClassSpecializations[0] == classes[i].points[0] &&
|
||||
generateClassSpecializations[1] == classes[i].points[1] &&
|
||||
generateClassSpecializations[2] == classes[i].points[2])
|
||||
{
|
||||
match = i;
|
||||
generateClass = classes[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match == -1)
|
||||
{
|
||||
if (generateClassSpecializations[0] >= 7)
|
||||
generateClass = "Thief";
|
||||
else if (generateClassSpecializations[1] >= 7)
|
||||
generateClass = "Warrior";
|
||||
else if (generateClassSpecializations[2] >= 7)
|
||||
generateClass = "Mage";
|
||||
else
|
||||
{
|
||||
std::cerr
|
||||
<< "Failed to deduce class from chosen answers in generate class dialog"
|
||||
<< std::endl;
|
||||
generateClass = "Thief";
|
||||
}
|
||||
}
|
||||
|
||||
if (generateClassResultDialog)
|
||||
removeDialog(generateClassResultDialog);
|
||||
generateClassResultDialog = new GenerateClassResultDialog(*this);
|
||||
generateClassResultDialog->setClassId(generateClass);
|
||||
generateClassResultDialog->eventBack = MyGUI::newDelegate(this, &WindowManager::onGenerateClassBack);
|
||||
generateClassResultDialog->eventDone = MyGUI::newDelegate(this, &WindowManager::onGenerateClassDone);
|
||||
generateClassResultDialog->open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (generateClassStep > generateClassSteps.size())
|
||||
{
|
||||
setGuiMode(GM_Class);
|
||||
return;
|
||||
}
|
||||
|
||||
if (generateClassQuestionDialog)
|
||||
removeDialog(generateClassQuestionDialog);
|
||||
generateClassQuestionDialog = new InfoBoxDialog(*this);
|
||||
|
||||
InfoBoxDialog::ButtonList buttons;
|
||||
generateClassQuestionDialog->setText(generateClassSteps[generateClassStep].text);
|
||||
buttons.push_back(generateClassSteps[generateClassStep].buttons[0]);
|
||||
buttons.push_back(generateClassSteps[generateClassStep].buttons[1]);
|
||||
buttons.push_back(generateClassSteps[generateClassStep].buttons[2]);
|
||||
generateClassQuestionDialog->setButtons(buttons);
|
||||
generateClassQuestionDialog->eventButtonSelected = MyGUI::newDelegate(this, &WindowManager::onClassQuestionChosen);
|
||||
generateClassQuestionDialog->open();
|
||||
}
|
||||
|
||||
void WindowManager::onClassQuestionChosen(int _index)
|
||||
{
|
||||
if (generateClassQuestionDialog)
|
||||
removeDialog(generateClassQuestionDialog);
|
||||
if (_index < 0 || _index >= 3)
|
||||
{
|
||||
setGuiMode(GM_Class);
|
||||
return;
|
||||
}
|
||||
|
||||
ESM::Class::Specialization specialization = generateClassSteps[generateClassStep].specializations[_index];
|
||||
if (specialization == ESM::Class::Stealth)
|
||||
++generateClassSpecializations[0];
|
||||
else if (specialization == ESM::Class::Combat)
|
||||
++generateClassSpecializations[1];
|
||||
else if (specialization == ESM::Class::Magic)
|
||||
++generateClassSpecializations[2];
|
||||
++generateClassStep;
|
||||
showClassQuestionDialog();
|
||||
}
|
||||
|
||||
void WindowManager::onGenerateClassBack()
|
||||
{
|
||||
if(creationStage < ClassChosen)
|
||||
creationStage = ClassChosen;
|
||||
|
||||
if (generateClassResultDialog)
|
||||
removeDialog(generateClassResultDialog);
|
||||
environment.mMechanicsManager->setPlayerClass(generateClass);
|
||||
|
||||
setGuiMode(GM_Class);
|
||||
}
|
||||
|
||||
void WindowManager::onGenerateClassDone(WindowBase* parWindow)
|
||||
{
|
||||
if (generateClassResultDialog)
|
||||
removeDialog(generateClassResultDialog);
|
||||
environment.mMechanicsManager->setPlayerClass(generateClass);
|
||||
|
||||
// Go to next dialog if class was previously chosen
|
||||
if (creationStage == ReviewNext)
|
||||
setGuiMode(GM_Review);
|
||||
else if (creationStage >= ClassChosen)
|
||||
setGuiMode(GM_Birth);
|
||||
else
|
||||
{
|
||||
creationStage = ClassChosen;
|
||||
setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WindowManager::onPickClassDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (pickClassDialog)
|
||||
{
|
||||
const std::string &classId = pickClassDialog->getClassId();
|
||||
if (!classId.empty())
|
||||
environment.mMechanicsManager->setPlayerClass(classId);
|
||||
const ESM::Class *klass = environment.mWorld->getStore().classes.find(classId);
|
||||
if (klass)
|
||||
playerClass = *klass;
|
||||
removeDialog(pickClassDialog);
|
||||
}
|
||||
|
||||
// Go to next dialog if class was previously chosen
|
||||
if (creationStage == ReviewNext)
|
||||
setGuiMode(GM_Review);
|
||||
else if (creationStage >= ClassChosen)
|
||||
setGuiMode(GM_Birth);
|
||||
else
|
||||
{
|
||||
creationStage = ClassChosen;
|
||||
setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::onPickClassDialogBack()
|
||||
{
|
||||
if (pickClassDialog)
|
||||
{
|
||||
const std::string classId = pickClassDialog->getClassId();
|
||||
if (!classId.empty())
|
||||
environment.mMechanicsManager->setPlayerClass(classId);
|
||||
removeDialog(pickClassDialog);
|
||||
}
|
||||
|
||||
setGuiMode(GM_Class);
|
||||
}
|
||||
|
||||
void WindowManager::onCreateClassDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (createClassDialog)
|
||||
{
|
||||
ESM::Class klass;
|
||||
klass.name = createClassDialog->getName();
|
||||
klass.description = createClassDialog->getDescription();
|
||||
klass.data.specialization = createClassDialog->getSpecializationId();
|
||||
klass.data.isPlayable = 0x1;
|
||||
|
||||
std::vector<int> attributes = createClassDialog->getFavoriteAttributes();
|
||||
assert(attributes.size() == 2);
|
||||
klass.data.attribute[0] = attributes[0];
|
||||
klass.data.attribute[1] = attributes[1];
|
||||
|
||||
std::vector<ESM::Skill::SkillEnum> majorSkills = createClassDialog->getMajorSkills();
|
||||
std::vector<ESM::Skill::SkillEnum> minorSkills = createClassDialog->getMinorSkills();
|
||||
assert(majorSkills.size() >= sizeof(klass.data.skills)/sizeof(klass.data.skills[0]));
|
||||
assert(minorSkills.size() >= sizeof(klass.data.skills)/sizeof(klass.data.skills[0]));
|
||||
for (size_t i = 0; i < sizeof(klass.data.skills)/sizeof(klass.data.skills[0]); ++i)
|
||||
{
|
||||
klass.data.skills[i][1] = majorSkills[i];
|
||||
klass.data.skills[i][0] = minorSkills[i];
|
||||
}
|
||||
environment.mMechanicsManager->setPlayerClass(klass);
|
||||
playerClass = klass;
|
||||
|
||||
removeDialog(createClassDialog);
|
||||
}
|
||||
|
||||
// Go to next dialog if class was previously chosen
|
||||
if (creationStage == ReviewNext)
|
||||
setGuiMode(GM_Review);
|
||||
else if (creationStage >= ClassChosen)
|
||||
setGuiMode(GM_Birth);
|
||||
else
|
||||
{
|
||||
creationStage = ClassChosen;
|
||||
setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::onCreateClassDialogBack()
|
||||
{
|
||||
if (createClassDialog)
|
||||
removeDialog(createClassDialog);
|
||||
|
||||
setGuiMode(GM_Class);
|
||||
}
|
||||
|
||||
void WindowManager::onBirthSignDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (birthSignDialog)
|
||||
{
|
||||
playerBirthSignId = birthSignDialog->getBirthId();
|
||||
if (!playerBirthSignId.empty())
|
||||
environment.mMechanicsManager->setPlayerBirthsign(playerBirthSignId);
|
||||
removeDialog(birthSignDialog);
|
||||
}
|
||||
|
||||
// Go to next dialog if birth sign was previously chosen
|
||||
if (creationStage >= BirthSignChosen)
|
||||
setGuiMode(GM_Review);
|
||||
else
|
||||
{
|
||||
creationStage = BirthSignChosen;
|
||||
setGuiMode(GM_Game);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::onBirthSignDialogBack()
|
||||
{
|
||||
if (birthSignDialog)
|
||||
{
|
||||
environment.mMechanicsManager->setPlayerBirthsign(birthSignDialog->getBirthId());
|
||||
removeDialog(birthSignDialog);
|
||||
}
|
||||
|
||||
setGuiMode(GM_Class);
|
||||
}
|
||||
|
||||
void WindowManager::onReviewDialogDone(WindowBase* parWindow)
|
||||
{
|
||||
if (reviewDialog)
|
||||
removeDialog(reviewDialog);
|
||||
|
||||
setGuiMode(GM_Game);
|
||||
}
|
||||
|
||||
void WindowManager::onReviewDialogBack()
|
||||
{
|
||||
if (reviewDialog)
|
||||
removeDialog(reviewDialog);
|
||||
|
||||
setGuiMode(GM_Birth);
|
||||
}
|
||||
|
||||
void WindowManager::onReviewActivateDialog(int parDialog)
|
||||
{
|
||||
if (reviewDialog)
|
||||
removeDialog(reviewDialog);
|
||||
creationStage = ReviewNext;
|
||||
|
||||
switch(parDialog)
|
||||
{
|
||||
case ReviewDialog::NAME_DIALOG:
|
||||
setGuiMode(GM_Name);
|
||||
break;
|
||||
case ReviewDialog::RACE_DIALOG:
|
||||
setGuiMode(GM_Race);
|
||||
break;
|
||||
case ReviewDialog::CLASS_DIALOG:
|
||||
setGuiMode(GM_Class);
|
||||
break;
|
||||
case ReviewDialog::BIRTHSIGN_DIALOG:
|
||||
setGuiMode(GM_Birth);
|
||||
};
|
||||
}
|
||||
|
||||
const ESMS::ESMStore& WindowManager::getStore() const
|
||||
{
|
||||
return environment.mWorld->getStore();
|
||||
|
@ -8,13 +8,15 @@
|
||||
|
||||
MyGUI should be initialized separately before creating instances of
|
||||
this class.
|
||||
*/
|
||||
**/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include <components/esm_store/store.hpp>
|
||||
#include <openengine/ogre/renderer.hpp>
|
||||
#include <openengine/gui/manager.hpp>
|
||||
#include "../mwmechanics/stat.hpp"
|
||||
#include "mode.hpp"
|
||||
|
||||
@ -32,6 +34,12 @@ namespace Compiler
|
||||
namespace MWWorld
|
||||
{
|
||||
class Environment;
|
||||
class World;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class MechanicsManager;
|
||||
}
|
||||
|
||||
namespace OEngine
|
||||
@ -52,17 +60,11 @@ namespace MWGui
|
||||
class InventoryWindow;
|
||||
class Console;
|
||||
class JournalWindow;
|
||||
class CharacterCreation;
|
||||
|
||||
class TextInputDialog;
|
||||
class InfoBoxDialog;
|
||||
class RaceDialog;
|
||||
class DialogueWindow;
|
||||
class ClassChoiceDialog;
|
||||
class GenerateClassResultDialog;
|
||||
class PickClassDialog;
|
||||
class CreateClassDialog;
|
||||
class BirthDialog;
|
||||
class ReviewDialog;
|
||||
class MessageBoxManager;
|
||||
|
||||
struct ClassPoint
|
||||
@ -80,95 +82,11 @@ namespace MWGui
|
||||
typedef std::vector<Faction> FactionList;
|
||||
typedef std::vector<int> SkillList;
|
||||
|
||||
private:
|
||||
MWWorld::Environment& environment;
|
||||
HUD *hud;
|
||||
MapWindow *map;
|
||||
MainMenu *menu;
|
||||
StatsWindow *stats;
|
||||
MessageBoxManager *mMessageBoxManager;
|
||||
#if 0
|
||||
InventoryWindow *inventory;
|
||||
#endif
|
||||
Console *console;
|
||||
JournalWindow* mJournal;
|
||||
|
||||
// Character creation
|
||||
TextInputDialog *nameDialog;
|
||||
RaceDialog *raceDialog;
|
||||
DialogueWindow *dialogueWindow;
|
||||
ClassChoiceDialog *classChoiceDialog;
|
||||
InfoBoxDialog *generateClassQuestionDialog;
|
||||
GenerateClassResultDialog *generateClassResultDialog;
|
||||
PickClassDialog *pickClassDialog;
|
||||
CreateClassDialog *createClassDialog;
|
||||
BirthDialog *birthSignDialog;
|
||||
ReviewDialog *reviewDialog;
|
||||
|
||||
// Keeps track of current step in Generate Class dialogs
|
||||
unsigned generateClassStep;
|
||||
// A counter for each specialization which is increased when an answer is chosen, in order: Stealth, Combat, Magic
|
||||
unsigned generateClassSpecializations[3];
|
||||
std::string generateClass;
|
||||
|
||||
// Various stats about player as needed by window manager
|
||||
std::string playerName;
|
||||
ESM::Class playerClass;
|
||||
std::string playerRaceId, playerBirthSignId;
|
||||
std::map<ESM::Attribute::AttributeID, MWMechanics::Stat<int> > playerAttributes;
|
||||
SkillList playerMajorSkills, playerMinorSkills;
|
||||
std::map<ESM::Skill::SkillEnum, MWMechanics::Stat<float> > playerSkillValues;
|
||||
MWMechanics::DynamicStat<int> playerHealth, playerMagicka, playerFatigue;
|
||||
|
||||
// Gui
|
||||
MyGUI::Gui *gui;
|
||||
|
||||
// Current gui mode
|
||||
GuiMode mode;
|
||||
|
||||
/**
|
||||
* Next mode to activate in update().
|
||||
*/
|
||||
GuiMode nextMode;
|
||||
/**
|
||||
* Whether a mode change is needed in update().
|
||||
* Will use @a nextMode as the new mode.
|
||||
*/
|
||||
bool needModeChange;
|
||||
|
||||
std::vector<OEngine::GUI::Layout*> garbageDialogs;
|
||||
void cleanupGarbage();
|
||||
|
||||
// Currently shown windows in inventory mode
|
||||
GuiWindow shown;
|
||||
|
||||
/* Currently ALLOWED windows in inventory mode. This is used at
|
||||
the start of the game, when windows are enabled one by one
|
||||
through script commands. You can manipulate this through using
|
||||
allow() and disableAll().
|
||||
|
||||
The setting should also affect visibility of certain HUD
|
||||
elements, but this is not done yet.
|
||||
*/
|
||||
GuiWindow allowed;
|
||||
|
||||
// Update visibility of all windows based on mode, shown and
|
||||
// allowed settings.
|
||||
void updateVisible();
|
||||
WindowManager(MWWorld::Environment& environment, const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string logpath);
|
||||
virtual ~WindowManager();
|
||||
|
||||
void setGuiMode(GuiMode newMode);
|
||||
|
||||
int showFPSLevel;
|
||||
float mFPS;
|
||||
size_t mTriangleCount;
|
||||
size_t mBatchCount;
|
||||
|
||||
public:
|
||||
/// The constructor needs the main Gui object
|
||||
WindowManager(MyGUI::Gui *_gui, MWWorld::Environment& environment,
|
||||
const Compiler::Extensions& extensions, int fpsLevel, bool newGame);
|
||||
virtual ~WindowManager();
|
||||
|
||||
/**
|
||||
* Should be called each frame to update windows/gui elements.
|
||||
* This could mean updating sizes of gui elements or opening
|
||||
@ -190,8 +108,7 @@ namespace MWGui
|
||||
|
||||
GuiMode getMode() const { return mode; }
|
||||
|
||||
// Everything that is not game mode is considered "gui mode"
|
||||
bool isGuiMode() const { return getMode() != GM_Game; }
|
||||
bool isGuiMode() const { return getMode() != GM_Game; } // Everything that is not game mode is considered "gui mode"
|
||||
|
||||
// Disallow all inventory mode windows
|
||||
void disallowAll()
|
||||
@ -218,54 +135,31 @@ namespace MWGui
|
||||
mBatchCount = batchCount;
|
||||
}
|
||||
|
||||
// MWMechanics::DynamicStat<int> getValue(const std::string& id);
|
||||
|
||||
///< Set value for the given ID.
|
||||
void setValue (const std::string& id, const MWMechanics::Stat<int>& value);
|
||||
///< Set value for the given ID.
|
||||
|
||||
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat<float>& value);
|
||||
///< Set value for the given ID.
|
||||
|
||||
void setValue (const std::string& id, const MWMechanics::DynamicStat<int>& value);
|
||||
///< Set value for the given ID.
|
||||
|
||||
void setValue (const std::string& id, const std::string& value);
|
||||
///< set value for the given ID.
|
||||
|
||||
void setValue (const std::string& id, int value);
|
||||
///< set value for the given ID.
|
||||
|
||||
void setPlayerClass (const ESM::Class &class_);
|
||||
///< set current class of player
|
||||
void setPlayerClass (const ESM::Class &class_); ///< set current class of player
|
||||
void configureSkills (const SkillList& major, const SkillList& minor); ///< configure skill groups, each set contains the skill ID for that group.
|
||||
void setFactions (const FactionList& factions); ///< set faction and rank to display on stat window, use an empty vector to disable
|
||||
void setBirthSign (const std::string &signId); ///< set birth sign to display on stat window, use an empty string to disable.
|
||||
void setReputation (int reputation); ///< set the current reputation value
|
||||
void setBounty (int bounty); ///< set the current bounty value
|
||||
void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty
|
||||
|
||||
void configureSkills (const SkillList& major, const SkillList& minor);
|
||||
///< configure skill groups, each set contains the skill ID for that group.
|
||||
|
||||
void setFactions (const FactionList& factions);
|
||||
///< set faction and rank to display on stat window, use an empty vector to disable
|
||||
|
||||
void setBirthSign (const std::string &signId);
|
||||
///< set birth sign to display on stat window, use an empty string to disable.
|
||||
|
||||
void setReputation (int reputation);
|
||||
///< set the current reputation value
|
||||
|
||||
void setBounty (int bounty);
|
||||
///< set the current bounty value
|
||||
|
||||
void updateSkillArea();
|
||||
///< update display of skills, factions, birth sign, reputation and bounty
|
||||
|
||||
template<typename T>
|
||||
void removeDialog(T*& dialog);
|
||||
///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr.
|
||||
void removeDialog(T*& dialog); ///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr.
|
||||
void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted.
|
||||
|
||||
void removeDialog(OEngine::GUI::Layout* dialog);
|
||||
///< Hides dialog and schedules dialog to be deleted.
|
||||
|
||||
void messageBox (const std::string& message, const std::vector<std::string>& buttons);
|
||||
|
||||
int readPressedButton ();
|
||||
///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox)
|
||||
|
||||
int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox)
|
||||
|
||||
void onFrame (float frameDuration);
|
||||
|
||||
/**
|
||||
@ -280,54 +174,58 @@ namespace MWGui
|
||||
const ESMS::ESMStore& getStore() const;
|
||||
|
||||
private:
|
||||
OEngine::GUI::MyGUIManager *mGuiManager;
|
||||
MWWorld::Environment& environment;
|
||||
HUD *hud;
|
||||
MapWindow *map;
|
||||
MainMenu *menu;
|
||||
StatsWindow *stats;
|
||||
MessageBoxManager *mMessageBoxManager;
|
||||
Console *console;
|
||||
JournalWindow* mJournal;
|
||||
DialogueWindow *dialogueWindow;
|
||||
|
||||
CharacterCreation* mCharGen;
|
||||
|
||||
// Various stats about player as needed by window manager
|
||||
ESM::Class playerClass;
|
||||
std::string playerName;
|
||||
std::string playerRaceId;
|
||||
std::string playerBirthSignId;
|
||||
std::map<ESM::Attribute::AttributeID, MWMechanics::Stat<int> > playerAttributes;
|
||||
SkillList playerMajorSkills, playerMinorSkills;
|
||||
std::map<ESM::Skill::SkillEnum, MWMechanics::Stat<float> > playerSkillValues;
|
||||
MWMechanics::DynamicStat<int> playerHealth, playerMagicka, playerFatigue;
|
||||
|
||||
|
||||
MyGUI::Gui *gui; // Gui
|
||||
GuiMode mode; // Current gui mode
|
||||
GuiMode nextMode; // Next mode to activate in update()
|
||||
bool needModeChange; //Whether a mode change is needed in update() [will use nextMode]
|
||||
|
||||
std::vector<OEngine::GUI::Layout*> garbageDialogs;
|
||||
void cleanupGarbage();
|
||||
|
||||
GuiWindow shown; // Currently shown windows in inventory mode
|
||||
|
||||
/* Currently ALLOWED windows in inventory mode. This is used at
|
||||
the start of the game, when windows are enabled one by one
|
||||
through script commands. You can manipulate this through using
|
||||
allow() and disableAll().
|
||||
|
||||
The setting should also affect visibility of certain HUD
|
||||
elements, but this is not done yet.
|
||||
*/
|
||||
GuiWindow allowed;
|
||||
|
||||
void updateVisible(); // Update visibility of all windows based on mode, shown and allowed settings
|
||||
|
||||
int showFPSLevel;
|
||||
float mFPS;
|
||||
size_t mTriangleCount;
|
||||
size_t mBatchCount;
|
||||
|
||||
void onDialogueWindowBye();
|
||||
|
||||
// Character generation: Name dialog
|
||||
void onNameDialogDone(WindowBase* parWindow);
|
||||
|
||||
// Character generation: Race dialog
|
||||
void onRaceDialogDone(WindowBase* parWindow);
|
||||
void onRaceDialogBack();
|
||||
|
||||
// Character generation: Choose class process
|
||||
void onClassChoice(int _index);
|
||||
|
||||
// Character generation: Generate Class
|
||||
void showClassQuestionDialog();
|
||||
void onClassQuestionChosen(int _index);
|
||||
void onGenerateClassBack();
|
||||
void onGenerateClassDone(WindowBase* parWindow);
|
||||
|
||||
// Character generation: Pick Class dialog
|
||||
void onPickClassDialogDone(WindowBase* parWindow);
|
||||
void onPickClassDialogBack();
|
||||
|
||||
// Character generation: Create Class dialog
|
||||
void onCreateClassDialogDone(WindowBase* parWindow);
|
||||
void onCreateClassDialogBack();
|
||||
|
||||
// Character generation: Birth sign dialog
|
||||
void onBirthSignDialogDone(WindowBase* parWindow);
|
||||
void onBirthSignDialogBack();
|
||||
|
||||
// Character generation: Review dialog
|
||||
void onReviewDialogDone(WindowBase* parWindow);
|
||||
void onReviewDialogBack();
|
||||
void onReviewActivateDialog(int parDialog);
|
||||
|
||||
enum CreationStageEnum
|
||||
{
|
||||
NotStarted,
|
||||
NameChosen,
|
||||
RaceChosen,
|
||||
ClassChosen,
|
||||
BirthSignChosen,
|
||||
ReviewNext
|
||||
};
|
||||
|
||||
// Which state the character creating is in, controls back/next/ok buttons
|
||||
CreationStageEnum creationStage;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -12,15 +12,11 @@ void Actors::setMwRoot(Ogre::SceneNode* root){
|
||||
mMwRoot = root;
|
||||
}
|
||||
void Actors::insertNPC(const MWWorld::Ptr& ptr){
|
||||
|
||||
|
||||
insertBegin(ptr, true, true);
|
||||
NpcAnimation* anim = new MWRender::NpcAnimation(ptr, mEnvironment, mRend);
|
||||
|
||||
//
|
||||
|
||||
mAllActors[ptr] = anim;
|
||||
|
||||
|
||||
|
||||
mAllActors[ptr] = anim;
|
||||
}
|
||||
void Actors::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_){
|
||||
Ogre::SceneNode* cellnode;
|
||||
@ -61,7 +57,7 @@ void Actors::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_){
|
||||
|
||||
}
|
||||
void Actors::insertCreature (const MWWorld::Ptr& ptr){
|
||||
|
||||
|
||||
insertBegin(ptr, true, true);
|
||||
CreatureAnimation* anim = new MWRender::CreatureAnimation(ptr, mEnvironment, mRend);
|
||||
//mAllActors.insert(std::pair<MWWorld::Ptr, Animation*>(ptr,anim));
|
||||
@ -75,7 +71,7 @@ bool Actors::deleteObject (const MWWorld::Ptr& 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 (
|
||||
@ -110,7 +106,7 @@ void Actors::removeCell(MWWorld::Ptr::CellStore* store){
|
||||
mAllActors.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Actors::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number){
|
||||
@ -121,10 +117,9 @@ void Actors::skipAnimation (const MWWorld::Ptr& ptr){
|
||||
if(mAllActors.find(ptr) != mAllActors.end())
|
||||
mAllActors[ptr]->stopScript();
|
||||
}
|
||||
void Actors::addTime(){
|
||||
//std::cout << "Adding time in actors\n";
|
||||
void Actors::update (float duration){
|
||||
for(std::map<MWWorld::Ptr, Animation*>::iterator iter = mAllActors.begin(); iter != mAllActors.end(); iter++)
|
||||
{
|
||||
(iter->second)->runAnimation(mEnvironment.mFrameDuration);
|
||||
(iter->second)->runAnimation(duration);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace MWRender{
|
||||
MWWorld::Environment& mEnvironment;
|
||||
std::map<MWWorld::Ptr, Animation*> mAllActors;
|
||||
|
||||
|
||||
|
||||
|
||||
public:
|
||||
Actors(OEngine::Render::OgreRenderer& _rend, MWWorld::Environment& _env): mRend(_rend), mEnvironment(_env){}
|
||||
@ -52,8 +52,8 @@ namespace MWRender{
|
||||
///< Skip the animation for the given MW-reference for one frame. Calls to this function for
|
||||
/// references that are currently not in the rendered scene should be ignored.
|
||||
|
||||
void addTime();
|
||||
|
||||
void update (float duration);
|
||||
|
||||
};
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -269,22 +269,6 @@ void NpcAnimation::runAnimation(float timepassed){
|
||||
handleAnimationTransforms();
|
||||
Ogre::Vector3 current = insert->_getWorldAABB().getCenter();
|
||||
|
||||
//This is the attempt at npc physics
|
||||
//mEnvironment.mWorld->setObjectPhysicsPosition(insert->getName(), current);
|
||||
|
||||
|
||||
|
||||
/*if(base->hasSkeleton())
|
||||
{
|
||||
|
||||
Ogre::Quaternion boneQuat = rotate;
|
||||
Ogre::Vector3 boneTrans = trans;
|
||||
mEnvironment.mWorld->setObjectPhysicsPosition(insert->getName(), boneTrans + insert->getPosition());
|
||||
//mEnvironment.mWorld->setObjectPhysicsRotation(insert->getName(), boneQuat * insert->getOrientation());
|
||||
|
||||
}*/
|
||||
|
||||
|
||||
std::vector<std::vector<Nif::NiTriShapeCopy>*>::iterator shapepartsiter = shapeparts.begin();
|
||||
std::vector<Ogre::Entity*>::iterator entitypartsiter = entityparts.begin();
|
||||
while(shapepartsiter != shapeparts.end())
|
||||
@ -301,7 +285,7 @@ void NpcAnimation::runAnimation(float timepassed){
|
||||
handleShapes(shapes, theentity, theentity->getSkeleton());
|
||||
shapepartsiter++;
|
||||
entitypartsiter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,8 +19,6 @@ using namespace Ogre;
|
||||
|
||||
namespace MWRender {
|
||||
|
||||
|
||||
|
||||
RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, OEngine::Physic::PhysicEngine* engine, MWWorld::Environment& environment)
|
||||
:mRendering(_rend), mObjects(mRendering), mActors(mRendering, environment), mDebugging(engine)
|
||||
{
|
||||
@ -123,7 +121,7 @@ void RenderingManager::moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Ve
|
||||
|
||||
void RenderingManager::update (float duration){
|
||||
|
||||
|
||||
mActors.update (duration);
|
||||
}
|
||||
|
||||
void RenderingManager::skyEnable ()
|
||||
@ -245,9 +243,5 @@ void RenderingManager::skipAnimation (const MWWorld::Ptr& ptr)
|
||||
{
|
||||
mActors.skipAnimation(ptr);
|
||||
}
|
||||
void RenderingManager::addTime(){
|
||||
mActors.addTime();
|
||||
//Notify each animation that time has passed
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,14 +105,12 @@ class RenderingManager: private RenderingInterface {
|
||||
///< Skip the animation for the given MW-reference for one frame. Calls to this function for
|
||||
/// references that are currently not in the rendered scene should be ignored.
|
||||
|
||||
void addTime();
|
||||
|
||||
private:
|
||||
|
||||
void setAmbientMode();
|
||||
SkyManager* mSkyManager;
|
||||
OEngine::Render::OgreRenderer &mRendering;
|
||||
|
||||
|
||||
MWRender::Objects mObjects;
|
||||
MWRender::Actors mActors;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
#include "statsextensions.hpp"
|
||||
#include "controlextensions.hpp"
|
||||
|
||||
#include <components/compiler/extensions.hpp>
|
||||
|
||||
@ -48,7 +48,7 @@ namespace MWScript
|
||||
|
||||
bool enabled = context.getWorld().toggleCollisionMode();
|
||||
|
||||
context.report (enabled ? "Collsion -> On" : "Collision -> Off");
|
||||
context.report (enabled ? "Collision -> On" : "Collision -> Off");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
||||
#include "interpretercontext.hpp"
|
||||
#include "ref.hpp"
|
||||
|
17
apps/openmw/mwworld/customdata.hpp
Normal file
17
apps/openmw/mwworld/customdata.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef GAME_MWWORLD_CUSTOMDATA_H
|
||||
#define GAME_MWWORLD_CUSTOMDATA_H
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
/// \brief Base class for the MW-class-specific part of RefData
|
||||
class CustomData
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~CustomData() {}
|
||||
|
||||
virtual CustomData *clone() const = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -3,6 +3,7 @@
|
||||
#include "physicssystem.hpp"
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/world.hpp" // FIXME
|
||||
#include <components/nifbullet/bullet_nif_loader.hpp>
|
||||
|
||||
#include "OgreRoot.h"
|
||||
#include "OgreRenderWindow.h"
|
||||
@ -16,16 +17,24 @@ using namespace Ogre;
|
||||
namespace MWWorld
|
||||
{
|
||||
|
||||
PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend , OEngine::Physic::PhysicEngine* physEng) :
|
||||
mRender(_rend), mEngine(physEng), mFreeFly (true)
|
||||
PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) :
|
||||
mRender(_rend), mEngine(0), mFreeFly (true)
|
||||
{
|
||||
|
||||
// Create physics. shapeLoader is deleted by the physic engine
|
||||
NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader();
|
||||
mEngine = new OEngine::Physic::PhysicEngine(shapeLoader);
|
||||
}
|
||||
|
||||
PhysicsSystem::~PhysicsSystem()
|
||||
{
|
||||
|
||||
delete mEngine;
|
||||
|
||||
}
|
||||
OEngine::Physic::PhysicEngine* PhysicsSystem::getEngine()
|
||||
{
|
||||
return mEngine;
|
||||
}
|
||||
|
||||
std::pair<std::string, float> PhysicsSystem::getFacedHandle (MWWorld::World& world)
|
||||
{
|
||||
std::string handle = "";
|
||||
|
@ -12,7 +12,7 @@ namespace MWWorld
|
||||
class PhysicsSystem
|
||||
{
|
||||
public:
|
||||
PhysicsSystem (OEngine::Render::OgreRenderer &_rend , OEngine::Physic::PhysicEngine* physEng);
|
||||
PhysicsSystem (OEngine::Render::OgreRenderer &_rend);
|
||||
~PhysicsSystem ();
|
||||
|
||||
std::vector< std::pair<std::string, Ogre::Vector3> > doPhysics (float duration,
|
||||
@ -39,6 +39,8 @@ namespace MWWorld
|
||||
|
||||
void insertActorPhysics(const MWWorld::Ptr&, std::string model);
|
||||
|
||||
OEngine::Physic::PhysicEngine* getEngine();
|
||||
|
||||
private:
|
||||
OEngine::Render::OgreRenderer &mRender;
|
||||
OEngine::Physic::PhysicEngine* mEngine;
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "../mwrender/player.hpp"
|
||||
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
|
||||
#include "world.hpp"
|
||||
#include "class.hpp"
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
#include <components/esm_store/cell_store.hpp>
|
||||
|
||||
#include "refdata.hpp"
|
||||
|
144
apps/openmw/mwworld/refdata.cpp
Normal file
144
apps/openmw/mwworld/refdata.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
|
||||
#include "refdata.hpp"
|
||||
|
||||
#include <components/esm_store/cell_store.hpp>
|
||||
|
||||
#include "customdata.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
void RefData::copy (const RefData& refData)
|
||||
{
|
||||
mBaseNode = refData.mBaseNode;
|
||||
mLocals = refData.mLocals;
|
||||
mHasLocals = refData.mHasLocals;
|
||||
mEnabled = refData.mEnabled;
|
||||
mCount = refData.mCount;
|
||||
mPosition = refData.mPosition;
|
||||
|
||||
mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0;
|
||||
}
|
||||
|
||||
void RefData::cleanup()
|
||||
{
|
||||
mBaseNode = 0;
|
||||
|
||||
delete mCustomData;
|
||||
mCustomData = 0;
|
||||
}
|
||||
|
||||
RefData::RefData (const ESM::CellRef& cellRef)
|
||||
: mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mPosition (cellRef.pos),
|
||||
mCustomData (0)
|
||||
{}
|
||||
|
||||
RefData::RefData (const RefData& refData)
|
||||
: mBaseNode(0), mCustomData (0)
|
||||
{
|
||||
try
|
||||
{
|
||||
copy (refData);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
cleanup();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
RefData& RefData::operator= (const RefData& refData)
|
||||
{
|
||||
try
|
||||
{
|
||||
cleanup();
|
||||
copy (refData);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
cleanup();
|
||||
throw;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
RefData::~RefData()
|
||||
{
|
||||
try
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
catch (...)
|
||||
{}
|
||||
}
|
||||
|
||||
std::string RefData::getHandle()
|
||||
{
|
||||
return mBaseNode->getName();
|
||||
}
|
||||
|
||||
Ogre::SceneNode* RefData::getBaseNode()
|
||||
{
|
||||
return mBaseNode;
|
||||
}
|
||||
|
||||
void RefData::setBaseNode(Ogre::SceneNode* base)
|
||||
{
|
||||
mBaseNode = base;
|
||||
}
|
||||
|
||||
int RefData::getCount() const
|
||||
{
|
||||
return mCount;
|
||||
}
|
||||
|
||||
void RefData::setLocals (const ESM::Script& script)
|
||||
{
|
||||
if (!mHasLocals)
|
||||
{
|
||||
mLocals.configure (script);
|
||||
mHasLocals = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RefData::setCount (int count)
|
||||
{
|
||||
mCount = count;
|
||||
}
|
||||
|
||||
MWScript::Locals& RefData::getLocals()
|
||||
{
|
||||
return mLocals;
|
||||
}
|
||||
|
||||
bool RefData::isEnabled() const
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
void RefData::enable()
|
||||
{
|
||||
mEnabled = true;
|
||||
}
|
||||
|
||||
void RefData::disable()
|
||||
{
|
||||
mEnabled = true;
|
||||
}
|
||||
|
||||
ESM::Position& RefData::getPosition()
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
void RefData::setCustomData (CustomData *data)
|
||||
{
|
||||
delete mCustomData;
|
||||
mCustomData = data;
|
||||
}
|
||||
|
||||
CustomData *RefData::getCustomData()
|
||||
{
|
||||
return mCustomData;
|
||||
}
|
||||
}
|
@ -3,24 +3,22 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <Ogre.h>
|
||||
|
||||
#include <components/esm/defs.hpp>
|
||||
|
||||
#include "../mwscript/locals.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
|
||||
#include "containerstore.hpp"
|
||||
#include <Ogre.h>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class Script;
|
||||
class CellRef;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class CustomData;
|
||||
|
||||
class RefData
|
||||
{
|
||||
Ogre::SceneNode* mBaseNode;
|
||||
@ -33,102 +31,58 @@ namespace MWWorld
|
||||
bool mEnabled;
|
||||
int mCount; // 0: deleted
|
||||
|
||||
// we are using shared pointer here to avoid having to create custom copy-constructor,
|
||||
// assignment operator and destructor. As a consequence though copying a RefData object
|
||||
// manually will probably give unexcepted results. This is not a problem since RefData
|
||||
// are never copied outside of container operations.
|
||||
boost::shared_ptr<MWMechanics::CreatureStats> mCreatureStats;
|
||||
boost::shared_ptr<MWMechanics::NpcStats> mNpcStats;
|
||||
boost::shared_ptr<MWMechanics::Movement> mMovement;
|
||||
|
||||
boost::shared_ptr<ContainerStore<RefData> > mContainerStore;
|
||||
|
||||
ESM::Position mPosition;
|
||||
|
||||
CustomData *mCustomData;
|
||||
|
||||
void copy (const RefData& refData);
|
||||
|
||||
void cleanup();
|
||||
|
||||
public:
|
||||
/// @param cr Used to copy constant data such as position into this class where it can
|
||||
/// be altered without effecting the original data. This makes it possible
|
||||
/// to reset the position as the orignal data is still held in the CellRef
|
||||
RefData(const ESMS::CellRef& cr) : mBaseNode(0), mHasLocals (false), mEnabled (true),
|
||||
mCount (1), mPosition(cr.pos) {}
|
||||
|
||||
/// @param cellRef Used to copy constant data such as position into this class where it can
|
||||
/// be altered without effecting the original data. This makes it possible
|
||||
/// to reset the position as the orignal data is still held in the CellRef
|
||||
RefData (const ESM::CellRef& cellRef);
|
||||
|
||||
std::string getHandle()
|
||||
{
|
||||
return mBaseNode->getName();
|
||||
}
|
||||
Ogre::SceneNode* getBaseNode(){
|
||||
return mBaseNode;
|
||||
}
|
||||
void setBaseNode(Ogre::SceneNode* base){
|
||||
mBaseNode = base;
|
||||
}
|
||||
RefData (const RefData& refData);
|
||||
|
||||
int getCount() const
|
||||
{
|
||||
return mCount;
|
||||
}
|
||||
~RefData();
|
||||
|
||||
void setLocals (const ESM::Script& script)
|
||||
{
|
||||
if (!mHasLocals)
|
||||
{
|
||||
mLocals.configure (script);
|
||||
mHasLocals = true;
|
||||
}
|
||||
}
|
||||
RefData& operator= (const RefData& refData);
|
||||
|
||||
/// Return OGRE handle (may be empty).
|
||||
std::string getHandle();
|
||||
|
||||
void setCount (int count)
|
||||
{
|
||||
mCount = count;
|
||||
}
|
||||
/// Return OGRE base node (can be a null pointer).
|
||||
Ogre::SceneNode* getBaseNode();
|
||||
|
||||
MWScript::Locals& getLocals()
|
||||
{
|
||||
return mLocals;
|
||||
}
|
||||
/// Set OGRE base node (can be a null pointer).
|
||||
void setBaseNode (Ogre::SceneNode* base);
|
||||
|
||||
bool isEnabled() const
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
int getCount() const;
|
||||
|
||||
void enable()
|
||||
{
|
||||
mEnabled = true;
|
||||
}
|
||||
void setLocals (const ESM::Script& script);
|
||||
|
||||
void disable()
|
||||
{
|
||||
mEnabled = true;
|
||||
}
|
||||
void setCount (int count);
|
||||
|
||||
boost::shared_ptr<MWMechanics::CreatureStats>& getCreatureStats()
|
||||
{
|
||||
return mCreatureStats;
|
||||
}
|
||||
MWScript::Locals& getLocals();
|
||||
|
||||
boost::shared_ptr<MWMechanics::NpcStats>& getNpcStats()
|
||||
{
|
||||
return mNpcStats;
|
||||
}
|
||||
bool isEnabled() const;
|
||||
|
||||
boost::shared_ptr<MWMechanics::Movement>& getMovement()
|
||||
{
|
||||
return mMovement;
|
||||
}
|
||||
void enable();
|
||||
|
||||
boost::shared_ptr<ContainerStore<RefData> >& getContainerStore()
|
||||
{
|
||||
return mContainerStore;
|
||||
}
|
||||
void disable();
|
||||
|
||||
ESM::Position& getPosition()
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
ESM::Position& getPosition();
|
||||
|
||||
void setCustomData (CustomData *data);
|
||||
///< Set custom data (potentially replacing old custom data). The ownership of \æ data is
|
||||
/// transferred to this.
|
||||
|
||||
CustomData *getCustomData();
|
||||
///< May return a 0-pointer. The ownership of the return data object is not transferred.
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -52,8 +52,8 @@ void insertCellRefList(MWRender::RenderingManager& rendering, MWWorld::Environme
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
void Scene::advanceTime(){
|
||||
mRendering.addTime();
|
||||
void Scene::update (float duration){
|
||||
mRendering.update (duration);
|
||||
}
|
||||
void Scene::unloadCell (CellStoreCollection::iterator iter)
|
||||
{
|
||||
@ -61,9 +61,9 @@ namespace MWWorld
|
||||
ListHandles functor;
|
||||
|
||||
MWWorld::Ptr::CellStore* active = *iter;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
active->forEach<ListHandles>(functor);
|
||||
|
||||
@ -83,7 +83,7 @@ namespace MWWorld
|
||||
mEnvironment.mMechanicsManager->dropActors (active);
|
||||
mEnvironment.mSoundManager->stopSound (active);
|
||||
mActiveCells.erase(active);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Scene::loadCell (Ptr::CellStore *cell)
|
||||
|
@ -101,7 +101,8 @@ namespace MWWorld
|
||||
void markCellAsUnchanged();
|
||||
|
||||
void insertCell(ESMS::CellStore<MWWorld::RefData> &cell, MWWorld::Environment& environment);
|
||||
void advanceTime();
|
||||
|
||||
void update (float duration);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -143,16 +143,17 @@ namespace MWWorld
|
||||
}
|
||||
}
|
||||
|
||||
World::World (OEngine::Render::OgreRenderer& renderer, OEngine::Physic::PhysicEngine* physEng,
|
||||
World::World (OEngine::Render::OgreRenderer& renderer,
|
||||
const Files::Collections& fileCollections,
|
||||
const std::string& master, const boost::filesystem::path& resDir,
|
||||
bool newGame, Environment& environment, const std::string& encoding)
|
||||
: mRendering (renderer,resDir, physEng, environment),mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0),
|
||||
: mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0),
|
||||
mSky (false), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this)
|
||||
{
|
||||
mPhysEngine = physEng;
|
||||
|
||||
mPhysics = new PhysicsSystem(renderer, physEng);
|
||||
mPhysics = new PhysicsSystem(renderer);
|
||||
mPhysEngine = mPhysics->getEngine();
|
||||
|
||||
mRendering = new MWRender::RenderingManager(renderer, resDir, mPhysEngine, environment);
|
||||
|
||||
boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master));
|
||||
|
||||
@ -163,7 +164,7 @@ namespace MWWorld
|
||||
mEsm.open (masterPath.string());
|
||||
mStore.load (mEsm);
|
||||
|
||||
MWRender::Player* play = &(mRendering.getPlayer());
|
||||
MWRender::Player* play = &(mRendering->getPlayer());
|
||||
mPlayer = new MWWorld::Player (play, mStore.npcs.find ("player"), *this);
|
||||
mPhysics->addActor (mPlayer->getPlayer().getRefData().getHandle(), "", Ogre::Vector3 (0, 0, 0));
|
||||
|
||||
@ -176,9 +177,7 @@ namespace MWWorld
|
||||
mGlobalVariables->setInt ("chargenstate", 1);
|
||||
}
|
||||
|
||||
mPhysEngine = physEng;
|
||||
|
||||
mWorldScene = new Scene(environment, this, mRendering, mPhysics);
|
||||
mWorldScene = new Scene(environment, this, *mRendering, mPhysics);
|
||||
|
||||
}
|
||||
|
||||
@ -186,7 +185,7 @@ namespace MWWorld
|
||||
{
|
||||
delete mWorldScene;
|
||||
delete mGlobalVariables;
|
||||
|
||||
delete mRendering;
|
||||
delete mPhysics;
|
||||
|
||||
delete mPlayer;
|
||||
@ -348,14 +347,13 @@ namespace MWWorld
|
||||
void World::advanceTime (double hours)
|
||||
{
|
||||
hours += mGlobalVariables->getFloat ("gamehour");
|
||||
|
||||
|
||||
setHour (hours);
|
||||
|
||||
int days = hours / 24;
|
||||
|
||||
if (days>0)
|
||||
mGlobalVariables->setInt ("dayspassed", days + mGlobalVariables->getInt ("dayspassed"));
|
||||
mWorldScene->advanceTime();
|
||||
}
|
||||
|
||||
void World::setHour (double hour)
|
||||
@ -369,7 +367,7 @@ namespace MWWorld
|
||||
|
||||
mGlobalVariables->setFloat ("gamehour", hour);
|
||||
|
||||
mRendering.skySetHour (hour);
|
||||
mRendering->skySetHour (hour);
|
||||
|
||||
if (days>0)
|
||||
setDay (days + mGlobalVariables->getInt ("day"));
|
||||
@ -404,7 +402,7 @@ namespace MWWorld
|
||||
mGlobalVariables->setInt ("day", day);
|
||||
mGlobalVariables->setInt ("month", month);
|
||||
|
||||
mRendering.skySetDate (day, month);
|
||||
mRendering->skySetDate (day, month);
|
||||
}
|
||||
|
||||
void World::setMonth (int month)
|
||||
@ -425,7 +423,7 @@ namespace MWWorld
|
||||
if (years>0)
|
||||
mGlobalVariables->setInt ("year", years+mGlobalVariables->getInt ("year"));
|
||||
|
||||
mRendering.skySetDate (mGlobalVariables->getInt ("day"), month);
|
||||
mRendering->skySetDate (mGlobalVariables->getInt ("day"), month);
|
||||
}
|
||||
|
||||
bool World::toggleSky()
|
||||
@ -433,34 +431,34 @@ namespace MWWorld
|
||||
if (mSky)
|
||||
{
|
||||
mSky = false;
|
||||
mRendering.skyDisable();
|
||||
mRendering->skyDisable();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSky = true;
|
||||
// TODO check for extorior or interior with sky.
|
||||
mRendering.skySetHour (mGlobalVariables->getFloat ("gamehour"));
|
||||
mRendering.skySetDate (mGlobalVariables->getInt ("day"),
|
||||
mRendering->skySetHour (mGlobalVariables->getFloat ("gamehour"));
|
||||
mRendering->skySetDate (mGlobalVariables->getInt ("day"),
|
||||
mGlobalVariables->getInt ("month"));
|
||||
mRendering.skyEnable();
|
||||
mRendering->skyEnable();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int World::getMasserPhase() const
|
||||
{
|
||||
return mRendering.skyGetMasserPhase();
|
||||
return mRendering->skyGetMasserPhase();
|
||||
}
|
||||
|
||||
int World::getSecundaPhase() const
|
||||
{
|
||||
return mRendering.skyGetSecundaPhase();
|
||||
return mRendering->skyGetSecundaPhase();
|
||||
}
|
||||
|
||||
void World::setMoonColour (bool red)
|
||||
{
|
||||
mRendering.skySetMoonColour (red);
|
||||
mRendering->skySetMoonColour (red);
|
||||
}
|
||||
|
||||
float World::getTimeScaleFactor() const
|
||||
@ -506,7 +504,7 @@ namespace MWWorld
|
||||
mEnvironment.mSoundManager->stopSound3D (ptr);
|
||||
|
||||
mPhysics->removeObject (ptr.getRefData().getHandle());
|
||||
mRendering.removeObject(ptr);
|
||||
mRendering->removeObject(ptr);
|
||||
|
||||
mLocalScripts.remove (ptr);
|
||||
}
|
||||
@ -543,7 +541,7 @@ namespace MWWorld
|
||||
|
||||
/// \todo cell change for non-player ref
|
||||
|
||||
mRendering.moveObject (ptr, Ogre::Vector3 (x, y, z));
|
||||
mRendering->moveObject (ptr, Ogre::Vector3 (x, y, z));
|
||||
}
|
||||
|
||||
void World::moveObject (Ptr ptr, float x, float y, float z)
|
||||
@ -617,7 +615,7 @@ namespace MWWorld
|
||||
|
||||
bool World::toggleRenderMode (RenderMode mode)
|
||||
{
|
||||
return mRendering.toggleRenderMode (mode);
|
||||
return mRendering->toggleRenderMode (mode);
|
||||
}
|
||||
|
||||
std::pair<std::string, const ESM::Potion *> World::createRecord (const ESM::Potion& record)
|
||||
@ -678,17 +676,16 @@ namespace MWWorld
|
||||
void World::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode,
|
||||
int number)
|
||||
{
|
||||
mRendering.playAnimationGroup (ptr, groupName, mode, number);
|
||||
mRendering->playAnimationGroup (ptr, groupName, mode, number);
|
||||
}
|
||||
|
||||
void World::skipAnimation (const MWWorld::Ptr& ptr)
|
||||
{
|
||||
mRendering.skipAnimation (ptr);
|
||||
mRendering->skipAnimation (ptr);
|
||||
}
|
||||
void World::setObjectPhysicsRotation(const std::string& handle, Ogre::Quaternion quat){
|
||||
mPhysics->rotateObject(handle, quat);
|
||||
}
|
||||
void World::setObjectPhysicsPosition(const std::string& handle, Ogre::Vector3 vec){
|
||||
mPhysics->moveObject(handle, vec);
|
||||
|
||||
void World::update (float duration)
|
||||
{
|
||||
mWorldScene->update (duration);
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ namespace MWWorld
|
||||
|
||||
private:
|
||||
|
||||
MWRender::RenderingManager mRendering;
|
||||
MWRender::RenderingManager* mRendering;
|
||||
|
||||
MWWorld::Scene *mWorldScene;
|
||||
MWWorld::Player *mPlayer;
|
||||
@ -95,7 +95,7 @@ namespace MWWorld
|
||||
|
||||
public:
|
||||
|
||||
World (OEngine::Render::OgreRenderer& renderer, OEngine::Physic::PhysicEngine* physEng,
|
||||
World (OEngine::Render::OgreRenderer& renderer,
|
||||
const Files::Collections& fileCollections,
|
||||
const std::string& master, const boost::filesystem::path& resDir, bool newGame,
|
||||
Environment& environment, const std::string& encoding);
|
||||
@ -140,12 +140,16 @@ namespace MWWorld
|
||||
void disable (Ptr reference);
|
||||
|
||||
void advanceTime (double hours);
|
||||
///< Advance in-game time.
|
||||
|
||||
void setHour (double hour);
|
||||
///< Set in-game time hour.
|
||||
|
||||
void setMonth (int month);
|
||||
///< Set in-game time month.
|
||||
|
||||
void setDay (int day);
|
||||
///< Set in-game time day.
|
||||
|
||||
bool toggleSky();
|
||||
///< \return Resulting mode
|
||||
@ -218,8 +222,8 @@ namespace MWWorld
|
||||
void skipAnimation (const MWWorld::Ptr& ptr);
|
||||
///< Skip the animation for the given MW-reference for one frame. Calls to this function for
|
||||
/// references that are currently not in the rendered scene should be ignored.
|
||||
void setObjectPhysicsRotation(const std::string& handle,Ogre::Quaternion quat);
|
||||
void setObjectPhysicsPosition(const std::string& handle,Ogre::Vector3 vector);
|
||||
|
||||
void update (float duration);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,110 +1,542 @@
|
||||
# Find OGRE includes and library
|
||||
#-------------------------------------------------------------------
|
||||
# This file is part of the CMake build system for OGRE
|
||||
# (Object-oriented Graphics Rendering Engine)
|
||||
# For the latest info, see http://www.ogre3d.org/
|
||||
#
|
||||
# This module defines
|
||||
# OGRE_INCLUDE_DIR
|
||||
# OGRE_LIBRARIES, the libraries to link against to use OGRE.
|
||||
# OGRE_LIB_DIR, the location of the libraries
|
||||
# OGRE_FOUND, If false, do not try to use OGRE
|
||||
# The contents of this file are placed in the public domain. Feel
|
||||
# free to make use of it in any way you like.
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# - Try to find OGRE
|
||||
# If you have multiple versions of Ogre installed, use the CMake or
|
||||
# the environment variable OGRE_HOME to point to the path where the
|
||||
# desired Ogre version can be found.
|
||||
# By default this script will look for a dynamic Ogre build. If you
|
||||
# need to link against static Ogre libraries, set the CMake variable
|
||||
# OGRE_STATIC to TRUE.
|
||||
#
|
||||
# Copyright © 2007, Matt Williams
|
||||
# Once done, this will define
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
CMAKE_POLICY(PUSH)
|
||||
# OGRE_FOUND - system has OGRE
|
||||
# OGRE_INCLUDE_DIRS - the OGRE include directories
|
||||
# OGRE_LIBRARIES - link these to use the OGRE core
|
||||
# OGRE_BINARY_REL - location of the main Ogre binary (win32 non-static only, release)
|
||||
# OGRE_BINARY_DBG - location of the main Ogre binaries (win32 non-static only, debug)
|
||||
#
|
||||
# Additionally this script searches for the following optional
|
||||
# parts of the Ogre package:
|
||||
# Plugin_BSPSceneManager, Plugin_CgProgramManager,
|
||||
# Plugin_OctreeSceneManager, Plugin_OctreeZone,
|
||||
# Plugin_ParticleFX, Plugin_PCZSceneManager,
|
||||
# RenderSystem_GL, RenderSystem_Direct3D9,
|
||||
# Paging, Terrain
|
||||
#
|
||||
# For each of these components, the following variables are defined:
|
||||
#
|
||||
# OGRE_${COMPONENT}_FOUND - ${COMPONENT} is available
|
||||
# OGRE_${COMPONENT}_INCLUDE_DIRS - additional include directories for ${COMPONENT}
|
||||
# OGRE_${COMPONENT}_LIBRARIES - link these to use ${COMPONENT}
|
||||
# OGRE_${COMPONENT}_BINARY_REL - location of the component binary (win32 non-static only, release)
|
||||
# OGRE_${COMPONENT}_BINARY_DBG - location of the component binary (win32 non-static only, debug)
|
||||
#
|
||||
# Finally, the following variables are defined:
|
||||
#
|
||||
# OGRE_PLUGIN_DIR_REL - The directory where the release versions of
|
||||
# the OGRE plugins are located
|
||||
# OGRE_PLUGIN_DIR_DBG - The directory where the debug versions of
|
||||
# the OGRE plugins are located
|
||||
# OGRE_MEDIA_DIR - The directory where the OGRE sample media is
|
||||
# located, if available
|
||||
|
||||
IF (OGRE_LIBRARIES AND OGRE_INCLUDE_DIR AND OGRE_LIB_DIR AND OGRE_PLUGIN_DIR)
|
||||
SET(OGRE_FIND_QUIETLY TRUE) # Already in cache, be silent
|
||||
ENDIF (OGRE_LIBRARIES AND OGRE_INCLUDE_DIR AND OGRE_LIB_DIR AND OGRE_PLUGIN_DIR)
|
||||
include(FindPkgMacros)
|
||||
include(PreprocessorUtils)
|
||||
findpkg_begin(OGRE)
|
||||
|
||||
IF (WIN32) #Windows
|
||||
MESSAGE(STATUS "Looking for OGRE")
|
||||
SET(OGRESDK $ENV{OGRE_HOME})
|
||||
SET(OGRESOURCE $ENV{OGRE_SRC})
|
||||
IF (OGRESDK)
|
||||
MESSAGE(STATUS "Using OGRE SDK")
|
||||
STRING(REGEX REPLACE "[\\]" "/" OGRESDK "${OGRESDK}")
|
||||
SET(OGRE_INCLUDE_DIR ${OGRESDK}/include)
|
||||
SET(OGRE_LIB_DIR ${OGRESDK}/lib)
|
||||
SET(OGRE_LIBRARIES debug OgreMain_d optimized OgreMain)
|
||||
ENDIF (OGRESDK)
|
||||
IF (OGRESOURCE)
|
||||
MESSAGE(STATUS "Using OGRE built from source")
|
||||
SET(OGRE_INCLUDE_DIR $ENV{OGRE_SRC}/OgreMain/include)
|
||||
SET(OGRE_LIB_DIR $ENV{OGRE_SRC}/lib)
|
||||
SET(OGRE_LIBRARIES debug OgreMain_d optimized OgreMain)
|
||||
ENDIF (OGRESOURCE)
|
||||
ENDIF (WIN32)
|
||||
|
||||
IF (UNIX AND NOT APPLE)
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.4.7 FATAL_ERROR)
|
||||
FIND_PACKAGE(PkgConfig REQUIRED)
|
||||
# Don't mark REQUIRED, but use PKG_CHECK_MODULES below (otherwise PkgConfig
|
||||
# complains even if OGRE_* are set by hand).
|
||||
PKG_SEARCH_MODULE(OGRE OGRE)
|
||||
SET(OGRE_INCLUDE_DIR ${OGRE_INCLUDE_DIRS})
|
||||
SET(OGRE_LIB_DIR ${OGRE_LIBDIR})
|
||||
SET(OGRE_LIBRARIES ${OGRE_LIBRARIES} CACHE STRING "")
|
||||
PKG_CHECK_MODULES(OGRE OGRE)
|
||||
ENDIF (UNIX AND NOT APPLE)
|
||||
# Get path, convert backslashes as ${ENV_${var}}
|
||||
getenv_path(OGRE_HOME)
|
||||
getenv_path(OGRE_SDK)
|
||||
getenv_path(OGRE_SOURCE)
|
||||
getenv_path(OGRE_BUILD)
|
||||
getenv_path(OGRE_DEPENDENCIES_DIR)
|
||||
getenv_path(PROGRAMFILES)
|
||||
|
||||
# on OS X we need Ogre SDK because framework doesn't include all libs, just Ogre Main lib
|
||||
IF (APPLE)
|
||||
IF (OGRESDK)
|
||||
MESSAGE(STATUS "Using Ogre SDK")
|
||||
SET(OGRE_LIB_DIR ${OGRESDK}/lib)
|
||||
ELSE (OGRESDK)
|
||||
MESSAGE(FATAL_ERROR "Path to Ogre SDK not specified. Specify OGRESDK.")
|
||||
ENDIF (OGRESDK)
|
||||
|
||||
# Determine whether to search for a dynamic or static build
|
||||
if (OGRE_STATIC)
|
||||
set(OGRE_LIB_SUFFIX "Static")
|
||||
else ()
|
||||
set(OGRE_LIB_SUFFIX "")
|
||||
endif ()
|
||||
|
||||
FIND_PATH(OGRE_INCLUDE_DIR Ogre.h
|
||||
PATHS
|
||||
/Library/Frameworks
|
||||
/opt/local
|
||||
)
|
||||
FIND_LIBRARY(OGRE_LIBRARIES
|
||||
NAMES Ogre
|
||||
PATHS
|
||||
/Library/Frameworks
|
||||
/opt/local
|
||||
)
|
||||
ENDIF (APPLE)
|
||||
|
||||
#Do some preparation
|
||||
SEPARATE_ARGUMENTS(OGRE_INCLUDE_DIR)
|
||||
SEPARATE_ARGUMENTS(OGRE_LIBRARIES)
|
||||
set(OGRE_LIBRARY_NAMES "OgreMain${OGRE_LIB_SUFFIX}")
|
||||
get_debug_names(OGRE_LIBRARY_NAMES)
|
||||
|
||||
SET(OGRE_INCLUDE_DIR ${OGRE_INCLUDE_DIR} CACHE PATH "")
|
||||
SET(OGRE_LIBRARIES ${OGRE_LIBRARIES} CACHE STRING "")
|
||||
SET(OGRE_LIB_DIR ${OGRE_LIB_DIR} CACHE PATH "")
|
||||
# construct search paths from environmental hints and
|
||||
# OS specific guesses
|
||||
if (WIN32)
|
||||
set(OGRE_PREFIX_GUESSES
|
||||
${ENV_PROGRAMFILES}/OGRE
|
||||
C:/OgreSDK
|
||||
)
|
||||
elseif (UNIX)
|
||||
set(OGRE_PREFIX_GUESSES
|
||||
/opt/ogre
|
||||
/opt/OGRE
|
||||
/usr/lib${LIB_SUFFIX}/ogre
|
||||
/usr/lib${LIB_SUFFIX}/OGRE
|
||||
/usr/local/lib${LIB_SUFFIX}/ogre
|
||||
/usr/local/lib${LIB_SUFFIX}/OGRE
|
||||
$ENV{HOME}/ogre
|
||||
$ENV{HOME}/OGRE
|
||||
)
|
||||
endif ()
|
||||
set(OGRE_PREFIX_PATH
|
||||
${OGRE_HOME} ${OGRE_SDK} ${ENV_OGRE_HOME} ${ENV_OGRE_SDK}
|
||||
${OGRE_PREFIX_GUESSES}
|
||||
)
|
||||
create_search_paths(OGRE)
|
||||
# If both OGRE_BUILD and OGRE_SOURCE are set, prepare to find Ogre in a build dir
|
||||
set(OGRE_PREFIX_SOURCE ${OGRE_SOURCE} ${ENV_OGRE_SOURCE})
|
||||
set(OGRE_PREFIX_BUILD ${OGRE_BUILD} ${ENV_OGRE_BUILD})
|
||||
set(OGRE_PREFIX_DEPENDENCIES_DIR ${OGRE_DEPENDENCIES_DIR} ${ENV_OGRE_DEPENDENCIES_DIR})
|
||||
if (OGRE_PREFIX_SOURCE AND OGRE_PREFIX_BUILD)
|
||||
foreach(dir ${OGRE_PREFIX_SOURCE})
|
||||
set(OGRE_INC_SEARCH_PATH ${dir}/OgreMain/include ${dir}/Dependencies/include ${dir}/iPhoneDependencies/include ${OGRE_INC_SEARCH_PATH})
|
||||
set(OGRE_LIB_SEARCH_PATH ${dir}/lib ${dir}/Dependencies/lib ${dir}/iPhoneDependencies/lib ${OGRE_LIB_SEARCH_PATH})
|
||||
set(OGRE_BIN_SEARCH_PATH ${dir}/Samples/Common/bin ${OGRE_BIN_SEARCH_PATH})
|
||||
endforeach(dir)
|
||||
foreach(dir ${OGRE_PREFIX_BUILD})
|
||||
set(OGRE_INC_SEARCH_PATH ${dir}/include ${OGRE_INC_SEARCH_PATH})
|
||||
set(OGRE_LIB_SEARCH_PATH ${dir}/lib ${OGRE_LIB_SEARCH_PATH})
|
||||
set(OGRE_BIN_SEARCH_PATH ${dir}/bin ${OGRE_BIN_SEARCH_PATH})
|
||||
set(OGRE_BIN_SEARCH_PATH ${dir}/Samples/Common/bin ${OGRE_BIN_SEARCH_PATH})
|
||||
endforeach(dir)
|
||||
|
||||
if (OGRE_PREFIX_DEPENDENCIES_DIR)
|
||||
set(OGRE_INC_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/include ${OGRE_INC_SEARCH_PATH})
|
||||
set(OGRE_LIB_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/lib ${OGRE_LIB_SEARCH_PATH})
|
||||
set(OGRE_BIN_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/bin ${OGRE_BIN_SEARCH_PATH})
|
||||
endif()
|
||||
else()
|
||||
set(OGRE_PREFIX_SOURCE "NOTFOUND")
|
||||
set(OGRE_PREFIX_BUILD "NOTFOUND")
|
||||
endif ()
|
||||
|
||||
if(OGRE_LIB_DIR)
|
||||
CMAKE_POLICY(SET CMP0009 NEW)
|
||||
IF (NOT APPLE)
|
||||
FILE(GLOB_RECURSE OGRE_PLUGINS "${OGRE_LIB_DIR}/Plugin_*.so")
|
||||
ENDIF (NOT APPLE)
|
||||
IF (APPLE)
|
||||
FILE(GLOB_RECURSE OGRE_PLUGINS "${OGRE_LIB_DIR}/Plugin_*.dylib")
|
||||
ENDIF (APPLE)
|
||||
FOREACH (OGRE_PLUGINS_FILE ${OGRE_PLUGINS})
|
||||
STRING(REGEX REPLACE "/[^/]*$" "" OGRE_PLUGIN_DIR ${OGRE_PLUGINS_FILE})
|
||||
ENDFOREACH(OGRE_PLUGINS_FILE)
|
||||
# redo search if any of the environmental hints changed
|
||||
set(OGRE_COMPONENTS Paging Terrain
|
||||
Plugin_BSPSceneManager Plugin_CgProgramManager Plugin_OctreeSceneManager
|
||||
Plugin_OctreeZone Plugin_PCZSceneManager Plugin_ParticleFX
|
||||
RenderSystem_Direct3D11 RenderSystem_Direct3D9 RenderSystem_GL RenderSystem_GLES RenderSystem_GLES2)
|
||||
set(OGRE_RESET_VARS
|
||||
OGRE_CONFIG_INCLUDE_DIR OGRE_INCLUDE_DIR
|
||||
OGRE_LIBRARY_FWK OGRE_LIBRARY_REL OGRE_LIBRARY_DBG
|
||||
OGRE_PLUGIN_DIR_DBG OGRE_PLUGIN_DIR_REL OGRE_MEDIA_DIR)
|
||||
foreach (comp ${OGRE_COMPONENTS})
|
||||
set(OGRE_RESET_VARS ${OGRE_RESET_VARS}
|
||||
OGRE_${comp}_INCLUDE_DIR OGRE_${comp}_LIBRARY_FWK
|
||||
OGRE_${comp}_LIBRARY_DBG OGRE_${comp}_LIBRARY_REL
|
||||
)
|
||||
endforeach (comp)
|
||||
set(OGRE_PREFIX_WATCH ${OGRE_PREFIX_PATH} ${OGRE_PREFIX_SOURCE} ${OGRE_PREFIX_BUILD})
|
||||
clear_if_changed(OGRE_PREFIX_WATCH ${OGRE_RESET_VARS})
|
||||
|
||||
# try to locate Ogre via pkg-config
|
||||
use_pkgconfig(OGRE_PKGC "OGRE${OGRE_LIB_SUFFIX}")
|
||||
|
||||
if(NOT OGRE_BUILD_PLATFORM_APPLE_IOS)
|
||||
# try to find framework on OSX
|
||||
findpkg_framework(OGRE)
|
||||
else()
|
||||
set(OGRE_LIBRARY_FWK "")
|
||||
endif()
|
||||
|
||||
IF (OGRE_INCLUDE_DIR AND OGRE_LIBRARIES)
|
||||
SET(OGRE_FOUND TRUE)
|
||||
ENDIF (OGRE_INCLUDE_DIR AND OGRE_LIBRARIES)
|
||||
# locate Ogre include files
|
||||
find_path(OGRE_CONFIG_INCLUDE_DIR NAMES OgreBuildSettings.h HINTS ${OGRE_INC_SEARCH_PATH} ${OGRE_FRAMEWORK_INCLUDES} ${OGRE_PKGC_INCLUDE_DIRS} PATH_SUFFIXES "OGRE")
|
||||
find_path(OGRE_INCLUDE_DIR NAMES OgreRoot.h HINTS ${OGRE_CONFIG_INCLUDE_DIR} ${OGRE_INC_SEARCH_PATH} ${OGRE_FRAMEWORK_INCLUDES} ${OGRE_PKGC_INCLUDE_DIRS} PATH_SUFFIXES "OGRE")
|
||||
set(OGRE_INCOMPATIBLE FALSE)
|
||||
|
||||
IF (OGRE_FOUND)
|
||||
IF (NOT OGRE_FIND_QUIETLY)
|
||||
MESSAGE(STATUS " libraries : ${OGRE_LIBRARIES} from ${OGRE_LIB_DIR}")
|
||||
MESSAGE(STATUS " includes : ${OGRE_INCLUDE_DIR}")
|
||||
MESSAGE(STATUS " plugins : ${OGRE_PLUGIN_DIR}")
|
||||
ENDIF (NOT OGRE_FIND_QUIETLY)
|
||||
ELSE (OGRE_FOUND)
|
||||
IF (OGRE_FIND_REQUIRED)
|
||||
MESSAGE(FATAL_ERROR "Could not find OGRE")
|
||||
ENDIF (OGRE_FIND_REQUIRED)
|
||||
ENDIF (OGRE_FOUND)
|
||||
if (OGRE_INCLUDE_DIR)
|
||||
if (NOT OGRE_CONFIG_INCLUDE_DIR)
|
||||
set(OGRE_CONFIG_INCLUDE_DIR ${OGRE_INCLUDE_DIR})
|
||||
endif ()
|
||||
# determine Ogre version
|
||||
file(READ ${OGRE_INCLUDE_DIR}/OgrePrerequisites.h OGRE_TEMP_VERSION_CONTENT)
|
||||
get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_MAJOR OGRE_VERSION_MAJOR)
|
||||
get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_MINOR OGRE_VERSION_MINOR)
|
||||
get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_PATCH OGRE_VERSION_PATCH)
|
||||
get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_NAME OGRE_VERSION_NAME)
|
||||
set(OGRE_VERSION "${OGRE_VERSION_MAJOR}.${OGRE_VERSION_MINOR}.${OGRE_VERSION_PATCH}")
|
||||
pkg_message(OGRE "Found Ogre ${OGRE_VERSION_NAME} (${OGRE_VERSION})")
|
||||
|
||||
# determine configuration settings
|
||||
set(OGRE_CONFIG_HEADERS
|
||||
${OGRE_CONFIG_INCLUDE_DIR}/OgreBuildSettings.h
|
||||
${OGRE_CONFIG_INCLUDE_DIR}/OgreConfig.h
|
||||
)
|
||||
foreach(CFG_FILE ${OGRE_CONFIG_HEADERS})
|
||||
if (EXISTS ${CFG_FILE})
|
||||
set(OGRE_CONFIG_HEADER ${CFG_FILE})
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
if (OGRE_CONFIG_HEADER)
|
||||
file(READ ${OGRE_CONFIG_HEADER} OGRE_TEMP_CONFIG_CONTENT)
|
||||
has_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_STATIC_LIB OGRE_CONFIG_STATIC)
|
||||
get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_THREAD_SUPPORT OGRE_CONFIG_THREADS)
|
||||
get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_THREAD_PROVIDER OGRE_CONFIG_THREAD_PROVIDER)
|
||||
get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_NO_FREEIMAGE OGRE_CONFIG_FREEIMAGE)
|
||||
if (OGRE_CONFIG_STATIC AND OGRE_STATIC)
|
||||
elseif (OGRE_CONFIG_STATIC OR OGRE_STATIC)
|
||||
pkg_message(OGRE "Build type (static, dynamic) does not match the requested one.")
|
||||
set(OGRE_INCOMPATIBLE TRUE)
|
||||
endif ()
|
||||
else ()
|
||||
pkg_message(OGRE "Could not determine Ogre build configuration.")
|
||||
set(OGRE_INCOMPATIBLE TRUE)
|
||||
endif ()
|
||||
else ()
|
||||
set(OGRE_INCOMPATIBLE FALSE)
|
||||
endif ()
|
||||
|
||||
find_library(OGRE_LIBRARY_REL NAMES ${OGRE_LIBRARY_NAMES} HINTS ${OGRE_LIB_SEARCH_PATH} ${OGRE_PKGC_LIBRARY_DIRS} ${OGRE_FRAMEWORK_SEARCH_PATH} PATH_SUFFIXES "" "release" "relwithdebinfo" "minsizerel")
|
||||
find_library(OGRE_LIBRARY_DBG NAMES ${OGRE_LIBRARY_NAMES_DBG} HINTS ${OGRE_LIB_SEARCH_PATH} ${OGRE_PKGC_LIBRARY_DIRS} ${OGRE_FRAMEWORK_SEARCH_PATH} PATH_SUFFIXES "" "debug")
|
||||
make_library_set(OGRE_LIBRARY)
|
||||
|
||||
if(APPLE)
|
||||
set(OGRE_LIBRARY_DBG ${OGRE_LIB_SEARCH_PATH})
|
||||
endif()
|
||||
if (OGRE_INCOMPATIBLE)
|
||||
set(OGRE_LIBRARY "NOTFOUND")
|
||||
endif ()
|
||||
|
||||
set(OGRE_INCLUDE_DIR ${OGRE_CONFIG_INCLUDE_DIR} ${OGRE_INCLUDE_DIR})
|
||||
list(REMOVE_DUPLICATES OGRE_INCLUDE_DIR)
|
||||
findpkg_finish(OGRE)
|
||||
add_parent_dir(OGRE_INCLUDE_DIRS OGRE_INCLUDE_DIR)
|
||||
if (OGRE_SOURCE)
|
||||
# If working from source rather than SDK, add samples include
|
||||
set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} "${OGRE_SOURCE}/Samples/Common/include")
|
||||
endif()
|
||||
|
||||
mark_as_advanced(OGRE_CONFIG_INCLUDE_DIR OGRE_MEDIA_DIR OGRE_PLUGIN_DIR_REL OGRE_PLUGIN_DIR_DBG)
|
||||
|
||||
if (NOT OGRE_FOUND)
|
||||
return()
|
||||
endif ()
|
||||
|
||||
|
||||
# look for required Ogre dependencies in case of static build and/or threading
|
||||
if (OGRE_STATIC)
|
||||
set(OGRE_DEPS_FOUND TRUE)
|
||||
find_package(Cg QUIET)
|
||||
find_package(DirectX QUIET)
|
||||
find_package(FreeImage QUIET)
|
||||
find_package(Freetype QUIET)
|
||||
find_package(OpenGL QUIET)
|
||||
find_package(OpenGLES QUIET)
|
||||
find_package(OpenGLES2 QUIET)
|
||||
find_package(ZLIB QUIET)
|
||||
find_package(ZZip QUIET)
|
||||
if (UNIX AND NOT APPLE)
|
||||
find_package(X11 QUIET)
|
||||
find_library(XAW_LIBRARY NAMES Xaw Xaw7 PATHS ${DEP_LIB_SEARCH_DIR} ${X11_LIB_SEARCH_PATH})
|
||||
if (NOT XAW_LIBRARY OR NOT X11_Xt_FOUND)
|
||||
set(X11_FOUND FALSE)
|
||||
endif ()
|
||||
endif ()
|
||||
if (APPLE AND NOT OGRE_BUILD_PLATFORM_APPLE_IOS)
|
||||
find_package(Cocoa QUIET)
|
||||
find_package(Carbon QUIET)
|
||||
find_package(CoreVideo QUIET)
|
||||
if (NOT Cocoa_FOUND OR NOT Carbon_FOUND OR NOT CoreVideo_FOUND)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
endif ()
|
||||
endif ()
|
||||
if (APPLE AND OGRE_BUILD_PLATFORM_APPLE_IOS)
|
||||
find_package(iPhoneSDK QUIET)
|
||||
if (NOT iPhoneSDK_FOUND)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} ${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES} )
|
||||
|
||||
if (APPLE AND NOT OGRE_BUILD_PLATFORM_APPLE_IOS)
|
||||
set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${X11_LIBRARIES} ${X11_Xt_LIBRARIES} ${XAW_LIBRARY} ${X11_Xrandr_LIB} ${Carbon_LIBRARIES} ${Cocoa_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if (NOT ZLIB_FOUND OR NOT ZZip_FOUND)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
endif ()
|
||||
if (NOT FreeImage_FOUND AND NOT OGRE_CONFIG_FREEIMAGE)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
endif ()
|
||||
if (NOT FREETYPE_FOUND)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
endif ()
|
||||
if (UNIX AND NOT APPLE)
|
||||
if (NOT X11_FOUND)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (OGRE_CONFIG_THREADS)
|
||||
if (OGRE_CONFIG_THREAD_PROVIDER EQUAL 1)
|
||||
find_package(Boost COMPONENTS thread QUIET)
|
||||
if (NOT Boost_THREAD_FOUND)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
else ()
|
||||
set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${Boost_LIBRARIES})
|
||||
set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
|
||||
endif ()
|
||||
elseif (OGRE_CONFIG_THREAD_PROVIDER EQUAL 2)
|
||||
find_package(POCO QUIET)
|
||||
if (NOT POCO_FOUND)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
else ()
|
||||
set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${POCO_LIBRARIES})
|
||||
set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${POCO_INCLUDE_DIRS})
|
||||
endif ()
|
||||
elseif (OGRE_CONFIG_THREAD_PROVIDER EQUAL 3)
|
||||
find_package(TBB QUIET)
|
||||
if (NOT TBB_FOUND)
|
||||
set(OGRE_DEPS_FOUND FALSE)
|
||||
else ()
|
||||
set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${TBB_LIBRARIES})
|
||||
set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${TBB_INCLUDE_DIRS})
|
||||
endif ()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT OGRE_DEPS_FOUND)
|
||||
pkg_message(OGRE "Could not find all required dependencies for the Ogre package.")
|
||||
set(OGRE_FOUND FALSE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT OGRE_FOUND)
|
||||
return()
|
||||
endif ()
|
||||
|
||||
|
||||
get_filename_component(OGRE_LIBRARY_DIR_REL "${OGRE_LIBRARY_REL}" PATH)
|
||||
get_filename_component(OGRE_LIBRARY_DIR_DBG "${OGRE_LIBRARY_DBG}" PATH)
|
||||
set(OGRE_LIBRARY_DIRS ${OGRE_LIBRARY_DIR_REL} ${OGRE_LIBRARY_DIR_DBG})
|
||||
|
||||
# find binaries
|
||||
if (NOT OGRE_STATIC)
|
||||
if (WIN32)
|
||||
find_file(OGRE_BINARY_REL NAMES "OgreMain.dll" HINTS ${OGRE_BIN_SEARCH_PATH}
|
||||
PATH_SUFFIXES "" release relwithdebinfo minsizerel)
|
||||
find_file(OGRE_BINARY_DBG NAMES "OgreMain_d.dll" HINTS ${OGRE_BIN_SEARCH_PATH}
|
||||
PATH_SUFFIXES "" debug )
|
||||
endif()
|
||||
mark_as_advanced(OGRE_BINARY_REL OGRE_BINARY_DBG)
|
||||
endif()
|
||||
|
||||
|
||||
#########################################################
|
||||
# Find Ogre components
|
||||
#########################################################
|
||||
|
||||
set(OGRE_COMPONENT_SEARCH_PATH_REL
|
||||
${OGRE_LIBRARY_DIR_REL}/..
|
||||
${OGRE_LIBRARY_DIR_REL}/../..
|
||||
${OGRE_BIN_SEARCH_PATH}
|
||||
)
|
||||
set(OGRE_COMPONENT_SEARCH_PATH_DBG
|
||||
${OGRE_LIBRARY_DIR_DBG}/..
|
||||
${OGRE_LIBRARY_DIR_DBG}/../..
|
||||
${OGRE_BIN_SEARCH_PATH}
|
||||
)
|
||||
|
||||
macro(ogre_find_component COMPONENT HEADER)
|
||||
findpkg_begin(OGRE_${COMPONENT})
|
||||
find_path(OGRE_${COMPONENT}_INCLUDE_DIR NAMES ${HEADER} HINTS ${OGRE_INCLUDE_DIRS} ${OGRE_PREFIX_SOURCE} PATH_SUFFIXES ${COMPONENT} OGRE/${COMPONENT} Components/${COMPONENT}/include)
|
||||
set(OGRE_${COMPONENT}_LIBRARY_NAMES "Ogre${COMPONENT}${OGRE_LIB_SUFFIX}")
|
||||
get_debug_names(OGRE_${COMPONENT}_LIBRARY_NAMES)
|
||||
find_library(OGRE_${COMPONENT}_LIBRARY_REL NAMES ${OGRE_${COMPONENT}_LIBRARY_NAMES} HINTS ${OGRE_LIBRARY_DIR_REL} PATH_SUFFIXES "" "release" "relwithdebinfo" "minsizerel")
|
||||
find_library(OGRE_${COMPONENT}_LIBRARY_DBG NAMES ${OGRE_${COMPONENT}_LIBRARY_NAMES_DBG} HINTS ${OGRE_LIBRARY_DIR_DBG} PATH_SUFFIXES "" "debug")
|
||||
make_library_set(OGRE_${COMPONENT}_LIBRARY)
|
||||
findpkg_finish(OGRE_${COMPONENT})
|
||||
if (OGRE_${COMPONENT}_FOUND)
|
||||
# find binaries
|
||||
if (NOT OGRE_STATIC)
|
||||
if (WIN32)
|
||||
find_file(OGRE_${COMPONENT}_BINARY_REL NAMES "Ogre${COMPONENT}.dll" HINTS ${OGRE_COMPONENT_SEARCH_PATH_REL} PATH_SUFFIXES "" bin bin/release bin/relwithdebinfo bin/minsizerel release)
|
||||
find_file(OGRE_${COMPONENT}_BINARY_DBG NAMES "Ogre${COMPONENT}_d.dll" HINTS ${OGRE_COMPONENT_SEARCH_PATH_DBG} PATH_SUFFIXES "" bin bin/debug debug)
|
||||
endif()
|
||||
mark_as_advanced(OGRE_${COMPONENT}_BINARY_REL OGRE_${COMPONENT}_BINARY_DBG)
|
||||
endif()
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# look for Paging component
|
||||
ogre_find_component(Paging OgrePaging.h)
|
||||
# look for Terrain component
|
||||
ogre_find_component(Terrain OgreTerrain.h)
|
||||
# look for Property component
|
||||
ogre_find_component(Property OgreProperty.h)
|
||||
# look for RTShaderSystem component
|
||||
ogre_find_component(RTShaderSystem OgreRTShaderSystem.h)
|
||||
|
||||
|
||||
#########################################################
|
||||
# Find Ogre plugins
|
||||
#########################################################
|
||||
|
||||
macro(ogre_find_plugin PLUGIN HEADER)
|
||||
# On Unix, the plugins might have no prefix
|
||||
if (CMAKE_FIND_LIBRARY_PREFIXES)
|
||||
set(TMP_CMAKE_LIB_PREFIX ${CMAKE_FIND_LIBRARY_PREFIXES})
|
||||
set(CMAKE_FIND_LIBRARY_PREFIXES ${CMAKE_FIND_LIBRARY_PREFIXES} "")
|
||||
endif()
|
||||
|
||||
# strip RenderSystem_ or Plugin_ prefix from plugin name
|
||||
string(REPLACE "RenderSystem_" "" PLUGIN_TEMP ${PLUGIN})
|
||||
string(REPLACE "Plugin_" "" PLUGIN_NAME ${PLUGIN_TEMP})
|
||||
|
||||
# header files for plugins are not usually needed, but find them anyway if they are present
|
||||
set(OGRE_PLUGIN_PATH_SUFFIXES
|
||||
PlugIns PlugIns/${PLUGIN_NAME} Plugins Plugins/${PLUGIN_NAME} ${PLUGIN}
|
||||
RenderSystems RenderSystems/${PLUGIN_NAME} ${ARGN})
|
||||
find_path(OGRE_${PLUGIN}_INCLUDE_DIR NAMES ${HEADER}
|
||||
HINTS ${OGRE_INCLUDE_DIRS} ${OGRE_PREFIX_SOURCE}
|
||||
PATH_SUFFIXES ${OGRE_PLUGIN_PATH_SUFFIXES})
|
||||
# find link libraries for plugins
|
||||
set(OGRE_${PLUGIN}_LIBRARY_NAMES "${PLUGIN}${OGRE_LIB_SUFFIX}")
|
||||
get_debug_names(OGRE_${PLUGIN}_LIBRARY_NAMES)
|
||||
set(OGRE_${PLUGIN}_LIBRARY_FWK ${OGRE_LIBRARY_FWK})
|
||||
find_library(OGRE_${PLUGIN}_LIBRARY_REL NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES}
|
||||
HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE opt release release/opt relwithdebinfo relwithdebinfo/opt minsizerel minsizerel/opt)
|
||||
find_library(OGRE_${PLUGIN}_LIBRARY_DBG NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES_DBG}
|
||||
HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE opt debug debug/opt)
|
||||
make_library_set(OGRE_${PLUGIN}_LIBRARY)
|
||||
|
||||
if (OGRE_${PLUGIN}_LIBRARY OR OGRE_${PLUGIN}_INCLUDE_DIR)
|
||||
set(OGRE_${PLUGIN}_FOUND TRUE)
|
||||
if (OGRE_${PLUGIN}_INCLUDE_DIR)
|
||||
set(OGRE_${PLUGIN}_INCLUDE_DIRS ${OGRE_${PLUGIN}_INCLUDE_DIR})
|
||||
endif()
|
||||
set(OGRE_${PLUGIN}_LIBRARIES ${OGRE_${PLUGIN}_LIBRARY})
|
||||
endif ()
|
||||
|
||||
mark_as_advanced(OGRE_${PLUGIN}_INCLUDE_DIR OGRE_${PLUGIN}_LIBRARY_REL OGRE_${PLUGIN}_LIBRARY_DBG OGRE_${PLUGIN}_LIBRARY_FWK)
|
||||
|
||||
# look for plugin dirs
|
||||
if (OGRE_${PLUGIN}_FOUND)
|
||||
if (NOT OGRE_PLUGIN_DIR_REL OR NOT OGRE_PLUGIN_DIR_DBG)
|
||||
if (WIN32)
|
||||
set(OGRE_PLUGIN_SEARCH_PATH_REL
|
||||
${OGRE_LIBRARY_DIR_REL}/..
|
||||
${OGRE_LIBRARY_DIR_REL}/../..
|
||||
${OGRE_BIN_SEARCH_PATH}
|
||||
)
|
||||
set(OGRE_PLUGIN_SEARCH_PATH_DBG
|
||||
${OGRE_LIBRARY_DIR_DBG}/..
|
||||
${OGRE_LIBRARY_DIR_DBG}/../..
|
||||
${OGRE_BIN_SEARCH_PATH}
|
||||
)
|
||||
find_path(OGRE_PLUGIN_DIR_REL NAMES "${PLUGIN}.dll" HINTS ${OGRE_PLUGIN_SEARCH_PATH_REL}
|
||||
PATH_SUFFIXES "" bin bin/release bin/relwithdebinfo bin/minsizerel release)
|
||||
find_path(OGRE_PLUGIN_DIR_DBG NAMES "${PLUGIN}_d.dll" HINTS ${OGRE_PLUGIN_SEARCH_PATH_DBG}
|
||||
PATH_SUFFIXES "" bin bin/debug debug)
|
||||
elseif (UNIX)
|
||||
get_filename_component(OGRE_PLUGIN_DIR_TMP ${OGRE_${PLUGIN}_LIBRARY_REL} PATH)
|
||||
set(OGRE_PLUGIN_DIR_REL ${OGRE_PLUGIN_DIR_TMP} CACHE STRING "Ogre plugin dir (release)" FORCE)
|
||||
get_filename_component(OGRE_PLUGIN_DIR_TMP ${OGRE_${PLUGIN}_LIBRARY_DBG} PATH)
|
||||
set(OGRE_PLUGIN_DIR_DBG ${OGRE_PLUGIN_DIR_TMP} CACHE STRING "Ogre plugin dir (debug)" FORCE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# find binaries
|
||||
if (NOT OGRE_STATIC)
|
||||
if (WIN32)
|
||||
find_file(OGRE_${PLUGIN}_REL NAMES "${PLUGIN}.dll" HINTS ${OGRE_PLUGIN_DIR_REL})
|
||||
find_file(OGRE_${PLUGIN}_DBG NAMES "${PLUGIN}_d.dll" HINTS ${OGRE_PLUGIN_DIR_DBG})
|
||||
endif()
|
||||
mark_as_advanced(OGRE_${PLUGIN}_REL OGRE_${PLUGIN}_DBG)
|
||||
endif()
|
||||
|
||||
endif ()
|
||||
|
||||
if (TMP_CMAKE_LIB_PREFIX)
|
||||
set(CMAKE_FIND_LIBRARY_PREFIXES ${TMP_CMAKE_LIB_PREFIX})
|
||||
endif ()
|
||||
endmacro(ogre_find_plugin)
|
||||
|
||||
ogre_find_plugin(Plugin_PCZSceneManager OgrePCZSceneManager.h PCZ PlugIns/PCZSceneManager/include)
|
||||
ogre_find_plugin(Plugin_OctreeZone OgreOctreeZone.h PCZ PlugIns/OctreeZone/include)
|
||||
ogre_find_plugin(Plugin_BSPSceneManager OgreBspSceneManager.h PlugIns/BSPSceneManager/include)
|
||||
ogre_find_plugin(Plugin_CgProgramManager OgreCgProgram.h PlugIns/CgProgramManager/include)
|
||||
ogre_find_plugin(Plugin_OctreeSceneManager OgreOctreeSceneManager.h PlugIns/OctreeSceneManager/include)
|
||||
ogre_find_plugin(Plugin_ParticleFX OgreParticleFXPrerequisites.h PlugIns/ParticleFX/include)
|
||||
ogre_find_plugin(RenderSystem_GL OgreGLRenderSystem.h RenderSystems/GL/include)
|
||||
ogre_find_plugin(RenderSystem_GLES OgreGLESRenderSystem.h RenderSystems/GLES/include)
|
||||
ogre_find_plugin(RenderSystem_GLES2 OgreGLES2RenderSystem.h RenderSystems/GLES2/include)
|
||||
ogre_find_plugin(RenderSystem_Direct3D9 OgreD3D9RenderSystem.h RenderSystems/Direct3D9/include)
|
||||
ogre_find_plugin(RenderSystem_Direct3D11 OgreD3D11RenderSystem.h RenderSystems/Direct3D11/include)
|
||||
|
||||
if (OGRE_STATIC)
|
||||
# check if dependencies for plugins are met
|
||||
if (NOT DirectX_FOUND)
|
||||
set(OGRE_RenderSystem_Direct3D9_FOUND FALSE)
|
||||
endif ()
|
||||
if (NOT DirectX_D3D11_FOUND)
|
||||
set(OGRE_RenderSystem_Direct3D11_FOUND FALSE)
|
||||
endif ()
|
||||
if (NOT OPENGL_FOUND)
|
||||
set(OGRE_RenderSystem_GL_FOUND FALSE)
|
||||
endif ()
|
||||
if (NOT OPENGLES_FOUND)
|
||||
set(OGRE_RenderSystem_GLES_FOUND FALSE)
|
||||
endif ()
|
||||
if (NOT OPENGLES2_FOUND)
|
||||
set(OGRE_RenderSystem_GLES2_FOUND FALSE)
|
||||
endif ()
|
||||
if (NOT Cg_FOUND)
|
||||
set(OGRE_Plugin_CgProgramManager_FOUND FALSE)
|
||||
endif ()
|
||||
|
||||
set(OGRE_RenderSystem_Direct3D9_LIBRARIES ${OGRE_RenderSystem_Direct3D9_LIBRARIES}
|
||||
${DirectX_LIBRARIES}
|
||||
)
|
||||
|
||||
set(OGRE_RenderSystem_Direct3D11_LIBRARIES ${OGRE_RenderSystem_Direct3D11_LIBRARIES}
|
||||
${DirectX_D3D11_LIBRARIES}
|
||||
)
|
||||
set(OGRE_RenderSystem_GL_LIBRARIES ${OGRE_RenderSystem_GL_LIBRARIES}
|
||||
${OPENGL_LIBRARIES}
|
||||
)
|
||||
set(OGRE_RenderSystem_GLES_LIBRARIES ${OGRE_RenderSystem_GLES_LIBRARIES}
|
||||
${OPENGLES_LIBRARIES}
|
||||
)
|
||||
set(OGRE_RenderSystem_GLES2_LIBRARIES ${OGRE_RenderSystem_GLES2_LIBRARIES}
|
||||
${OPENGLES2_LIBRARIES}
|
||||
)
|
||||
set(OGRE_Plugin_CgProgramManager_LIBRARIES ${OGRE_Plugin_CgProgramManager_LIBRARIES}
|
||||
${Cg_LIBRARIES}
|
||||
)
|
||||
endif ()
|
||||
|
||||
# look for the media directory
|
||||
set(OGRE_MEDIA_SEARCH_PATH
|
||||
${OGRE_SOURCE}
|
||||
${OGRE_LIBRARY_DIR_REL}/..
|
||||
${OGRE_LIBRARY_DIR_DBG}/..
|
||||
${OGRE_LIBRARY_DIR_REL}/../..
|
||||
${OGRE_LIBRARY_DIR_DBG}/../..
|
||||
${OGRE_PREFIX_SOURCE}
|
||||
)
|
||||
set(OGRE_MEDIA_SEARCH_SUFFIX
|
||||
Samples/Media
|
||||
Media
|
||||
media
|
||||
share/OGRE/media
|
||||
)
|
||||
|
||||
clear_if_changed(OGRE_PREFIX_WATCH OGRE_MEDIA_DIR)
|
||||
find_path(OGRE_MEDIA_DIR NAMES packs/cubemapsJS.zip HINTS ${OGRE_MEDIA_SEARCH_PATH}
|
||||
PATHS ${OGRE_PREFIX_PATH} PATH_SUFFIXES ${OGRE_MEDIA_SEARCH_SUFFIX})
|
||||
|
||||
CMAKE_POLICY(POP)
|
||||
|
@ -50,6 +50,11 @@ endmacro(create_search_paths)
|
||||
# clear cache variables if a certain variable changed
|
||||
macro(clear_if_changed TESTVAR)
|
||||
# test against internal check variable
|
||||
# HACK: Apparently, adding a variable to the cache cleans up the list
|
||||
# a bit. We need to also remove any empty strings from the list, but
|
||||
# at the same time ensure that we are actually dealing with a list.
|
||||
list(APPEND ${TESTVAR} "")
|
||||
list(REMOVE_ITEM ${TESTVAR} "")
|
||||
if (NOT "${${TESTVAR}}" STREQUAL "${${TESTVAR}_INT_CHECK}")
|
||||
message(STATUS "${TESTVAR} changed.")
|
||||
foreach(var ${ARGN})
|
||||
@ -129,9 +134,18 @@ MACRO(findpkg_framework fwk)
|
||||
/System/Library/Frameworks
|
||||
/Network/Library/Frameworks
|
||||
/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk/System/Library/Frameworks/
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../lib/Release
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../lib/Debug
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lib/Release
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lib/Debug
|
||||
)
|
||||
# These could be arrays of paths, add each individually to the search paths
|
||||
foreach(i ${OGRE_PREFIX_PATH})
|
||||
set(${fwk}_FRAMEWORK_PATH ${${fwk}_FRAMEWORK_PATH} ${i}/lib/Release ${i}/lib/Debug)
|
||||
endforeach(i)
|
||||
|
||||
foreach(i ${OGRE_PREFIX_BUILD})
|
||||
set(${fwk}_FRAMEWORK_PATH ${${fwk}_FRAMEWORK_PATH} ${i}/lib/Release ${i}/lib/Debug)
|
||||
endforeach(i)
|
||||
|
||||
FOREACH(dir ${${fwk}_FRAMEWORK_PATH})
|
||||
SET(fwkpath ${dir}/${fwk}.framework)
|
||||
IF(EXISTS ${fwkpath})
|
||||
|
@ -2,7 +2,7 @@
|
||||
macro (add_openmw_dir dir)
|
||||
set (files)
|
||||
foreach (u ${ARGN})
|
||||
file (GLOB ALL RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.*")
|
||||
file (GLOB ALL ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.[ch]pp")
|
||||
foreach (f ${ALL})
|
||||
list (APPEND files "${f}")
|
||||
list (APPEND OPENMW_FILES "${f}")
|
||||
@ -14,7 +14,7 @@ endmacro (add_openmw_dir)
|
||||
macro (add_component_dir dir)
|
||||
set (files)
|
||||
foreach (u ${ARGN})
|
||||
file (GLOB ALL RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.*")
|
||||
file (GLOB ALL ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.[ch]pp")
|
||||
foreach (f ${ALL})
|
||||
list (APPEND files "${f}")
|
||||
list (APPEND COMPONENT_FILES "${f}")
|
||||
|
60
cmake/PreprocessorUtils.cmake
Normal file
60
cmake/PreprocessorUtils.cmake
Normal file
@ -0,0 +1,60 @@
|
||||
#-------------------------------------------------------------------
|
||||
# This file is part of the CMake build system for OGRE
|
||||
# (Object-oriented Graphics Rendering Engine)
|
||||
# For the latest info, see http://www.ogre3d.org/
|
||||
#
|
||||
# The contents of this file are placed in the public domain. Feel
|
||||
# free to make use of it in any way you like.
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
macro(get_preprocessor_entry CONTENTS KEYWORD VARIABLE)
|
||||
string(REGEX MATCH
|
||||
"# *define +${KEYWORD} +((\"([^\n]*)\")|([^ \n]*))"
|
||||
PREPROC_TEMP_VAR
|
||||
${${CONTENTS}}
|
||||
)
|
||||
if (CMAKE_MATCH_3)
|
||||
set(${VARIABLE} ${CMAKE_MATCH_3})
|
||||
else ()
|
||||
set(${VARIABLE} ${CMAKE_MATCH_4})
|
||||
endif ()
|
||||
endmacro()
|
||||
|
||||
macro(has_preprocessor_entry CONTENTS KEYWORD VARIABLE)
|
||||
string(REGEX MATCH
|
||||
"\n *# *define +(${KEYWORD})"
|
||||
PREPROC_TEMP_VAR
|
||||
${${CONTENTS}}
|
||||
)
|
||||
if (CMAKE_MATCH_1)
|
||||
set(${VARIABLE} TRUE)
|
||||
else ()
|
||||
set(${VARIABLE} FALSE)
|
||||
endif ()
|
||||
endmacro()
|
||||
|
||||
macro(replace_preprocessor_entry VARIABLE KEYWORD NEW_VALUE)
|
||||
string(REGEX REPLACE
|
||||
"(// *)?# *define +${KEYWORD} +[^ \n]*"
|
||||
"#define ${KEYWORD} ${NEW_VALUE}"
|
||||
${VARIABLE}_TEMP
|
||||
${${VARIABLE}}
|
||||
)
|
||||
set(${VARIABLE} ${${VARIABLE}_TEMP})
|
||||
endmacro()
|
||||
|
||||
macro(set_preprocessor_entry VARIABLE KEYWORD ENABLE)
|
||||
if (${ENABLE})
|
||||
set(TMP_REPLACE_STR "#define ${KEYWORD}")
|
||||
else ()
|
||||
set(TMP_REPLACE_STR "// #define ${KEYWORD}")
|
||||
endif ()
|
||||
string(REGEX REPLACE
|
||||
"(// *)?# *define +${KEYWORD} *\n"
|
||||
${TMP_REPLACE_STR}
|
||||
${VARIABLE}_TEMP
|
||||
${${VARIABLE}}
|
||||
)
|
||||
set(${VARIABLE} ${${VARIABLE}_TEMP})
|
||||
endmacro()
|
||||
|
@ -63,6 +63,10 @@ add_component_dir (interpreter
|
||||
|
||||
include_directories(${BULLET_INCLUDE_DIRS})
|
||||
|
||||
add_library (components STATIC ${COMPONENT_FILES})
|
||||
add_library(components STATIC ${COMPONENT_FILES})
|
||||
|
||||
target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES})
|
||||
|
||||
# Make the variable accessible for other subdirectories
|
||||
set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE)
|
||||
|
||||
target_link_libraries (components ${Boost_LIBRARIES} ${OGRE_LIBRARIES})
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
#include "store.hpp"
|
||||
#include "components/esm/records.hpp"
|
||||
#include "components/esm/loadcell.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
@ -36,7 +35,7 @@ namespace ESMS
|
||||
{
|
||||
LiveCellRef(const CellRef& cref, const X* b = NULL) : base(b), ref(cref),
|
||||
mData(ref) {}
|
||||
|
||||
|
||||
|
||||
LiveCellRef(const X* b = NULL) : base(b), mData(ref) {}
|
||||
|
||||
@ -187,7 +186,7 @@ namespace ESMS
|
||||
++iter)
|
||||
if (!functor (iter->ref, iter->mData))
|
||||
return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
2
extern/caelum/include/CaelumPrecompiled.h
vendored
2
extern/caelum/include/CaelumPrecompiled.h
vendored
@ -19,7 +19,7 @@ along with Caelum. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "Ogre/Ogre.h"
|
||||
#include "OGRE/Ogre.h"
|
||||
#else
|
||||
#include "Ogre.h"
|
||||
#endif
|
||||
|
2
extern/caelum/src/InternalUtilities.cpp
vendored
2
extern/caelum/src/InternalUtilities.cpp
vendored
@ -75,7 +75,7 @@ namespace Caelum
|
||||
stream.unsetf(std::ios::dec);
|
||||
stream.setf(std::ios::hex);
|
||||
stream.setf(std::ios::uppercase);
|
||||
stream << reinterpret_cast<ptrdiff_t>(pointer);
|
||||
stream << reinterpret_cast<std::ptrdiff_t>(pointer);
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Defines plugins to load
|
||||
|
||||
# Define plugin folder
|
||||
PluginFolder=${OGRE_PLUGIN_DIR}
|
||||
PluginFolder=${OGRE_PLUGIN_DIR_REL}
|
||||
|
||||
# Define plugins
|
||||
Plugin=RenderSystem_GL
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 2f5eca9d878526bdd9dce93ece7f42093b481545
|
||||
Subproject commit 6c7e5d00e4f5bf954afe15f10e56f03520abfee4
|
66
old_d_version/.gitignore
vendored
66
old_d_version/.gitignore
vendored
@ -1,66 +0,0 @@
|
||||
|
||||
# /
|
||||
/cache
|
||||
/later
|
||||
/openmw.ini.*
|
||||
/rr.sh
|
||||
/fontdump
|
||||
/MyGUI.log
|
||||
/upm.sh
|
||||
/raw.txt
|
||||
/vids
|
||||
/include
|
||||
/includes
|
||||
/.thumbnails
|
||||
/*.jpg
|
||||
/*.dll
|
||||
/*.exe
|
||||
/*.def
|
||||
/*.a
|
||||
/*.map
|
||||
/*.rsp
|
||||
/ogre.cfg
|
||||
/openmw
|
||||
/bored
|
||||
/bsatool
|
||||
/niftool
|
||||
/esmtool
|
||||
/bored.highscores
|
||||
/Ogre.log
|
||||
/openmw.ini
|
||||
/openmw.ini.old
|
||||
/dsss_*
|
||||
/dsss.last
|
||||
/objs
|
||||
/nifobjs
|
||||
|
||||
# /bullet/
|
||||
/bullet/OgreOpcode*
|
||||
/bullet/demo
|
||||
/bullet/*.a
|
||||
|
||||
# /media_mygui/
|
||||
/media_mygui/core.skin.orig
|
||||
/media_mygui/.thumbnails
|
||||
|
||||
# /monster/
|
||||
/monster/*openmw_last
|
||||
|
||||
# /mscripts/
|
||||
/mscripts/draft
|
||||
|
||||
# /nif/
|
||||
/nif/bumpmap
|
||||
/nif/*.nif
|
||||
|
||||
# /ogre/
|
||||
/ogre/*.nif
|
||||
/ogre/cs
|
||||
|
||||
# /util/
|
||||
/util/iconv
|
||||
|
||||
*.o
|
||||
*.patch
|
||||
*.diff
|
||||
.directory
|
@ -1,85 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (bindings.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module bullet.bindings;
|
||||
|
||||
/*
|
||||
* This module is the interface between D and the C++ code that
|
||||
* handles Bullet.
|
||||
*/
|
||||
|
||||
typedef void* BulletShape;
|
||||
|
||||
extern(C):
|
||||
|
||||
// Initialize the dynamic world. Returns non-zero if an error occurs.
|
||||
int bullet_init();
|
||||
|
||||
// Set physics modes
|
||||
void bullet_nextMode();
|
||||
void bullet_walk();
|
||||
void bullet_fly();
|
||||
void bullet_ghost();
|
||||
|
||||
// Warp the player to a specific location.
|
||||
void bullet_movePlayer(float x, float y, float z);
|
||||
|
||||
// Request that the player moves in this direction
|
||||
void bullet_setPlayerDir(float x, float y, float z);
|
||||
|
||||
// Get the current player position, after physics and collision have
|
||||
// been applied.
|
||||
void bullet_getPlayerPos(float *x, float *y, float *z);
|
||||
|
||||
// Create a box shape. Used for bounding boxes. The box is a trimesh
|
||||
// and is hollow (you can walk inside it.)
|
||||
void bullet_createBoxShape(float minX, float minY, float minZ,
|
||||
float maxX, float maxY, float maxZ,
|
||||
float *trans,float *matrix);
|
||||
|
||||
// Create a triangle shape. This is cumulative, all meshes created
|
||||
// with this function are added to the same shape. Since the various
|
||||
// parts of a mesh can be differently transformed and we are putting
|
||||
// them all in one shape, we must transform the vertices manually.
|
||||
void bullet_createTriShape(int numFaces,
|
||||
void *triArray,
|
||||
int numVerts,
|
||||
void *vertArray,
|
||||
float *trans,float *matrix);
|
||||
|
||||
// "Flushes" the meshes created with createTriShape, returning the
|
||||
// pointer to the final shape object.
|
||||
BulletShape bullet_getFinalShape();
|
||||
|
||||
// Insert a static mesh with the given translation, quaternion
|
||||
// rotation and scale. The quaternion is assumed to be in Ogre format,
|
||||
// ie. with the W first.
|
||||
void bullet_insertStatic(BulletShape shp, float *pos,
|
||||
float *quat, float scale);
|
||||
|
||||
// Move the physics simulation 'delta' seconds forward in time
|
||||
void bullet_timeStep(float delta);
|
||||
|
||||
// Deallocate objects
|
||||
void bullet_cleanup();
|
||||
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (bullet.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module bullet.bullet;
|
||||
|
||||
import bullet.bindings;
|
||||
|
||||
void initBullet()
|
||||
{
|
||||
if(bullet_init())
|
||||
throw new Exception("Bullet setup failed");
|
||||
}
|
||||
|
||||
void cleanupBullet() { bullet_cleanup(); }
|
@ -1,502 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (cpp_bullet.cpp) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
#include "btBulletDynamicsCommon.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../util/dbg.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class CustomOverlappingPairCallback;
|
||||
|
||||
enum
|
||||
{
|
||||
MASK_PLAYER = 1,
|
||||
MASK_STATIC = 2
|
||||
};
|
||||
|
||||
// System variables
|
||||
btDefaultCollisionConfiguration* g_collisionConfiguration;
|
||||
btCollisionDispatcher *g_dispatcher;
|
||||
//btBroadphaseInterface *g_broadphase;
|
||||
btAxisSweep3 *g_broadphase;
|
||||
btSequentialImpulseConstraintSolver* g_solver;
|
||||
btDynamicsWorld *g_dynamicsWorld;
|
||||
|
||||
// Player variables
|
||||
btCollisionObject* g_playerObject;
|
||||
btConvexShape *g_playerShape;
|
||||
|
||||
// Player position. This is updated automatically by the physics
|
||||
// system based on g_walkDirection and collisions. It is read by D
|
||||
// code through bullet_getPlayerPos().
|
||||
btVector3 g_playerPosition;
|
||||
|
||||
// Walking vector - defines direction and speed that the player
|
||||
// intends to move right now. This is updated from D code each frame
|
||||
// through bullet_setPlayerDir(), based on player input (and later, AI
|
||||
// decisions.) The units of the vector are points per second.
|
||||
btVector3 g_walkDirection;
|
||||
|
||||
// The current trimesh shape being built. All new inserted meshes are
|
||||
// added into this, until bullet_getFinalShape() is called.
|
||||
btTriangleIndexVertexArray *g_currentMesh;
|
||||
|
||||
// These variables and the class below are used in player collision
|
||||
// detection. The callback is injected into the broadphase and keeps a
|
||||
// continuously updated list of what objects are colliding with the
|
||||
// player (in g_pairCache). This list is used in the function called
|
||||
// recoverFromPenetration().
|
||||
btHashedOverlappingPairCache* g_pairCache;
|
||||
CustomOverlappingPairCallback *g_customPairCallback;
|
||||
|
||||
// Three physics modes: walking (with gravity and collision), flying
|
||||
// (collision but no gravity) and ghost mode (fly through walls)
|
||||
enum
|
||||
{
|
||||
PHYS_WALK,
|
||||
PHYS_FLY,
|
||||
PHYS_GHOST
|
||||
};
|
||||
int g_physMode;
|
||||
|
||||
// Include the player physics
|
||||
#include "cpp_player.cpp"
|
||||
|
||||
// Include the uniform shape scaler
|
||||
#include "cpp_scale.cpp"
|
||||
|
||||
class CustomOverlappingPairCallback : public btOverlappingPairCallback
|
||||
{
|
||||
public:
|
||||
virtual btBroadphasePair* addOverlappingPair(btBroadphaseProxy* proxy0,
|
||||
btBroadphaseProxy* proxy1)
|
||||
{
|
||||
if (proxy0->m_clientObject==g_playerObject ||
|
||||
proxy1->m_clientObject==g_playerObject)
|
||||
return g_pairCache->addOverlappingPair(proxy0,proxy1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void* removeOverlappingPair(btBroadphaseProxy* proxy0,
|
||||
btBroadphaseProxy* proxy1,
|
||||
btDispatcher* dispatcher)
|
||||
{
|
||||
if (proxy0->m_clientObject==g_playerObject ||
|
||||
proxy1->m_clientObject==g_playerObject)
|
||||
return g_pairCache->removeOverlappingPair(proxy0,proxy1,dispatcher);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void removeOverlappingPairsContainingProxy(btBroadphaseProxy* proxy0,
|
||||
btDispatcher* dispatcher)
|
||||
{ if (proxy0->m_clientObject==g_playerObject)
|
||||
g_pairCache->removeOverlappingPairsContainingProxy(proxy0,dispatcher);
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" int32_t bullet_init()
|
||||
{
|
||||
// ------- SET UP THE WORLD -------
|
||||
|
||||
// Set up basic objects
|
||||
g_collisionConfiguration = new btDefaultCollisionConfiguration();
|
||||
g_dispatcher = new btCollisionDispatcher(g_collisionConfiguration);
|
||||
//g_broadphase = new btDbvtBroadphase();
|
||||
g_solver = new btSequentialImpulseConstraintSolver;
|
||||
|
||||
// TODO: Figure out what to do with this. We need the user callback
|
||||
// function used below (I think), but this is only offered by this
|
||||
// broadphase implementation (as far as I can see.) Maybe we can
|
||||
// scan through the cell first and find good values that covers all
|
||||
// the objects before we set up the dynamic world. Another option is
|
||||
// to create a custom broadphase designed for our purpose. (We
|
||||
// should probably use different ones for interior and exterior
|
||||
// cells in any case.)
|
||||
btVector3 worldMin(-20000,-20000,-20000);
|
||||
btVector3 worldMax(20000,20000,20000);
|
||||
g_broadphase = new btAxisSweep3(worldMin,worldMax);
|
||||
|
||||
g_dynamicsWorld =
|
||||
new btDiscreteDynamicsWorld(g_dispatcher,
|
||||
g_broadphase,
|
||||
g_solver,
|
||||
g_collisionConfiguration);
|
||||
|
||||
//g_dynamicsWorld->setGravity(btVector3(0,-10,0));
|
||||
|
||||
|
||||
// ------- SET UP THE PLAYER -------
|
||||
|
||||
// Create the player collision shape.
|
||||
float width = 30;
|
||||
|
||||
/*
|
||||
float height = 50;
|
||||
btVector3 spherePositions[2];
|
||||
btScalar sphereRadii[2];
|
||||
sphereRadii[0] = width;
|
||||
sphereRadii[1] = width;
|
||||
spherePositions[0] = btVector3 (0,0,0);
|
||||
spherePositions[1] = btVector3 (0,0,-height);
|
||||
|
||||
// One possible shape is the convex hull around two spheres
|
||||
g_playerShape = new btMultiSphereShape(btVector3(width/2.0, height/2.0,
|
||||
width/2.0), &spherePositions[0], &sphereRadii[0], 2);
|
||||
*/
|
||||
|
||||
// Other posibilities - most are too slow, except the sphere
|
||||
//g_playerShape = new btCylinderShapeZ(btVector3(width, width, height));
|
||||
g_playerShape = new btSphereShape(width);
|
||||
//g_playerShape = new btCapsuleShapeZ(width, height);
|
||||
|
||||
// Create the collision object
|
||||
g_playerObject = new btCollisionObject ();
|
||||
g_playerObject->setCollisionShape (g_playerShape);
|
||||
g_playerObject->setCollisionFlags (btCollisionObject::CF_NO_CONTACT_RESPONSE);
|
||||
|
||||
|
||||
// ------- OTHER STUFF -------
|
||||
|
||||
// Create a custom callback to pick out all the objects colliding
|
||||
// with the player. We use this in the collision recovery phase.
|
||||
g_pairCache = new btHashedOverlappingPairCache();
|
||||
g_customPairCallback = new CustomOverlappingPairCallback();
|
||||
g_broadphase->setOverlappingPairUserCallback(g_customPairCallback);
|
||||
|
||||
// Set up the callback that moves the player at the end of each
|
||||
// simulation step.
|
||||
g_dynamicsWorld->setInternalTickCallback(playerStepCallback);
|
||||
|
||||
// Add the character collision object to the world.
|
||||
g_dynamicsWorld->addCollisionObject(g_playerObject,
|
||||
MASK_PLAYER,
|
||||
MASK_STATIC);
|
||||
|
||||
// Make sure these is zero at startup
|
||||
g_currentMesh = NULL;
|
||||
|
||||
// Start out walking
|
||||
g_physMode = PHYS_WALK;
|
||||
|
||||
// Success!
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set physics modes
|
||||
extern "C" void bullet_walk()
|
||||
{
|
||||
g_physMode = PHYS_WALK;
|
||||
cout << "Walk mode\n";
|
||||
}
|
||||
|
||||
extern "C" void bullet_fly()
|
||||
{
|
||||
g_physMode = PHYS_FLY;
|
||||
cout << "Fly mode\n";
|
||||
}
|
||||
|
||||
extern "C" void bullet_ghost()
|
||||
{
|
||||
g_physMode = PHYS_GHOST;
|
||||
cout << "Ghost mode\n";
|
||||
}
|
||||
|
||||
// Switch to the next physics mode
|
||||
extern "C" void bullet_nextMode()
|
||||
{
|
||||
switch(g_physMode)
|
||||
{
|
||||
case PHYS_WALK:
|
||||
bullet_fly();
|
||||
break;
|
||||
case PHYS_FLY:
|
||||
bullet_ghost();
|
||||
break;
|
||||
case PHYS_GHOST:
|
||||
bullet_walk();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Warp the player to a specific location. We do not bother setting
|
||||
// rotation, since it's completely irrelevant for collision detection,
|
||||
// and doubly so since the collision mesh is a sphere.
|
||||
extern "C" void bullet_movePlayer(float x, float y, float z)
|
||||
{
|
||||
btTransform tr;
|
||||
tr.setIdentity();
|
||||
tr.setOrigin(btVector3(x,y,z));
|
||||
g_playerObject->setWorldTransform(tr);
|
||||
}
|
||||
|
||||
// Request that the player moves in this direction
|
||||
extern "C" void bullet_setPlayerDir(float x, float y, float z)
|
||||
{ g_walkDirection.setValue(x,y,z); }
|
||||
|
||||
// Get the current player position, after physics and collision have
|
||||
// been applied.
|
||||
extern "C" void bullet_getPlayerPos(float *x, float *y, float *z)
|
||||
{
|
||||
*x = g_playerPosition.getX();
|
||||
*y = g_playerPosition.getY();
|
||||
*z = g_playerPosition.getZ();
|
||||
}
|
||||
|
||||
void* copyBuffer(const void *buf, int elemSize, int len)
|
||||
{
|
||||
int size = elemSize * len;
|
||||
void *res = malloc(size);
|
||||
memcpy(res, buf, size);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Internal version that does not copy buffers
|
||||
void createTriShape(int32_t numFaces, const void *triArray,
|
||||
int32_t numVerts, const void *vertArray,
|
||||
const float *trans, const float *matrix)
|
||||
{
|
||||
// This struct holds the index and vertex buffers of a single
|
||||
// trimesh.
|
||||
btIndexedMesh im;
|
||||
|
||||
// Set up the triangles
|
||||
int numTriangles = numFaces / 3;
|
||||
im.m_numTriangles = numTriangles;
|
||||
im.m_triangleIndexStride = 6; // 3 indices * 2 bytes per short
|
||||
im.m_triangleIndexBase = (unsigned char*)triArray;
|
||||
|
||||
// Set up the vertices
|
||||
im.m_numVertices = numVerts;
|
||||
im.m_vertexStride = 12; // 4 bytes per float * 3 floats per vertex
|
||||
im.m_vertexBase = (unsigned char*)vertArray;
|
||||
|
||||
// Transform vertex values in vb according to 'trans' and 'matrix'
|
||||
float *vb = (float*)im.m_vertexBase;
|
||||
for(int i=0; i<numVerts; i++)
|
||||
{
|
||||
float x,y,z;
|
||||
|
||||
// Reinventing basic linear algebra for the win!
|
||||
x = matrix[0]*vb[0]+matrix[1]*vb[1]+matrix[2]*vb[2] + trans[0];
|
||||
y = matrix[3]*vb[0]+matrix[4]*vb[1]+matrix[5]*vb[2] + trans[1];
|
||||
z = matrix[6]*vb[0]+matrix[7]*vb[1]+matrix[8]*vb[2] + trans[2];
|
||||
*(vb++) = x;
|
||||
*(vb++) = y;
|
||||
*(vb++) = z;
|
||||
}
|
||||
|
||||
// If no mesh is currently active, create one
|
||||
if(g_currentMesh == NULL)
|
||||
g_currentMesh = new btTriangleIndexVertexArray;
|
||||
|
||||
// Add the mesh. Nif data stores triangle indices as shorts.
|
||||
g_currentMesh->addIndexedMesh(im, PHY_SHORT);
|
||||
}
|
||||
|
||||
// Define a cube with coordinates 0,0,0 - 1,1,1.
|
||||
const float cube_verts[] =
|
||||
{
|
||||
0,0,0, 1,0,0, 0,1,0,
|
||||
1,1,0, 0,0,1, 1,0,1,
|
||||
0,1,1, 1,1,1
|
||||
};
|
||||
|
||||
// Triangles of the cube. The orientation of each triange doesn't
|
||||
// matter.
|
||||
const short cube_tris[] =
|
||||
{
|
||||
// bottom side
|
||||
0, 1, 2,
|
||||
1, 2, 3,
|
||||
// top side
|
||||
4, 5, 6,
|
||||
5, 6, 7,
|
||||
// front side
|
||||
0, 4, 5,
|
||||
0, 1, 5,
|
||||
// back side
|
||||
2, 3, 7,
|
||||
2, 6, 7,
|
||||
// left side
|
||||
0, 2, 4,
|
||||
2, 4, 6,
|
||||
// right side
|
||||
1, 3, 5,
|
||||
3, 5, 7
|
||||
};
|
||||
|
||||
const int cube_num_verts = 8;
|
||||
const int cube_num_tris = 12;
|
||||
|
||||
// Create a (trimesh) box with the given dimensions. Used for bounding
|
||||
// boxes. TODO: I guess we should use the NIF-specified bounding box
|
||||
// for this, not our automatically calculated one.
|
||||
extern "C" void bullet_createBoxShape(float xmin, float ymin, float zmin,
|
||||
float xmax, float ymax, float zmax,
|
||||
float *trans, float *matrix)
|
||||
{
|
||||
// Make a copy of the vertex buffer, since we need to change it
|
||||
float *vbuffer = (float*)copyBuffer(cube_verts, 12, cube_num_verts);
|
||||
|
||||
// Calculate the widths
|
||||
float xwidth = xmax-xmin;
|
||||
float ywidth = ymax-ymin;
|
||||
float zwidth = zmax-zmin;
|
||||
|
||||
// Transform the cube to (xmin,xmax) etc
|
||||
float *vb = vbuffer;
|
||||
for(int i=0; i<cube_num_verts; i++)
|
||||
{
|
||||
*vb = (*vb)*xwidth + xmin; vb++;
|
||||
*vb = (*vb)*ywidth + ymin; vb++;
|
||||
*vb = (*vb)*zwidth + zmin; vb++;
|
||||
}
|
||||
|
||||
// Insert the trimesh
|
||||
createTriShape(cube_num_tris*3, cube_tris,
|
||||
cube_num_verts, vbuffer,
|
||||
trans, matrix);
|
||||
}
|
||||
|
||||
// Create a triangle shape and insert it into the current index/vertex
|
||||
// array. If no array is active, create one.
|
||||
extern "C" void bullet_createTriShape(int32_t numFaces,
|
||||
void *triArray,
|
||||
int32_t numVerts,
|
||||
void *vertArray,
|
||||
float *trans,
|
||||
float *matrix)
|
||||
{
|
||||
createTriShape(numFaces, copyBuffer(triArray, 2, numFaces),
|
||||
numVerts, copyBuffer(vertArray, 12, numVerts),
|
||||
trans, matrix);
|
||||
}
|
||||
|
||||
// Get the shape built up so far, if any. This clears g_currentMesh,
|
||||
// so the next call to createTriShape will start a new shape.
|
||||
extern "C" btCollisionShape *bullet_getFinalShape()
|
||||
{
|
||||
btCollisionShape *shape;
|
||||
|
||||
// Create a shape from all the inserted completed meshes
|
||||
shape = NULL;
|
||||
if(g_currentMesh != NULL)
|
||||
shape = new btBvhTriangleMeshShape(g_currentMesh, false);
|
||||
|
||||
// Clear these for the next NIF
|
||||
g_currentMesh = NULL;
|
||||
return shape;
|
||||
}
|
||||
|
||||
// Insert a static mesh
|
||||
extern "C" void bullet_insertStatic(btConcaveShape *shape,
|
||||
float *pos,
|
||||
float *quat,
|
||||
float scale)
|
||||
{
|
||||
// FIXME: Scaling does NOT work.
|
||||
|
||||
// Are we scaled?
|
||||
if(scale != 1.0)
|
||||
{
|
||||
//cout << "Scaling shape " << shape << " by " << scale << endl;
|
||||
|
||||
// Not quite sure how to handle local scaling yet. Our initial
|
||||
// attempt was to create a wrapper that showed a scale mesh to
|
||||
// the "outside world" while referencing the original, but I
|
||||
// suspect it ended up altering the original data. At least it
|
||||
// doesn't work the way it is now, and only crashes.
|
||||
|
||||
// The alternative is to create a new copy of the shape for each
|
||||
// scaled version we insert. This is wasteful, but might be
|
||||
// acceptable.
|
||||
|
||||
// It's also possible we can achieve this effect by changing
|
||||
// larger parts of the Bullet library - but I hope I don't have
|
||||
// to create my own dispatcher and such. Finally, even if the
|
||||
// transformations given to objects are supposed to be uniform
|
||||
// in length, maybe we can cheat the system and scale the
|
||||
// transformation instead. Try it just for kicks, and go through
|
||||
// the system to see what parts of Bullet it would break.
|
||||
|
||||
// In any case, when we find a solution we should apply it to
|
||||
// all shapes (not just scale!=1.0) to get a better impression
|
||||
// of any performance and memory overhead.
|
||||
|
||||
// Also, as an optimization, it looks like multiple instances of
|
||||
// the same shape are often inserted with the same scale
|
||||
// factor. We could easily cache this. The scale-recreation of
|
||||
// meshes (in necessary) could be done as a separate function,
|
||||
// and the caching could be done in D code.
|
||||
}
|
||||
|
||||
btTransform trafo;
|
||||
trafo.setIdentity();
|
||||
trafo.setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
||||
|
||||
// Ogre uses WXYZ quaternions, Bullet uses XYZW.
|
||||
trafo.setRotation(btQuaternion(quat[1], quat[2], quat[3], quat[0]));
|
||||
|
||||
// Create and insert the collision object
|
||||
btCollisionObject *obj = new btCollisionObject();
|
||||
obj->setCollisionShape(shape);
|
||||
obj->setWorldTransform(trafo);
|
||||
g_dynamicsWorld->addCollisionObject(obj, MASK_STATIC, MASK_PLAYER);
|
||||
}
|
||||
|
||||
// Move the physics simulation 'delta' seconds forward in time
|
||||
extern "C" void bullet_timeStep(float delta)
|
||||
{
|
||||
TRACE("bullet_timeStep");
|
||||
// TODO: We might experiment with the number of time steps. Remember
|
||||
// that the function also returns the number of steps performed.
|
||||
g_dynamicsWorld->stepSimulation(delta,2);
|
||||
}
|
||||
|
||||
// Cleanup in the reverse order of creation/initialization
|
||||
extern "C" void bullet_cleanup()
|
||||
{
|
||||
// Remove the rigidbodies from the dynamics world and delete them
|
||||
for (int i=g_dynamicsWorld->getNumCollisionObjects()-1; i>=0 ;i--)
|
||||
{
|
||||
btCollisionObject* obj = g_dynamicsWorld->getCollisionObjectArray()[i];
|
||||
btRigidBody* body = btRigidBody::upcast(obj);
|
||||
|
||||
if (body && body->getMotionState())
|
||||
delete body->getMotionState();
|
||||
|
||||
g_dynamicsWorld->removeCollisionObject( obj );
|
||||
delete obj;
|
||||
}
|
||||
|
||||
delete g_dynamicsWorld;
|
||||
delete g_solver;
|
||||
delete g_broadphase;
|
||||
delete g_dispatcher;
|
||||
delete g_collisionConfiguration;
|
||||
}
|
@ -1,381 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
(see additional copyrights for this file below)
|
||||
|
||||
This file (cpp_player.cpp) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
----
|
||||
|
||||
Parts of this file is based on the kinematic character controller
|
||||
demo included with the Bullet library. The copyright statement for
|
||||
these parts follow:
|
||||
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software. Permission is
|
||||
granted to anyone to use this software for any purpose, including
|
||||
commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must
|
||||
not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
|
||||
// This file handles player-specific physics and collision detection
|
||||
|
||||
// TODO: Later we might handle various physics modes, eg. dynamic
|
||||
// (full physics), player_walk, player_fall, player_swim,
|
||||
// player_float, player_levitate, player_ghost. These would be
|
||||
// applicable to any object (through Monster script), allowing the
|
||||
// physics code to be shared between NPCs, creatures and the player.
|
||||
|
||||
// Variables used internally in this file. Once we make per-object
|
||||
// player collision, these will be member variables.
|
||||
bool g_touchingContact;
|
||||
btVector3 g_touchingNormal;
|
||||
btScalar g_currentStepOffset;
|
||||
float g_stepHeight = 5;
|
||||
|
||||
// Returns the reflection direction of a ray going 'direction' hitting
|
||||
// a surface with normal 'normal'
|
||||
btVector3 reflect (const btVector3& direction, const btVector3& normal)
|
||||
{ return direction - (btScalar(2.0) * direction.dot(normal)) * normal; }
|
||||
|
||||
// Returns the portion of 'direction' that is perpendicular to
|
||||
// 'normal'
|
||||
btVector3 perpComponent (const btVector3& direction, const btVector3& normal)
|
||||
{ return direction - normal * direction.dot(normal); }
|
||||
|
||||
btManifoldArray manifoldArray;
|
||||
|
||||
// Callback used for collision detection sweep tests. It prevents self
|
||||
// collision and is used in calls to convexSweepTest(). TODO: It might
|
||||
// be enough to just set the filters on this. If we set the group and
|
||||
// mask so that we only collide with static objects, self collision
|
||||
// would never happen. The sweep test function should have had a
|
||||
// version where you only specify the filters - I might add that
|
||||
// myself.
|
||||
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||
{
|
||||
public:
|
||||
ClosestNotMeConvexResultCallback()
|
||||
: btCollisionWorld::ClosestConvexResultCallback
|
||||
(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
|
||||
{
|
||||
m_collisionFilterGroup = g_playerObject->
|
||||
getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
|
||||
m_collisionFilterMask = g_playerObject->
|
||||
getBroadphaseHandle()->m_collisionFilterMask;
|
||||
}
|
||||
|
||||
btScalar addSingleResult(btCollisionWorld::LocalConvexResult&
|
||||
convexResult, bool normalInWorldSpace)
|
||||
{
|
||||
if (convexResult.m_hitCollisionObject == g_playerObject) return 1.0;
|
||||
|
||||
return ClosestConvexResultCallback::addSingleResult
|
||||
(convexResult, normalInWorldSpace);
|
||||
}
|
||||
};
|
||||
|
||||
// Used to step up small steps and slopes.
|
||||
void stepUp()
|
||||
{
|
||||
// phase 1: up
|
||||
btVector3 targetPosition = g_playerPosition +
|
||||
btVector3(0.0, 0.0, g_stepHeight);
|
||||
btTransform start, end;
|
||||
|
||||
start.setIdentity ();
|
||||
end.setIdentity ();
|
||||
|
||||
// FIXME: Handle penetration properly
|
||||
start.setOrigin (g_playerPosition + btVector3(0.0, 0.1, 0.0));
|
||||
end.setOrigin (targetPosition);
|
||||
|
||||
ClosestNotMeConvexResultCallback callback;
|
||||
g_dynamicsWorld->convexSweepTest (g_playerShape, start, end, callback);
|
||||
|
||||
if (callback.hasHit())
|
||||
{
|
||||
// we moved up only a fraction of the step height
|
||||
g_currentStepOffset = g_stepHeight * callback.m_closestHitFraction;
|
||||
g_playerPosition.setInterpolate3(g_playerPosition, targetPosition,
|
||||
callback.m_closestHitFraction);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_currentStepOffset = g_stepHeight;
|
||||
g_playerPosition = targetPosition;
|
||||
}
|
||||
}
|
||||
|
||||
void updateTargetPositionBasedOnCollision (const btVector3& hitNormal,
|
||||
btVector3 &targetPosition)
|
||||
{
|
||||
btVector3 movementDirection = targetPosition - g_playerPosition;
|
||||
btScalar movementLength = movementDirection.length();
|
||||
|
||||
if (movementLength <= SIMD_EPSILON)
|
||||
return;
|
||||
|
||||
// Is this needed?
|
||||
movementDirection.normalize();
|
||||
|
||||
btVector3 reflectDir = reflect(movementDirection, hitNormal);
|
||||
reflectDir.normalize();
|
||||
|
||||
btVector3 perpendicularDir = perpComponent (reflectDir, hitNormal);
|
||||
|
||||
targetPosition = g_playerPosition;
|
||||
targetPosition += perpendicularDir * movementLength;
|
||||
}
|
||||
|
||||
// This covers all normal forward movement and collision, including
|
||||
// walking sideways when hitting a wall at an angle. It does NOT
|
||||
// handle walking up slopes and steps, or falling/gravity.
|
||||
void stepForward(btVector3& walkMove)
|
||||
{
|
||||
btVector3 originalDir = walkMove.normalized();
|
||||
|
||||
// If no walking direction is given, we still run the function. This
|
||||
// allows moving forces to push the player around even if she is
|
||||
// standing still.
|
||||
if (walkMove.length() < SIMD_EPSILON)
|
||||
originalDir.setValue(0.f,0.f,0.f);
|
||||
|
||||
btTransform start, end;
|
||||
btVector3 targetPosition = g_playerPosition + walkMove;
|
||||
start.setIdentity ();
|
||||
end.setIdentity ();
|
||||
|
||||
btScalar fraction = 1.0;
|
||||
btScalar distance2 = (g_playerPosition-targetPosition).length2();
|
||||
|
||||
if (g_touchingContact)
|
||||
if (originalDir.dot(g_touchingNormal) > btScalar(0.0))
|
||||
updateTargetPositionBasedOnCollision (g_touchingNormal, targetPosition);
|
||||
|
||||
int maxIter = 10;
|
||||
|
||||
while (fraction > btScalar(0.01) && maxIter-- > 0)
|
||||
{
|
||||
start.setOrigin (g_playerPosition);
|
||||
end.setOrigin (targetPosition);
|
||||
|
||||
ClosestNotMeConvexResultCallback callback;
|
||||
g_dynamicsWorld->convexSweepTest (g_playerShape, start, end, callback);
|
||||
|
||||
fraction -= callback.m_closestHitFraction;
|
||||
|
||||
if (callback.hasHit())
|
||||
{
|
||||
// We moved only a fraction
|
||||
btScalar hitDistance = (callback.m_hitPointWorld - g_playerPosition).length();
|
||||
// If the distance is further than the collision margin,
|
||||
// move
|
||||
if (hitDistance > 0.05)
|
||||
g_playerPosition.setInterpolate3(g_playerPosition, targetPosition,
|
||||
callback.m_closestHitFraction);
|
||||
|
||||
updateTargetPositionBasedOnCollision(callback.m_hitNormalWorld,
|
||||
targetPosition);
|
||||
btVector3 currentDir = targetPosition - g_playerPosition;
|
||||
distance2 = currentDir.length2();
|
||||
|
||||
if (distance2 <= SIMD_EPSILON)
|
||||
break;
|
||||
|
||||
currentDir.normalize();
|
||||
|
||||
if (currentDir.dot(originalDir) <= btScalar(0.0))
|
||||
break;
|
||||
}
|
||||
else
|
||||
// we moved the whole way
|
||||
g_playerPosition = targetPosition;
|
||||
}
|
||||
}
|
||||
|
||||
void stepDown (btScalar dt)
|
||||
{
|
||||
btTransform start, end;
|
||||
|
||||
// phase 3: down
|
||||
btVector3 step_drop = btVector3(0,0,g_currentStepOffset);
|
||||
btVector3 gravity_drop = btVector3(0,0,g_stepHeight);
|
||||
|
||||
btVector3 targetPosition = g_playerPosition - step_drop - gravity_drop;
|
||||
|
||||
start.setIdentity ();
|
||||
end.setIdentity ();
|
||||
|
||||
start.setOrigin (g_playerPosition);
|
||||
end.setOrigin (targetPosition);
|
||||
|
||||
ClosestNotMeConvexResultCallback callback;
|
||||
g_dynamicsWorld->convexSweepTest(g_playerShape, start, end, callback);
|
||||
|
||||
if (callback.hasHit())
|
||||
// we dropped a fraction of the height -> hit floor
|
||||
g_playerPosition.setInterpolate3(g_playerPosition, targetPosition,
|
||||
callback.m_closestHitFraction);
|
||||
else
|
||||
// we dropped the full height
|
||||
g_playerPosition = targetPosition;
|
||||
}
|
||||
|
||||
// Check if the player currently collides with anything, and adjust
|
||||
// its position accordingly. Returns true if collisions were found.
|
||||
bool recoverFromPenetration()
|
||||
{
|
||||
bool penetration = false;
|
||||
|
||||
// Update the collision pair cache
|
||||
g_dispatcher->dispatchAllCollisionPairs(g_pairCache,
|
||||
g_dynamicsWorld->getDispatchInfo(),
|
||||
g_dispatcher);
|
||||
|
||||
btScalar maxPen = 0.0;
|
||||
for (int i = 0; i < g_pairCache->getNumOverlappingPairs(); i++)
|
||||
{
|
||||
manifoldArray.resize(0);
|
||||
|
||||
btBroadphasePair* collisionPair = &g_pairCache->getOverlappingPairArray()[i];
|
||||
// Get the contact points
|
||||
if (collisionPair->m_algorithm)
|
||||
collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);
|
||||
|
||||
// And handle them
|
||||
for (int j=0;j<manifoldArray.size();j++)
|
||||
{
|
||||
btPersistentManifold* manifold = manifoldArray[j];
|
||||
btScalar directionSign = manifold->getBody0() ==
|
||||
g_playerObject ? btScalar(-1.0) : btScalar(1.0);
|
||||
|
||||
for (int p=0;p<manifold->getNumContacts();p++)
|
||||
{
|
||||
const btManifoldPoint &pt = manifold->getContactPoint(p);
|
||||
|
||||
if (pt.getDistance() < 0.0)
|
||||
{
|
||||
// Pick out the maximum penetration normal and store
|
||||
// it
|
||||
if (pt.getDistance() < maxPen)
|
||||
{
|
||||
maxPen = pt.getDistance();
|
||||
g_touchingNormal = pt.m_normalWorldOnB * directionSign;//??
|
||||
|
||||
}
|
||||
g_playerPosition += pt.m_normalWorldOnB * directionSign *
|
||||
pt.getDistance() * btScalar(0.2);
|
||||
|
||||
penetration = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btTransform newTrans = g_playerObject->getWorldTransform();
|
||||
newTrans.setOrigin(g_playerPosition);
|
||||
g_playerObject->setWorldTransform(newTrans);
|
||||
|
||||
return penetration;
|
||||
}
|
||||
|
||||
// Callback called at the end of each simulation cycle. This is the
|
||||
// main function is responsible for player movement.
|
||||
void playerStepCallback(btDynamicsWorld* dynamicsWorld, btScalar timeStep)
|
||||
{
|
||||
// The walking direction is set from D code each frame, and the
|
||||
// final player position is read back from D code after the
|
||||
// simulation.
|
||||
btVector3 walkStep = g_walkDirection * timeStep;
|
||||
|
||||
float len = walkStep.length();
|
||||
|
||||
// In walk mode, it shouldn't matter whether or not we look up or
|
||||
// down. Rotate the vector back to the horizontal plane.
|
||||
if(g_physMode == PHYS_WALK)
|
||||
{
|
||||
walkStep.setZ(0);
|
||||
float len2 = walkStep.length();
|
||||
if(len2 > 0)
|
||||
walkStep *= len/len2;
|
||||
}
|
||||
|
||||
// Get the player position
|
||||
g_playerPosition = g_playerObject->getWorldTransform().getOrigin();
|
||||
|
||||
if(g_physMode == PHYS_GHOST)
|
||||
{
|
||||
// Ghost mode - just move, no collision
|
||||
g_playerPosition += walkStep;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Collision detection is active
|
||||
|
||||
// Before moving, recover from current penetrations
|
||||
int numPenetrationLoops = 0;
|
||||
g_touchingContact = false;
|
||||
while (recoverFromPenetration())
|
||||
{
|
||||
numPenetrationLoops++;
|
||||
g_touchingContact = true;
|
||||
|
||||
// Make sure we don't stay here indefinitely
|
||||
if (numPenetrationLoops > 4)
|
||||
break;
|
||||
}
|
||||
|
||||
// recoverFromPenetration updates g_playerPosition and the
|
||||
// collision mesh, so they are still in sync at this point
|
||||
|
||||
// Next, do the walk. The following functions only updates
|
||||
// g_playerPosition, they do not move the collision object.
|
||||
|
||||
if(g_physMode == PHYS_WALK)
|
||||
{
|
||||
stepUp();
|
||||
stepForward(walkStep);
|
||||
stepDown(timeStep);
|
||||
}
|
||||
else if(g_physMode == PHYS_FLY)
|
||||
stepForward(walkStep);
|
||||
else
|
||||
cout << "WARNING: Unknown physics mode " << g_physMode << "!\n";
|
||||
}
|
||||
|
||||
// Move the player collision mesh
|
||||
btTransform xform = g_playerObject->getWorldTransform ();
|
||||
xform.setOrigin (g_playerPosition);
|
||||
g_playerObject->setWorldTransform (xform);
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (cpp_scale.cpp) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// WARNING: This file does NOT work, and it is not used yet.
|
||||
|
||||
class ScaleCallback : public btTriangleCallback
|
||||
{
|
||||
btTriangleCallback *call;
|
||||
float factor;
|
||||
|
||||
public:
|
||||
ScaleCallback(btTriangleCallback *c, float f)
|
||||
{ call = c; factor = f; }
|
||||
|
||||
void processTriangle(btVector3 *tri, int partid, int triindex)
|
||||
{
|
||||
btVector3 vecs[3];
|
||||
vecs[0] = tri[0]*factor;
|
||||
vecs[1] = tri[1]*factor;
|
||||
vecs[2] = tri[2]*factor;
|
||||
|
||||
call->processTriangle(vecs, partid, triindex);
|
||||
}
|
||||
};
|
||||
|
||||
// This class is used to uniformly scale a triangle mesh by a
|
||||
// factor. It wraps around an existing shape and does not copy the
|
||||
// data.
|
||||
class ScaleShape : public btConcaveShape
|
||||
{
|
||||
btConcaveShape* child;
|
||||
float factor, fact3, facthalf;
|
||||
|
||||
public:
|
||||
|
||||
ScaleShape(btConcaveShape* ch, float ft)
|
||||
{
|
||||
child = ch;
|
||||
factor = ft;
|
||||
fact3 = factor*factor*factor;
|
||||
facthalf = factor*0.5;
|
||||
}
|
||||
|
||||
void calculateLocalInertia(btScalar mass,btVector3& inertia) const
|
||||
{
|
||||
btVector3 tmpInertia;
|
||||
child->calculateLocalInertia(mass,tmpInertia);
|
||||
inertia = tmpInertia * fact3;
|
||||
}
|
||||
|
||||
const char* getName()const { return "ScaleShape"; }
|
||||
|
||||
void getAabb(const btTransform& t,btVector3& aabbMin,btVector3& aabbMax) const
|
||||
{
|
||||
child->getAabb(t,aabbMin,aabbMax);
|
||||
btVector3 aabbCenter = (aabbMax+aabbMin)*0.5;
|
||||
btVector3 scaledAabbHalfExtends = (aabbMax-aabbMin)*facthalf;
|
||||
|
||||
aabbMin = aabbCenter - scaledAabbHalfExtends;
|
||||
aabbMax = aabbCenter + scaledAabbHalfExtends;
|
||||
}
|
||||
|
||||
void processAllTriangles(btTriangleCallback *callback,const btVector3& aabbMin,const btVector3& aabbMax) const
|
||||
{
|
||||
ScaleCallback scb(callback, factor);
|
||||
|
||||
child->processAllTriangles(&scb, aabbMin, aabbMax);
|
||||
}
|
||||
|
||||
void setLocalScaling(const btVector3& scaling)
|
||||
{ child->setLocalScaling(scaling); }
|
||||
|
||||
const btVector3& getLocalScaling() const
|
||||
{ return child->getLocalScaling(); }
|
||||
|
||||
int getShapeType() const
|
||||
{ return TRIANGLE_MESH_SHAPE_PROXYTYPE; }
|
||||
};
|
@ -1,437 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (config.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module core.config;
|
||||
|
||||
import std.string;
|
||||
import std.file;
|
||||
import std.path;
|
||||
import std.stdio;
|
||||
|
||||
import monster.monster;
|
||||
import monster.util.string;
|
||||
|
||||
import core.inifile;
|
||||
import core.filefinder;
|
||||
|
||||
import sound.audio;
|
||||
|
||||
import input.keys;
|
||||
import input.ois;
|
||||
|
||||
import ogre.ogre;
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
/*
|
||||
* Structure that handles all user adjustable configuration options,
|
||||
* including things like file paths, plugins, graphics resolution,
|
||||
* game settings, window positions, etc. It is also responsible for
|
||||
* reading and writing configuration files, for importing settings
|
||||
* from Morrowind.ini and for configuring OGRE. It doesn't currently
|
||||
* DO all of this, but it is supposed to in the future.
|
||||
*/
|
||||
|
||||
struct ConfigManager
|
||||
{
|
||||
MonsterObject *mo;
|
||||
|
||||
IniWriter iniWriter;
|
||||
|
||||
// Mouse sensitivity
|
||||
float *mouseSensX;
|
||||
float *mouseSensY;
|
||||
bool *flipMouseY;
|
||||
|
||||
// Ogre configuration
|
||||
bool showOgreConfig; // The configuration setting
|
||||
// The actual result, overridable by a command line switch, and also
|
||||
// set to true if firstRun is true.
|
||||
bool finalOgreConfig;
|
||||
|
||||
// Other settings
|
||||
bool firstRun;
|
||||
|
||||
// Set to true if sound is completely disabled
|
||||
bool noSound = false;
|
||||
|
||||
// Number of current screen shot. Saved upon exit, so that shots
|
||||
// from separate sessions don't overwrite each other.
|
||||
int screenShotNum;
|
||||
|
||||
// Game files to load (max 255)
|
||||
char[][] gameFiles;
|
||||
|
||||
// Directories
|
||||
char[] dataDir;
|
||||
char[] esmDir;
|
||||
char[] bsaDir;
|
||||
char[] sndDir;
|
||||
char[] fontDir;
|
||||
char[] musDir; // Explore music
|
||||
char[] musDir2; // Battle music
|
||||
|
||||
// Configuration file
|
||||
char[] confFile = "openmw.ini";
|
||||
|
||||
// Cell to load at startup
|
||||
char[] defaultCell;
|
||||
|
||||
// These set the volume to a new value and updates all sounds to
|
||||
// take notice.
|
||||
void setMusicVolume(float vol)
|
||||
{
|
||||
stack.pushFloat(vol);
|
||||
mo.call("setMusicVolume");
|
||||
}
|
||||
float getMusicVolume()
|
||||
{ return mo.getFloat("musicVolume"); }
|
||||
|
||||
void setSfxVolume(float vol)
|
||||
{
|
||||
stack.pushFloat(vol);
|
||||
mo.call("setSfxVolume");
|
||||
}
|
||||
float getSfxVolume()
|
||||
{ return mo.getFloat("sfxVolume"); }
|
||||
|
||||
void setMainVolume(float vol)
|
||||
{
|
||||
stack.pushFloat(vol);
|
||||
mo.call("setMainVolume");
|
||||
}
|
||||
float getMainVolume()
|
||||
{ return mo.getFloat("mainVolume"); }
|
||||
|
||||
// Initialize the config manager. Send a 'true' parameter to reset
|
||||
// all keybindings to the default. A lot of this stuff will be moved
|
||||
// to script code at some point. In general, all input mechanics and
|
||||
// distribution of key events should happen in native code, while
|
||||
// all setup and control should be handled in script code.
|
||||
void initialize(bool reset = false)
|
||||
{
|
||||
// Initialize variables from Monster.
|
||||
assert(mo !is null);
|
||||
mouseSensX = mo.getFloatPtr("mouseSensX");
|
||||
mouseSensY = mo.getFloatPtr("mouseSensY");
|
||||
flipMouseY = mo.getBoolPtr("flipMouseY");
|
||||
|
||||
// Initialize the key binding manager
|
||||
keyBindings.initKeys();
|
||||
|
||||
/* Disable this at the moment. It's a good idea to put
|
||||
configuration in a central location, but it's useless as long
|
||||
as Ogre expects to find it's files in the current working
|
||||
directory. The best permanent solution would be to let the
|
||||
locations of ogre.cfg and plugins.cfg be determined by
|
||||
openmw.ini - I will fix that later.
|
||||
|
||||
version(Posix)
|
||||
{
|
||||
if(!exists(confFile))
|
||||
confFile = expandTilde("~/.openmw/openmw.ini");
|
||||
}
|
||||
*/
|
||||
|
||||
readIni(reset);
|
||||
}
|
||||
|
||||
// Read config from morro.ini, if it exists. The reset parameter is
|
||||
// set to true if we should use default key bindings instead of the
|
||||
// ones from the config file.
|
||||
void readIni(bool reset)
|
||||
{
|
||||
// Read configuration file, if it exists.
|
||||
IniReader ini;
|
||||
|
||||
ini.readFile(confFile);
|
||||
|
||||
screenShotNum = ini.getInt("General", "Screenshots", 0);
|
||||
float mainVolume = saneVol(ini.getFloat("Sound", "Main Volume", 0.7));
|
||||
float musicVolume = saneVol(ini.getFloat("Sound", "Music Volume", 0.5));
|
||||
float sfxVolume = saneVol(ini.getFloat("Sound", "SFX Volume", 0.5));
|
||||
bool useMusic = ini.getBool("Sound", "Enable Music", true);
|
||||
|
||||
|
||||
lightConst = ini.getInt("LightAttenuation", "UseConstant", 0);
|
||||
lightConstValue = ini.getFloat("LightAttenuation", "ConstantValue", 0.0);
|
||||
|
||||
lightLinear = ini.getInt("LightAttenuation", "UseLinear", 1);
|
||||
lightLinearMethod = ini.getInt("LightAttenuation", "LinearMethod", 1);
|
||||
lightLinearValue = ini.getFloat("LightAttenuation", "LinearValue", 3.0);
|
||||
lightLinearRadiusMult = ini.getFloat("LightAttenuation", "LinearRadiusMult", 1.0);
|
||||
|
||||
lightQuadratic = ini.getInt("LightAttenuation", "UseQuadratic", 0);
|
||||
lightQuadraticMethod = ini.getInt("LightAttenuation", "QuadraticMethod", 2);
|
||||
lightQuadraticValue = ini.getFloat("LightAttenuation", "QuadraticValue", 16.0);
|
||||
lightQuadraticRadiusMult = ini.getFloat("LightAttenuation", "QuadraticRadiusMult", 1.0);
|
||||
|
||||
lightOutQuadInLin = ini.getInt("LightAttenuation", "OutQuadInLin", 0);
|
||||
|
||||
|
||||
*mouseSensX = ini.getFloat("Controls", "Mouse Sensitivity X", 0.2);
|
||||
*mouseSensY = ini.getFloat("Controls", "Mouse Sensitivity Y", 0.2);
|
||||
*flipMouseY = ini.getBool("Controls", "Flip Mouse Y Axis", false);
|
||||
|
||||
mo.setFloat("mainVolume", mainVolume);
|
||||
mo.setFloat("musicVolume", musicVolume);
|
||||
mo.setFloat("sfxVolume", sfxVolume);
|
||||
mo.setBool("useMusic", useMusic);
|
||||
|
||||
defaultCell = ini.getString("General", "Default Cell", "Assu");
|
||||
|
||||
firstRun = ini.getBool("General", "First Run", true);
|
||||
showOgreConfig = ini.getBool("General", "Show Ogre Config", false);
|
||||
|
||||
// This flag determines whether we will actually show the Ogre
|
||||
// config dialogue. The EITHER of the following are true, the
|
||||
// config box will be shown:
|
||||
// - The program is being run for the first time
|
||||
// - The "Show Ogre Config" option in openmw.ini is set.
|
||||
// - The -oc option is specified on the command line
|
||||
// - The file ogre.cfg is missing
|
||||
|
||||
finalOgreConfig = showOgreConfig || firstRun ||
|
||||
!exists("ogre.cfg");
|
||||
|
||||
// Set default key bindings first.
|
||||
with(keyBindings)
|
||||
{
|
||||
// Bind some default keys
|
||||
bind(Keys.MoveLeft, KC.A, KC.LEFT);
|
||||
bind(Keys.MoveRight, KC.D, KC.RIGHT);
|
||||
bind(Keys.MoveForward, KC.W, KC.UP);
|
||||
bind(Keys.MoveBackward, KC.S, KC.DOWN);
|
||||
bind(Keys.MoveUp, KC.LSHIFT);
|
||||
bind(Keys.MoveDown, KC.LCONTROL);
|
||||
|
||||
bind(Keys.MainVolUp, KC.ADD);
|
||||
bind(Keys.MainVolDown, KC.SUBTRACT);
|
||||
bind(Keys.MusVolDown, KC.N1);
|
||||
bind(Keys.MusVolUp, KC.N2);
|
||||
bind(Keys.SfxVolDown, KC.N3);
|
||||
bind(Keys.SfxVolUp, KC.N4);
|
||||
bind(Keys.Mute, KC.M);
|
||||
|
||||
bind(Keys.Fullscreen, KC.F);
|
||||
|
||||
bind(Keys.ToggleBattleMusic, KC.SPACE);
|
||||
bind(Keys.PhysMode, KC.T);
|
||||
bind(Keys.Nighteye, KC.N);
|
||||
bind(Keys.ToggleGui, KC.Mouse1);
|
||||
bind(Keys.Console, KC.F1, KC.GRAVE);
|
||||
bind(Keys.Debug, KC.G);
|
||||
|
||||
bind(Keys.Pause, KC.PAUSE, KC.P);
|
||||
bind(Keys.ScreenShot, KC.SYSRQ);
|
||||
bind(Keys.Exit, KC.Q, KC.ESCAPE);
|
||||
}
|
||||
|
||||
// Unless the ini file was missing or we were asked to reset all
|
||||
// keybindings to default, replace all present bindings with the
|
||||
// values from the ini.
|
||||
if(!reset && ini.wasRead)
|
||||
{
|
||||
// Read key bindings
|
||||
for(int i; i<Keys.Length; i++)
|
||||
{
|
||||
char[] s = keyToString[i];
|
||||
if(s.length)
|
||||
{
|
||||
char[] iniVal = ini.getString("Bindings", s, "_def");
|
||||
|
||||
// Was the setting present in the ini file?
|
||||
if(iniVal != "_def")
|
||||
// If so, bind it!
|
||||
keyBindings.bindComma(cast(Keys)i, iniVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read data file directory
|
||||
dataDir = ini.getString("General", "Data Directory", "data/");
|
||||
|
||||
// Make sure there's a trailing slash at the end. The forward slash
|
||||
// / works on all platforms, while the backslash \ does not. This
|
||||
// isn't super robust, but we will fix a general path handle
|
||||
// mechanism later (or use an existing one.)
|
||||
if(dataDir.ends("\\")) dataDir[$-1] = '/';
|
||||
if(!dataDir.ends("/")) dataDir ~= '/';
|
||||
|
||||
bsaDir = dataDir;
|
||||
esmDir = dataDir;
|
||||
sndDir = dataDir ~ "Sound/";
|
||||
fontDir = dataDir ~ "Fonts/";
|
||||
musDir = dataDir ~ "Music/Explore/";
|
||||
musDir2 = dataDir ~ "Music/Battle/";
|
||||
|
||||
// A maximum of 255 game files are allowed. Search the whole range
|
||||
// in case some holes developed in the number sequence. This isn't
|
||||
// a great way of specifying files (it's just a copy of the flawed
|
||||
// model that Morrowind uses), but it will do for the time being.
|
||||
FileFinder srch = new FileFinder(esmDir, null, Recurse.No);
|
||||
for(int i = 0;i < 255;i++)
|
||||
{
|
||||
char[] s = ini.getString("Game Files", format("GameFile[%d]",i), null);
|
||||
if(s != null && srch.has(s))
|
||||
gameFiles ~= esmDir ~ s;
|
||||
}
|
||||
delete srch;
|
||||
|
||||
if(gameFiles.length == 0)
|
||||
{
|
||||
// No game files set. Look in the esmDir for Morrowind.esm.
|
||||
// We can add Tribunal.esm, and Bloodmoon.esm as defaults too
|
||||
// later, when we're out of testing mode.
|
||||
char[][] baseFiles = ["Morrowind.esm"];
|
||||
//char[][] baseFiles = ["Morrowind.esm","Tribunal.esm","Bloodmoon.esm"];
|
||||
srch = new FileFinder(esmDir, "esm", Recurse.No);
|
||||
|
||||
foreach(ref s; baseFiles)
|
||||
{
|
||||
if(srch.has(s))
|
||||
{
|
||||
writefln("Adding game file %s", s);
|
||||
gameFiles ~= esmDir ~ s;
|
||||
}
|
||||
}
|
||||
delete srch;
|
||||
}
|
||||
|
||||
// FIXME: Must sort gameFiles so that ESMs come first, then ESPs.
|
||||
// I don't know if this needs to be done by filename, or by the
|
||||
// actual file type..
|
||||
// Further sort the two groups by file date (oldest first).
|
||||
|
||||
/* Don't bother reading every directory seperately
|
||||
bsaDir = ini.getString("General", "BSA Directory", "data/");
|
||||
esmDir = ini.getString("General", "ESM Directory", "data/");
|
||||
sndDir = ini.getString("General", "SFX Directory", "data/Sound/");
|
||||
musDir = ini.getString("General", "Explore Music Directory", "data/Music/Explore/");
|
||||
musDir2 = ini.getString("General", "Battle Music Directory", "data/Music/Battle/");
|
||||
*/
|
||||
}
|
||||
|
||||
// Create the config file
|
||||
void writeConfig()
|
||||
{
|
||||
//writefln("writeConfig(%s)", confFile);
|
||||
with(iniWriter)
|
||||
{
|
||||
openFile(confFile);
|
||||
|
||||
comment("Don't write your own comments in this file, they");
|
||||
comment("will disappear when the file is rewritten.");
|
||||
section("General");
|
||||
writeString("Data Directory", dataDir);
|
||||
/*
|
||||
writeString("ESM Directory", esmDir);
|
||||
writeString("BSA Directory", bsaDir);
|
||||
writeString("SFX Directory", sndDir);
|
||||
writeString("Explore Music Directory", musDir);
|
||||
writeString("Battle Music Directory", musDir2);
|
||||
*/
|
||||
writeInt("Screenshots", screenShotNum);
|
||||
writeString("Default Cell", defaultCell);
|
||||
|
||||
// Save the setting as it appeared in the input. The setting
|
||||
// you specify in the ini is persistent, specifying the -oc
|
||||
// parameter does not change it.
|
||||
writeBool("Show Ogre Config", showOgreConfig);
|
||||
|
||||
// The next run is never the first run.
|
||||
writeBool("First Run", false);
|
||||
|
||||
section("Controls");
|
||||
writeFloat("Mouse Sensitivity X", *mouseSensX);
|
||||
writeFloat("Mouse Sensitivity Y", *mouseSensY);
|
||||
writeBool("Flip Mouse Y Axis", *flipMouseY);
|
||||
|
||||
section("Bindings");
|
||||
comment("Key bindings. The strings must match exactly.");
|
||||
foreach(int i, KeyBind b; keyBindings.bindings)
|
||||
{
|
||||
char[] s = keyToString[i];
|
||||
if(s.length)
|
||||
writeString(s, b.getString());
|
||||
}
|
||||
|
||||
section("Sound");
|
||||
writeFloat("Main Volume", mo.getFloat("mainVolume"));
|
||||
writeFloat("Music Volume", mo.getFloat("musicVolume"));
|
||||
writeFloat("SFX Volume", mo.getFloat("sfxVolume"));
|
||||
writeBool("Enable Music", mo.getBool("useMusic"));
|
||||
|
||||
section("LightAttenuation");
|
||||
comment("For constant attenuation");
|
||||
writeInt("UseConstant", lightConst);
|
||||
writeFloat("ConstantValue", lightConstValue);
|
||||
comment("For linear attenuation");
|
||||
writeInt("UseLinear", lightLinear);
|
||||
writeInt("LinearMethod", lightLinearMethod);
|
||||
writeFloat("LinearValue", lightLinearValue);
|
||||
writeFloat("LinearRadiusMult", lightLinearRadiusMult);
|
||||
comment("For quadratic attenuation");
|
||||
writeInt("UseQuadratic", lightQuadratic);
|
||||
writeInt("QuadraticMethod", lightQuadraticMethod);
|
||||
writeFloat("QuadraticValue", lightQuadraticValue);
|
||||
writeFloat("QuadraticRadiusMult", lightQuadraticRadiusMult);
|
||||
comment("For quadratic in exteriors and linear in interiors");
|
||||
writeInt("OutQuadInLin", lightOutQuadInLin);
|
||||
|
||||
section("Game Files");
|
||||
foreach(int i, ref s; gameFiles)
|
||||
writeString(format("GameFile[%d]",i), s[esmDir.length..$]);
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
// In the future this will import settings from Morrowind.ini, as
|
||||
// far as this is sensible.
|
||||
void importIni()
|
||||
{
|
||||
/*
|
||||
IniReader ini;
|
||||
ini.readFile("../Morrowind.ini");
|
||||
|
||||
// Example of sensible options to convert:
|
||||
|
||||
tryArchiveFirst = ini.getInt("General", "TryArchiveFirst");
|
||||
useAudio = ( ini.getInt("General", "Disable Audio") == 0 );
|
||||
footStepVolume = ini.getFloat("General", "PC Footstep Volume");
|
||||
subtitles = ini.getInt("General", "Subtitles") == 1;
|
||||
|
||||
The plugin list (all esm and esp files) would be handled a bit
|
||||
differently. In our system they might be a per-user (per
|
||||
"character") setting, or even per-savegame. It should be safe and
|
||||
intuitive to try out a new mod without risking your savegame data
|
||||
or original settings. So these would be handled in a separate
|
||||
plugin manager.
|
||||
|
||||
In any case, the import should be interactive and user-driven, so
|
||||
there is no use in making it before we have a gui of some sort up
|
||||
and running.
|
||||
*/
|
||||
}
|
||||
}
|
@ -1,229 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (filefinder.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module core.filefinder;
|
||||
|
||||
import std.file;
|
||||
import std.string;
|
||||
|
||||
import monster.util.string;
|
||||
import monster.util.aa;
|
||||
|
||||
import core.memory;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
class FileFinderException : Exception
|
||||
{
|
||||
this(char[] msg, char[] ext, char[] dir)
|
||||
{
|
||||
if(ext.length) super(format("FileFinder for %s files in %s: %s", ext, dir, msg));
|
||||
else super(format("FileFinder for %s: %s", dir, msg));
|
||||
}
|
||||
}
|
||||
|
||||
// Do we traverse directories recursively? Default is yes.
|
||||
enum Recurse { Yes, No }
|
||||
|
||||
// The file finder is used to list all files in a directory so we can
|
||||
// look up files without searching the filesystem each time. It is
|
||||
// case insensitive on all platforms, and transparently converts to
|
||||
// the right directory separator character (\ or /). We might extend
|
||||
// it later with code from other projects.
|
||||
class FileFinder
|
||||
{
|
||||
private:
|
||||
char[][] files; // Use GC for this, it's not too big and we don't
|
||||
// have to manage roots pointing to the filenames.
|
||||
HashTable!(char[], int, ESMRegionAlloc, FilenameHasher) lookup;
|
||||
|
||||
char[] dir; // Base directory to search
|
||||
char[] ext; // Extensions to pick out
|
||||
|
||||
void fail(char[] err)
|
||||
{
|
||||
throw new FileFinderException(err, ext, dir);
|
||||
}
|
||||
|
||||
// Removes the part of a path that is stored in 'dir'
|
||||
char[] removeDir(char[] path)
|
||||
{
|
||||
//TODO: Should this be case insensitive?
|
||||
assert(path[0..dir.length] == dir);
|
||||
|
||||
return path[dir.length..$];
|
||||
}
|
||||
|
||||
void insert(char[] filename)
|
||||
{
|
||||
// Only keep the part of the filename not given in 'dir'.
|
||||
char[] name = removeDir(filename);
|
||||
|
||||
if(!name.iEnds(ext)) return;
|
||||
|
||||
// We start counting from 1
|
||||
uint newVal = files.length+1;
|
||||
|
||||
// Insert it, or get the old value if it already exists
|
||||
uint oldVal = lookup[name, newVal];
|
||||
if(oldVal != newVal)
|
||||
fail("Already have " ~ name ~ "\nPreviously inserted as " ~ files[oldVal-1]);
|
||||
|
||||
// Store it
|
||||
files ~= filename;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
static char[] addSlash(char[] dir)
|
||||
{
|
||||
// Add a trailing slash
|
||||
version(Windows) if(!dir.ends("\\")) dir ~= '\\';
|
||||
version(Posix) if(!dir.ends("/")) dir ~= '/';
|
||||
return dir;
|
||||
}
|
||||
|
||||
int length() { return lookup.length; }
|
||||
|
||||
this(char[] dir, char[] ext = null, Recurse r = Recurse.Yes)
|
||||
in
|
||||
{
|
||||
if(!dir.length) fail("'dir' can not be empty");
|
||||
}
|
||||
out
|
||||
{
|
||||
assert(files.length == lookup.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
// Add a trailing slash
|
||||
dir = addSlash(dir);
|
||||
|
||||
this.dir = dir;
|
||||
|
||||
if(ext.length && ext[0] != '.') ext = "." ~ ext;
|
||||
this.ext = ext;
|
||||
|
||||
bool callback(DirEntry* de)
|
||||
{
|
||||
if (de.isdir)
|
||||
{
|
||||
if(r == Recurse.Yes)
|
||||
listdir(de.name, &callback);
|
||||
}
|
||||
else
|
||||
insert(de.name);
|
||||
return true;
|
||||
}
|
||||
|
||||
try listdir(dir, &callback);
|
||||
catch(FileException e)
|
||||
fail(e.toString);
|
||||
}
|
||||
|
||||
char[] opIndex(int i) { return files[i-1]; }
|
||||
|
||||
int opIndex(char[] file)
|
||||
{
|
||||
int i;
|
||||
|
||||
// Get value if it exists
|
||||
if(lookup.inList(file, i))
|
||||
return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has(char[] file)
|
||||
{
|
||||
return lookup.inList(file);
|
||||
}
|
||||
|
||||
int opApply(int delegate(ref char[]) del)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
foreach(char[] s; files)
|
||||
{
|
||||
char[] tmp = removeDir(s);
|
||||
res = del(tmp);
|
||||
if(res) break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
char[] result;
|
||||
foreach(char[] s; this)
|
||||
result ~= s ~ "\n";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Hash functions that does not differentiate between linux and
|
||||
// windows file names. This means that it is case insensitive, and
|
||||
// treats '\' and '/' as the same character. Only needed in linux, in
|
||||
// windows just use CITextHasher.
|
||||
version(Posix)
|
||||
struct FilenameHasher
|
||||
{
|
||||
static const char conv = 'a'-'A';
|
||||
|
||||
static int isEqual(char[] aa, char[] bb)
|
||||
{
|
||||
if(aa.length != bb.length) return 0;
|
||||
|
||||
foreach(int i, char a; aa)
|
||||
{
|
||||
char b = bb[i];
|
||||
|
||||
if(a == b)
|
||||
continue;
|
||||
|
||||
// Convert both to lowercase and "/ case"
|
||||
if(a <= 'Z' && a >= 'A') a += conv;
|
||||
else if(a == '\\') a = '/';
|
||||
if(b <= 'Z' && b >= 'A') b += conv;
|
||||
else if(b == '\\') b = '/';
|
||||
|
||||
if(a != b) return 0;
|
||||
}
|
||||
|
||||
// No differences were found
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint hash(char[] s)
|
||||
{
|
||||
uint hash;
|
||||
foreach (char c; s)
|
||||
{
|
||||
if(c <= 'Z' && c >= 'A') c += conv;
|
||||
else if(c == '\\') c = '/';
|
||||
hash = (hash * 37) + c;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
version(Windows) alias CITextHash FilenameHasher;
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (defs.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module esm.defs;
|
||||
|
||||
public import std.string;
|
||||
public import monster.util.string;
|
||||
import monster.monster;
|
||||
|
||||
/*
|
||||
* Types and definitions related to parsing esm and esp files
|
||||
*/
|
||||
|
||||
alias char[4] NAME;
|
||||
alias char[32] NAME32;
|
||||
alias char[256] NAME256;
|
||||
|
||||
union Color
|
||||
{
|
||||
align(1) struct
|
||||
{
|
||||
ubyte red, green, blue, alpha;
|
||||
}
|
||||
|
||||
ubyte[4] array;
|
||||
uint value;
|
||||
|
||||
char[] toString() { return format("RGBA:%s", array); }
|
||||
}
|
||||
static assert(Color.sizeof==4);
|
||||
|
||||
// State of a record struct
|
||||
enum LoadState
|
||||
{
|
||||
Unloaded, // This record is not loaded, it has just been
|
||||
// referenced.
|
||||
Loaded, // This record has been loaded by the current file
|
||||
Previous // The record has been loaded by a previous file
|
||||
|
||||
// Finalized - might be the case for some record types, but I
|
||||
// don't know if this actual state value would be used for
|
||||
// anything.
|
||||
}
|
||||
|
||||
enum VarType { Unknown, None, Short, Int, Long, Float, String, Ignored }
|
||||
|
||||
enum SpellSchool : int
|
||||
{
|
||||
Alteration = 0,
|
||||
Conjuration = 1,
|
||||
Destruction = 2,
|
||||
Illusion = 3,
|
||||
Mysticism = 4,
|
||||
Restoration = 5,
|
||||
Length
|
||||
}
|
||||
|
||||
enum Attribute : int
|
||||
{
|
||||
Strength = 0,
|
||||
Intelligence = 1,
|
||||
Willpower = 2,
|
||||
Agility = 3,
|
||||
Speed = 4,
|
||||
Endurance = 5,
|
||||
Personality = 6,
|
||||
Luck = 7,
|
||||
Length
|
||||
}
|
||||
|
||||
enum SkillEnum : int
|
||||
{
|
||||
Block = 0,
|
||||
Armorer = 1,
|
||||
MediumArmor = 2,
|
||||
HeavyArmor = 3,
|
||||
BluntWeapon = 4,
|
||||
LongBlade = 5,
|
||||
Axe = 6,
|
||||
Spear = 7,
|
||||
Athletics = 8,
|
||||
Enchant = 9,
|
||||
Destruction = 10,
|
||||
Alteration = 11,
|
||||
Illusion = 12,
|
||||
Conjuration = 13,
|
||||
Mysticism = 14,
|
||||
Restoration = 15,
|
||||
Alchemy = 16,
|
||||
Unarmored = 17,
|
||||
Security = 18,
|
||||
Sneak = 19,
|
||||
Acrobatics = 20,
|
||||
LightArmor = 21,
|
||||
ShortBlade = 22,
|
||||
Marksman = 23,
|
||||
Mercantile = 24,
|
||||
Speechcraft = 25,
|
||||
HandToHand = 26,
|
||||
Length
|
||||
}
|
||||
|
||||
// Shared between SPEL (Spells), ALCH (Potions) and ENCH (Item
|
||||
// enchantments) records
|
||||
align(1) struct ENAMstruct
|
||||
{
|
||||
// Magical effect
|
||||
short effectID; // ID of magic effect
|
||||
|
||||
// Which skills/attributes are affected (for restore/drain spells etc.)
|
||||
byte skill, attribute; // -1 if N/A
|
||||
|
||||
// Other spell parameters
|
||||
int range; // 0 - self, 1 - touch, 2 - target
|
||||
int area, duration, magnMin, magnMax;
|
||||
|
||||
static assert(ENAMstruct.sizeof==24);
|
||||
}
|
||||
|
||||
// Common stuff for all the load* structs
|
||||
template LoadTT(T)
|
||||
{
|
||||
LoadState state;
|
||||
char[] name, id;
|
||||
|
||||
MonsterObject *proto;
|
||||
static MonsterClass mc;
|
||||
|
||||
void makeProto(char[] clsName = null)
|
||||
{
|
||||
// Set up a prototype object
|
||||
if(mc is null)
|
||||
{
|
||||
// Use the template type name as the Monster class name if
|
||||
// none is specified.
|
||||
if(clsName == "")
|
||||
{
|
||||
clsName = typeid(T).toString;
|
||||
|
||||
// Remove the module name
|
||||
int i = clsName.rfind('.');
|
||||
if(i != -1)
|
||||
clsName = clsName[i+1..$];
|
||||
}
|
||||
|
||||
// All the game objects are in the 'game' package
|
||||
clsName = "game." ~ clsName;
|
||||
mc = vm.load(clsName);
|
||||
}
|
||||
|
||||
proto = mc.createObject();
|
||||
|
||||
proto.setString8("id", id);
|
||||
proto.setString8("name", name);
|
||||
|
||||
static if(is(typeof(data.weight) == float))
|
||||
{
|
||||
proto.setFloat("weight", data.weight);
|
||||
proto.setInt("value", data.value);
|
||||
}
|
||||
|
||||
static if(is(typeof(data.enchant)==int))
|
||||
proto.setInt("enchant", data.enchant);
|
||||
}
|
||||
}
|
||||
|
||||
template LoadT() { mixin LoadTT!(typeof(*this)); }
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (esmmain.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module esm.esmmain;
|
||||
|
||||
public import esm.records;
|
||||
|
||||
import ogre.ogre;
|
||||
|
||||
/* This file is the main module for loading from ESM, ESP and ESS
|
||||
files. It stores all the data in the appropriate data structures
|
||||
for later referal. TODO: Put this in a class or whatever? Nah, we
|
||||
definately only need one structure like this at any one
|
||||
time. However, we have to deal with unloading and reloading it,
|
||||
even though that should be the exceptional case (change of plugins,
|
||||
etc), not the rule (loading a savegame should not alter the base
|
||||
data set, I think, but it's to early do decide.)*/
|
||||
|
||||
// Load a set of esm and esp files. For now, we just traverse in the
|
||||
// order given. Later, we should sort these into 'masters' and
|
||||
// 'plugins', because esms are always supposed to be loaded
|
||||
// first. TODO: I'm not sure if I should load all these in one
|
||||
// function. Do we need to be able to respond to errors in each file?
|
||||
// Nah, if anything fails, give a general error message, remove the
|
||||
// file from the list and try again. We have to be able to get a list
|
||||
// of which files depend upon which, though... this can be done before
|
||||
// this function is called.
|
||||
void loadTESFiles(char[][] files)
|
||||
{
|
||||
// Set up all the lists to hold our data
|
||||
initializeLists();
|
||||
|
||||
foreach(char[] filename; files)
|
||||
{
|
||||
esFile.open(filename, esmRegion);
|
||||
while(esFile.hasMoreRecs())
|
||||
{
|
||||
uint flags;
|
||||
|
||||
// Read record header
|
||||
char[] recName = esFile.getRecName();
|
||||
esFile.getRecHeader(flags);
|
||||
|
||||
if(flags & RecordFlags.Unknown)
|
||||
esFile.fail(format("UNKNOWN record flags: %xh", flags));
|
||||
|
||||
loadRecord(recName);
|
||||
}
|
||||
|
||||
// We have to loop through the lists and check for broken
|
||||
// references at this point, and if all forward references were
|
||||
// loaded. There might be other end-of-file things to do also.
|
||||
endFiles();
|
||||
}
|
||||
|
||||
esFile.close();
|
||||
|
||||
// Put all inventory items into one list
|
||||
items.addList(appas, ItemType.Apparatus);
|
||||
items.addList(lockpicks, ItemType.Pick);
|
||||
items.addList(probes, ItemType.Probe);
|
||||
items.addList(repairs, ItemType.Repair);
|
||||
items.addList(lights, ItemType.Light);
|
||||
items.addList(ingreds, ItemType.Ingredient);
|
||||
items.addList(potions, ItemType.Potion);
|
||||
items.addList(armors, ItemType.Armor);
|
||||
items.addList(weapons, ItemType.Weapon);
|
||||
items.addList(books, ItemType.Book);
|
||||
items.addList(clothes, ItemType.Clothing);
|
||||
items.addList(miscItems, ItemType.Misc);
|
||||
items.addList(itemLists, ItemType.ItemList); // Leveled item lists
|
||||
|
||||
// Same with all actors
|
||||
actors.addList(creatures, ItemType.Creature);
|
||||
actors.addList(creatureLists, ItemType.CreatureList);
|
||||
actors.addList(npcs, ItemType.NPC);
|
||||
|
||||
// Finally, add everything that might be looked up in a cell into
|
||||
// one list
|
||||
cellRefs.addList(items);
|
||||
cellRefs.addList(actors);
|
||||
cellRefs.addList(doors, ItemType.Door);
|
||||
cellRefs.addList(activators, ItemType.Activator);
|
||||
cellRefs.addList(statics, ItemType.Static);
|
||||
cellRefs.addList(containers, ItemType.Container);
|
||||
|
||||
// Check that all references are resolved
|
||||
items.endMerge();
|
||||
actors.endMerge();
|
||||
cellRefs.endMerge();
|
||||
|
||||
// Put all NPC dialogues into the hyperlink list
|
||||
foreach(char[] id, ref Dialogue dl; dialogues.names)
|
||||
hyperlinks.add(id, &dl);
|
||||
|
||||
// Finally, sort the hyperlink lists
|
||||
hyperlinks.sort();
|
||||
}
|
||||
|
||||
// Contains the small bits of information that we currently extract
|
||||
// from savegames.
|
||||
struct PlayerSaveInfo
|
||||
{
|
||||
char[] cellName;
|
||||
char[] playerName;
|
||||
Placement pos;
|
||||
}
|
||||
|
||||
// Load a TES savegame file (.ess). Currently VERY limited, reads the
|
||||
// player's cell name and position
|
||||
PlayerSaveInfo importSavegame(char[] file)
|
||||
{
|
||||
PlayerSaveInfo pi;
|
||||
|
||||
esFile.open(file, esmRegion);
|
||||
scope(exit) esFile.close();
|
||||
|
||||
if(esFile.getFileType != FileType.Ess)
|
||||
throw new TES3FileException(file ~ " is not a savegame");
|
||||
|
||||
with(esFile.saveData)
|
||||
{
|
||||
pi.cellName = stripz(cell);
|
||||
pi.playerName = stripz(player);
|
||||
}
|
||||
|
||||
with(esFile)
|
||||
{
|
||||
while(hasMoreRecs())
|
||||
{
|
||||
if(isNextHRec("REFR"))
|
||||
{
|
||||
while(hasMoreSubs())
|
||||
{
|
||||
getSubName();
|
||||
if(retSubName() == "DATA")
|
||||
readHExact(&pi.pos, pi.pos.sizeof);
|
||||
else
|
||||
skipHSub();
|
||||
}
|
||||
}
|
||||
else
|
||||
skipHRecord();
|
||||
}
|
||||
}
|
||||
return pi;
|
||||
}
|
@ -1,783 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (filereader.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module esm.filereader;
|
||||
|
||||
private:
|
||||
import std.stdio;
|
||||
import std.stream;
|
||||
import std.string;
|
||||
|
||||
import util.regions;
|
||||
import util.utfconvert;
|
||||
import monster.util.string;
|
||||
import core.resource;
|
||||
|
||||
import esm.listkeeper;
|
||||
import esm.defs;
|
||||
|
||||
public:
|
||||
|
||||
/*
|
||||
* Exception class for TES3File
|
||||
*/
|
||||
|
||||
class TES3FileException: Exception
|
||||
{
|
||||
this(char[] msg) {super("Error reading TES3 file: " ~ msg);}
|
||||
this() {this("Unknown error");}
|
||||
}
|
||||
|
||||
// Some flags are in use that we don't know. But we don't really know
|
||||
// any of them.
|
||||
enum RecordFlags : uint
|
||||
{
|
||||
Flag6 = 0x20, // Eg. adventurers_v2.0.esp (only once per file?)
|
||||
Persistent = 0x400,
|
||||
Flag13 = 0x1000, // Eg. Astarsis_BR.esm (several times per file?)
|
||||
Blocked = 0x2000,
|
||||
|
||||
Unknown = 0xffffffff - 0x3420
|
||||
}
|
||||
|
||||
enum FileType
|
||||
{
|
||||
Unknown,
|
||||
Esp, Plugin = Esp,
|
||||
Esm, Master = Esm,
|
||||
Ess, Savegame = Ess
|
||||
}
|
||||
|
||||
// Special files
|
||||
enum SpecialFile
|
||||
{
|
||||
Other,
|
||||
Morrowind,
|
||||
Tribunal,
|
||||
Bloodmoon
|
||||
}
|
||||
|
||||
enum Version { Unknown, v12, v13 }
|
||||
|
||||
// This struct should contain enough data to put a TES3File object
|
||||
// back into a specific file position and state. We use it to save the
|
||||
// "position" of objects in a file (eg. a cell), so we can return
|
||||
// there later and continue where we stopped (eg. when we want to load
|
||||
// that specific cell.)
|
||||
struct TES3FileContext
|
||||
{
|
||||
char[] filename;
|
||||
uint leftRec, leftSub;
|
||||
ulong leftFile;
|
||||
NAME recName, subName;
|
||||
FileType type;
|
||||
Version ver;
|
||||
|
||||
ulong filepos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance used to read TES3 files. Since we will only be reading one
|
||||
* file at a time, we might as well make one global instance.
|
||||
*/
|
||||
TES3File esFile;
|
||||
|
||||
/**
|
||||
* This struct reads an Elder Scrolls 3 file (esp, esm or ess)
|
||||
*
|
||||
* Makes heavy use of private variables to represent current
|
||||
* state.
|
||||
*
|
||||
* Relevant exceptions are
|
||||
* TES3FileException - error interpreting file
|
||||
* StreamFileException - file IO error
|
||||
*/
|
||||
struct TES3File
|
||||
{
|
||||
private:
|
||||
BufferedFile file;// Input file
|
||||
|
||||
// These are only used by getRecHeader and getSubHeader for
|
||||
// asserting the file's integrity.
|
||||
ulong leftFile; // Number of unread bytes in file
|
||||
uint leftRec; // Number of unread bytes in record
|
||||
|
||||
// This is used by sub-record readers for integrity checking.
|
||||
uint leftSub; // Number of bytes in subrecord
|
||||
|
||||
// Name of current record and current sub-record.
|
||||
NAME recName, subName;
|
||||
|
||||
char[] filename; // Filename
|
||||
FileType type; // File type
|
||||
Version ver; // File format version
|
||||
char[] author; // File author (max 32 bytes (with null?))
|
||||
char[] desc; // Description (max 256 bytes (ditto?))
|
||||
uint records; // Number of records in the file (doesn't seem to be right?)
|
||||
SpecialFile spf; // Is this a file we have to treat in a special way?
|
||||
|
||||
struct _mast
|
||||
{
|
||||
char[] name; // File name of an esm master for this file
|
||||
ulong size; // The master file's size in bytes (used for
|
||||
// version control)
|
||||
}
|
||||
|
||||
// List of esm masters for this file. For savegames this list also
|
||||
// contains all plugins.
|
||||
_mast masters[];
|
||||
|
||||
|
||||
// TES3.HEDR, file header struct
|
||||
align(1) struct HEDRstruct
|
||||
{
|
||||
union
|
||||
{
|
||||
float ver; // File format version, 1.2 and 1.3 supported.
|
||||
uint verHex; // 1.2 = 0x3f99999a, 1.3 = 0x3fa66666
|
||||
}
|
||||
int type; // 0=esp, 1=esm, 32=ess
|
||||
NAME32 author; // Author's name
|
||||
NAME256 desc; // File description blurb
|
||||
uint records; // Number of records in file (?)
|
||||
}
|
||||
|
||||
static assert(HEDRstruct.sizeof == 300);
|
||||
|
||||
// Which memory region to use for allocations.
|
||||
RegionManager region;
|
||||
|
||||
public:
|
||||
|
||||
// A struct found in the headers of savegame files. Contains quick
|
||||
// information to get us going, like the cell name and the player
|
||||
// name.
|
||||
struct _saveData
|
||||
{
|
||||
float[6] unknown;
|
||||
char[64] cell; // Cell name
|
||||
float unk2; // Unknown value
|
||||
char[32] player; // Player name
|
||||
}
|
||||
static assert(_saveData.sizeof == 124);
|
||||
_saveData saveData;
|
||||
|
||||
// Get file information
|
||||
char[] getFilename() { return filename; }
|
||||
ulong getFileSize() { return file.size; }
|
||||
ulong getPosition() { return file.position; }
|
||||
SpecialFile getSpecial() { return spf; }
|
||||
|
||||
char[] retSubName() { return subName; }
|
||||
|
||||
bool isVer12() { return ver == Version.v12;}
|
||||
bool isVer13() { return ver == Version.v13;}
|
||||
FileType getFileType() { return type; }
|
||||
_mast[] getMasters() { return masters; }
|
||||
uint getRecords() { return records; }
|
||||
char[] getAuthor() { return author; }
|
||||
RegionManager getRegion() { return region; }
|
||||
|
||||
// Store the current file state (position, file name, version, debug
|
||||
// info). The info should be enough to get us back on track for
|
||||
// reading from a file, without having to reread the header or any
|
||||
// previous records.
|
||||
void getContext(ref TES3FileContext c)
|
||||
{
|
||||
c.filename = filename;
|
||||
c.leftFile = leftFile;
|
||||
c.leftRec = leftRec;
|
||||
c.leftSub = leftSub;
|
||||
c.recName[] = recName;
|
||||
c.subName[] = subName;
|
||||
c.type = type;
|
||||
c.ver = ver;
|
||||
c.filepos = file.position;
|
||||
}
|
||||
|
||||
// Opens the file if it is not already opened. A region manager has
|
||||
// to be specified.
|
||||
void restoreContext(TES3FileContext c, RegionManager r)
|
||||
{
|
||||
if(filename != c.filename)
|
||||
openFile(c.filename, r);
|
||||
file.seekSet(cast(long)c.filepos);
|
||||
|
||||
// File is now open, copy state information
|
||||
filename = c.filename;
|
||||
leftFile = c.leftFile;
|
||||
leftRec = c.leftRec;
|
||||
leftSub = c.leftSub;
|
||||
recName[] = c.recName;
|
||||
subName[] = c.subName;
|
||||
type = c.type;
|
||||
ver = c.ver;
|
||||
}
|
||||
|
||||
// Open a new file and assign a region
|
||||
private void openFile(char[] filename, RegionManager r)
|
||||
{
|
||||
close();
|
||||
debug writefln("Opening file");
|
||||
if(file is null) file = new BufferedFile(new File());
|
||||
file.open(filename);
|
||||
|
||||
region = r;
|
||||
}
|
||||
|
||||
void open(char[] filename, RegionManager r)
|
||||
{
|
||||
uint flags;
|
||||
|
||||
debug writefln("openFile(%s, %s)", filename, r);
|
||||
openFile(filename, r);
|
||||
|
||||
if(iEnds(filename, "Morrowind.esm")) spf = SpecialFile.Morrowind;
|
||||
else if(iEnds(filename, "Tribunal.esm")) spf = SpecialFile.Tribunal;
|
||||
else if(iEnds(filename, "Bloodmoon.esm")) spf = SpecialFile.Bloodmoon;
|
||||
else spf = SpecialFile.Other;
|
||||
|
||||
debug writefln("Reading header");
|
||||
|
||||
// Do NOT .dup this filename, since it is referenced outside the
|
||||
// GC's reach and might be deleted.
|
||||
this.filename = filename;
|
||||
|
||||
leftFile = file.size;
|
||||
|
||||
// First things first
|
||||
if(getRecName() != "TES3")
|
||||
fail("Not a valid Morrowind file");
|
||||
|
||||
// Record header
|
||||
getRecHeader(flags);
|
||||
if(flags)
|
||||
writefln("WARNING: Header flags are non-zero");
|
||||
|
||||
// Read and analyse the header data
|
||||
HEDRstruct hedr;
|
||||
readHNExact(&hedr, hedr.sizeof, "HEDR");
|
||||
|
||||
// The float hedr.ver signifies the file format version. It can
|
||||
// take on these two values:
|
||||
// 0x3f99999a = 1.2
|
||||
// 0x3fa66666 = 1.3
|
||||
if( hedr.verHex == 0x3f99999a )
|
||||
ver = Version.v12;
|
||||
else if( hedr.verHex == 0x3fa66666 )
|
||||
ver = Version.v13;
|
||||
else
|
||||
{
|
||||
ver = Version.Unknown;
|
||||
writefln("WARNING: Unknown version: ", hedr.ver);
|
||||
writefln(" Hex: %X h", *(cast(uint*)&hedr.ver));
|
||||
}
|
||||
|
||||
switch(hedr.type)
|
||||
{
|
||||
case 0: type = FileType.Esp; break;
|
||||
case 1: type = FileType.Esm; break;
|
||||
case 32: type = FileType.Ess; break;
|
||||
default:
|
||||
type = FileType.Unknown;
|
||||
writefln("WARNING: Unknown file type: ", hedr.type);
|
||||
}
|
||||
|
||||
author = region.copy(stripz(hedr.author));
|
||||
desc = region.copy(stripz(hedr.desc));
|
||||
records = hedr.records;
|
||||
|
||||
masters = null;
|
||||
// Reads a MAST and a DATA fields
|
||||
while(isNextSub("MAST"))
|
||||
{
|
||||
_mast ma;
|
||||
|
||||
// MAST entry - master file name
|
||||
ma.name = getHString();
|
||||
|
||||
// DATA entry - master file size
|
||||
ma.size = getHNUlong("DATA");
|
||||
|
||||
// Add to the master list!
|
||||
masters ~= ma;
|
||||
}
|
||||
|
||||
if(type == FileType.Savegame)
|
||||
{
|
||||
// Savegame-related data
|
||||
|
||||
// Cell name, player name and player position
|
||||
readHNExact(&saveData, 124, "GMDT");
|
||||
|
||||
// Contains eg. 0xff0000, 0xff00, 0xff, 0x0, 0x20. No idea.
|
||||
getSubNameIs("SCRD");
|
||||
skipHSubSize(20);
|
||||
|
||||
// Screenshot. Fits with 128x128x4 bytes
|
||||
getSubNameIs("SCRS");
|
||||
skipHSubSize(65536);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the file. We do not clear any object data at this point.
|
||||
void close()
|
||||
{
|
||||
debug writefln("close()");
|
||||
if(file !is null)
|
||||
file.close();
|
||||
leftFile = leftRec = leftSub = 0;
|
||||
debug writefln("Clearing strings");
|
||||
|
||||
recName[] = '\0';
|
||||
subName[] = '\0';
|
||||
|
||||
// This tells restoreContext() that we have to reopen the file
|
||||
filename = null;
|
||||
|
||||
debug writefln("exit close()");
|
||||
}
|
||||
|
||||
/*
|
||||
* Error reporting
|
||||
*/
|
||||
|
||||
void fail(char[] msg)
|
||||
{
|
||||
throw new TES3FileException
|
||||
(msg ~ "\nFile: " ~ filename ~ "\nRecord name: " ~ recName
|
||||
~ "\nSubrecord name: " ~ subName);
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
*
|
||||
* Highest level readers, reads a name and looks it up in the given
|
||||
* list.
|
||||
*
|
||||
************************************************************************/
|
||||
|
||||
// This should be more than big enough for references.
|
||||
private char lookupBuffer[200];
|
||||
|
||||
// Get a temporary string. This is faster and more memory efficient
|
||||
// that the other string functions (because it is allocation free),
|
||||
// but the returned string is only valid until tmpHString() is
|
||||
// called again.
|
||||
char[] tmpHString()
|
||||
{
|
||||
getSubHeader();
|
||||
assert(leftSub <= lookupBuffer.length, "lookupBuffer wasn't large enough");
|
||||
|
||||
// Use this to test the difference in memory consumption.
|
||||
return getString(lookupBuffer[0..leftSub]);
|
||||
}
|
||||
|
||||
// These are used for file lookups
|
||||
MeshIndex getMesh()
|
||||
{ getSubNameIs("MODL"); return resources.lookupMesh(tmpHString()); }
|
||||
SoundIndex getSound()
|
||||
{ getSubNameIs("FNAM"); return resources.lookupSound(tmpHString()); }
|
||||
IconIndex getIcon(char[] s = "ITEX")
|
||||
{ getSubNameIs(s); return resources.lookupIcon(tmpHString()); }
|
||||
TextureIndex getTexture()
|
||||
{ getSubNameIs("DATA"); return resources.lookupTexture(tmpHString()); }
|
||||
|
||||
// The getO* functions read optional records. If they are not
|
||||
// present, return null.
|
||||
|
||||
MeshIndex getOMesh()
|
||||
{ return isNextSub("MODL") ? resources.lookupMesh(tmpHString()) : MeshIndex.init; }
|
||||
/*
|
||||
SoundIndex getOSound()
|
||||
{ return isNextSub("FNAM") ? resources.lookupSound(tmpHString()) : SoundIndex.init; }
|
||||
*/
|
||||
IconIndex getOIcon()
|
||||
{ return isNextSub("ITEX") ? resources.lookupIcon(tmpHString()) : IconIndex.init; }
|
||||
TextureIndex getOTexture(char[] s="TNAM")
|
||||
{ return isNextSub(s) ? resources.lookupTexture(tmpHString()) : TextureIndex.init; }
|
||||
|
||||
// Reference with name s
|
||||
template getHNPtr(Type)
|
||||
{
|
||||
Type* getHNPtr(char[] s, ListKeeper list)
|
||||
{ getSubNameIs(s); return cast(Type*) list.lookup(tmpHString()); }
|
||||
}
|
||||
|
||||
// Reference, only get header
|
||||
template getHPtr(Type)
|
||||
{
|
||||
Type* getHPtr(ListKeeper list)
|
||||
{ return cast(Type*) list.lookup(tmpHString()); }
|
||||
}
|
||||
|
||||
// Optional reference with name s
|
||||
template getHNOPtr(Type)
|
||||
{
|
||||
Type* getHNOPtr(char[] s, ListKeeper list)
|
||||
{ return isNextSub(s) ? cast(Type*)list.lookup(tmpHString()) : null; }
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
*
|
||||
* Somewhat high level reading methods. Knows about headers and
|
||||
* leftFile/leftRec/leftSub.
|
||||
*
|
||||
************************************************************************/
|
||||
|
||||
// "Automatic" versions. Sets and returns recName and subName and
|
||||
// updates leftFile/leftRec.
|
||||
char[] getRecName()
|
||||
{
|
||||
if(!hasMoreRecs())
|
||||
fail("No more records, getRecName() failed");
|
||||
getName(recName);
|
||||
leftFile-= 4;
|
||||
return recName;
|
||||
}
|
||||
|
||||
// This is specially optimized for LoadINFO
|
||||
bool isEmptyOrGetName()
|
||||
{
|
||||
if(leftRec)
|
||||
{
|
||||
file.readBlock(subName.ptr, 4);
|
||||
leftRec -= 4;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// I've tried to optimize this slightly, since it gets called a LOT.
|
||||
void getSubName()
|
||||
{
|
||||
if(leftRec <= 0)
|
||||
fail("No more sub-records, getSubName() failed");
|
||||
|
||||
// Don't bother with error checking, we will catch an EOF upon
|
||||
// reading the subrecord data anyway.
|
||||
file.readBlock(subName.ptr, 4);
|
||||
|
||||
leftRec -= 4;
|
||||
}
|
||||
|
||||
// We often expect a certain subrecord type, this makes it easy to
|
||||
// check.
|
||||
void getSubNameIs(char[] s)
|
||||
{
|
||||
getSubName();
|
||||
if( subName != s )
|
||||
fail("Expected subrecord "~s~" but got "~subName);
|
||||
}
|
||||
|
||||
// Checks if the next sub-record is called s. If it is, run
|
||||
// getSubName, if not, return false.
|
||||
bool isNextSub(char[] s)
|
||||
{
|
||||
if(!leftRec) return false;
|
||||
|
||||
getName(subName);
|
||||
if(subName != s)
|
||||
{
|
||||
file.seekCur(-4);
|
||||
return false;
|
||||
}
|
||||
leftRec -= 4;
|
||||
|
||||
//getSubName();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Same as isNextSub, only it works on records instead of
|
||||
// sub-records. It also loads the record header.
|
||||
bool isNextHRec(char[] s)
|
||||
{
|
||||
if(!leftFile) return false;
|
||||
getName(recName);
|
||||
if(recName != s)
|
||||
{
|
||||
file.seekCur(-4);
|
||||
return false;
|
||||
}
|
||||
leftFile -= 4;
|
||||
|
||||
uint flags;
|
||||
getRecHeader(flags);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hasMoreSubs() { return leftRec > 0; }
|
||||
bool hasMoreRecs() { return leftFile > 0; }
|
||||
|
||||
// Remaining size of current record
|
||||
uint getRecLeft() { return leftRec; }
|
||||
// Size of current sub record
|
||||
uint getSubSize() { return leftSub; }
|
||||
|
||||
// Skip the rest of this record. Assumes the name and header have
|
||||
// already been read
|
||||
void skipRecord()
|
||||
{
|
||||
file.seekCur(leftRec);
|
||||
leftRec = 0;
|
||||
}
|
||||
|
||||
// Skip an entire record
|
||||
void skipHRecord()
|
||||
{
|
||||
if(!leftFile) return;
|
||||
|
||||
uint flags;
|
||||
|
||||
getRecName();
|
||||
getRecHeader(flags);
|
||||
skipRecord();
|
||||
}
|
||||
|
||||
// Skip current sub record and return size
|
||||
uint skipHSub()
|
||||
{
|
||||
getSubHeader();
|
||||
file.seekCur(leftSub);
|
||||
return leftSub;
|
||||
}
|
||||
|
||||
// Skip sub record and check it's size
|
||||
void skipHSubSize(uint size)
|
||||
{
|
||||
getSubHeader();
|
||||
if(leftSub != size)
|
||||
fail(format("Size mismatch: got %d, wanted %d", leftSub, size));
|
||||
file.seekCur(leftSub);
|
||||
}
|
||||
|
||||
// Check the name and size before skipping
|
||||
void skipHNSub(char[] name, uint size)
|
||||
{
|
||||
getSubNameIs(name);
|
||||
skipHSubSize(size);
|
||||
}
|
||||
|
||||
// These read an entire sub-record, including the header. They also
|
||||
// adjust and check leftSub and leftRecord variables through calling
|
||||
// getSubHeader().
|
||||
void readHExact(void * p, uint size)
|
||||
{
|
||||
getSubHeader();
|
||||
if(leftSub != size)
|
||||
fail(format("Size mismatch: got %d, wanted %d", leftSub, size));
|
||||
readExact(p, leftSub);
|
||||
}
|
||||
|
||||
template TgetHType(T)
|
||||
{ T TgetHType() { T t; readHExact(&t, t.sizeof); return t;} }
|
||||
|
||||
// To make these easier to use (and to further distinguish them from
|
||||
// the above "raw" versions), these return their value instead of
|
||||
// using an ref argument.
|
||||
alias TgetHType!(uint) getHUint;
|
||||
alias TgetHType!(int) getHInt;
|
||||
alias TgetHType!(float) getHFloat;
|
||||
alias TgetHType!(ulong) getHUlong;
|
||||
alias TgetHType!(byte) getHByte;
|
||||
|
||||
// Reads a string sub-record, including header
|
||||
char[] getHString()
|
||||
{
|
||||
getSubHeader();
|
||||
|
||||
// Hack to make MultiMark.esp load. Zero-length strings do not
|
||||
// occur in any of the official mods, but MultiMark makes use of
|
||||
// them. For some reason, they break the rules, and contain a
|
||||
// byte (value 0) even if the header says there is no data. If
|
||||
// Morrowind accepts it, so should we.
|
||||
if(leftSub == 0)
|
||||
{
|
||||
// Skip the following zero byte
|
||||
leftRec--;
|
||||
assert(file.getc() == 0);
|
||||
// TODO: Report this by setting a flag or something?
|
||||
return null;
|
||||
}
|
||||
|
||||
return getString(region.getString(leftSub));
|
||||
}
|
||||
|
||||
// Other quick aliases (this is starting to get messy)
|
||||
// Get string sub record string with name s
|
||||
char[] getHNString(char[] s)
|
||||
{ getSubNameIs(s); return getHString(); }
|
||||
|
||||
// Get optional sub record string with name s
|
||||
char[] getHNOString(char[] s)
|
||||
{ return isNextSub(s) ? getHString() : null; }
|
||||
|
||||
template TgetHNType(T)
|
||||
{ T TgetHNType(char[] s) { T t; readHNExact(&t, t.sizeof, s); return t;} }
|
||||
|
||||
template TgetHNOType(T)
|
||||
{
|
||||
T TgetHNOType(char[] s, T def)
|
||||
{
|
||||
if(isNextSub(s))
|
||||
{
|
||||
T t;
|
||||
readHExact(&t, t.sizeof);
|
||||
return t;
|
||||
}
|
||||
else return def;
|
||||
}
|
||||
}
|
||||
|
||||
alias TgetHNType!(uint) getHNUint;
|
||||
alias TgetHNType!(int) getHNInt;
|
||||
alias TgetHNType!(float) getHNFloat;
|
||||
alias TgetHNType!(ulong) getHNUlong;
|
||||
alias TgetHNType!(byte) getHNByte;
|
||||
alias TgetHNType!(short) getHNShort;
|
||||
alias TgetHNType!(byte) getHNByte;
|
||||
|
||||
alias TgetHNOType!(float) getHNOFloat;
|
||||
alias TgetHNOType!(int) getHNOInt;
|
||||
alias TgetHNOType!(byte) getHNOByte;
|
||||
|
||||
void readHNExact(void* p, uint size, char[] s)
|
||||
{ getSubNameIs(s); readHExact(p,size); }
|
||||
|
||||
// Record header
|
||||
// This updates the leftFile variable BEYOND the data that follows
|
||||
// the header, ie beyond the entire record. You are supposed to use
|
||||
// the leftRec variable when reading record data.
|
||||
void getRecHeader(out uint flags)
|
||||
{
|
||||
// General error checking
|
||||
if(leftFile < 12)
|
||||
fail("End of file while reading record header");
|
||||
if(leftRec)
|
||||
fail(format("Previous record contains %d unread bytes", leftRec));
|
||||
|
||||
getUint(leftRec);
|
||||
getUint(flags);// This header entry is always zero
|
||||
assert(flags == 0);
|
||||
getUint(flags);
|
||||
leftFile -= 12;
|
||||
|
||||
// Check that sizes add up
|
||||
if(leftFile < leftRec)
|
||||
fail(format(leftFile, " bytes left in file, but next record contains ",
|
||||
leftRec," bytes"));
|
||||
|
||||
// Adjust number of bytes left in file
|
||||
leftFile -= leftRec;
|
||||
}
|
||||
|
||||
// Sub-record head
|
||||
// This updates leftRec beyond the current sub-record as
|
||||
// well. leftSub contains size of current sub-record.
|
||||
void getSubHeader()
|
||||
{
|
||||
if(leftRec < 4)
|
||||
fail("End of record while reading sub-record header");
|
||||
|
||||
if(file.readBlock(&leftSub, 4) != 4)
|
||||
fail("getSubHeader could not read header length");
|
||||
|
||||
leftRec -= 4;
|
||||
|
||||
// Adjust number of record bytes left
|
||||
leftRec -= leftSub;
|
||||
|
||||
// Check that sizes add up
|
||||
if(leftRec < 0)
|
||||
fail(format(leftRec+leftSub,
|
||||
" bytes left in record, but next sub-record contains ",
|
||||
leftSub," bytes"));
|
||||
}
|
||||
|
||||
void getSubHeaderIs(uint size)
|
||||
{
|
||||
getSubHeader();
|
||||
if(leftSub != size)
|
||||
fail(format("Expected header size to be ", size, ", not ", leftSub));
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* Low level reading methods
|
||||
*
|
||||
*************************************************************************/
|
||||
|
||||
/// Raw data of any size
|
||||
void readExact(void *buf, uint size)
|
||||
{
|
||||
assert(size != 0);
|
||||
file.readExact(buf,size);
|
||||
}
|
||||
|
||||
// One byte
|
||||
void getByte(out byte b) { file.read(b); }
|
||||
void getUByte(out ubyte b) { file.read(b); }
|
||||
// Two bytes
|
||||
void getUShort(out ushort s) { file.read(s); }
|
||||
// Four bytes
|
||||
void getUint(out uint u) { file.read(u); }
|
||||
void getInt(out int i) { file.read(i); }
|
||||
void getFloat(out float f) { file.read(f); }
|
||||
// Eight bytes
|
||||
void getUlong(out ulong l) { file.read(l); }
|
||||
|
||||
// Get a record or subrecord name, four bytes
|
||||
void getName(NAME name)
|
||||
{
|
||||
file.readBlock(name.ptr, 4);
|
||||
/*
|
||||
if(file.readBlock(name.ptr, 4) != 4)
|
||||
fail("getName() could not find more data");
|
||||
*/
|
||||
}
|
||||
|
||||
// Fill buffer of predefined length. If actual string is shorter
|
||||
// (ie. null terminated), the buffer length is set
|
||||
// accordingly. Chopped string is returned. All strings pass through
|
||||
// this function, so any character encoding conversions should
|
||||
// happen here.
|
||||
char[] getString(char[] str)
|
||||
{
|
||||
if(str.length != file.readBlock(str.ptr,str.length))
|
||||
fail("getString() could not find enough data in stream");
|
||||
|
||||
str = stripz(str);
|
||||
makeUTF8(str); // TODO: A hack. Will replace non-utf characters
|
||||
// with question marks. This is neither a very
|
||||
// desirable result nor a very optimized
|
||||
// implementation of it.
|
||||
return str;
|
||||
}
|
||||
|
||||
// Use this to allocate and read strings of predefined length
|
||||
char[] getString(int l)
|
||||
{
|
||||
char[] str = region.getString(l);
|
||||
return getString(str);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
module esm.imports;
|
||||
|
||||
/* This is a file that imports common modules used by the load*.d
|
||||
record loaders. It is really a cut down version of what used to be
|
||||
the start of records.d.
|
||||
|
||||
This file MUST NOT import records.d - directly or indirectly -
|
||||
because that will trigger a nice three page long list of template
|
||||
forwarding errors from the compiler.
|
||||
|
||||
What happens is that when DMD/GDC compiles one of the load* files,
|
||||
it is forced to read records.d first (since it is an imported
|
||||
module) - but then it sees a template that referes to a struct in
|
||||
the current load* file, before that struct is defined. Curriously
|
||||
enough, DMD has no problems when you specify all the source files
|
||||
on the command line simultaneously. This trick doesn't work with
|
||||
GDC though, and DSSS doesn't use it either.
|
||||
|
||||
This file was created to work around this compiler bug.
|
||||
*/
|
||||
|
||||
public
|
||||
{
|
||||
import esm.defs;
|
||||
import esm.filereader;
|
||||
import esm.listkeeper;
|
||||
|
||||
import core.resource;
|
||||
import core.memory;
|
||||
|
||||
import util.regions;
|
||||
import monster.util.aa;
|
||||
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
|
||||
alias RegionBuffer!(ENAMstruct) EffectList;
|
||||
|
||||
// Records that are cross referenced often
|
||||
import esm.loadscpt;
|
||||
import esm.loadsoun;
|
||||
import esm.loadspel;
|
||||
import esm.loadench;
|
||||
|
||||
import monster.monster;
|
||||
}
|
||||
|
@ -1,330 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (listkeeper.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module esm.listkeeper;
|
||||
|
||||
import monster.util.aa;
|
||||
|
||||
import core.memory;
|
||||
|
||||
import esm.filereader;
|
||||
import esm.defs;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
// Item types, used in the lookup table for inventory items, creature
|
||||
// lists and leveled lists. We also use it for all types of references
|
||||
// that can exist in cells.
|
||||
enum ItemType
|
||||
{
|
||||
// Items
|
||||
None = 0, Potion, Apparatus, Armor, Weapon, Book, Clothing,
|
||||
Light, Ingredient, Pick, Probe, Repair, Misc, ItemList,
|
||||
|
||||
// Used for creature lists
|
||||
Creature, CreatureList, NPC,
|
||||
|
||||
// Other cell references
|
||||
Door, Activator, Static, Container//, SoundGen
|
||||
}
|
||||
|
||||
abstract class ListKeeper
|
||||
{
|
||||
int listIndex;
|
||||
|
||||
new(uint size)
|
||||
{
|
||||
return esmRegion.allocate(size).ptr;
|
||||
}
|
||||
|
||||
delete(void *p) { assert(0); }
|
||||
|
||||
this()
|
||||
{
|
||||
// Store our index for later use
|
||||
listIndex = recordLists.length;
|
||||
|
||||
// Add the class to the global list
|
||||
recordLists ~= this;
|
||||
}
|
||||
|
||||
// Load a record from a master or plugin file
|
||||
void load();
|
||||
|
||||
// Looks up a reference. If it does not exist it is assumed to be a
|
||||
// forward reference within a file, and is inserted.
|
||||
void* lookup(char[] s);
|
||||
|
||||
// Tell the loader that current file has ended, so it can do things
|
||||
// like check that all referenced objects have been loaded.
|
||||
void endFile();
|
||||
|
||||
// Number of inserted elements
|
||||
uint length();
|
||||
|
||||
void addToList(ref ItemBaseList l, ItemType t) { assert(0); }
|
||||
}
|
||||
|
||||
ListKeeper recordLists[];
|
||||
|
||||
// Keep the list of Type structures for records where the first
|
||||
// subrecord is an id string called NAME. This id is used for
|
||||
// lookup. Although almost all lookups match in case, there are a few
|
||||
// sounds that don't, so we treat these id lookups as generally case
|
||||
// insensitive. This hasn't posed any problems so far.
|
||||
class ListID(Type) : ListKeeper
|
||||
{
|
||||
HashTable!(char[], Type, ESMRegionAlloc, CITextHash) names;
|
||||
|
||||
this(uint size)
|
||||
{
|
||||
names = names.init;
|
||||
if(size) names.rehash(size);
|
||||
}
|
||||
|
||||
// Reads the id for this header. Override if the id is not simply
|
||||
// getHNString("NAME")
|
||||
char[] getID()
|
||||
{
|
||||
return esFile.getHNString("NAME");
|
||||
}
|
||||
|
||||
// Load a record from a master of plugin file
|
||||
void load()
|
||||
{
|
||||
assert(esFile.getFileType == FileType.Esm ||
|
||||
esFile.getFileType == FileType.Esp);
|
||||
|
||||
// Get the identifier of this record
|
||||
char[] id = getID();
|
||||
|
||||
// Get pointer to a new or existing object.
|
||||
Type *p;
|
||||
if(names.insertEdit(id, p))
|
||||
// A new item was inserted
|
||||
{
|
||||
p.state = LoadState.Unloaded;
|
||||
p.id = id;
|
||||
p.load();
|
||||
p.state = LoadState.Loaded;
|
||||
}
|
||||
else
|
||||
// Item already existed, either from a previous file or as a
|
||||
// forward reference from this file. Load on top of it. The
|
||||
// LoadState tells the struct whether it contains loaded data.
|
||||
{
|
||||
/*
|
||||
if(p.state == LoadState.Loaded)
|
||||
// Make a special case for this, perhaps, or just ignore it.
|
||||
writefln("WARNING: Duplicate record in file %s: '%s'",
|
||||
esFile.getFilename(), id);
|
||||
*/
|
||||
|
||||
assert(icmp(p.id, id) == 0);
|
||||
p.load();
|
||||
p.state = LoadState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks up a reference. If it does not exist it is assumed to be a
|
||||
// forward reference within a file, and is inserted.
|
||||
void* lookup(char[] id)
|
||||
{
|
||||
if(!id.length) return null; // Empty reference
|
||||
|
||||
Type *p = names.lookup(id);
|
||||
// Is the value in the list?
|
||||
if(!p)
|
||||
// No, assume it is a forward reference.
|
||||
{
|
||||
// Since the lookup name is stored in an internal buffer in
|
||||
// esFile, we have to copy it.
|
||||
id = esmRegion.copy(id);
|
||||
|
||||
// To avoid copying the string on every lookup, we have to
|
||||
// insert in a separate step. But a double lookup isn't
|
||||
// really THAT expensive. Besides, my tests show that this
|
||||
// is used in less than 10% of the cases.
|
||||
names.insertEdit(id, p);
|
||||
p.id = id;
|
||||
p.state = LoadState.Unloaded;
|
||||
}
|
||||
return cast(void*)p;
|
||||
}
|
||||
|
||||
// Check that all referenced objects are actually loaded.
|
||||
void endFile()
|
||||
in
|
||||
{
|
||||
// We can skip this in release builds
|
||||
names.validate();
|
||||
}
|
||||
body
|
||||
{
|
||||
foreach(char[] id, ref Type t; names)
|
||||
// Current file is now counted as done
|
||||
if(t.state == LoadState.Loaded) t.state = LoadState.Previous;
|
||||
else if(t.state == LoadState.Unloaded)
|
||||
//writefln("WARNING: Unloaded reference " ~ id);
|
||||
esFile.fail("Unloaded reference " ~ id);
|
||||
}
|
||||
|
||||
// Number of inserted elements
|
||||
uint length() {return names.length;}
|
||||
|
||||
// Add the names in this list to an ItemList
|
||||
void addToList(ref ItemBaseList l, ItemType t)
|
||||
{
|
||||
foreach(char[] id, ref Type s; names)
|
||||
l.insert(id, &s, t);
|
||||
}
|
||||
}
|
||||
|
||||
// A pointer to an item
|
||||
struct ItemBase
|
||||
{
|
||||
ItemType type;
|
||||
void *p;
|
||||
}
|
||||
|
||||
struct ItemBaseList
|
||||
{
|
||||
HashTable!(char[],ItemBase,ESMRegionAlloc) list;
|
||||
|
||||
void addList(ItemBaseList l)
|
||||
{
|
||||
foreach(char[] id, ItemBase b; l.list)
|
||||
insert(id, b.p, b.type);
|
||||
}
|
||||
|
||||
void addList(ListKeeper source, ItemType type)
|
||||
{
|
||||
source.addToList(*this, type);
|
||||
}
|
||||
|
||||
void insert(char[] id, void* p, ItemType type)
|
||||
{
|
||||
ItemBase *b;
|
||||
if(!list.insertEdit(id, b))
|
||||
{
|
||||
//writefln("Replacing item ", id);
|
||||
if(b.type != ItemType.None)
|
||||
esFile.fail("Replaced valid item: " ~ id);
|
||||
}
|
||||
//else writefln("Inserting new item ", id);
|
||||
|
||||
b.type = type;
|
||||
b.p = p;
|
||||
}
|
||||
|
||||
// Called at the end to check that all referenced items have been resolved
|
||||
void endMerge()
|
||||
{
|
||||
foreach(char[] id, ref ItemBase t; list)
|
||||
// Current file is now counted as done
|
||||
if(t.type == ItemType.None)
|
||||
// TODO: Don't use esFile.fail for this
|
||||
esFile.fail("ItemBaseList: Unresolved forward reference: " ~ id);
|
||||
}
|
||||
|
||||
// Look up an item, return a pointer to the ItemBase representing
|
||||
// it. If it does not exist, it is inserted.
|
||||
ItemBase *lookup(char[] id)
|
||||
{
|
||||
if(!id.length) return null; // Empty reference
|
||||
ItemBase *b = list.lookup(id);
|
||||
// Is the value in the list?
|
||||
if(!b)
|
||||
// No, assume it is a forward reference.
|
||||
{
|
||||
// Since the lookup name is stored in an internal buffer in
|
||||
// esFile, we have to copy it.
|
||||
id = esmRegion.copy(id);
|
||||
|
||||
// To avoid copying the string on every lookup, we have to
|
||||
// insert in a separate step. But a double lookup isn't
|
||||
// really THAT expensive.
|
||||
list.insertEdit(id, b);
|
||||
|
||||
b.p = null;
|
||||
b.type = ItemType.None;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
// An item. Contains a reference to an ItemBase, which again is a
|
||||
// reference to an item. The ItemBase might change after we have
|
||||
// looked it up (for forward references), so we have to use a pointer.
|
||||
struct Item
|
||||
{
|
||||
ItemBase *i;
|
||||
|
||||
void* getPtr(ItemType type)
|
||||
{
|
||||
if(i != null && i.type == type) return i.p;
|
||||
return null;
|
||||
}
|
||||
|
||||
T* getType(T, ItemType Type)()
|
||||
{
|
||||
return cast(T*)getPtr(Type);
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemList
|
||||
{
|
||||
private:
|
||||
ItemBaseList list;
|
||||
|
||||
public:
|
||||
void addList(ItemList l)
|
||||
{ list.addList(l.list); }
|
||||
|
||||
void addList(ListKeeper source, ItemType type)
|
||||
{ list.addList(source, type); }
|
||||
|
||||
Item lookup(char[] id)
|
||||
{
|
||||
Item i;
|
||||
i.i = list.lookup(id);
|
||||
return i;
|
||||
}
|
||||
|
||||
void endMerge()
|
||||
{ list.endMerge(); }
|
||||
|
||||
void endFile()
|
||||
in { list.list.validate(); }
|
||||
body {}
|
||||
|
||||
void rehash(uint size)
|
||||
{ list.list.rehash(size); }
|
||||
|
||||
uint length() { return list.list.length(); }
|
||||
}
|
||||
|
||||
// Aggregate lists, made by concatinating several other lists.
|
||||
ItemList items; // All inventory items, including leveled item lists
|
||||
ItemList actors; // All actors, ie. NPCs, creatures and leveled lists
|
||||
ItemList cellRefs; // All things that are referenced from cells
|
@ -1,189 +0,0 @@
|
||||
/*
|
||||
This file contains some leftovers which have not yet been ported to
|
||||
C++.
|
||||
*/
|
||||
|
||||
align(1) struct AMBIStruct
|
||||
{
|
||||
Color ambient, sunlight, fog;
|
||||
float fogDensity;
|
||||
|
||||
static assert(AMBIStruct.sizeof == 16);
|
||||
}
|
||||
|
||||
int max(int x, int y)
|
||||
{ return x>=y?x:y; }
|
||||
|
||||
struct ExtCellHash
|
||||
{
|
||||
// This is a pretty good hash, gives no collisions for all of
|
||||
// Morrowind.esm when the table size is 2048, and it gives very few
|
||||
// collisions overall. Not that it matters that much.
|
||||
static uint hash(uint val)
|
||||
{
|
||||
uint res = cast(ushort)val;
|
||||
res += *(cast(ushort*)&val+1)*41;
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool isEqual(uint a, uint b) { return a==b; }
|
||||
}
|
||||
|
||||
class CellList : ListKeeper
|
||||
{
|
||||
// Again, these are here to avoid DMD template bugs
|
||||
alias _aaNode!(char[], InteriorCell) _unused1;
|
||||
alias _aaNode!(uint, ExteriorCell) _unused2;
|
||||
|
||||
HashTable!(char[], InteriorCell, ESMRegionAlloc) in_cells;
|
||||
HashTable!(uint, ExteriorCell, ESMRegionAlloc, ExtCellHash) ex_cells;
|
||||
|
||||
// Store the maximum x or y coordinate (in absolute value). This is
|
||||
// used in the landscape pregen process.
|
||||
int maxXY;
|
||||
|
||||
this()
|
||||
{
|
||||
in_cells = in_cells.init;
|
||||
in_cells.rehash(1600);
|
||||
|
||||
ex_cells = ex_cells.init;
|
||||
ex_cells.rehash(1800);
|
||||
}
|
||||
|
||||
align(1) struct DATAstruct
|
||||
{
|
||||
CellFlags flags;
|
||||
int gridX, gridY;
|
||||
static assert(DATAstruct.sizeof==12);
|
||||
}
|
||||
|
||||
DATAstruct data;
|
||||
|
||||
// Look up an interior cell, throws an error if not found (might
|
||||
// change later)
|
||||
InteriorCell *getInt(char[] s)
|
||||
{
|
||||
return in_cells.getPtr(s);
|
||||
}
|
||||
|
||||
// Exterior cell, same as above
|
||||
ExteriorCell *getExt(int x, int y)
|
||||
{
|
||||
return ex_cells.getPtr(compound(x,y));
|
||||
}
|
||||
|
||||
// Check whether we have a given exterior cell
|
||||
bool hasExt(int x, int y)
|
||||
{
|
||||
return ex_cells.inList(compound(x,y));
|
||||
}
|
||||
|
||||
void *lookup(char[] s)
|
||||
{ assert(0); }
|
||||
|
||||
void endFile()
|
||||
out
|
||||
{
|
||||
in_cells.validate();
|
||||
ex_cells.validate();
|
||||
}
|
||||
body
|
||||
{
|
||||
foreach(id, ref c; in_cells)
|
||||
{
|
||||
if(c.state == LoadState.Loaded) c.state = LoadState.Previous;
|
||||
// We never forward reference cells!
|
||||
assert(c.state != LoadState.Unloaded);
|
||||
}
|
||||
|
||||
foreach(id, ref c; ex_cells)
|
||||
{
|
||||
if(c.state == LoadState.Loaded) c.state = LoadState.Previous;
|
||||
// We never forward reference cells!
|
||||
assert(c.state != LoadState.Unloaded);
|
||||
}
|
||||
}
|
||||
|
||||
uint length() { return numInt() + numExt(); }
|
||||
uint numInt() { return in_cells.length; }
|
||||
uint numExt() { return ex_cells.length; }
|
||||
|
||||
// Turn an exterior cell grid position into a unique number
|
||||
static uint compound(int gridX, int gridY)
|
||||
{
|
||||
return cast(ushort)gridX + ((cast(ushort)gridY)<<16);
|
||||
}
|
||||
|
||||
static void decompound(uint val, out int gridX, out int gridY)
|
||||
{
|
||||
gridX = cast(short)(val&0xffff);
|
||||
gridY = cast(int)(val&0xffff0000) >> 16;
|
||||
}
|
||||
|
||||
void load()
|
||||
{with(esFile){
|
||||
char[] id = getHNString("NAME");
|
||||
|
||||
// Just ignore this, don't know what it does. I assume it
|
||||
// deletes the cell, but we can't handle that yet.
|
||||
if(isNextSub("DELE")) getHInt();
|
||||
|
||||
readHNExact(&data, data.sizeof, "DATA");
|
||||
|
||||
if(data.flags & CellFlags.Interior)
|
||||
{
|
||||
InteriorCell *p;
|
||||
if(in_cells.insertEdit(id, p))
|
||||
// New item was inserted
|
||||
{
|
||||
p.state = LoadState.Unloaded;
|
||||
p.id = id;
|
||||
p.flags = data.flags;
|
||||
p.load();
|
||||
p.state = LoadState.Loaded;
|
||||
}
|
||||
else
|
||||
// Overloading an existing cell
|
||||
{
|
||||
if(p.state != LoadState.Previous)
|
||||
fail("Duplicate interior cell " ~ id);
|
||||
|
||||
assert(id == p.id);
|
||||
p.load();
|
||||
p.state = LoadState.Loaded;
|
||||
}
|
||||
}
|
||||
else // Exterior cell
|
||||
{
|
||||
uint key = compound(data.gridX, data.gridY);
|
||||
|
||||
ExteriorCell *p;
|
||||
if(ex_cells.insertEdit(key, p))
|
||||
// New cell
|
||||
{
|
||||
p.state = LoadState.Unloaded;
|
||||
p.name = id;
|
||||
p.flags = data.flags;
|
||||
p.gridX = data.gridX;
|
||||
p.gridY = data.gridY;
|
||||
p.load();
|
||||
p.state = LoadState.Loaded;
|
||||
|
||||
int mx = max(abs(p.gridX), abs(p.gridY));
|
||||
maxXY = max(maxXY, mx);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(p.state != LoadState.Previous)
|
||||
fail(format("Duplicate exterior cell %d %d",
|
||||
data.gridX, data.gridY));
|
||||
assert(p.gridX == data.gridX);
|
||||
assert(p.gridY == data.gridY);
|
||||
p.load();
|
||||
p.state = LoadState.Loaded;
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
CellList cells;
|
@ -1,289 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (loaddial.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module esm.loaddial;
|
||||
import esm.imports;
|
||||
import esm.loadinfo;
|
||||
|
||||
/*
|
||||
* Dialogue topic and journal entries. The acutal data is contained in
|
||||
* the following INFO records.
|
||||
*/
|
||||
|
||||
// Keep a list of possible hyper links. This list is used when parsing
|
||||
// text and finding references to topic names. Eg. if a character says
|
||||
// "Would you like to join the mages guild?", we must be able to pick
|
||||
// out the phrase "join the mages guild?" and make a hyperlink of
|
||||
// it. Each link is indexed by their first word. The structure
|
||||
// contains the rest of the phrase, so the phrase above would be
|
||||
// indexed by "join" and contain the string "the mages guild?", for
|
||||
// quick comparison with the text are currently parsing. It also
|
||||
// contains a pointer to the corresponding dialogue struct. The lists
|
||||
// are sorted by descending string length, in order to match the
|
||||
// longest possible term first.
|
||||
|
||||
struct Hyperlink
|
||||
{
|
||||
Dialogue *ptr;
|
||||
char[] rest;
|
||||
|
||||
// Returns a < b if a.length > b.length.
|
||||
int opCmp(Hyperlink *h) {return h.rest.length - rest.length;}
|
||||
}
|
||||
|
||||
alias RegionBuffer!(Hyperlink) HyperlinkArray;
|
||||
|
||||
// This is much nicer now that we use our own AA.
|
||||
struct HyperlinkList
|
||||
{
|
||||
// Make a case insensitive hash table of Hyperlink arrays.
|
||||
HashTable!(char[], HyperlinkArray, ESMRegionAlloc, CITextHash) list;
|
||||
|
||||
void add(char[] topic, Dialogue* ptr)
|
||||
{
|
||||
// Only add dialogues
|
||||
if(ptr.type != Dialogue.Type.Topic) return;
|
||||
|
||||
Hyperlink l;
|
||||
l.ptr = ptr;
|
||||
l.rest = topic;
|
||||
|
||||
// Use the first word as the index
|
||||
topic = nextWord(l.rest);
|
||||
|
||||
// Insert a new array, or get an already existing one
|
||||
HyperlinkArray ha = list.get(topic,
|
||||
// Create a new array
|
||||
delegate void (ref HyperlinkArray a)
|
||||
{ a = esmRegion.getBuffer!(Hyperlink)(0,1); }
|
||||
);
|
||||
|
||||
// Finally, add it to the list
|
||||
ha ~= l;
|
||||
}
|
||||
|
||||
Hyperlink[] getList(char[] word)
|
||||
{
|
||||
HyperlinkArray p;
|
||||
if(list.inList(word, p)) return p.array();
|
||||
return null;
|
||||
}
|
||||
|
||||
void rehash(uint size)
|
||||
{
|
||||
list.rehash(size);
|
||||
}
|
||||
|
||||
// We wouldn't need this if we only dealt with one file, since the
|
||||
// topics are already sorted in Morrowind.esm. However, other files
|
||||
// might add items out of order later, so we have to sort it. To
|
||||
// understand why this is needed, consider the following example:
|
||||
//
|
||||
// Morrowind.esm contains the topic 'join us'. When ever the text
|
||||
// ".. join us blahblah ..." is encountered, this match is
|
||||
// found. However, if a plugin adds the topic 'join us today', we
|
||||
// have to place this _before_ 'join us' in the list, or else it
|
||||
// will never be matched.
|
||||
void sort()
|
||||
{
|
||||
foreach(char[] s, HyperlinkArray l; list)
|
||||
{
|
||||
l.array().sort;
|
||||
/*
|
||||
writefln("%s: ", s, l.length);
|
||||
foreach(Hyperlink h; l.array())
|
||||
writefln(" %s (%s)", h.rest, h.ptr.id);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List of dialogue hyperlinks
|
||||
HyperlinkList hyperlinks;
|
||||
|
||||
struct Dialogue
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
Topic = 0,
|
||||
Voice = 1,
|
||||
Greeting = 2,
|
||||
Persuasion = 3,
|
||||
Journal = 4,
|
||||
Deleted = -1
|
||||
}
|
||||
|
||||
//Type type;
|
||||
DialogueType type;
|
||||
|
||||
DialInfoList infoList;
|
||||
|
||||
char[] id; // This is the 'dialogue topic' that the user actually
|
||||
// sees.
|
||||
LoadState state;
|
||||
|
||||
void load()
|
||||
{with(esFile){
|
||||
getSubNameIs("DATA");
|
||||
|
||||
getSubHeader();
|
||||
int si = getSubSize();
|
||||
if(si == 1)
|
||||
{
|
||||
byte b;
|
||||
getByte(b);
|
||||
DialogueType t = cast(DialogueType)b;
|
||||
|
||||
// Meet the new type, same as the old type
|
||||
if(t != this.type && state == LoadState.Previous)
|
||||
fail("Type changed in dialogue " ~ id);
|
||||
|
||||
this.type = t;
|
||||
}
|
||||
else if(si == 4)
|
||||
{
|
||||
// These are just markers, their values are not used.
|
||||
int i;
|
||||
getInt(i);
|
||||
//writefln("In file %s:", getFilename());
|
||||
//writefln(" WARNING: DIAL.DATA was size 4 and contains: ", i);
|
||||
i = getHNInt("DELE");
|
||||
//writefln(" DELE contains ", i);
|
||||
this.type = Type.Deleted;
|
||||
}
|
||||
else fail("Unknown sub record size " ~ toString(si));
|
||||
|
||||
infoList.state = state;
|
||||
while(isNextHRec("INFO"))
|
||||
infoList.load(this.type);
|
||||
//skipRecord();
|
||||
}}
|
||||
}
|
||||
|
||||
typedef Dialogue.Type DialogueType;
|
||||
|
||||
/+
|
||||
// I don't remember when I commented out this code or what state
|
||||
// it is in. Probably highly experimental.
|
||||
// --------------
|
||||
|
||||
// Loop through the info blocks in this dialogue, and update the
|
||||
// master as necessary.
|
||||
|
||||
// TODO: Note that the dialogue system in Morrowind isn't very
|
||||
// robust. If several mods insert dialogues at exactly the same
|
||||
// place, the mods loaded last might overwrite the previous mods,
|
||||
// completely removing the previous entry even if the two entries
|
||||
// do not have the same id. This is because a change also
|
||||
// overwrites the previous and the next entry, in order to update
|
||||
// their "previous" and "next" fields. Furthermore, we might put
|
||||
// ourselves in a situation where the forward and backward chains
|
||||
// do not match, or in a situation where we update a deleted
|
||||
// info. For now I do nothing about it, but I will have to develop
|
||||
// a "conflict manager" later on. It SHOULD be possible to merge
|
||||
// these info lists automatically in most cases, but it
|
||||
// complicates the code.
|
||||
|
||||
// Whoa, seems we have a case study already with just tribunal and
|
||||
// bloodmoon loaded! See comments below.
|
||||
|
||||
foreach(char[] id, ref DialInfoLoad m; mod.infoList)
|
||||
{
|
||||
// Remove the response if it is marked as deleted.
|
||||
if(m.deleted)
|
||||
{
|
||||
if((id in master.infoList) == null)
|
||||
writefln("Cannot delete info %s, does not exist", id);
|
||||
else master.infoList.remove(id);
|
||||
}
|
||||
else
|
||||
// Just plain copy it in.
|
||||
master.infoList[id] = m;
|
||||
}
|
||||
}
|
||||
|
||||
// Here we have to fix inconsistencies. A good case example is the
|
||||
// dialogue "Apelles Matius" in trib/blood. Trib creates a
|
||||
// dialogue of a few items, bloodmoon adds another. But since the
|
||||
// two are independent, the list in bloodmoon does not change the
|
||||
// one in trib but rather creates a new one. In other words, we
|
||||
// will have to deal with the possibility of several "independent"
|
||||
// lists within each topic. We can do this by looking for several
|
||||
// start points (ie. infos with prev="") and just latch them onto
|
||||
// each other. I'm not sure it gives the correct result,
|
||||
// though. For example, which list comes first would be rather
|
||||
// arbitrarily decided by the order we traverse the infoList AA. I
|
||||
// will just have to assume that they truly are "independent".
|
||||
|
||||
// There still seems to be a problem though. Bloodmoon overwrites
|
||||
// some stuff added by Tribunal, see "Boots of the Apostle" for an
|
||||
// example. Looks like the editor handles it just fine... We need
|
||||
// to make sure that all the items in our AA are put into the
|
||||
// list, and in the right place too. We obviously cannot fully
|
||||
// trust the 'next' and 'prev' fields, but they are the only
|
||||
// guidance we have. Deal with it later!
|
||||
|
||||
// At this point we assume "master" to contain the final dialogue
|
||||
// list, so at this point we can set it in stone.
|
||||
infoList.length = master.infoList.length;
|
||||
|
||||
// Find the first entry
|
||||
DialInfoLoad* starts[]; // starting points for linked lists
|
||||
DialInfoLoad *current;
|
||||
foreach(char[] id, ref DialInfoLoad l; master.infoList)
|
||||
if(l.prev == "") starts ~= &l;
|
||||
|
||||
foreach(int num, ref DialInfo m; infoList)
|
||||
{
|
||||
if(current == null)
|
||||
{
|
||||
if(starts.length == 0)
|
||||
{
|
||||
writefln("Error: No starting points!");
|
||||
infoList.length = num;
|
||||
break;
|
||||
}
|
||||
// Pick the next starting point
|
||||
current = starts[0];
|
||||
starts = starts[1..$];
|
||||
}
|
||||
m.copy(*current, this);
|
||||
|
||||
if((*current).next == "")
|
||||
current = null;
|
||||
else
|
||||
{
|
||||
current = (*current).next in master.infoList;
|
||||
if(current == null)
|
||||
{
|
||||
writefln("Error in dialouge info lookup!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(infoList.length != master.infoList.length)
|
||||
writefln("Dialogue list lengths do not match, %d != %d",
|
||||
infoList.length, master.infoList.length);
|
||||
}
|
||||
}
|
||||
+/
|
@ -1,219 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (records.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
module esm.records;
|
||||
|
||||
public
|
||||
{
|
||||
import monster.util.aa;
|
||||
import util.regions;
|
||||
|
||||
import core.memory;
|
||||
import core.resource;
|
||||
|
||||
import esm.filereader;
|
||||
import esm.defs;
|
||||
import esm.listkeeper;
|
||||
|
||||
import std.stdio; // Remove later
|
||||
}
|
||||
|
||||
public import
|
||||
esm.loadacti, esm.loaddoor, esm.loadglob, esm.loadscpt, esm.loadsoun, esm.loadgmst,
|
||||
esm.loadfact, esm.loadstat, esm.loadspel, esm.loadalch, esm.loadappa, esm.loadarmo,
|
||||
esm.loadbody, esm.loadench, esm.loadbook, esm.loadbsgn, esm.loadltex, esm.loadmgef,
|
||||
esm.loadweap, esm.loadlocks,esm.loadcell, esm.loadregn, esm.loadligh, esm.loadskil,
|
||||
esm.loadsndg, esm.loadrace, esm.loadmisc, esm.loadclot, esm.loadingr, esm.loadclas,
|
||||
esm.loadcont, esm.loadcrea, esm.loadnpc, esm.loaddial, esm.loadinfo, esm.loadsscr,
|
||||
esm.loadlevlist;
|
||||
|
||||
void loadRecord(char[] recName)
|
||||
{
|
||||
switch(recName)
|
||||
{
|
||||
case "ACTI": activators.load(); break;
|
||||
case "DOOR": doors.load(); break;
|
||||
case "GLOB": globals.load(); break;
|
||||
case "SCPT": scripts.load(); break;
|
||||
case "SOUN": sounds.load(); break;
|
||||
case "GMST": gameSettings.load(); break;
|
||||
case "FACT": factions.load(); break;
|
||||
case "STAT": statics.load(); break;
|
||||
case "SPEL": spells.load(); break;
|
||||
case "ALCH": potions.load(); break;
|
||||
case "APPA": appas.load(); break;
|
||||
case "ARMO": armors.load(); break;
|
||||
case "BODY": bodyParts.load(); break;
|
||||
case "ENCH": enchants.load(); break;
|
||||
case "BOOK": books.load(); break;
|
||||
case "BSGN": birthSigns.load(); break;
|
||||
case "LTEX": landTextures.load(); break;
|
||||
case "MGEF": effects.load(); break;
|
||||
case "WEAP": weapons.load(); break;
|
||||
case "REPA": repairs.load(); break;
|
||||
case "LOCK": lockpicks.load(); break;
|
||||
case "PROB": probes.load(); break;
|
||||
case "CELL": cells.load(); break;
|
||||
case "REGN": regions.load(); break;
|
||||
case "LIGH": lights.load(); break;
|
||||
case "SKIL": skills.load(); break;
|
||||
case "SNDG": soundGens.load(); break;
|
||||
case "RACE": races.load(); break;
|
||||
case "MISC": miscItems.load(); break;
|
||||
case "CLOT": clothes.load(); break;
|
||||
case "INGR": ingreds.load(); break;
|
||||
case "CLAS": classes.load(); break;
|
||||
case "CONT": containers.load(); break;
|
||||
case "CREA": creatures.load(); break;
|
||||
case "LEVI": itemLists.load(); break;
|
||||
case "LEVC": creatureLists.load(); break;
|
||||
case "NPC_": npcs.load(); break;
|
||||
case "DIAL": dialogues.load(); break;
|
||||
case "SSCR": startScripts.load(); break;
|
||||
/*
|
||||
|
||||
// Tribunal / Bloodmoon only
|
||||
case "SSCR": loadSSCR.load(); break;
|
||||
|
||||
*/
|
||||
// For save games:
|
||||
// case "NPCC": loadNPCC;
|
||||
// case "CNTC": loadCNTC;
|
||||
// case "CREC": loadCREC;
|
||||
|
||||
// These should never be looked up
|
||||
case "TES3":
|
||||
case "INFO":
|
||||
case "LAND":
|
||||
case "PGRD":
|
||||
esFile.fail("Misplaced record " ~ recName);
|
||||
default:
|
||||
esFile.fail("Unknown record type " ~ recName);
|
||||
}
|
||||
//*/
|
||||
}
|
||||
|
||||
// Um, this has to be in this file for some reason.
|
||||
ListID!(Dialogue) dialogues;
|
||||
|
||||
struct ItemT
|
||||
{
|
||||
Item item;
|
||||
ItemBase *i;
|
||||
|
||||
T* getType(T, ItemType Type)()
|
||||
{
|
||||
return item.getType!(T, Type)();
|
||||
}
|
||||
|
||||
alias getType!(Potion, ItemType.Potion) getPotion;
|
||||
alias getType!(Apparatus, ItemType.Apparatus) getApparatus;
|
||||
alias getType!(Armor, ItemType.Armor) getArmor;
|
||||
alias getType!(Weapon, ItemType.Weapon) getWeapon;
|
||||
alias getType!(Book, ItemType.Book) getBook;
|
||||
alias getType!(Clothing, ItemType.Clothing) getClothing;
|
||||
alias getType!(Light, ItemType.Light) getLight;
|
||||
alias getType!(Ingredient, ItemType.Ingredient) getIngredient;
|
||||
alias getType!(Tool, ItemType.Pick) getPick;
|
||||
alias getType!(Tool, ItemType.Probe) getProbe;
|
||||
alias getType!(Tool, ItemType.Repair) getRepair;
|
||||
alias getType!(Misc, ItemType.Misc) getMisc;
|
||||
alias getType!(LeveledItems, ItemType.ItemList) getItemList;
|
||||
alias getType!(Creature, ItemType.Creature) getCreature;
|
||||
alias getType!(LeveledCreatures, ItemType.CreatureList) getCreatureList;
|
||||
alias getType!(NPC, ItemType.NPC) getNPC;
|
||||
alias getType!(Door, ItemType.Door) getDoor;
|
||||
alias getType!(Activator, ItemType.Activator) getActivator;
|
||||
alias getType!(Static, ItemType.Static) getStatic;
|
||||
alias getType!(Container, ItemType.Container) getContainer;
|
||||
|
||||
static ItemT opCall(Item it)
|
||||
{
|
||||
ItemT itm;
|
||||
itm.item = it;
|
||||
itm.i = it.i;
|
||||
return itm;
|
||||
}
|
||||
}
|
||||
|
||||
void endFiles()
|
||||
{
|
||||
foreach(ListKeeper l; recordLists)
|
||||
l.endFile();
|
||||
|
||||
items.endFile();
|
||||
}
|
||||
|
||||
void initializeLists()
|
||||
{
|
||||
recordLists = null;
|
||||
|
||||
// Initialize all the lists here. The sizes have been chosen big
|
||||
// enough to hold the main ESM files and a large number of mods
|
||||
// without rehashing.
|
||||
|
||||
activators = new ListID!(Activator)(1400);
|
||||
doors = new ListID!(Door)(300);
|
||||
globals = new ListID!(Global)(300);
|
||||
scripts = new ScriptList(1800);
|
||||
sounds = new ListID!(Sound)(1000);
|
||||
gameSettings = new ListID!(GameSetting)(1600);
|
||||
factions = new ListID!(Faction)(30);
|
||||
statics = new ListID!(Static)(4000);
|
||||
spells = new ListID!(Spell)(1300);
|
||||
potions = new ListID!(Potion)(300);
|
||||
appas = new ListID!(Apparatus)(30);
|
||||
armors = new ListID!(Armor)(500);
|
||||
bodyParts = new ListID!(BodyPart)(2300);
|
||||
enchants = new ListID!(Enchantment)(1000);
|
||||
books = new ListID!(Book)(700);
|
||||
birthSigns = new ListID!(BirthSign)(30);
|
||||
landTextures = new LandTextureList;
|
||||
effects = new MagicEffectList;
|
||||
weapons = new ListID!(Weapon)(700);
|
||||
lockpicks = new ListID!(Tool)(10);
|
||||
probes = new ListID!(Tool)(10);
|
||||
repairs = new ListID!(Tool)(10);
|
||||
cells = new CellList;
|
||||
regions = new ListID!(Region)(20);
|
||||
lights = new ListID!(Light)(1000);
|
||||
skills = new SkillList;
|
||||
soundGens = new ListID!(SoundGenerator)(500);
|
||||
races = new ListID!(Race)(100);
|
||||
miscItems = new ListID!(Misc)(700);
|
||||
clothes = new ListID!(Clothing)(700);
|
||||
ingreds = new ListID!(Ingredient)(200);
|
||||
classes = new ListID!(Class)(100);
|
||||
containers = new ListID!(Container)(1200);
|
||||
creatures = new ListID!(Creature)(800);
|
||||
itemLists = new ListID!(LeveledItems)(600);
|
||||
creatureLists = new ListID!(LeveledCreatures)(400);
|
||||
npcs = new ListID!(NPC)(3500);
|
||||
dialogues = new ListID!(Dialogue)(3000);
|
||||
startScripts.init();
|
||||
|
||||
hyperlinks.rehash(1600);
|
||||
|
||||
items.rehash(5500);
|
||||
actors.rehash(5000);
|
||||
cellRefs.rehash(17000);
|
||||
}
|
@ -1,377 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (esmtool.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module esmtool;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
import core.memory;
|
||||
import esm.esmmain;
|
||||
import monster.util.string;
|
||||
import mscripts.setup;
|
||||
|
||||
import std.gc;
|
||||
import gcstats;
|
||||
|
||||
|
||||
// Not used, but we have to link it in along with the C++ stuff.
|
||||
import input.events;
|
||||
|
||||
void poolSize()
|
||||
{
|
||||
GCStats gc;
|
||||
getStats(gc);
|
||||
writefln("Pool size: ", comma(gc.poolsize));
|
||||
writefln("Used size: ", comma(gc.usedsize));
|
||||
}
|
||||
|
||||
//alias int[Dialogue*] TopicList;
|
||||
|
||||
void main(char[][] args)
|
||||
{
|
||||
char[][] files;
|
||||
bool raw;
|
||||
|
||||
bool scptList; // List scripts
|
||||
bool scptShow; // Show a script
|
||||
char[] scptName; // Script to show
|
||||
|
||||
bool ciList; // List interior cells
|
||||
bool ceList; // List exterior cells that have names
|
||||
|
||||
bool weList; // List weapons
|
||||
|
||||
bool gmst; // List game settings
|
||||
|
||||
bool numbers; // List how many there are of each record type
|
||||
|
||||
foreach(char[] a; args[1..$])
|
||||
if(a == "-r") raw = true;
|
||||
|
||||
else if(a == "-sl") scptList = true;
|
||||
else if(a == "-s") scptShow = true;
|
||||
else if(scptShow && scptName == "") scptName = a;
|
||||
|
||||
else if(a == "-g") gmst = true;
|
||||
else if(a == "-cil") ciList = true;
|
||||
else if(a == "-cel") ceList = true;
|
||||
|
||||
else if(a == "-wl") weList = true;
|
||||
|
||||
else if(a == "-n") numbers = true;
|
||||
|
||||
else if(a.begins("-")) writefln("Ignoring unknown option %s", a);
|
||||
else files ~= a;
|
||||
|
||||
int help(char[] msg)
|
||||
{
|
||||
writefln("%s", msg);
|
||||
writefln("Syntax: %s [options] esm-file [esm-file ... ]", args[0]);
|
||||
writefln(" Options:");
|
||||
writefln(" -r Display all records in raw format");
|
||||
writefln(" -n List the number of each record type");
|
||||
writefln(" -sl List scripts");
|
||||
writefln(" -g List game settings (GMST)");
|
||||
writefln(" -s name Show given script");
|
||||
writefln(" -cil List interior cells");
|
||||
writefln(" -cel List exterior cells with names");
|
||||
writefln(" -wl List weapons");
|
||||
return 1;
|
||||
}
|
||||
if(files.length == 0) return help("No input files given");
|
||||
|
||||
if(scptShow && scptName == "") return help("No script name given");
|
||||
|
||||
initializeMemoryRegions();
|
||||
initMonsterScripts();
|
||||
|
||||
if(raw)
|
||||
{
|
||||
foreach(int fileNum, char[] filename; files)
|
||||
{
|
||||
try
|
||||
{
|
||||
esFile.open(filename, esmRegion);
|
||||
printRaw();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
try {writefln(e);}
|
||||
catch {}
|
||||
writefln("Error on file %s", filename);
|
||||
}
|
||||
catch
|
||||
{
|
||||
writefln("Error: Unkown failure on file %s", filename);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable resource lookups.
|
||||
resources.dummy = true;
|
||||
|
||||
try loadTESFiles(files);
|
||||
catch(Exception e)
|
||||
{
|
||||
writefln(e);
|
||||
}
|
||||
catch { writefln("Error: Unkown failure"); }
|
||||
|
||||
// List weapons
|
||||
if(weList) foreach(n, m; weapons.names)
|
||||
{
|
||||
alias Weapon.Type WT;
|
||||
switch(m.data.type)
|
||||
{
|
||||
case WT.ShortBladeOneHand: writef("Short Sword"); break;
|
||||
case WT.LongBladeOneHand: writef("Long Sword, One-Handed"); break;
|
||||
case WT.LongBladeTwoHand: writef("Long Sword, Two-Handed"); break;
|
||||
case WT.BluntOneHand: writef("Blunt, One-Handed"); break;
|
||||
case WT.BluntTwoClose: writef("Blunt, Two-Handed"); break;
|
||||
case WT.BluntTwoWide: writef("Blunt, Two-Handed Wide"); break;
|
||||
case WT.SpearTwoWide: writef("Spear, Two-Handed"); break;
|
||||
case WT.AxeOneHand: writef("Axe, One-Handed"); break;
|
||||
case WT.AxeTwoHand: writef("Axe, Two-Handed"); break;
|
||||
case WT.MarksmanBow: writef("Bow"); break;
|
||||
case WT.MarksmanCrossbow: writef("Crossbow"); break;
|
||||
case WT.MarksmanThrown: writef("Thrown weapon"); break;
|
||||
case WT.Arrow: writef("Arrow"); break;
|
||||
case WT.Bolt: writef("Bolt"); break;
|
||||
default: assert(0);
|
||||
}
|
||||
writefln(" id '%s': name '%s'", n, m.name);
|
||||
|
||||
if(m.data.flags & Weapon.Flags.Magical)
|
||||
writefln("Magical");
|
||||
if(m.data.flags & Weapon.Flags.Silver)
|
||||
writefln("Silver");
|
||||
|
||||
writefln("Weight: ", m.data.weight);
|
||||
writefln("Value: ", m.data.value);
|
||||
writefln("Health: ", m.data.health);
|
||||
writefln("Speed: ", m.data.speed);
|
||||
writefln("Reach: ", m.data.reach);
|
||||
writefln("Enchantment points: ", m.data.enchant);
|
||||
writefln("Combat: ", m.data.chop, m.data.slash, m.data.thrust);
|
||||
|
||||
if(m.enchant) writefln("Has enchantment '%s'", m.enchant.id);
|
||||
if(m.script) writefln("Has script '%s'", m.script.id);
|
||||
|
||||
writefln();
|
||||
}
|
||||
|
||||
if(numbers)
|
||||
{
|
||||
writefln("Activators: ", activators.length);
|
||||
writefln("Doors: ", doors.length);
|
||||
writefln("Globals: ", globals.length);
|
||||
writefln("Sounds: ", sounds.length);
|
||||
writefln("Game Settings: ", gameSettings.length);
|
||||
writefln("Factions: ", factions.length);
|
||||
writefln("Statics: ", statics.length);
|
||||
writefln("Spells: ", spells.length);
|
||||
writefln("Potions: ", potions.length);
|
||||
writefln("Apparatus: ", appas.length);
|
||||
writefln("Armors: ", armors.length);
|
||||
writefln("Body parts: ", bodyParts.length);
|
||||
writefln("Enchantments: ", enchants.length);
|
||||
writefln("Books: ", books.length);
|
||||
writefln("Birth signs: ", birthSigns.length);
|
||||
writefln("Land texture files: ", landTextures.length);
|
||||
writefln("Weapons: ", weapons.length);
|
||||
writefln("Lockpicks: ", lockpicks.length);
|
||||
writefln("Probes: ", probes.length);
|
||||
writefln("Repairs: ", repairs.length);
|
||||
writefln("Cells: ", cells.length);
|
||||
writefln(" Interior: ", cells.numInt);
|
||||
writefln(" Exterior: ", cells.numExt);
|
||||
writefln("Regions: ", regions.length);
|
||||
writefln("Lights: ", lights.length);
|
||||
writefln("Skills: ", skills.length);
|
||||
writefln("Sound generators: ", soundGens.length);
|
||||
writefln("Races: ", races.length);
|
||||
writefln("Misc items: ", miscItems.length);
|
||||
writefln("Cloths: ", clothes.length);
|
||||
writefln("Ingredients: ", ingreds.length);
|
||||
writefln("Classes: ", classes.length);
|
||||
writefln("Containers: ", containers.length);
|
||||
writefln("Creatures: ", creatures.length);
|
||||
writefln("Leveled item lists: ", itemLists.length);
|
||||
writefln("Leveled creature lists: ", creatureLists.length);
|
||||
writefln("NPCs: ", npcs.length);
|
||||
writefln("Scripts: ", scripts.length);
|
||||
writefln("Dialogues: ", dialogues.length);
|
||||
writefln("Hyperlinks: ", hyperlinks.list.length);
|
||||
writefln("Start scripts: ", startScripts.length);
|
||||
writefln("\nTotal items: ", items.length);
|
||||
writefln("Total actors: ", actors.length);
|
||||
writefln("Total cell placable items: ", cellRefs.length);
|
||||
}
|
||||
if(gmst)
|
||||
{
|
||||
foreach(a, b; gameSettings.names)
|
||||
{
|
||||
writef(a, " (");
|
||||
if(b.type == VarType.Int) writefln("int) = ", b.i);
|
||||
else if(b.type == VarType.Float) writefln("float) = ", b.f);
|
||||
else if(b.type == VarType.String) writefln("string) = '%s'", b.str);
|
||||
else writefln("no value)", cast(int)b.type);
|
||||
}
|
||||
}
|
||||
|
||||
if(scptList) foreach(a, b; scripts.names) writefln(a);
|
||||
if(ciList)
|
||||
foreach(a, b; cells.in_cells)
|
||||
writefln(a);
|
||||
if(ceList)
|
||||
foreach(uint i, c; .cells.ex_cells)
|
||||
{
|
||||
int x, y;
|
||||
CellList.decompound(i, x, y);
|
||||
if(c.name.length)
|
||||
writefln("%s,%s: %s", x, y, c.name);
|
||||
}
|
||||
|
||||
if(scptShow)
|
||||
{
|
||||
Script *p = scripts.names.lookup(scptName);
|
||||
if(p)
|
||||
writefln("Script '%s', text is:\n-------\n%s\n-------", p.id, p.scriptText);
|
||||
else writefln("Script '%s' not found", scptName);
|
||||
writefln();
|
||||
}
|
||||
|
||||
writefln(esmRegion);
|
||||
|
||||
poolSize();
|
||||
}
|
||||
|
||||
// Quick function that simply iterates through an ES file and prints
|
||||
// out all the records and subrecords. Some of this code is really old
|
||||
// (about 2004-2005)
|
||||
void printRaw()
|
||||
{
|
||||
with(esFile)
|
||||
{
|
||||
// Variable length integer (this only works for unsigned ints!)
|
||||
ulong getHVUint()
|
||||
{
|
||||
ulong l;
|
||||
|
||||
getSubHeader();
|
||||
if( (getSubSize != 4) &&
|
||||
(getSubSize != 2) &&
|
||||
(getSubSize != 8) )
|
||||
fail(format("Unknown integer size: ", getSubSize));
|
||||
|
||||
readExact(&l, getSubSize);
|
||||
return l;
|
||||
}
|
||||
|
||||
writefln("Filename: ", getFilename);
|
||||
writef("Filetype: ");
|
||||
switch(getFileType())
|
||||
{
|
||||
case FileType.Plugin: writefln("Plugin"); break;
|
||||
case FileType.Master: writefln("Master"); break;
|
||||
case FileType.Savegame: writefln("Savegame"); break;
|
||||
case FileType.Unknown: writefln("Unknown"); break;
|
||||
default: assert(0);
|
||||
}
|
||||
writef("Version: ");
|
||||
if(isVer12()) writefln("1.2");
|
||||
else if(isVer13()) writefln("1.3");
|
||||
else writefln("Unknown");
|
||||
|
||||
writefln("Records: ", getRecords);
|
||||
writefln("Master files:");
|
||||
for(int i; i<getMasters.length; i++)
|
||||
writefln(" %s", getMasters[i].name, ", ", getMasters[i].size, " bytes");
|
||||
|
||||
writefln("Author: %s", getAuthor);
|
||||
//writefln("Description: %s", desc);
|
||||
writefln("Total file size: %d\n", getFileSize);
|
||||
|
||||
writefln("List of records:");
|
||||
|
||||
while(hasMoreRecs())
|
||||
{
|
||||
uint flags;
|
||||
|
||||
// Read record header
|
||||
char[] recName = getRecName();
|
||||
getRecHeader(flags);
|
||||
|
||||
if(flags)
|
||||
{
|
||||
writef("Flags: ");
|
||||
if(flags & RecordFlags.Persistent) writef("Persistent ");
|
||||
if(flags & RecordFlags.Blocked) writef("Blocked ");
|
||||
if(flags & RecordFlags.Flag6) writef("Flag6 ");
|
||||
if(flags & RecordFlags.Flag13) writef("Flag13 ");
|
||||
writefln();
|
||||
if(flags & RecordFlags.Unknown)
|
||||
writefln("UNKNOWN flags are set: %xh", flags);
|
||||
}
|
||||
|
||||
// Process sub record
|
||||
writef("%s %d bytes", recName, getRecLeft());
|
||||
writefln();
|
||||
while(hasMoreSubs())
|
||||
{
|
||||
getSubName();
|
||||
char[] subName = retSubName();
|
||||
writef(" %s = ", subName);
|
||||
|
||||
// Process header
|
||||
if(subName == "NAME" || subName == "STRV" ||
|
||||
subName == "FNAM" || subName == "MODL" ||
|
||||
subName == "SCRI" || subName == "RGNN" ||
|
||||
subName == "BNAM" || subName == "ONAM" ||
|
||||
subName == "INAM" || subName == "SCVR" ||
|
||||
subName == "RNAM" || subName == "DNAM" ||
|
||||
subName == "ANAM")
|
||||
//subName == "SCTX") // For script text
|
||||
//getHString();
|
||||
{
|
||||
writefln("'%s'", getHString());
|
||||
}
|
||||
else if(subName == "FLTV" || subName == "XSCL")
|
||||
{
|
||||
float f = getHFloat();
|
||||
writefln("f=", f, " i=", *(cast(int*)&f));
|
||||
}
|
||||
else if(subName == "INTV" /*|| subName == "NAM0"*/ || subName == "FRMR")
|
||||
writefln(getHVUint());
|
||||
else
|
||||
{
|
||||
int left = skipHSub();
|
||||
writefln(left, " bytes");
|
||||
}
|
||||
}
|
||||
writefln();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,336 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (events.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module input.events;
|
||||
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
|
||||
import sound.audio;
|
||||
|
||||
import core.config;
|
||||
|
||||
import scene.soundlist;
|
||||
import scene.player;
|
||||
|
||||
import bullet.bindings;
|
||||
|
||||
import monster.monster;
|
||||
import monster.vm.dbg;
|
||||
|
||||
import ogre.bindings;
|
||||
import gui.bindings;
|
||||
import gui.gui;
|
||||
|
||||
import input.keys;
|
||||
import input.ois;
|
||||
|
||||
// Debug output
|
||||
//debug=printMouse; // Mouse button events
|
||||
//debug=printMouseMove; // Mouse movement events
|
||||
//debug=printKeys; // Keypress events
|
||||
|
||||
// TODO: Jukebox controls and other state-related data will later be
|
||||
// handled entirely in script code, as will some of the key bindings.
|
||||
|
||||
// Pause?
|
||||
bool pause = false;
|
||||
int *guiMode;
|
||||
|
||||
const float volDiff = 0.05;
|
||||
|
||||
void musVolume(bool increase)
|
||||
{
|
||||
float diff = -volDiff;
|
||||
if(increase) diff = -diff;
|
||||
config.setMusicVolume(diff + config.getMusicVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " music volume to ", config.getMusicVolume);
|
||||
}
|
||||
|
||||
void sfxVolume(bool increase)
|
||||
{
|
||||
float diff = -volDiff;
|
||||
if(increase) diff = -diff;
|
||||
config.setSfxVolume(diff + config.getSfxVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " sound effect volume to ", config.getSfxVolume);
|
||||
}
|
||||
|
||||
void mainVolume(bool increase)
|
||||
{
|
||||
float diff = -volDiff;
|
||||
if(increase) diff = -diff;
|
||||
config.setMainVolume(diff + config.getMainVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " main volume to ", config.getMainVolume);
|
||||
}
|
||||
|
||||
void updateMouseSensitivity()
|
||||
{
|
||||
effMX = *config.mouseSensX;
|
||||
effMY = *config.mouseSensY;
|
||||
if(*config.flipMouseY) effMY = -effMY;
|
||||
}
|
||||
|
||||
void togglePause()
|
||||
{
|
||||
pause = !pause;
|
||||
if(pause) writefln("Pause");
|
||||
else writefln("Pause off");
|
||||
}
|
||||
|
||||
extern(C) void d_handleMouseButton(MouseState *state, int button)
|
||||
{
|
||||
debug(printMouse)
|
||||
writefln("handleMouseButton %s: Abs(%s, %s, %s)", button,
|
||||
state.X.abs, state.Y.abs, state.Z.abs);
|
||||
|
||||
// For the moment, just treat mouse clicks as normal key presses.
|
||||
d_handleKey(cast(KC) (KC.Mouse0 + button));
|
||||
}
|
||||
|
||||
// Handle a keyboard event through key bindings. Direct movement
|
||||
// (eg. arrow keys) is not handled here, see d_frameStarted() below.
|
||||
extern(C) void d_handleKey(KC keycode, dchar text = 0)
|
||||
{
|
||||
// Do some preprocessing on the data to account for OIS
|
||||
// shortcommings.
|
||||
|
||||
// Some keys (especially international keys) have no key code but
|
||||
// return a character instead.
|
||||
if(keycode == 0)
|
||||
{
|
||||
// If no character is given, just drop this event since OIS did
|
||||
// not manage to give us any useful data at all.
|
||||
if(text == 0) return;
|
||||
|
||||
keycode = KC.CharOnly;
|
||||
}
|
||||
|
||||
// Debug output
|
||||
debug(printKeys)
|
||||
{
|
||||
char[] str;
|
||||
if(keycode >= 0 && keycode < keysymToString.length)
|
||||
str = keysymToString[keycode];
|
||||
else str = "OUT_OF_RANGE";
|
||||
writefln("Key %s, text '%s', name '%s'", keycode, text, str);
|
||||
}
|
||||
|
||||
// Look up the key binding. We have to send both the keycode and the
|
||||
// text.
|
||||
Keys k = keyBindings.findMatch(keycode, text);
|
||||
|
||||
// These are handled even if we are in gui mode:
|
||||
if(k)
|
||||
switch(k)
|
||||
{
|
||||
case Keys.ToggleGui: gui_toggleGui(); return;
|
||||
case Keys.Console: gui_toggleConsole(); return;
|
||||
case Keys.ScreenShot: takeScreenShot(); return;
|
||||
default:
|
||||
}
|
||||
|
||||
if(*guiMode) return;
|
||||
|
||||
if(k)
|
||||
switch(k)
|
||||
{
|
||||
case Keys.ToggleBattleMusic:
|
||||
Music.toggle();
|
||||
return;
|
||||
|
||||
case Keys.MainVolUp: mainVolume(true); return;
|
||||
case Keys.MainVolDown: mainVolume(false); return;
|
||||
case Keys.MusVolUp: musVolume(true); return;
|
||||
case Keys.MusVolDown: musVolume(false); return;
|
||||
case Keys.SfxVolUp: sfxVolume(true); return;
|
||||
case Keys.SfxVolDown: sfxVolume(false); return;
|
||||
case Keys.Mute: Music.toggleMute(); return;
|
||||
case Keys.Fullscreen: toggleFullscreen(); return;
|
||||
|
||||
case Keys.PhysMode: bullet_nextMode(); return;
|
||||
case Keys.Nighteye: ogre_toggleLight(); return;
|
||||
|
||||
case Keys.Debug: return;
|
||||
case Keys.Pause: togglePause(); return;
|
||||
case Keys.Exit: exitProgram(); return;
|
||||
default:
|
||||
assert(k >= 0 && k < keyToString.length);
|
||||
writefln("WARNING: Event %s has no effect", keyToString[k]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh rate for sfx placements, in seconds.
|
||||
const float sndRefresh = 0.17;
|
||||
|
||||
// Refresh rate for music fadeing, seconds.
|
||||
const float musRefresh = 0.05;
|
||||
|
||||
// Walking / floating speed, in points per second.
|
||||
float speed = 300;
|
||||
|
||||
float sndCumTime = 0;
|
||||
float musCumTime = 0;
|
||||
|
||||
// Move the player according to playerData.position
|
||||
void movePlayer()
|
||||
{
|
||||
// Move the player into place. TODO: This isn't really input-related
|
||||
// at all, and should be moved.
|
||||
with(*playerData.position)
|
||||
{
|
||||
ogre_moveCamera(position[0], position[1], position[2]);
|
||||
ogre_setCameraRotation(rotation[0], rotation[1], rotation[2]);
|
||||
|
||||
bullet_movePlayer(position[0], position[1], position[2]);
|
||||
}
|
||||
}
|
||||
|
||||
void initializeInput()
|
||||
{
|
||||
movePlayer();
|
||||
|
||||
// TODO/FIXME: This should have been in config, but DMD's module
|
||||
// system is on the brink of collapsing, and it won't compile if I
|
||||
// put another import in core.config. I should probably check the
|
||||
// bug list and report it.
|
||||
updateMouseSensitivity();
|
||||
|
||||
// Get a pointer to the 'guiMode' flag in cpp_ogre.cpp
|
||||
guiMode = gui_getGuiModePtr();
|
||||
}
|
||||
|
||||
extern(C) int ois_isPressed(int keysym);
|
||||
|
||||
// Check if a key is currently down
|
||||
bool isPressed(Keys key)
|
||||
{
|
||||
KeyBind *b = &keyBindings.bindings[key];
|
||||
foreach(i; b.syms)
|
||||
if(i != 0 && ois_isPressed(i)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable superman mode, ie. flight and super-speed. Only used for
|
||||
// debugging the terrain mode.
|
||||
extern(C) void d_terr_superman()
|
||||
{
|
||||
bullet_fly();
|
||||
speed = 8000;
|
||||
|
||||
with(*playerData.position)
|
||||
{
|
||||
position[0] = 20000;
|
||||
position[1] = -70000;
|
||||
position[2] = 30000;
|
||||
}
|
||||
movePlayer();
|
||||
}
|
||||
|
||||
extern(C) int d_frameStarted(float time)
|
||||
{
|
||||
if(doExit) return 0;
|
||||
|
||||
dbg.trace("d_frameStarted");
|
||||
scope(exit) dbg.untrace();
|
||||
|
||||
// Run the Monster scheduler
|
||||
vm.frame(time);
|
||||
|
||||
musCumTime += time;
|
||||
if(musCumTime > musRefresh)
|
||||
{
|
||||
Music.updateBuffers();
|
||||
musCumTime -= musRefresh;
|
||||
}
|
||||
|
||||
// The rest is ignored in pause or GUI mode
|
||||
if(pause || *guiMode > 0) return 1;
|
||||
|
||||
// Check if the movement keys are pressed
|
||||
float moveX = 0, moveY = 0, moveZ = 0;
|
||||
float x, y, z, ox, oy, oz;
|
||||
|
||||
if(isPressed(Keys.MoveLeft)) moveX -= speed;
|
||||
if(isPressed(Keys.MoveRight)) moveX += speed;
|
||||
if(isPressed(Keys.MoveForward)) moveZ -= speed;
|
||||
if(isPressed(Keys.MoveBackward)) moveZ += speed;
|
||||
|
||||
// TODO: These should be enabled for floating modes (like swimming
|
||||
// and levitation) and disabled for everything else.
|
||||
if(isPressed(Keys.MoveUp)) moveY += speed;
|
||||
if(isPressed(Keys.MoveDown)) moveY -= speed;
|
||||
|
||||
// This isn't very elegant, but it's simple and it works.
|
||||
|
||||
// Get the current coordinates
|
||||
ogre_getCameraPos(&ox, &oy, &oz);
|
||||
|
||||
// Move camera using relative coordinates. TODO: We won't really
|
||||
// need to move the camera here (since it's moved below anyway), we
|
||||
// only want the transformation from camera space to world
|
||||
// space. This can likely be done more efficiently.
|
||||
ogre_moveCameraRel(moveX, moveY, moveZ);
|
||||
|
||||
// Get the result
|
||||
ogre_getCameraPos(&x, &y, &z);
|
||||
|
||||
// The result is the real movement direction, in world coordinates
|
||||
moveX = x-ox;
|
||||
moveY = y-oy;
|
||||
moveZ = z-oz;
|
||||
|
||||
// Tell Bullet that this is where we want to go
|
||||
bullet_setPlayerDir(moveX, moveY, moveZ);
|
||||
|
||||
// Perform a Bullet time step
|
||||
bullet_timeStep(time);
|
||||
|
||||
// Get the final (actual) player position and update the camera
|
||||
bullet_getPlayerPos(&x, &y, &z);
|
||||
ogre_moveCamera(x,y,z);
|
||||
|
||||
// Store it in the player object
|
||||
playerData.position.position[0] = x;
|
||||
playerData.position.position[1] = y;
|
||||
playerData.position.position[2] = z;
|
||||
|
||||
if(!config.noSound)
|
||||
{
|
||||
// Tell the sound scene that the player has moved
|
||||
sndCumTime += time;
|
||||
if(sndCumTime > sndRefresh)
|
||||
{
|
||||
float fx, fy, fz;
|
||||
float ux, uy, uz;
|
||||
|
||||
ogre_getCameraOrientation(&fx, &fy, &fz, &ux, &uy, &uz);
|
||||
|
||||
soundScene.update(x,y,z,fx,fy,fz,ux,uy,uz);
|
||||
sndCumTime -= sndRefresh;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool collides = false;
|
@ -1,428 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (ois.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module input.ois;
|
||||
|
||||
// Mouse buttons
|
||||
enum MB : int
|
||||
{
|
||||
Button0 = 0,
|
||||
Left = Button0,
|
||||
Button1 = 1,
|
||||
Right = Button1,
|
||||
Button2 = 2,
|
||||
Middle = Button2,
|
||||
|
||||
Button3 = 3,
|
||||
Button4 = 4,
|
||||
Button5 = 5,
|
||||
Button6 = 6,
|
||||
Button7 = 7,
|
||||
|
||||
LastMouse
|
||||
}
|
||||
|
||||
// Keyboard scan codes
|
||||
enum KC : int
|
||||
{
|
||||
UNASSIGNED = 0x00,
|
||||
ESCAPE = 0x01,
|
||||
N1 = 0x02,
|
||||
N2 = 0x03,
|
||||
N3 = 0x04,
|
||||
N4 = 0x05,
|
||||
N5 = 0x06,
|
||||
N6 = 0x07,
|
||||
N7 = 0x08,
|
||||
N8 = 0x09,
|
||||
N9 = 0x0A,
|
||||
N0 = 0x0B,
|
||||
MINUS = 0x0C, // - on main keyboard
|
||||
EQUALS = 0x0D,
|
||||
BACK = 0x0E, // backspace
|
||||
TAB = 0x0F,
|
||||
Q = 0x10,
|
||||
W = 0x11,
|
||||
E = 0x12,
|
||||
R = 0x13,
|
||||
T = 0x14,
|
||||
Y = 0x15,
|
||||
U = 0x16,
|
||||
I = 0x17,
|
||||
O = 0x18,
|
||||
P = 0x19,
|
||||
LBRACKET = 0x1A,
|
||||
RBRACKET = 0x1B,
|
||||
RETURN = 0x1C, // Enter on main keyboard
|
||||
LCONTROL = 0x1D,
|
||||
A = 0x1E,
|
||||
S = 0x1F,
|
||||
D = 0x20,
|
||||
F = 0x21,
|
||||
G = 0x22,
|
||||
H = 0x23,
|
||||
J = 0x24,
|
||||
K = 0x25,
|
||||
L = 0x26,
|
||||
SEMICOLON = 0x27,
|
||||
APOSTROPHE = 0x28,
|
||||
GRAVE = 0x29, // accent
|
||||
LSHIFT = 0x2A,
|
||||
BACKSLASH = 0x2B,
|
||||
Z = 0x2C,
|
||||
X = 0x2D,
|
||||
C = 0x2E,
|
||||
V = 0x2F,
|
||||
B = 0x30,
|
||||
N = 0x31,
|
||||
M = 0x32,
|
||||
COMMA = 0x33,
|
||||
PERIOD = 0x34, // . on main keyboard
|
||||
SLASH = 0x35, // / on main keyboard
|
||||
RSHIFT = 0x36,
|
||||
MULTIPLY = 0x37, // * on numeric keypad
|
||||
LMENU = 0x38, // left Alt
|
||||
SPACE = 0x39,
|
||||
CAPITAL = 0x3A,
|
||||
F1 = 0x3B,
|
||||
F2 = 0x3C,
|
||||
F3 = 0x3D,
|
||||
F4 = 0x3E,
|
||||
F5 = 0x3F,
|
||||
F6 = 0x40,
|
||||
F7 = 0x41,
|
||||
F8 = 0x42,
|
||||
F9 = 0x43,
|
||||
F10 = 0x44,
|
||||
NUMLOCK = 0x45,
|
||||
SCROLL = 0x46, // Scroll Lock
|
||||
NUMPAD7 = 0x47,
|
||||
NUMPAD8 = 0x48,
|
||||
NUMPAD9 = 0x49,
|
||||
SUBTRACT = 0x4A, // - on numeric keypad
|
||||
NUMPAD4 = 0x4B,
|
||||
NUMPAD5 = 0x4C,
|
||||
NUMPAD6 = 0x4D,
|
||||
ADD = 0x4E, // + on numeric keypad
|
||||
NUMPAD1 = 0x4F,
|
||||
NUMPAD2 = 0x50,
|
||||
NUMPAD3 = 0x51,
|
||||
NUMPAD0 = 0x52,
|
||||
DECIMAL = 0x53, // . on numeric keypad
|
||||
OEM_102 = 0x56, // < > | on UK/Germany keyboards
|
||||
F11 = 0x57,
|
||||
F12 = 0x58,
|
||||
F13 = 0x64, // (NEC PC98)
|
||||
F14 = 0x65, // (NEC PC98)
|
||||
F15 = 0x66, // (NEC PC98)
|
||||
KANA = 0x70, // (Japanese keyboard)
|
||||
ABNT_C1 = 0x73, // / ? on Portugese (Brazilian) keyboards
|
||||
CONVERT = 0x79, // (Japanese keyboard)
|
||||
NOCONVERT = 0x7B, // (Japanese keyboard)
|
||||
YEN = 0x7D, // (Japanese keyboard)
|
||||
ABNT_C2 = 0x7E, // Numpad . on Portugese (Brazilian) keyboards
|
||||
NUMPADEQUALS= 0x8D, // = on numeric keypad (NEC PC98)
|
||||
PREVTRACK = 0x90, // Previous Track (CIRCUMFLEX on Japanese keyboard)
|
||||
AT = 0x91, // (NEC PC98)
|
||||
COLON = 0x92, // (NEC PC98)
|
||||
UNDERLINE = 0x93, // (NEC PC98)
|
||||
KANJI = 0x94, // (Japanese keyboard)
|
||||
STOP = 0x95, // (NEC PC98)
|
||||
AX = 0x96, // (Japan AX)
|
||||
UNLABELED = 0x97, // (J3100)
|
||||
NEXTTRACK = 0x99, // Next Track
|
||||
NUMPADENTER = 0x9C, // Enter on numeric keypad
|
||||
RCONTROL = 0x9D,
|
||||
MUTE = 0xA0, // Mute
|
||||
CALCULATOR = 0xA1, // Calculator
|
||||
PLAYPAUSE = 0xA2, // Play / Pause
|
||||
MEDIASTOP = 0xA4, // Media Stop
|
||||
VOLUMEDOWN = 0xAE, // Volume -
|
||||
VOLUMEUP = 0xB0, // Volume +
|
||||
WEBHOME = 0xB2, // Web home
|
||||
NUMPADCOMMA = 0xB3, // , on numeric keypad (NEC PC98)
|
||||
DIVIDE = 0xB5, // / on numeric keypad
|
||||
SYSRQ = 0xB7, // Also called print screen
|
||||
RMENU = 0xB8, // right Alt
|
||||
PAUSE = 0xC5, // Pause
|
||||
HOME = 0xC7, // Home on arrow keypad
|
||||
UP = 0xC8, // UpArrow on arrow keypad
|
||||
PGUP = 0xC9, // PgUp on arrow keypad
|
||||
LEFT = 0xCB, // LeftArrow on arrow keypad
|
||||
RIGHT = 0xCD, // RightArrow on arrow keypad
|
||||
END = 0xCF, // End on arrow keypad
|
||||
DOWN = 0xD0, // DownArrow on arrow keypad
|
||||
PGDOWN = 0xD1, // PgDn on arrow keypad
|
||||
INSERT = 0xD2, // Insert on arrow keypad
|
||||
DELETE = 0xD3, // Delete on arrow keypad
|
||||
LWIN = 0xDB, // Left Windows key
|
||||
RWIN = 0xDC, // Right Windows key
|
||||
APPS = 0xDD, // AppMenu key
|
||||
POWER = 0xDE, // System Power
|
||||
SLEEP = 0xDF, // System Sleep
|
||||
WAKE = 0xE3, // System Wake
|
||||
WEBSEARCH = 0xE5, // Web Search
|
||||
WEBFAVORITES= 0xE6, // Web Favorites
|
||||
WEBREFRESH = 0xE7, // Web Refresh
|
||||
WEBSTOP = 0xE8, // Web Stop
|
||||
WEBFORWARD = 0xE9, // Web Forward
|
||||
WEBBACK = 0xEA, // Web Back
|
||||
MYCOMPUTER = 0xEB, // My Computer
|
||||
MAIL = 0xEC, // Mail
|
||||
MEDIASELECT = 0xED, // Media Select
|
||||
|
||||
CharOnly = 0xFF, // Set when the keysym is 0 but the
|
||||
// character is set. This happens with many
|
||||
// international characters or reassigned
|
||||
// characters.
|
||||
|
||||
Mouse0 = 0x100, // Mouse button events can be handled as
|
||||
Mouse1 = 0x101, // keypresses too.
|
||||
Mouse2 = 0x102,
|
||||
Mouse3 = 0x103,
|
||||
Mouse4 = 0x104,
|
||||
Mouse5 = 0x105,
|
||||
Mouse6 = 0x106,
|
||||
Mouse7 = 0x107,
|
||||
}
|
||||
|
||||
// Sigh. I guess we have to do this for Monster at some poing anyway,
|
||||
// so this work isn't completely wasted. Later we can make a generic
|
||||
// conversion between OIS-keysyms, SDL-keysyms and others to the
|
||||
// Monster keysyms. It sucks that everybody tries to reinvent the
|
||||
// wheel as often as they can, but that's the way it goes.
|
||||
|
||||
const char[][] keysymToString =
|
||||
[
|
||||
KC.UNASSIGNED : "UNASSIGNED",
|
||||
KC.ESCAPE : "escape",
|
||||
KC.N1 : "1",
|
||||
KC.N2 : "2",
|
||||
KC.N3 : "3",
|
||||
KC.N4 : "4",
|
||||
KC.N5 : "5",
|
||||
KC.N6 : "6",
|
||||
KC.N7 : "7",
|
||||
KC.N8 : "8",
|
||||
KC.N9 : "9",
|
||||
KC.N0 : "0",
|
||||
KC.MINUS : "minus",
|
||||
KC.EQUALS : "equals",
|
||||
KC.BACK : "backspace",
|
||||
KC.TAB : "tab",
|
||||
KC.Q : "q",
|
||||
KC.W : "w",
|
||||
KC.E : "e",
|
||||
KC.R : "r",
|
||||
KC.T : "t",
|
||||
KC.Y : "y",
|
||||
KC.U : "u",
|
||||
KC.I : "i",
|
||||
KC.O : "o",
|
||||
KC.P : "p",
|
||||
KC.LBRACKET : "{",
|
||||
KC.RBRACKET : "}",
|
||||
KC.RETURN : "enter",
|
||||
KC.LCONTROL : "left_ctrl",
|
||||
KC.A : "a",
|
||||
KC.S : "s",
|
||||
KC.D : "d",
|
||||
KC.F : "f",
|
||||
KC.G : "g",
|
||||
KC.H : "h",
|
||||
KC.J : "j",
|
||||
KC.K : "k",
|
||||
KC.L : "l",
|
||||
KC.SEMICOLON : "semicolon",
|
||||
KC.APOSTROPHE : "apostrophe",
|
||||
KC.GRAVE : "grave",
|
||||
KC.LSHIFT : "left_shift",
|
||||
KC.BACKSLASH : "backslash",
|
||||
KC.Z : "z",
|
||||
KC.X : "x",
|
||||
KC.C : "c",
|
||||
KC.V : "v",
|
||||
KC.B : "b",
|
||||
KC.N : "n",
|
||||
KC.M : "m",
|
||||
KC.COMMA : "comma",
|
||||
KC.PERIOD : "period",
|
||||
KC.SLASH : "slash",
|
||||
KC.RSHIFT : "right_shift",
|
||||
KC.MULTIPLY : "numpad_mult",
|
||||
KC.LMENU : "left_alt",
|
||||
KC.SPACE : "space",
|
||||
KC.CAPITAL : "capital",
|
||||
KC.F1 : "f1",
|
||||
KC.F2 : "f2",
|
||||
KC.F3 : "f3",
|
||||
KC.F4 : "f4",
|
||||
KC.F5 : "f5",
|
||||
KC.F6 : "f6",
|
||||
KC.F7 : "f7",
|
||||
KC.F8 : "f8",
|
||||
KC.F9 : "f9",
|
||||
KC.F10 : "f10",
|
||||
KC.NUMLOCK : "numlock",
|
||||
KC.SCROLL : "scroll",
|
||||
KC.NUMPAD7 : "numpad_7",
|
||||
KC.NUMPAD8 : "numpad_8",
|
||||
KC.NUMPAD9 : "numpad_9",
|
||||
KC.SUBTRACT : "numpad_minus",
|
||||
KC.NUMPAD4 : "numpad_4",
|
||||
KC.NUMPAD5 : "numpad_5",
|
||||
KC.NUMPAD6 : "numpad_6",
|
||||
KC.ADD : "numpad_plus",
|
||||
KC.NUMPAD1 : "numpad_1",
|
||||
KC.NUMPAD2 : "numpad_2",
|
||||
KC.NUMPAD3 : "numpad_3",
|
||||
KC.NUMPAD0 : "numpad_0",
|
||||
KC.DECIMAL : "numpad_period",
|
||||
KC.OEM_102 : "oem102",
|
||||
KC.F11 : "f11",
|
||||
KC.F12 : "f12",
|
||||
KC.F13 : "f13",
|
||||
KC.F14 : "f14",
|
||||
KC.F15 : "f15",
|
||||
KC.KANA : "kana",
|
||||
KC.ABNT_C1 : "abnt_c1",
|
||||
KC.CONVERT : "convert",
|
||||
KC.NOCONVERT : "noconvert",
|
||||
KC.YEN : "yen",
|
||||
KC.ABNT_C2 : "abnt_c2",
|
||||
KC.NUMPADEQUALS: "numpad_equals",
|
||||
KC.PREVTRACK : "prev_track",
|
||||
KC.AT : "at",
|
||||
KC.COLON : "colon",
|
||||
KC.UNDERLINE : "underline",
|
||||
KC.KANJI : "kanji",
|
||||
KC.STOP : "stop",
|
||||
KC.AX : "ax",
|
||||
KC.UNLABELED : "unlabeled",
|
||||
KC.NEXTTRACK : "next_track",
|
||||
KC.NUMPADENTER : "numpad_enter",
|
||||
KC.RCONTROL : "right_control",
|
||||
KC.MUTE : "mute",
|
||||
KC.CALCULATOR : "calculator",
|
||||
KC.PLAYPAUSE : "play_pause",
|
||||
KC.MEDIASTOP : "media_stop",
|
||||
KC.VOLUMEDOWN : "volume_down",
|
||||
KC.VOLUMEUP : "volume_up",
|
||||
KC.WEBHOME : "webhome",
|
||||
KC.NUMPADCOMMA : "numpad_comma",
|
||||
KC.DIVIDE : "numpad_divide",
|
||||
KC.SYSRQ : "print_screen",
|
||||
KC.RMENU : "right_alt",
|
||||
KC.PAUSE : "pause",
|
||||
KC.HOME : "home",
|
||||
KC.UP : "up",
|
||||
KC.PGUP : "page_up",
|
||||
KC.LEFT : "left",
|
||||
KC.RIGHT : "right",
|
||||
KC.END : "end",
|
||||
KC.DOWN : "down",
|
||||
KC.PGDOWN : "page_down",
|
||||
KC.INSERT : "insert",
|
||||
KC.DELETE : "delete",
|
||||
KC.LWIN : "left_win",
|
||||
KC.RWIN : "right_win",
|
||||
KC.APPS : "app_menu",
|
||||
KC.POWER : "power",
|
||||
KC.SLEEP : "sleep",
|
||||
KC.WAKE : "wake",
|
||||
KC.WEBSEARCH : "web_search",
|
||||
KC.WEBFAVORITES: "web_favorites",
|
||||
KC.WEBREFRESH : "web_refresh",
|
||||
KC.WEBSTOP : "web_stop",
|
||||
KC.WEBFORWARD : "web_forward",
|
||||
KC.WEBBACK : "web_back",
|
||||
KC.MYCOMPUTER : "my_computer",
|
||||
KC.MAIL : "mail",
|
||||
KC.MEDIASELECT : "media_select",
|
||||
|
||||
|
||||
KC.CharOnly : "CHAR_ONLY", // Set when the keysym is 0 but the
|
||||
// character is set. This happens
|
||||
// with many international
|
||||
// characters or reassigned
|
||||
// characters in OIS (and it
|
||||
// SUCKS.)
|
||||
|
||||
KC.Mouse0 : "mouse0",
|
||||
KC.Mouse1 : "mouse1",
|
||||
KC.Mouse2 : "mouse2",
|
||||
KC.Mouse3 : "mouse3",
|
||||
KC.Mouse4 : "mouse4",
|
||||
KC.Mouse5 : "mouse5",
|
||||
KC.Mouse6 : "mouse6",
|
||||
KC.Mouse7 : "mouse7",
|
||||
];
|
||||
|
||||
enum ComponentType : int
|
||||
{
|
||||
Unknown = 0,
|
||||
Button = 1, // ie. Key, mouse button, joy button, etc
|
||||
Axis = 2, // ie. A joystick or mouse axis
|
||||
Slider = 3, //
|
||||
POV = 4, // ie. Arrow direction keys
|
||||
Vector3 = 5 // ie. WiiMote orientation
|
||||
}
|
||||
|
||||
align(4) struct Axis
|
||||
{
|
||||
ComponentType type;
|
||||
int abs, rel;
|
||||
bool absOnly;
|
||||
}
|
||||
|
||||
// The C++ size of Axis is 16
|
||||
static assert(Axis.sizeof == 16);
|
||||
|
||||
struct MouseState
|
||||
{
|
||||
/* Represents the height/width of your display area.. used if mouse
|
||||
clipping or mouse grabbed in case of X11 - defaults to 50.. Make
|
||||
sure to set this and change when your size changes.. */
|
||||
int width, height;
|
||||
|
||||
// X Axis component
|
||||
Axis X;
|
||||
|
||||
// Y Axis Component
|
||||
Axis Y;
|
||||
|
||||
// Z Axis Component
|
||||
Axis Z;
|
||||
|
||||
// represents all buttons - bit position indicates button down
|
||||
int buttons;
|
||||
|
||||
// Button down test
|
||||
bool buttonDown( MB button )
|
||||
{
|
||||
return (buttons & ( 1 << button )) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we match the C++ size
|
||||
static assert(MouseState.sizeof == 60);
|
@ -1,41 +0,0 @@
|
||||
// Get current camera orientation, in the form of 'front' and 'up'
|
||||
// vectors.
|
||||
extern "C" void ogre_getCameraOrientation(float *fx, float *fy, float *fz,
|
||||
float *ux, float *uy, float *uz)
|
||||
{
|
||||
Vector3 front = mCamera->getDirection();
|
||||
Vector3 up = mCamera->getUp();
|
||||
*fx = front[0];
|
||||
*fy = -front[2];
|
||||
*fz = front[1];
|
||||
*ux = up[0];
|
||||
*uy = -up[2];
|
||||
*uz = up[1];
|
||||
}
|
||||
|
||||
// Move camera
|
||||
extern "C" void ogre_moveCamera(float x, float y, float z)
|
||||
{
|
||||
// Transforms Morrowind coordinates to OGRE coordinates. The camera
|
||||
// is not affected by the rotation of the root node, so we must
|
||||
// transform this manually.
|
||||
mCamera->setPosition(Vector3(x,z+90,-y));
|
||||
}
|
||||
|
||||
// Rotate camera using Morrowind rotation specifiers
|
||||
extern "C" void ogre_setCameraRotation(float r1, float r2, float r3)
|
||||
{
|
||||
// TODO: This translation is probably not correct, but for now I
|
||||
// have no reference point. Fix it later when we teleport from one
|
||||
// cell to another, so we have something to compare against.
|
||||
|
||||
// Rotate around X axis
|
||||
Quaternion xr(Radian(-r1), Vector3::UNIT_X);
|
||||
// Rotate around Y axis
|
||||
Quaternion yr(Radian(r3+3.14), Vector3::UNIT_Y);
|
||||
// Rotate around Z axis
|
||||
Quaternion zr(Radian(-r2), Vector3::UNIT_Z);
|
||||
|
||||
// Rotates first around z, then y, then x
|
||||
mCamera->setOrientation(xr*yr*zr);
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
// Copy a scene node and all its children
|
||||
void cloneNode(SceneNode *from, SceneNode *to, char* name)
|
||||
{
|
||||
to->setPosition(from->getPosition());
|
||||
to->setOrientation(from->getOrientation());
|
||||
to->setScale(from->getScale());
|
||||
|
||||
SceneNode::ObjectIterator it = from->getAttachedObjectIterator();
|
||||
while(it.hasMoreElements())
|
||||
{
|
||||
// We can't handle non-entities.
|
||||
Entity *e = dynamic_cast<Entity*> (it.getNext());
|
||||
if(e)
|
||||
{
|
||||
e = e->clone(String(name) + ":" + e->getName());
|
||||
to->attachObject(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively clone all child nodes
|
||||
SceneNode::ChildNodeIterator it2 = from->getChildIterator();
|
||||
while(it2.hasMoreElements())
|
||||
{
|
||||
cloneNode((SceneNode*)it2.getNext(), to->createChildSceneNode(), name);
|
||||
}
|
||||
}
|
||||
|
||||
// Supposed to insert a copy of the node, for now it just inserts the
|
||||
// actual node.
|
||||
extern "C" SceneNode *ogre_insertNode(SceneNode *base, char* name,
|
||||
float *pos, float *quat,
|
||||
float scale)
|
||||
{
|
||||
//std::cout << "ogre_insertNode(" << name << ")\n";
|
||||
SceneNode *node = mwRoot->createChildSceneNode(name);
|
||||
|
||||
// Make a copy of the node
|
||||
cloneNode(base, node, name);
|
||||
|
||||
// Apply transformations
|
||||
node->setPosition(pos[0], pos[1], pos[2]);
|
||||
node->setOrientation(quat[0], quat[1], quat[2], quat[3]);
|
||||
|
||||
node->setScale(scale, scale, scale);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// Get the world transformation of a node (the total transformation of
|
||||
// this node and all parent nodes). Return it as a translation
|
||||
// (3-vector) and a rotation / scaling part (3x3 matrix)
|
||||
extern "C" void ogre_getWorldTransform(SceneNode *node,
|
||||
float *trans, // Storage for translation
|
||||
float *matrix)// For 3x3 matrix
|
||||
{
|
||||
// Get the world transformation first
|
||||
Matrix4 trafo;
|
||||
node->getWorldTransforms(&trafo);
|
||||
|
||||
// Extract the translation part and pass it to the caller
|
||||
Vector3 tr = trafo.getTrans();
|
||||
trans[0] = tr[0];
|
||||
trans[1] = tr[1];
|
||||
trans[2] = tr[2];
|
||||
|
||||
// Next extract the matrix
|
||||
Matrix3 mat;
|
||||
trafo.extract3x3Matrix(mat);
|
||||
matrix[0] = mat[0][0];
|
||||
matrix[1] = mat[0][1];
|
||||
matrix[2] = mat[0][2];
|
||||
matrix[3] = mat[1][0];
|
||||
matrix[4] = mat[1][1];
|
||||
matrix[5] = mat[1][2];
|
||||
matrix[6] = mat[2][0];
|
||||
matrix[7] = mat[2][1];
|
||||
matrix[8] = mat[2][2];
|
||||
}
|
||||
|
||||
// Create the water plane. It doesn't really resemble "water" yet
|
||||
// though.
|
||||
extern "C" void ogre_createWater(float level)
|
||||
{
|
||||
// Create a plane aligned with the xy-plane.
|
||||
MeshManager::getSingleton().createPlane("water",
|
||||
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Plane(Vector3::UNIT_Z, level),
|
||||
150000,150000
|
||||
);
|
||||
Entity *ent = mSceneMgr->createEntity( "WaterEntity", "water" );
|
||||
mwRoot->createChildSceneNode()->attachObject(ent);
|
||||
ent->setCastShadows(false);
|
||||
}
|
||||
|
||||
extern "C" SceneNode *ogre_getDetachedNode()
|
||||
{
|
||||
SceneNode *node = mwRoot->createChildSceneNode();
|
||||
mwRoot->removeChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
extern "C" SceneNode* ogre_createNode(
|
||||
char *name,
|
||||
float *trafo,
|
||||
SceneNode *parent,
|
||||
int32_t noRot)
|
||||
{
|
||||
//std::cout << "ogre_createNode(" << name << ")";
|
||||
SceneNode *node = parent->createChildSceneNode(name);
|
||||
//std::cout << " ... done\n";
|
||||
|
||||
// First is the translation vector
|
||||
|
||||
// TODO should be "if(!noRot)" only for exterior cells!? Yay for
|
||||
// consistency. Apparently, the displacement of the base node in NIF
|
||||
// files must be ignored for meshes in interior cells, but not for
|
||||
// exterior cells. Or at least that's my hypothesis, and it seems
|
||||
// work. There might be some other NIF trickery going on though, you
|
||||
// never know when you're reverse engineering someone else's file
|
||||
// format. We will handle this later.
|
||||
if(!noRot)
|
||||
node->setPosition(trafo[0], trafo[1], trafo[2]);
|
||||
|
||||
// Then a 3x3 rotation matrix.
|
||||
if(!noRot)
|
||||
node->setOrientation(Quaternion(Matrix3(trafo[3], trafo[4], trafo[5],
|
||||
trafo[6], trafo[7], trafo[8],
|
||||
trafo[9], trafo[10], trafo[11]
|
||||
)));
|
||||
|
||||
// Scale is at the end
|
||||
node->setScale(trafo[12],trafo[12],trafo[12]);
|
||||
|
||||
return node;
|
||||
}
|
@ -1,391 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (meshloader.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module ogre.meshloader;
|
||||
|
||||
import std.stdio;
|
||||
import std.stream;
|
||||
|
||||
import nif.nif;
|
||||
import nif.record;
|
||||
|
||||
import core.resource;
|
||||
import ogre.bindings;
|
||||
|
||||
import bullet.bindings;
|
||||
|
||||
import util.uniquename;
|
||||
|
||||
/*
|
||||
There are some problems that will have to be looked into later:
|
||||
|
||||
- Some meshes crash Ogre when shadows are turned on. (Not tested in
|
||||
newer versions of Ogre). Shadows are completely disabled for now.
|
||||
- There are obviously some boundry problems, some times the mesh
|
||||
disappears even though some part of it is inside the screen. This
|
||||
is especially a problem with animated meshes, since the animation
|
||||
might step outside the original bounding box.
|
||||
*/
|
||||
|
||||
MeshLoader meshLoader;
|
||||
|
||||
struct MeshLoader
|
||||
{
|
||||
// Not sure how to handle the bounding box, just ignore it for now.
|
||||
|
||||
char[] baseName; // NIF file name. Used in scene node names etc. so
|
||||
// that we can identify where they came from in
|
||||
// case of error messages.
|
||||
|
||||
// Load a NIF mesh. Assumes nifMesh is already opened. This creates
|
||||
// a "template" scene node containing this mesh, and removes it from
|
||||
// the main scene. This node can later be "cloned" so that multiple
|
||||
// instances of the object can be inserted into the world without
|
||||
// inserting the mesh more than once.
|
||||
void loadMesh(char[] name, out NodePtr base, out BulletShape shape)
|
||||
{
|
||||
baseName = name;
|
||||
|
||||
// Check if the first record is a node
|
||||
Node n = cast(Node) nifMesh.records[0];
|
||||
|
||||
if(n is null)
|
||||
{
|
||||
// TODO: Figure out what to do in this case, we should
|
||||
// probably throw.
|
||||
writefln("NIF '%s' IS NOT A MESH", name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a fresh SceneNode and detatch it from the root. We use this
|
||||
// as the base for our mesh.
|
||||
base = ogre_getDetachedNode();
|
||||
|
||||
// Recursively insert nodes (don't rotate the first node)
|
||||
insertNode(n, base, 0, true);
|
||||
|
||||
// Get the final shape, if any
|
||||
shape = bullet_getFinalShape();
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void insertNode(Node data, NodePtr parent,
|
||||
int flags,
|
||||
bool noRot = false)
|
||||
{
|
||||
// Add the flags to the previous node's flags
|
||||
flags = data.flags | flags;
|
||||
|
||||
// Create a scene node, move and rotate it into place. The name
|
||||
// must be unique, however we might have to recognize some special
|
||||
// names later, in order to attach arms and legs on NPCs
|
||||
// etc. Always ignore transformation of the first node? This is a
|
||||
// problem, I don't know when to do this and when not to. Neither
|
||||
// is always right. Update: I originally thought noRot should be
|
||||
// false for exteriors and true for interiors, but this isn't so.
|
||||
NodePtr node = ogre_createNode(UniqueName(data.name).ptr, &data.trafo,
|
||||
parent, cast(int)noRot);
|
||||
|
||||
// Handle any general properties here
|
||||
|
||||
// Call functions that do node-specific things, like handleNiNode
|
||||
// or handleNiTriShape.
|
||||
{
|
||||
NiNode n = cast(NiNode)data;
|
||||
if(n !is null)
|
||||
// Handle the NiNode, and any children it might have
|
||||
handleNiNode(n, node, flags);
|
||||
}
|
||||
|
||||
{
|
||||
NiTriShape n = cast(NiTriShape)data;
|
||||
if(n !is null)
|
||||
// Trishape, with a mesh
|
||||
handleNiTriShape(n, node, flags);
|
||||
}
|
||||
}
|
||||
|
||||
void handleNiNode(NiNode data, NodePtr node, int flags)
|
||||
{
|
||||
// Ignore sound activators and similar objects.
|
||||
NiStringExtraData d = cast(NiStringExtraData) data.extra;
|
||||
if(d !is null)
|
||||
{
|
||||
// Marker objects are only visible in the editor. We
|
||||
// completely ignore them.
|
||||
if(d.string == "MRK")
|
||||
return;
|
||||
|
||||
// No collision
|
||||
if(d.string == "NCO")
|
||||
flags |= 0x800; // Temporary internal marker
|
||||
}
|
||||
|
||||
// Handle any effects here
|
||||
|
||||
// In the cases where meshes have skeletal animations, we must
|
||||
// insert the children as bones in a skeleton instead, like we
|
||||
// originally did for all nodes. Update: A much better way is to
|
||||
// first insert the nodes normally, and then create the
|
||||
// skeleton. The nodes can then be moved one by one over to the
|
||||
// appropriate bones.
|
||||
|
||||
// Check the controller
|
||||
auto cont = data.controller;
|
||||
while(cont !is null)
|
||||
{
|
||||
auto kc = cast(NiKeyframeController)cont;
|
||||
auto pc = cast(NiPathController)cont;
|
||||
if(kc !is null)
|
||||
{
|
||||
/*
|
||||
writefln("Found keyframe controller");
|
||||
writefln(" Node name was: %s", data.name);
|
||||
assert(cont.target is data);
|
||||
|
||||
auto kcd = kc.data;
|
||||
writefln(" Types: %s %s %s",
|
||||
kcd.rotType, kcd.traType, kcd.scaleType);
|
||||
*/
|
||||
|
||||
/*
|
||||
Adding keyframes:
|
||||
|
||||
Skeleton -> Animation -> NodeAnimationTrack nt;
|
||||
|
||||
TransformKeyFrame * tf = nt->createNodeKeyFrame(time);
|
||||
tf->setTranslate(Vector3);
|
||||
tf->setScale(Vector3);
|
||||
tf->setRotation(Quaternion);
|
||||
|
||||
nt->applyToNode(node, time);
|
||||
evt
|
||||
Animation an;
|
||||
an->apply(skeleton, time);
|
||||
*/
|
||||
}
|
||||
else if(pc !is null)
|
||||
{
|
||||
//writefln("Found path controller");
|
||||
assert(cont.target is data);
|
||||
}
|
||||
//else writefln("Other controller (%s)", cont);
|
||||
cont = cont.next;
|
||||
}
|
||||
|
||||
// Loop through children
|
||||
foreach(Node n; data.children)
|
||||
insertNode(n, node, flags);
|
||||
}
|
||||
|
||||
void handleNiTriShape(NiTriShape shape, NodePtr node, int flags)
|
||||
{
|
||||
char[] texture;
|
||||
char[] material;
|
||||
char[] newName = UniqueName(baseName);
|
||||
NiMaterialProperty mp;
|
||||
|
||||
// Special alpha settings, if the NiAlphaProperty is present
|
||||
int alphaFlags = -1;
|
||||
ubyte alphaTest;
|
||||
|
||||
bool hidden = (flags & 0x01) != 0; // Not displayed
|
||||
bool collide = (flags & 0x02) != 0; // Use this mesh for collision
|
||||
bool bbcollide = (flags & 0x04) != 0; // Use bounding box for
|
||||
// collision
|
||||
// Always use mesh collision for now
|
||||
if(bbcollide) collide = true;
|
||||
bbcollide = false;
|
||||
|
||||
// Things marked "NCO" should not collide with anything.
|
||||
if(flags & 0x800)
|
||||
{ collide = false; bbcollide=false; }
|
||||
|
||||
// Skip the entire material phase for hidden nodes
|
||||
if(hidden) goto nomaterial;
|
||||
|
||||
// Scan the property list for textures
|
||||
foreach(Property p; shape.properties)
|
||||
{
|
||||
// NiTexturingProperty block
|
||||
{
|
||||
NiTexturingProperty t = cast(NiTexturingProperty) p;
|
||||
if(t !is null && t.textures[0].inUse)
|
||||
{
|
||||
// Ignore all other options for now
|
||||
NiSourceTexture st = t.textures[0].texture;
|
||||
if(st.external)
|
||||
{
|
||||
// Find the resource for this texture
|
||||
TextureIndex ti = resources.lookupTexture(st.filename);
|
||||
// Insert a manual loader into OGRE
|
||||
// ti.load();
|
||||
|
||||
// Get the resource name. We use getNewName to get
|
||||
// the real texture name, not the lookup
|
||||
// name. NewName has been converted to .dds if
|
||||
// necessary, to match the file name in the bsa
|
||||
// archives.
|
||||
texture = ti.getNewName();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Internal textures
|
||||
texture = "BLAH";
|
||||
writefln("Internal texture, cannot read this yet.");
|
||||
writefln("Final resource name: '%s'", texture);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// NiAlphaProperty
|
||||
{
|
||||
NiAlphaProperty a = cast(NiAlphaProperty) p;
|
||||
if(a !is null)
|
||||
{
|
||||
alphaFlags = a.flags;
|
||||
alphaTest = a.threshold;
|
||||
}
|
||||
}
|
||||
|
||||
// NiMaterialProperty block
|
||||
{
|
||||
NiMaterialProperty tmp = cast(NiMaterialProperty) p;
|
||||
if(tmp !is null)
|
||||
{
|
||||
if(mp !is null) writefln("WARNING: More than one material!");
|
||||
mp = tmp;
|
||||
material = newName;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get a pointer to the texture name
|
||||
char* texturePtr;
|
||||
if(texture.length) texturePtr = toStringz(texture);
|
||||
else texturePtr = null;
|
||||
|
||||
// Create the material
|
||||
if(material.length)
|
||||
// A material is present. Use it.
|
||||
ogre_createMaterial(material.ptr, mp.ambient.array.ptr,
|
||||
mp.diffuse.array.ptr,
|
||||
mp.specular.array.ptr, mp.emissive.array.ptr,
|
||||
mp.glossiness, mp.alpha, texturePtr,
|
||||
alphaFlags, alphaTest);
|
||||
else if(texturePtr)
|
||||
{
|
||||
// Texture, but no material. Make a default one.
|
||||
writefln("WARNING: Making default material for %s", texture);
|
||||
float[3] zero;
|
||||
float[3] one;
|
||||
zero[] = 0.0;
|
||||
one[] = 1.0;
|
||||
|
||||
ogre_createMaterial(newName.ptr, one.ptr, one.ptr, zero.ptr, zero.ptr,
|
||||
0.0, 1.0, texturePtr, alphaFlags, alphaTest);
|
||||
}
|
||||
|
||||
nomaterial:
|
||||
|
||||
with(shape.data)
|
||||
{
|
||||
//writefln("Number of vertices: ", vertices.length);
|
||||
|
||||
float *normalsPtr;
|
||||
float *colorsPtr;
|
||||
float *uvsPtr;
|
||||
short *facesPtr;
|
||||
|
||||
// Point pointers into the correct arrays, if they are present. If
|
||||
// not, the pointers retain their values of null.
|
||||
if(normals.length) normalsPtr = normals.ptr;
|
||||
if(colors.length) colorsPtr = colors.ptr;
|
||||
if(uvlist.length) uvsPtr = uvlist.ptr;
|
||||
if(triangles.length) facesPtr = triangles.ptr;
|
||||
|
||||
float
|
||||
minX = float.infinity,
|
||||
minY = float.infinity,
|
||||
minZ = float.infinity,
|
||||
maxX = -float.infinity,
|
||||
maxY = -float.infinity,
|
||||
maxZ = -float.infinity;
|
||||
|
||||
// Calculate the bounding box. TODO: This is really a
|
||||
// hack. IIRC the bounding box supplied by the NIF could not
|
||||
// be trusted, but I can't remember why :/
|
||||
for( int i; i < vertices.length; i+=3 )
|
||||
{
|
||||
if( vertices[i] < minX ) minX = vertices[i];
|
||||
if( vertices[i+1] < minY ) minY = vertices[i+1];
|
||||
if( vertices[i+2] < minZ) minZ = vertices[i+2];
|
||||
|
||||
if( vertices[i] > maxX) maxX = vertices[i];
|
||||
if( vertices[i+1] > maxY) maxY = vertices[i+1];
|
||||
if( vertices[i+2] > maxZ) maxZ = vertices[i+2];
|
||||
}
|
||||
|
||||
// Get the node world transformation, needed to set up
|
||||
// the collision shape properly.
|
||||
float[3] trans;
|
||||
float[9] matrix;
|
||||
ogre_getWorldTransform(node, trans.ptr, matrix.ptr);
|
||||
|
||||
// Next we must create the actual OGRE mesh and the collision
|
||||
// objects, based on the flags we have been given. TODO: I
|
||||
// guess the NIF bounding box is better than the one we have
|
||||
// calculated ourselves? This code definitely doesn't work,
|
||||
// but I haven't look into why yet.
|
||||
assert(!bbcollide);
|
||||
if(bbcollide)
|
||||
// Insert the bounding box into the collision system
|
||||
bullet_createBoxShape(minX, minY, minZ, maxX, maxY, maxZ,
|
||||
trans.ptr, matrix.ptr);
|
||||
|
||||
// Create a bullet collision shape from the trimesh. Pass
|
||||
// along the world transformation as well, since we must
|
||||
// transform the trimesh data manually.
|
||||
else if(collide)
|
||||
{
|
||||
assert(facesPtr !is null,
|
||||
"cannot create collision shape without a mesh");
|
||||
bullet_createTriShape(triangles.length, facesPtr,
|
||||
vertices.length, vertices.ptr,
|
||||
trans.ptr, matrix.ptr);
|
||||
}
|
||||
|
||||
// Create the ogre mesh, associate it with the node. Skip for
|
||||
// hidden nodes.
|
||||
if(!hidden)
|
||||
ogre_createMesh(newName.ptr, vertices.length, vertices.ptr,
|
||||
normalsPtr, colorsPtr, uvsPtr, triangles.length,
|
||||
facesPtr, radius, material.ptr, minX, minY, minZ,
|
||||
maxX, maxY, maxZ, node);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,471 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008-2009 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (openmw.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module openmw;
|
||||
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.cstream;
|
||||
import std.file;
|
||||
|
||||
import ogre.ogre;
|
||||
import ogre.bindings;
|
||||
import gui.bindings;
|
||||
import gui.gui;
|
||||
|
||||
import bullet.bullet;
|
||||
|
||||
import scene.celldata;
|
||||
import scene.soundlist;
|
||||
import scene.gamesettings;
|
||||
import scene.player;
|
||||
|
||||
import core.resource;
|
||||
import core.memory;
|
||||
import core.config;
|
||||
|
||||
import monster.util.string;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.dbg;
|
||||
import mscripts.setup;
|
||||
|
||||
import sound.audio;
|
||||
|
||||
import input.events;
|
||||
|
||||
import terrain.terrain;
|
||||
|
||||
// Set up exit handler
|
||||
alias void function() c_func;
|
||||
extern(C) int atexit(c_func);
|
||||
|
||||
bool cleanExit = false;
|
||||
|
||||
void exitHandler()
|
||||
{
|
||||
// If we exit uncleanly, print the function stack.
|
||||
if(!cleanExit)
|
||||
writefln(dbg.getTrace());
|
||||
}
|
||||
|
||||
|
||||
//*
|
||||
import std.gc;
|
||||
import gcstats;
|
||||
|
||||
void poolSize()
|
||||
{
|
||||
GCStats gc;
|
||||
getStats(gc);
|
||||
writefln("Pool size: ", comma(gc.poolsize));
|
||||
writefln("Used size: ", comma(gc.usedsize));
|
||||
}
|
||||
//*/
|
||||
|
||||
void main(char[][] args)
|
||||
{
|
||||
bool render = true;
|
||||
bool help = false;
|
||||
bool resetKeys = false;
|
||||
bool showOgreFlag = false;
|
||||
bool debugOut = false;
|
||||
bool extTest = false;
|
||||
bool doGen = false;
|
||||
bool nextSave = false;
|
||||
bool loadSave = false;
|
||||
|
||||
// Some examples to try:
|
||||
//
|
||||
// "Abandoned Shipwreck, Upper Level";
|
||||
// "Gro-Bagrat Plantation";
|
||||
// "Abinabi";
|
||||
// "Abebaal Egg Mine";
|
||||
// "Ald-ruhn, Ald Skar Inn";
|
||||
// "Koal Cave";
|
||||
// "Ald-ruhn, Arobar Manor Bedrooms"
|
||||
// "Sud";
|
||||
// "Vivec, The Lizard's Head";
|
||||
// "ToddTest";
|
||||
|
||||
// Cells to load
|
||||
char[][] cells;
|
||||
|
||||
// Savegame to load
|
||||
char[] savefile;
|
||||
|
||||
foreach(char[] a; args[1..$])
|
||||
if(a == "-n") render = false;
|
||||
else if(a == "-ex") extTest = true;
|
||||
else if(a == "-gen") doGen = true;
|
||||
else if(a == "-h") help=true;
|
||||
else if(a == "-rk") resetKeys = true;
|
||||
else if(a == "-oc") showOgreFlag = true;
|
||||
else if(a == "-ns") config.noSound = true;
|
||||
else if(a == "-save") nextSave = true;
|
||||
else if(a == "-debug")
|
||||
{
|
||||
// Enable Monster debug output
|
||||
dbg.dbgOut = dout;
|
||||
|
||||
// Tell OGRE to do the same later on
|
||||
debugOut = true;
|
||||
}
|
||||
else if(nextSave)
|
||||
{
|
||||
savefile = a;
|
||||
nextSave = false;
|
||||
}
|
||||
else cells ~= a;
|
||||
|
||||
if(cells.length > 1)
|
||||
{
|
||||
writefln("More than one cell specified, rendering disabled");
|
||||
render=false;
|
||||
}
|
||||
|
||||
void showHelp()
|
||||
{
|
||||
writefln("Syntax: %s [options] cell-name [cell-name]", args[0]);
|
||||
writefln(" Options:");
|
||||
writefln(" -n Only load, do not render");
|
||||
writefln(" -ex Test the terrain system");
|
||||
writefln(" -gen Generate landscape cache");
|
||||
writefln(" -rk Reset key bindings to default");
|
||||
writefln(" -oc Show the Ogre config dialogue");
|
||||
writefln(" -ns Completely disable sound");
|
||||
writefln(" -debug Print debug information");
|
||||
writefln(" -save <file> Load cell/pos from savegame");
|
||||
writefln(" -h Show this help");
|
||||
writefln("");
|
||||
writefln("Specifying more than one cell implies -n");
|
||||
}
|
||||
|
||||
if(help)
|
||||
{
|
||||
showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
initializeMemoryRegions();
|
||||
initMonsterScripts();
|
||||
|
||||
// This is getting increasingly hackish, but this entire engine
|
||||
// design is now quickly outgrowing its usefulness, and a rewrite is
|
||||
// coming soon anyway.
|
||||
PlayerSaveInfo pi;
|
||||
if(savefile != "")
|
||||
{
|
||||
if(cells.length)
|
||||
{
|
||||
writefln("Please don't specify both a savegame file (%s) and cell names (%s)", savefile, cells);
|
||||
return;
|
||||
}
|
||||
|
||||
loadSave = true;
|
||||
writefln("Loading savegame %s", savefile);
|
||||
pi = importSavegame(savefile);
|
||||
writefln(" Player name: %s", pi.playerName);
|
||||
writefln(" Cell name: %s", pi.cellName);
|
||||
writefln(" Pos: %s", pi.pos.position);
|
||||
writefln(" Rot: %s", pi.pos.rotation);
|
||||
|
||||
cells = [pi.cellName];
|
||||
}
|
||||
|
||||
config.initialize(resetKeys);
|
||||
scope(exit) config.writeConfig();
|
||||
|
||||
// Check if the data directory exists
|
||||
if(!exists(config.dataDir) || !isdir(config.dataDir))
|
||||
{
|
||||
writefln("Cannot find data directory '", config.dataDir,
|
||||
"' - please edit openmw.ini.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the -oc parameter is specified, we override any config
|
||||
// setting.
|
||||
if(showOgreFlag) config.finalOgreConfig = true;
|
||||
|
||||
if(cells.length == 0)
|
||||
if(config.defaultCell.length)
|
||||
cells ~= config.defaultCell;
|
||||
|
||||
if(cells.length == 1 && !loadSave)
|
||||
config.defaultCell = cells[0];
|
||||
|
||||
if(cells.length == 0)
|
||||
{
|
||||
showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!config.noSound) initializeSound();
|
||||
resources.initResources();
|
||||
|
||||
// Load all ESM and ESP files
|
||||
loadTESFiles(config.gameFiles);
|
||||
|
||||
scene.gamesettings.loadGameSettings();
|
||||
|
||||
CellData cd = cellList.get();
|
||||
|
||||
foreach(char[] cName; cells)
|
||||
{
|
||||
// Release the last cell data
|
||||
cellList.release(cd);
|
||||
|
||||
// Get a cell data holder and load an interior cell
|
||||
cd = cellList.get();
|
||||
|
||||
try cd.loadIntCell(cName);
|
||||
catch(Exception e)
|
||||
{
|
||||
writefln("\nUnable to load cell '%s'.", cName);
|
||||
writefln("\nDetails: %s", e);
|
||||
writefln("
|
||||
Perhaps this cell does not exist in your Morrowind language version?
|
||||
Try specifying another cell name on the command line, or edit openmw.ini.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're loading from save, override the player position
|
||||
if(loadSave)
|
||||
*playerData.position = pi.pos;
|
||||
}
|
||||
|
||||
// Simple safety hack
|
||||
NodePtr putObject(MeshIndex m, Placement *pos, float scale,
|
||||
bool collide=false)
|
||||
{
|
||||
if(m is null)
|
||||
writefln("WARNING: CANNOT PUT NULL OBJECT");
|
||||
else if(!m.isEmpty)
|
||||
return placeObject(m, pos, scale, collide);
|
||||
|
||||
//writefln("WARNING: CANNOT INSERT EMPTY MESH '%s'", m.getName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if(cd.inCell !is null)
|
||||
// Set the name for the GUI (temporary hack)
|
||||
gui_setCellName(cd.inCell.id.ptr);
|
||||
|
||||
// Set up the exit handler
|
||||
atexit(&exitHandler);
|
||||
scope(exit) cleanExit = true;
|
||||
|
||||
if(render)
|
||||
{
|
||||
// Warm up OGRE
|
||||
setupOgre(debugOut);
|
||||
scope(exit) cleanupOgre();
|
||||
|
||||
// Create the GUI system
|
||||
initGUI(debugOut);
|
||||
|
||||
// Set up Bullet
|
||||
initBullet();
|
||||
scope(exit) cleanupBullet();
|
||||
|
||||
// Initialize the internal input and event manager. The
|
||||
// lower-level input system (OIS) is initialized by the
|
||||
// setupOgre() call further up.
|
||||
initializeInput();
|
||||
|
||||
// Play some old tunes
|
||||
if(extTest)
|
||||
{
|
||||
// Exterior cell
|
||||
/*
|
||||
Color c;
|
||||
c.red = 180;
|
||||
c.green = 180;
|
||||
c.blue = 180;
|
||||
setAmbient(c, c, c, 0);
|
||||
|
||||
// Put in the water
|
||||
ogre_createWater(cd.water);
|
||||
|
||||
// Create an ugly sky
|
||||
ogre_makeSky();
|
||||
*/
|
||||
|
||||
initTerrain(doGen);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior cell
|
||||
assert(cd.inCell !is null);
|
||||
setAmbient(cd.ambi.ambient, cd.ambi.sunlight,
|
||||
cd.ambi.fog, cd.ambi.fogDensity);
|
||||
|
||||
// Not all interior cells have water
|
||||
if(cd.inCell.flags & CellFlags.HasWater)
|
||||
ogre_createWater(cd.water);
|
||||
|
||||
// Insert the meshes of statics into the scene
|
||||
foreach(ref LiveStatic ls; cd.statics)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
|
||||
// Inventory lights
|
||||
foreach(ref LiveLight ls; cd.lights)
|
||||
{
|
||||
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
|
||||
if(!config.noSound)
|
||||
{
|
||||
Sound *s = ls.m.sound;
|
||||
if(s)
|
||||
{
|
||||
ls.loopSound = soundScene.insert(s, true);
|
||||
if(ls.loopSound)
|
||||
{
|
||||
auto p = ls.getPos();
|
||||
ls.loopSound.setPos(p.position[0],
|
||||
p.position[1],
|
||||
p.position[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Static lights
|
||||
foreach(ref LiveLight ls; cd.statLights)
|
||||
{
|
||||
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
|
||||
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
|
||||
if(!config.noSound)
|
||||
{
|
||||
Sound *s = ls.m.sound;
|
||||
if(s)
|
||||
{
|
||||
ls.loopSound = soundScene.insert(s, true);
|
||||
if(ls.loopSound)
|
||||
{
|
||||
auto p = ls.getPos();
|
||||
ls.loopSound.setPos(p.position[0],
|
||||
p.position[1],
|
||||
p.position[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Misc items
|
||||
foreach(ref LiveMisc ls; cd.miscItems)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
/*
|
||||
// NPCs (these are complicated, usually do not have normal meshes)
|
||||
foreach(ref LiveNPC ls; cd.npcs)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
*/
|
||||
// Containers
|
||||
foreach(ref LiveContainer ls; cd.containers)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
|
||||
// Doors
|
||||
foreach(ref LiveDoor ls; cd.doors)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Activators (including beds etc)
|
||||
foreach(ref LiveActivator ls; cd.activators)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
|
||||
// Potions
|
||||
foreach(ref LivePotion ls; cd.potions)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Apparatus
|
||||
foreach(ref LiveApparatus ls; cd.appas)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Ingredients
|
||||
foreach(ref LiveIngredient ls; cd.ingredients)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Armors
|
||||
foreach(ref LiveArmor ls; cd.armors)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Weapons
|
||||
foreach(ref LiveWeapon ls; cd.weapons)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Books
|
||||
foreach(ref LiveBook ls; cd.books)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Clothes
|
||||
foreach(ref LiveClothing ls; cd.clothes)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Tools
|
||||
foreach(ref LiveTool ls; cd.tools)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
// Creatures (not displayed very well yet)
|
||||
foreach(ref LiveCreature ls; cd.creatures)
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
|
||||
// End of interior cell
|
||||
}
|
||||
|
||||
// Run GUI system
|
||||
startGUI();
|
||||
|
||||
// Play some old tunes
|
||||
if(!config.noSound)
|
||||
Music.play();
|
||||
|
||||
// Run it until the user tells us to quit
|
||||
startRendering();
|
||||
}
|
||||
else if(debugOut) writefln("Skipping rendering");
|
||||
|
||||
if(!config.noSound)
|
||||
{
|
||||
soundScene.kill();
|
||||
shutdownSound();
|
||||
}
|
||||
|
||||
if(debugOut)
|
||||
{
|
||||
writefln();
|
||||
writefln("%d statics", cd.statics.length);
|
||||
writefln("%d misc items", cd.miscItems.length);
|
||||
writefln("%d inventory lights", cd.lights.length);
|
||||
writefln("%d static lights", cd.statLights.length);
|
||||
writefln("%d NPCs", cd.npcs.length);
|
||||
writefln("%d containers", cd.containers.length);
|
||||
writefln("%d doors", cd.doors.length);
|
||||
writefln("%d activators", cd.activators.length);
|
||||
writefln("%d potions", cd.potions.length);
|
||||
writefln("%d apparatuses", cd.appas.length);
|
||||
writefln("%d ingredients", cd.ingredients.length);
|
||||
writefln("%d armors", cd.armors.length);
|
||||
writefln("%d weapons", cd.weapons.length);
|
||||
writefln("%d books", cd.books.length);
|
||||
writefln("%d tools", cd.tools.length);
|
||||
writefln("%d clothes", cd.clothes.length);
|
||||
writefln("%d creatures", cd.creatures.length);
|
||||
writefln();
|
||||
}
|
||||
|
||||
// This isn't necessary but it's here for testing purposes.
|
||||
cellList.release(cd);
|
||||
|
||||
// Write some statistics
|
||||
if(debugOut)
|
||||
{
|
||||
poolSize();
|
||||
writefln(esmRegion);
|
||||
writefln("Total objects: ", MonsterClass.getTotalObjects);
|
||||
}
|
||||
}
|
@ -1,461 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2009 Nicolay Korslund
|
||||
WWW: http://openmw.sourceforge.net/
|
||||
|
||||
This file (archive.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module terrain.archive;
|
||||
|
||||
const float TEX_SCALE = 1.0/16;
|
||||
|
||||
// This should be part of the generic cache system.
|
||||
const int CACHE_MAGIC = 0x345AF815;
|
||||
|
||||
import std.mmfile;
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import terrain.myfile;
|
||||
|
||||
version(Windows)
|
||||
static int pageSize = 64*1024;
|
||||
else
|
||||
static int pageSize = 4*1024;
|
||||
|
||||
extern(C)
|
||||
{
|
||||
// Convert a texture index to string
|
||||
char *d_terr_getTexName(int index)
|
||||
{ return g_archive.getString(index).ptr; }
|
||||
|
||||
// Fill various hardware buffers from cache
|
||||
void d_terr_fillVertexBuffer(MeshInfo *mi, float *buffer, ulong size)
|
||||
{ mi.fillVertexBuffer(buffer[0..size]); }
|
||||
|
||||
void d_terr_fillIndexBuffer(MeshInfo *mi, ushort *buffer, ulong size)
|
||||
{ mi.fillIndexBuffer(buffer[0..size]); }
|
||||
|
||||
void d_terr_fillAlphaBuffer(AlphaInfo *mi, ubyte *buffer, ulong size)
|
||||
{ mi.fillAlphaBuffer(buffer[0..size]); }
|
||||
|
||||
// Get a given alpha map struct belonging to a mesh
|
||||
AlphaInfo *d_terr_getAlphaInfo(MeshInfo *mi, int index)
|
||||
{ return mi.getAlphaInfo(index); }
|
||||
|
||||
int d_terr_getAlphaSize() { return g_archive.alphaSize; }
|
||||
}
|
||||
|
||||
// Info about the entire quad. TODO: Some of this (such as the texture
|
||||
// scale and probably the width and radius) can be generated at
|
||||
// loadtime and is common for all quads on the same level. We could
|
||||
// just make a QuadLevelInfo struct.
|
||||
struct QuadInfo
|
||||
{
|
||||
// Basic info
|
||||
int cellX, cellY;
|
||||
int level;
|
||||
|
||||
// Bounding box info
|
||||
float minHeight, maxHeight;
|
||||
float worldWidth;
|
||||
float boundingRadius;
|
||||
|
||||
// True if we should make the given child
|
||||
bool hasChild[4];
|
||||
|
||||
// Number of mesh segments in this quad
|
||||
int meshNum;
|
||||
|
||||
// Location of this quad in the main archive file. The size includes
|
||||
// everything related to this quad, including mesh data, alpha maps,
|
||||
// etc.
|
||||
size_t offset, size;
|
||||
}
|
||||
|
||||
// Info about an alpha map belonging to a mesh
|
||||
struct AlphaInfo
|
||||
{
|
||||
// Position of the actual image data
|
||||
ulong bufSize, bufOffset;
|
||||
|
||||
// The texture name for this layer. The actual string is stored in
|
||||
// the archive's string buffer.
|
||||
int texName = -1;
|
||||
int alphaName = -1;
|
||||
|
||||
// Fill the alpha texture buffer
|
||||
void fillAlphaBuffer(ubyte abuf[])
|
||||
{
|
||||
assert(abuf.length == bufSize);
|
||||
g_archive.copy(abuf.ptr, bufOffset, bufSize);
|
||||
}
|
||||
}
|
||||
static assert(AlphaInfo.sizeof == 6*4);
|
||||
|
||||
// Info about each submesh
|
||||
// If you change this struct please check whether align(1) still fits.
|
||||
align(1)
|
||||
struct MeshInfo
|
||||
{
|
||||
// Bounding box info
|
||||
float minHeight, maxHeight;
|
||||
float worldWidth;
|
||||
|
||||
// Vertex and index numbers
|
||||
int vertRows, vertCols;
|
||||
|
||||
// Height offset to apply to all vertices
|
||||
float heightOffset;
|
||||
|
||||
// Size and offset of the vertex buffer
|
||||
ulong vertBufSize, vertBufOffset;
|
||||
|
||||
// Texture name. Index to the string table.
|
||||
int texName = -1;
|
||||
|
||||
// Number and offset of AlphaInfo blocks
|
||||
int alphaNum;
|
||||
ulong alphaOffset;
|
||||
|
||||
// Fill the given vertex buffer
|
||||
void fillVertexBuffer(float vdest[])
|
||||
{
|
||||
// The height map and normals from the archive
|
||||
byte *hmap = cast(byte*)g_archive.getRelSlice(vertBufOffset, vertBufSize).ptr;
|
||||
// The generic part, containing the x,y coordinates and the uv
|
||||
// maps.
|
||||
float *gmap = g_archive.getVertexBuffer(getLevel()).ptr;
|
||||
|
||||
// Destination pointer
|
||||
float *vbuf = vdest.ptr;
|
||||
assert(vdest.length == vertRows*vertCols*8);
|
||||
|
||||
// Merge the two data sets together into the output buffer.
|
||||
float offset = heightOffset;
|
||||
for(int y=0; y<vertRows; y++)
|
||||
{
|
||||
// The offset for the entire row is determined by the first
|
||||
// height value. All the values in a row gives the height
|
||||
// relative to the previous value, and the first value in each
|
||||
// row is relative to the first value in the previous row.
|
||||
offset += *cast(short*)hmap;
|
||||
|
||||
// This is the 'sliding offset' for this row. It's adjusted
|
||||
// for each vertex that's added, but only affects this row.
|
||||
float rowofs = offset;
|
||||
for(int x=0; x<vertCols; x++)
|
||||
{
|
||||
hmap+=2; // Skip the height we just read
|
||||
|
||||
// X and Y from the pregenerated buffer
|
||||
*vbuf++ = *gmap++;
|
||||
*vbuf++ = *gmap++;
|
||||
|
||||
// The height is calculated from the current offset
|
||||
*vbuf++ = rowofs * 8;
|
||||
|
||||
// Normal vector.
|
||||
*vbuf++ = *hmap++;
|
||||
*vbuf++ = *hmap++;
|
||||
*vbuf++ = *hmap++;
|
||||
|
||||
// UV
|
||||
*vbuf++ = *gmap++;
|
||||
*vbuf++ = *gmap++;
|
||||
|
||||
// Adjust the offset for the next vertex.
|
||||
if(x < vertCols-1)
|
||||
rowofs += *cast(short*)hmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the index buffer
|
||||
void fillIndexBuffer(ushort ibuf[])
|
||||
{
|
||||
// The index buffer is pregenerated. It is identical for all
|
||||
// meshes on the same level, so just copy it over.
|
||||
ushort generic[] = g_archive.getIndexBuffer();
|
||||
assert(ibuf.length == generic.length);
|
||||
ibuf[] = generic[];
|
||||
}
|
||||
|
||||
int getLevel()
|
||||
{
|
||||
assert(g_archive.curQuad);
|
||||
return g_archive.curQuad.level;
|
||||
}
|
||||
|
||||
// Get an alpha map belonging to this mesh
|
||||
AlphaInfo *getAlphaInfo(int num)
|
||||
{
|
||||
assert(num < alphaNum && num >= 0);
|
||||
assert(getLevel() == 1);
|
||||
AlphaInfo *res = cast(AlphaInfo*)g_archive.getRelSlice
|
||||
(alphaOffset, alphaNum*AlphaInfo.sizeof);
|
||||
res += num;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
static assert(MeshInfo.sizeof == 14*4);
|
||||
|
||||
// The first part of the .index file
|
||||
struct ArchiveHeader
|
||||
{
|
||||
// "Magic" number to make sure we're actually reading an archive
|
||||
// file
|
||||
int magic;
|
||||
|
||||
// Total number of quads in the archive
|
||||
int quads;
|
||||
|
||||
// Level of the 'root' quad. There will only be one quad on this
|
||||
// level.
|
||||
int rootLevel;
|
||||
|
||||
// Size of the alpha maps, in pixels along one side.
|
||||
int alphaSize;
|
||||
|
||||
// Number of strings in the string table
|
||||
int stringNum;
|
||||
|
||||
// Size of the string buffer
|
||||
size_t stringSize;
|
||||
}
|
||||
|
||||
TerrainArchive g_archive;
|
||||
|
||||
// This class handles the cached terrain data.
|
||||
struct TerrainArchive
|
||||
{
|
||||
MeshInfo *curMesh;
|
||||
QuadInfo *curQuad;
|
||||
QuadInfo *rootQuad;
|
||||
|
||||
void openFile(char[] name)
|
||||
{
|
||||
mmf = new MmFile(name,
|
||||
MmFile.Mode.Read,
|
||||
0, null, pageSize);
|
||||
|
||||
// Read the index file first
|
||||
MyFile ifile = new MyFile(name ~ ".index");
|
||||
|
||||
ArchiveHeader head;
|
||||
ifile.fill(head);
|
||||
|
||||
// Reads data into an array. Would be better if this was part of
|
||||
// the stream.
|
||||
|
||||
// Sanity check
|
||||
assert(head.magic == CACHE_MAGIC);
|
||||
assert(head.quads > 0 && head.quads < 8192);
|
||||
|
||||
// Store header info
|
||||
alphaSize = head.alphaSize;
|
||||
|
||||
// Read all the quads
|
||||
quadList = new QuadInfo[head.quads];
|
||||
ifile.fillArray(quadList);
|
||||
|
||||
// Create an index of all the quads
|
||||
foreach(int index, qn; quadList)
|
||||
{
|
||||
int x = qn.cellX;
|
||||
int y = qn.cellY;
|
||||
int l = qn.level;
|
||||
|
||||
assert(l >= 1);
|
||||
|
||||
quadMap[l][x][y] = index;
|
||||
assert(index == quadMap[l][x][y]);
|
||||
|
||||
// Store the root quad
|
||||
if(l == head.rootLevel)
|
||||
{
|
||||
assert(rootQuad == null);
|
||||
rootQuad = &quadList[index];
|
||||
}
|
||||
else
|
||||
assert(l < head.rootLevel);
|
||||
}
|
||||
|
||||
// Make sure the root was set
|
||||
assert(rootQuad !is null);
|
||||
|
||||
// Next read the string table. First read the main string buffer.
|
||||
stringBuf = new char[head.stringSize];
|
||||
ifile.fillArray(stringBuf);
|
||||
|
||||
// Then read the string offsets
|
||||
int[] offsets = new int[head.stringNum];
|
||||
ifile.fillArray(offsets);
|
||||
|
||||
// Set up the string table
|
||||
char *strptr = stringBuf.ptr;
|
||||
strings.length = head.stringNum;
|
||||
foreach(int i, ref str; strings)
|
||||
{
|
||||
// toString(char*) returns the string up to the zero
|
||||
// terminator byte
|
||||
str = toString(strptr + offsets[i]);
|
||||
assert(str.ptr + str.length <=
|
||||
stringBuf.ptr + stringBuf.length);
|
||||
}
|
||||
delete offsets;
|
||||
|
||||
// Read the vertex buffer data
|
||||
int bufNum = head.rootLevel;
|
||||
assert(bufNum == 7);
|
||||
vertBufData.length = bufNum;
|
||||
|
||||
// Fill the vertex buffers. Start at level 1.
|
||||
for(int i=1;i<bufNum;i++)
|
||||
{
|
||||
// Vertex buffer
|
||||
ifile.readArray(vertBufData[i]);
|
||||
}
|
||||
|
||||
// Index buffer
|
||||
ifile.readArray(indexBufData);
|
||||
}
|
||||
|
||||
bool hasQuad(int X, int Y, int level)
|
||||
{
|
||||
if((level in quadMap) is null ||
|
||||
(X in quadMap[level]) is null ||
|
||||
(Y in quadMap[level][X]) is null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get info about a given quad from the index.
|
||||
QuadInfo *getQuad(int X, int Y, int level)
|
||||
{
|
||||
assert(hasQuad(X,Y,level), format("Cannot find quad %s %s level %s",
|
||||
X, Y, level));
|
||||
int ind = quadMap[level][X][Y];
|
||||
QuadInfo *res = &quadList[ind];
|
||||
assert(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Maps the terrain and material info for a given quad into
|
||||
// memory. This is typically called right before the meshes are
|
||||
// created.
|
||||
void mapQuad(QuadInfo *info)
|
||||
{
|
||||
assert(info);
|
||||
|
||||
// Store the quad for later
|
||||
curQuad = info;
|
||||
|
||||
doMap(info.offset, info.size);
|
||||
}
|
||||
|
||||
// Get the info struct for a given segment. Remembers the MeshInfo
|
||||
// for all later calls.
|
||||
MeshInfo *getMeshInfo(int segNum)
|
||||
{
|
||||
assert(curQuad);
|
||||
assert(segNum < curQuad.meshNum);
|
||||
|
||||
// The mesh headers are at the beginning of the mapped segment.
|
||||
curMesh = cast(MeshInfo*) getRelSlice(0, MeshInfo.sizeof*curQuad.meshNum);
|
||||
curMesh += segNum;
|
||||
|
||||
return curMesh;
|
||||
}
|
||||
|
||||
float[] getVertexBuffer(int level)
|
||||
{
|
||||
assert(level>=1 && level<vertBufData.length);
|
||||
return vertBufData[level];
|
||||
}
|
||||
|
||||
ushort[] getIndexBuffer()
|
||||
{
|
||||
return indexBufData;
|
||||
}
|
||||
|
||||
private:
|
||||
// All quad headers (from the index) are stored in this array
|
||||
QuadInfo quadList[];
|
||||
|
||||
// A map of all quads. Contain indices to the above array. Indexed
|
||||
// by [level][X][Y].
|
||||
int[int][int][int] quadMap;
|
||||
|
||||
// These contain pregenerated mesh data that is common for all
|
||||
// meshes on a given level.
|
||||
float[][] vertBufData;
|
||||
ushort[] indexBufData;
|
||||
|
||||
// Used for the mmapped file
|
||||
MmFile mmf;
|
||||
|
||||
ubyte mapped[];
|
||||
|
||||
// Stores the string table
|
||||
char[] stringBuf;
|
||||
char[][] strings;
|
||||
|
||||
// Texture size of the alpha maps.
|
||||
int alphaSize;
|
||||
|
||||
char[] getString(int index)
|
||||
{
|
||||
assert(index >= 0);
|
||||
assert(index < strings.length);
|
||||
|
||||
return strings[index];
|
||||
}
|
||||
|
||||
void doMap(size_t offset, size_t size)
|
||||
{
|
||||
assert(mmf !is null);
|
||||
assert(size);
|
||||
mapped = cast(ubyte[])mmf[offset..offset+size];
|
||||
assert(mapped.length == size);
|
||||
}
|
||||
|
||||
// Get a slice of a given buffer within the mapped window. The
|
||||
// offset is relative to the start of the window, and the size must
|
||||
// fit inside the window.
|
||||
ubyte[] getRelSlice(size_t offset, size_t size)
|
||||
{
|
||||
assert(mapped.length);
|
||||
|
||||
return mapped[offset..offset+size];
|
||||
}
|
||||
|
||||
// Copy a given buffer from the file. The buffer might be a
|
||||
// compressed stream, so it's important that the buffers are written
|
||||
// in the same block sizes as they are read. (Ie. you can't write a
|
||||
// buffer as one operation and read it as two, or vice versa. Also,
|
||||
// buffers cannot overlap.) The offset is relative to the current
|
||||
// mapped file window.
|
||||
void copy(void *dst, size_t offset, size_t inSize)
|
||||
{
|
||||
ubyte source[] = getRelSlice(offset, inSize);
|
||||
|
||||
// Just copy it for now
|
||||
ubyte* dest = cast(ubyte*)dst;
|
||||
dest[0..source.length] = source[];
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
module terrain.bindings;
|
||||
|
||||
alias void *SceneNode;
|
||||
alias void *Bounds;
|
||||
alias void *MeshObj;
|
||||
|
||||
// These are all defined in cpp_terrain.cpp:
|
||||
extern(C):
|
||||
|
||||
SceneNode terr_createChildNode(float relX, float relY, SceneNode);
|
||||
void terr_destroyNode(SceneNode);
|
||||
Bounds terr_makeBounds(float minHeight, float maxHeight, float width, SceneNode);
|
||||
void terr_killBounds(Bounds);
|
||||
float terr_getSqCamDist(Bounds);
|
||||
MeshObj terr_makeMesh(SceneNode,void*,int,float);
|
||||
void terr_killMesh(MeshObj);
|
||||
|
||||
void terr_genData();
|
||||
void terr_setupRendering();
|
||||
|
||||
void terr_makeLandMaterial(char*,float);
|
||||
ubyte *terr_makeAlphaLayer(char*,int);
|
||||
void terr_closeAlpha(char*,char*,float);
|
||||
void terr_cleanupAlpha(char*,void*,int);
|
||||
|
||||
void terr_resize(void*,void*,int,int);
|
||||
void terr_saveImage(void*,int,char*);
|
@ -1,337 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2009 Nicolay Korslund
|
||||
WWW: http://openmw.sourceforge.net/
|
||||
|
||||
This file (cachewriter.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module terrain.cachewriter;
|
||||
|
||||
import terrain.archive;
|
||||
|
||||
import terrain.outbuffer;
|
||||
import std.stdio, std.stream, std.string;
|
||||
import terrain.myfile;
|
||||
import std.math2;
|
||||
import monster.util.string;
|
||||
import monster.vm.dbg;
|
||||
|
||||
// Helper structs
|
||||
struct AlphaHolder
|
||||
{
|
||||
AlphaInfo info;
|
||||
|
||||
// Actual pixel buffer
|
||||
ubyte[] buffer;
|
||||
}
|
||||
|
||||
struct MeshHolder
|
||||
{
|
||||
MeshInfo info;
|
||||
|
||||
// Actual buffers
|
||||
byte[] vertexBuffer;
|
||||
|
||||
// Alpha maps (if any)
|
||||
AlphaHolder alphas[];
|
||||
}
|
||||
|
||||
// A struct that gathers all the relevant quad data in one place.
|
||||
struct QuadHolder
|
||||
{
|
||||
QuadInfo info;
|
||||
|
||||
MeshHolder meshes[];
|
||||
}
|
||||
|
||||
struct CacheWriter
|
||||
{
|
||||
// Opens the main archive file for output
|
||||
void openFile(char[] fname)
|
||||
{
|
||||
mainFile = new File(fname, FileMode.OutNew);
|
||||
iname = fname ~ ".index";
|
||||
|
||||
buf = new OutBuffer;
|
||||
}
|
||||
|
||||
void setParams(int mxLev, int alphSize)
|
||||
{
|
||||
maxLevel = mxLev;
|
||||
alphaSize = alphSize;
|
||||
|
||||
vertBuf.length = maxLevel;
|
||||
}
|
||||
|
||||
// Closes the main archive file and writes the index.
|
||||
void finish()
|
||||
{
|
||||
mainFile.close();
|
||||
|
||||
// Write the index file
|
||||
scope MyFile ofile = new MyFile(iname, FileMode.OutNew);
|
||||
|
||||
// Header first
|
||||
ArchiveHeader head;
|
||||
head.magic = CACHE_MAGIC;
|
||||
head.quads = quadList.length;
|
||||
head.rootLevel = maxLevel;
|
||||
head.alphaSize = alphaSize;
|
||||
head.stringNum = stringList.length;
|
||||
head.stringSize = totalStringLength;
|
||||
ofile.dump(head);
|
||||
|
||||
// Write the quads
|
||||
ofile.dumpArray(quadList);
|
||||
|
||||
// String table next. We need to sort it in order of the indices
|
||||
// first.
|
||||
char[][] strVector;
|
||||
strVector.length = head.stringNum;
|
||||
|
||||
foreach(char[] key, int value; stringList)
|
||||
strVector[value] = key;
|
||||
|
||||
// Next, write the strings to file while we fill in the offset
|
||||
// list
|
||||
int[] offsets = new int[head.stringNum];
|
||||
|
||||
size_t curOffs = 0;
|
||||
for(int i=0; i<head.stringNum; i++)
|
||||
{
|
||||
// Add one byte for the zero terminator
|
||||
int len = strVector[i].length + 1;
|
||||
char *ptr = strVector[i].ptr;
|
||||
|
||||
if(ptr[len-1] != 0)
|
||||
ptr = toStringz(strVector[i]);
|
||||
|
||||
assert(ptr[len-1] == 0);
|
||||
|
||||
ofile.writeExact(ptr, len);
|
||||
|
||||
// Store the offset
|
||||
offsets[i] = curOffs;
|
||||
curOffs += len;
|
||||
}
|
||||
// At the end the offset should match the buffer size we set in
|
||||
// the header.
|
||||
assert(curOffs == head.stringSize);
|
||||
|
||||
// Finally, write the offset table itself
|
||||
ofile.dumpArray(offsets);
|
||||
|
||||
// Write the common vertex and index buffers
|
||||
assert(maxLevel == 7);
|
||||
for(int i=1;i<maxLevel;i++)
|
||||
{
|
||||
// Write vertex buffer
|
||||
ofile.writeArray(vertBuf[i]);
|
||||
delete vertBuf[i];
|
||||
}
|
||||
|
||||
// Then the index buffer
|
||||
ofile.writeArray(indexBuf);
|
||||
|
||||
// Don't need these anymore
|
||||
delete offsets;
|
||||
delete strVector;
|
||||
delete quadList;
|
||||
delete vertBuf;
|
||||
delete indexBuf;
|
||||
delete buf;
|
||||
delete mainFile;
|
||||
}
|
||||
|
||||
// Add a common vertex buffer for a given level
|
||||
void addVertexBuffer(int level, float[] buf)
|
||||
{
|
||||
assert(vertBuf.length > level);
|
||||
vertBuf[level] = buf;
|
||||
}
|
||||
|
||||
// Add a common index buffer
|
||||
void setIndexBuffer(ushort[] buf)
|
||||
{
|
||||
indexBuf = buf;
|
||||
}
|
||||
|
||||
// Write a finished quad to the archive file. All the offsets and
|
||||
// numbers in the *Info structs are filled in automatically based on
|
||||
// the additional data in the Holder structs.
|
||||
void writeQuad(ref QuadHolder qh)
|
||||
{
|
||||
scope auto _trc = new MTrace("writeQuad");
|
||||
|
||||
// Write the MeshInfo's first
|
||||
int meshNum = qh.meshes.length;
|
||||
|
||||
MeshInfo meshes[] = buf.write!(MeshInfo)(meshNum);
|
||||
|
||||
float minh = float.infinity;
|
||||
float maxh = -float.infinity;
|
||||
|
||||
// Then write the mesh data in approximately the order it's read
|
||||
for(int i=0; i<meshNum; i++)
|
||||
{
|
||||
assert(meshes !is null);
|
||||
|
||||
auto mh = &qh.meshes[i];
|
||||
|
||||
// Copy the basic data first
|
||||
meshes[i] = mh.info;
|
||||
|
||||
minh = min(minh,mh.info.minHeight);
|
||||
maxh = max(maxh,mh.info.maxHeight);
|
||||
|
||||
// Set everything else except the offsets
|
||||
int alphaNum = mh.alphas.length;
|
||||
meshes[i].alphaNum = alphaNum;
|
||||
|
||||
// Write the vertex buffer
|
||||
meshes[i].vertBufOffset = buf.size;
|
||||
meshes[i].vertBufSize = mh.vertexBuffer.length;
|
||||
writeBuf(mh.vertexBuffer);
|
||||
assert(buf.size == meshes[i].vertBufOffset + meshes[i].vertBufSize);
|
||||
|
||||
// Next write the alpha maps, if any
|
||||
meshes[i].alphaOffset = buf.size;
|
||||
AlphaInfo ais[] = buf.write!(AlphaInfo)(alphaNum);
|
||||
|
||||
// Loop through the alpha maps
|
||||
foreach(int k, ref ai; ais)
|
||||
{
|
||||
AlphaHolder ah = mh.alphas[k];
|
||||
ai = ah.info;
|
||||
|
||||
// Write the alpha pixel buffer
|
||||
ai.bufOffset = buf.size;
|
||||
ai.bufSize = ah.buffer.length;
|
||||
writeBuf(ah.buffer);
|
||||
}
|
||||
}
|
||||
// Finally set up the QuadInfo itself
|
||||
QuadInfo qi;
|
||||
|
||||
// Copy basic info
|
||||
qi = qh.info;
|
||||
|
||||
// Derived info
|
||||
qi.meshNum = meshNum;
|
||||
qi.offset = fileOffset;
|
||||
qi.size = buf.size;
|
||||
qi.minHeight = minh;
|
||||
qi.maxHeight = maxh;
|
||||
|
||||
// Get the side length, or the height difference if that is bigger
|
||||
qi.boundingRadius = max(maxh-minh,qi.worldWidth);
|
||||
|
||||
// Multiply with roughly sqrt(1/2), converts from side length to
|
||||
// radius with some extra slack
|
||||
qi.boundingRadius *= 0.8;
|
||||
|
||||
// The quad cache is done, write it to file
|
||||
buf.writeTo(mainFile);
|
||||
|
||||
// Update the main offset
|
||||
fileOffset += qi.size;
|
||||
|
||||
// Add the quad to the list. This list isn't written to the main
|
||||
// cache file, but to the index file.
|
||||
quadList ~= qi;
|
||||
}
|
||||
|
||||
// Add a texture name as a string. Will convert .tga file names to
|
||||
// .dds as a convenience. TODO: Use the resource system to do this,
|
||||
// it automatically searches for the dds variant.
|
||||
int addTexture(char[] orig)
|
||||
{
|
||||
if(orig.iEnds(".tga"))
|
||||
orig = orig[0..$-3] ~ "dds";
|
||||
return addString(orig);
|
||||
}
|
||||
|
||||
// Convert a string to an index
|
||||
int addString(char[] str)
|
||||
{
|
||||
// Do we already have the string?
|
||||
if(str in stringList)
|
||||
return stringList[str];
|
||||
|
||||
// Nope, insert it
|
||||
int index = stringList.length;
|
||||
stringList[str] = index;
|
||||
stringLookup[index] = str;
|
||||
|
||||
// Sum up the string lengths + 1 byte for the zero
|
||||
totalStringLength += str.length + 1;
|
||||
return index;
|
||||
}
|
||||
|
||||
char[] getString(int index)
|
||||
{
|
||||
char[] res = stringLookup[index];
|
||||
assert(stringList[res] == index);
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
// Write the given block of memory to 'buf', possibly compressing
|
||||
// the data.
|
||||
void writeBuf(void[] ptr)
|
||||
{
|
||||
ulong size = ptr.length;
|
||||
|
||||
// Reserve the maximum bytes needed.
|
||||
void toPtr[] = buf.reserve(size);
|
||||
|
||||
// Store the data
|
||||
toPtr[] = ptr[];
|
||||
|
||||
// Add the result buffer
|
||||
buf.add(toPtr[0..size]);
|
||||
}
|
||||
|
||||
// Used for 'writing' to a changable memory buffer before writing to
|
||||
// file
|
||||
OutBuffer buf;
|
||||
|
||||
// Common vertex and index buffers for all quads. One buffer per
|
||||
// level.
|
||||
float[][] vertBuf;
|
||||
ushort[] indexBuf;
|
||||
|
||||
// Variables that must be set during the gen phase
|
||||
int maxLevel;
|
||||
int alphaSize;
|
||||
|
||||
// Contains a unique index for each string
|
||||
int[char[]] stringList;
|
||||
char[][int] stringLookup;
|
||||
size_t totalStringLength;
|
||||
|
||||
// List of all quads
|
||||
QuadInfo[] quadList;
|
||||
|
||||
// Output file
|
||||
File mainFile;
|
||||
size_t fileOffset;
|
||||
|
||||
// Index file name
|
||||
char[] iname;
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
class BaseLand
|
||||
{
|
||||
public:
|
||||
BaseLand()
|
||||
{
|
||||
createMesh();
|
||||
}
|
||||
|
||||
~BaseLand()
|
||||
{
|
||||
destroyMesh();
|
||||
}
|
||||
|
||||
// Repositions the mesh based on camera location
|
||||
void update()
|
||||
{
|
||||
Ogre::Real vd = mCamera->getFarClipDistance();
|
||||
// Recreate the mesh if the view distance has increased
|
||||
if ( vd > mMeshDistance )
|
||||
{
|
||||
destroyMesh();
|
||||
createMesh();
|
||||
}
|
||||
|
||||
Ogre::Vector3 p = mCamera->getDerivedPosition();
|
||||
p.x -= ((int)p.x % CELL_WIDTH);
|
||||
p.z -= ((int)p.z % CELL_WIDTH);
|
||||
|
||||
float h = (p.y + 2048)*2.0/CELL_WIDTH;
|
||||
h *= h;
|
||||
|
||||
mNode->setPosition(p.x, -p.z, -32 -h);
|
||||
}
|
||||
|
||||
private:
|
||||
void createMesh()
|
||||
{
|
||||
float vd = mCamera->getFarClipDistance();
|
||||
|
||||
mMeshDistance = vd;
|
||||
|
||||
vd = vd/CELL_WIDTH * 32;
|
||||
|
||||
mMat = Ogre::MaterialManager::getSingleton().
|
||||
create("BaseLandMat",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
|
||||
Ogre::TextureUnitState* us = mMat->getTechnique(0)->getPass(0)->createTextureUnitState("_land_default.dds");
|
||||
us->setTextureScale(1.0f/vd,1.0f/vd);
|
||||
|
||||
mMat->getTechnique(0)->getPass(0)->setDepthBias(-1);
|
||||
|
||||
mObject = mSceneMgr->createManualObject("BaseLand");
|
||||
mObject->begin("BaseLandMat", Ogre::RenderOperation::OT_TRIANGLE_LIST);
|
||||
|
||||
vd = mMeshDistance;
|
||||
|
||||
const int HEIGHT = -2048 - 10;
|
||||
|
||||
mObject->position(-vd,vd,HEIGHT);
|
||||
mObject->textureCoord(0, 1);
|
||||
|
||||
mObject->position(-vd,-vd,HEIGHT);
|
||||
mObject->textureCoord(0, 0);
|
||||
|
||||
mObject->position(vd,-vd,HEIGHT);
|
||||
mObject->textureCoord(1, 0);
|
||||
|
||||
mObject->position(vd,vd,HEIGHT);
|
||||
mObject->textureCoord(1, 1);
|
||||
|
||||
mObject->quad(0,1,2,3);
|
||||
|
||||
mObject->end();
|
||||
|
||||
mNode = g_rootTerrainNode->createChildSceneNode();
|
||||
mNode->attachObject(mObject);
|
||||
}
|
||||
|
||||
void destroyMesh()
|
||||
{
|
||||
mNode->detachAllObjects();
|
||||
mSceneMgr->destroyManualObject(mObject);
|
||||
mNode->getParentSceneNode()->removeAndDestroyChild(mNode->getName());
|
||||
|
||||
mMat->getCreator()->remove(mMat->getHandle());
|
||||
mMat = Ogre::MaterialPtr();
|
||||
}
|
||||
|
||||
///the created mesh
|
||||
Ogre::ManualObject* mObject;
|
||||
|
||||
///The material for the mesh
|
||||
Ogre::MaterialPtr mMat;
|
||||
|
||||
///scene node for the mesh
|
||||
Ogre::SceneNode* mNode;
|
||||
|
||||
///In essence, the farViewDistance of the camera last frame
|
||||
Ogre::Real mMeshDistance;
|
||||
};
|
@ -1,286 +0,0 @@
|
||||
// The Ogre renderable used to hold and display the terrain meshes.
|
||||
class TerrainMesh : public Ogre::Renderable, public Ogre::MovableObject
|
||||
{
|
||||
public:
|
||||
|
||||
TerrainMesh(Ogre::SceneNode *parent, const MeshInfo &info,
|
||||
int level, float scale)
|
||||
: Ogre::Renderable(),
|
||||
Ogre::MovableObject()
|
||||
{
|
||||
TRACE("TerrainMesh()");
|
||||
|
||||
mLevel = level;
|
||||
|
||||
// This is a bit messy, with everything in one function. We could
|
||||
// split it up later.
|
||||
|
||||
// Use MW coordinates all the way
|
||||
assert(info.worldWidth > 0);
|
||||
assert(info.minHeight <= info.maxHeight);
|
||||
mBounds.setExtents(0,0,info.minHeight,
|
||||
info.worldWidth, info.worldWidth,
|
||||
info.maxHeight);
|
||||
mCenter = mBounds.getCenter();
|
||||
mBoundingRadius = mBounds.getHalfSize().length();
|
||||
|
||||
// TODO: VertexData has a clone() function. This probably means we
|
||||
// can set this up once and then clone it, to get a completely
|
||||
// unnoticable increase in performance :)
|
||||
mVertices = new VertexData();
|
||||
mVertices->vertexStart = 0;
|
||||
mVertices->vertexCount = info.vertRows*info.vertCols;
|
||||
|
||||
VertexDeclaration* vertexDecl = mVertices->vertexDeclaration;
|
||||
size_t currOffset = 0;
|
||||
|
||||
vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
|
||||
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
|
||||
|
||||
vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_NORMAL);
|
||||
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
|
||||
|
||||
vertexDecl->addElement(0, currOffset, VET_FLOAT2,
|
||||
VES_TEXTURE_COORDINATES, 0);
|
||||
currOffset += VertexElement::getTypeSize(VET_FLOAT2);
|
||||
|
||||
assert(vertexDecl->getVertexSize(0) == currOffset);
|
||||
|
||||
HardwareVertexBufferSharedPtr mMainBuffer;
|
||||
mMainBuffer = HardwareBufferManager::getSingleton().createVertexBuffer
|
||||
(
|
||||
vertexDecl->getVertexSize(0), // size of one whole vertex
|
||||
mVertices->vertexCount, // number of vertices
|
||||
HardwareBuffer::HBU_STATIC_WRITE_ONLY, // usage
|
||||
false); // no shadow buffer
|
||||
|
||||
// Bind the data
|
||||
mVertices->vertexBufferBinding->setBinding(0, mMainBuffer);
|
||||
|
||||
// Fill the buffer
|
||||
float* verts = static_cast<float*>
|
||||
(mMainBuffer->lock(HardwareBuffer::HBL_DISCARD));
|
||||
info.fillVertexBuffer(verts,8*mVertices->vertexCount);
|
||||
mMainBuffer->unlock();
|
||||
|
||||
// Create the index data holder
|
||||
mIndices = new IndexData();
|
||||
mIndices->indexCount = 64*64*6; // TODO: Shouldn't be hard-coded
|
||||
mIndices->indexBuffer =
|
||||
HardwareBufferManager::getSingleton().createIndexBuffer
|
||||
( HardwareIndexBuffer::IT_16BIT,
|
||||
mIndices->indexCount,
|
||||
HardwareBuffer::HBU_STATIC_WRITE_ONLY,
|
||||
false);
|
||||
|
||||
// Fill the buffer with warm fuzzy archive data
|
||||
unsigned short* indices = static_cast<unsigned short*>
|
||||
(mIndices->indexBuffer->lock
|
||||
(0, mIndices->indexBuffer->getSizeInBytes(),
|
||||
HardwareBuffer::HBL_DISCARD));
|
||||
info.fillIndexBuffer(indices,mIndices->indexCount);
|
||||
mIndices->indexBuffer->unlock();
|
||||
|
||||
// Finally, create the material
|
||||
const std::string texName = info.getTexName();
|
||||
|
||||
// Set up the scene node.
|
||||
mNode = parent->createChildSceneNode();
|
||||
mNode->attachObject(this);
|
||||
|
||||
// Finally, create or retrieve the material
|
||||
if(MaterialManager::getSingleton().resourceExists(texName))
|
||||
{
|
||||
mMaterial = MaterialManager::getSingleton().getByName
|
||||
(texName);
|
||||
return;
|
||||
}
|
||||
|
||||
// No existing material. Create a new one.
|
||||
mMaterial = MaterialManager::getSingleton().create
|
||||
(texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
|
||||
Pass* pass = mMaterial->getTechnique(0)->getPass(0);
|
||||
pass->setLightingEnabled(false);
|
||||
|
||||
if(level > 1)
|
||||
{
|
||||
// This material just has a normal texture
|
||||
pass->createTextureUnitState(texName)
|
||||
//->setTextureAddressingMode(TextureUnitState::TAM_CLAMP)
|
||||
;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(level == 1);
|
||||
|
||||
// Get the background texture. TODO: We should get this from
|
||||
// somewhere, no file names should be hard coded. The texture
|
||||
// might exist as a .tga in earlier versions of the game, and
|
||||
// we might also want to specify a different background
|
||||
// texture on some meshes.
|
||||
//const char *bgTex = info.getBackgroundTex();
|
||||
|
||||
const char *bgTex = "_land_default.dds";
|
||||
pass->createTextureUnitState(bgTex)
|
||||
->setTextureScale(scale,scale);
|
||||
|
||||
// Loop through all the textures in this mesh
|
||||
for(int tnum=0; tnum<info.alphaNum; tnum++)
|
||||
{
|
||||
const AlphaInfo &alpha = *info.getAlphaInfo(tnum);
|
||||
|
||||
// Name of the alpha map texture to create
|
||||
std::string alphaName = alpha.getAlphaName();
|
||||
|
||||
// Name of the texture
|
||||
std::string tname = alpha.getTexName();
|
||||
|
||||
// Create the alpha texture if it doesn't exist
|
||||
if(!TextureManager::getSingleton().resourceExists(alphaName))
|
||||
{
|
||||
TexturePtr texPtr = Ogre::TextureManager::
|
||||
getSingleton().createManual
|
||||
(alphaName,
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D,
|
||||
g_alphaSize,g_alphaSize,
|
||||
1,0, // depth, mipmaps
|
||||
Ogre::PF_A8, // One-channel alpha
|
||||
Ogre::TU_STATIC_WRITE_ONLY);
|
||||
|
||||
// Get the pointer
|
||||
Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer();
|
||||
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
|
||||
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
||||
Ogre::uint8* pDest = static_cast<Ogre::uint8*>(pixelBox.data);
|
||||
|
||||
// Copy alpha data from file
|
||||
alpha.fillAlphaBuffer(pDest,g_alphaSize*g_alphaSize);
|
||||
|
||||
// Close the buffer
|
||||
pixelBuffer->unlock();
|
||||
}
|
||||
|
||||
pass = mMaterial->getTechnique(0)->createPass();
|
||||
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
|
||||
pass->setLightingEnabled(false);
|
||||
pass->setDepthFunction(Ogre::CMPF_EQUAL);
|
||||
|
||||
Ogre::TextureUnitState* tus = pass->createTextureUnitState(alphaName);
|
||||
//tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
||||
|
||||
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_TEXTURE);
|
||||
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_TEXTURE);
|
||||
tus->setIsAlpha(true);
|
||||
|
||||
// Add the actual texture on top of the alpha map.
|
||||
tus = pass->createTextureUnitState(tname);
|
||||
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_CURRENT);
|
||||
|
||||
tus->setTextureScale(scale, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~TerrainMesh()
|
||||
{
|
||||
assert(mNode);
|
||||
mNode->detachAllObjects();
|
||||
mNode->getCreator()->destroySceneNode(mNode);
|
||||
|
||||
// TODO: This still crashes on level1 meshes. Find out why!
|
||||
if(mLevel!=1)delete mVertices;
|
||||
delete mIndices;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// These are all Ogre functions that we have to override
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
// Internal Ogre function. We should call visitor->visit on all
|
||||
// Renderables that are part of this object. In our case, this is
|
||||
// only ourselves.
|
||||
void visitRenderables(Renderable::Visitor* visitor,
|
||||
bool debugRenderables = false) {
|
||||
visitor->visit(this, 0, false);
|
||||
}
|
||||
|
||||
void getRenderOperation( Ogre::RenderOperation& op ) {
|
||||
op.useIndexes = true;
|
||||
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
|
||||
op.vertexData = mVertices;
|
||||
op.indexData = mIndices;
|
||||
}
|
||||
|
||||
void getWorldTransforms( Ogre::Matrix4* xform ) const {
|
||||
*xform = mNode->_getFullTransform();
|
||||
}
|
||||
|
||||
const Ogre::Quaternion& getWorldOrientation(void) const {
|
||||
return mNode->_getDerivedOrientation();
|
||||
}
|
||||
|
||||
const Ogre::Vector3& getWorldPosition(void) const {
|
||||
return mNode->_getDerivedPosition();
|
||||
}
|
||||
|
||||
Ogre::Real getSquaredViewDepth(const Ogre::Camera *cam) const {
|
||||
Ogre::Vector3 diff = mCenter - cam->getDerivedPosition();
|
||||
// Use squared length to avoid square root
|
||||
return diff.squaredLength();
|
||||
}
|
||||
|
||||
const Ogre::LightList& getLights(void) const {
|
||||
if (mLightListDirty) {
|
||||
getParentSceneNode()->getCreator()->_populateLightList
|
||||
(mCenter, mBoundingRadius, mLightList);
|
||||
mLightListDirty = false;
|
||||
}
|
||||
return mLightList;
|
||||
}
|
||||
virtual const Ogre::String& getMovableType( void ) const {
|
||||
static Ogre::String t = "MW_TERRAIN";
|
||||
return t;
|
||||
}
|
||||
void _updateRenderQueue( Ogre::RenderQueue* queue ) {
|
||||
mLightListDirty = true;
|
||||
queue->addRenderable(this, mRenderQueueID);
|
||||
}
|
||||
const Ogre::AxisAlignedBox& getBoundingBox( void ) const
|
||||
{
|
||||
return mBounds;
|
||||
}
|
||||
|
||||
Ogre::Real getBoundingRadius(void) const {
|
||||
return mBoundingRadius;
|
||||
}
|
||||
virtual const MaterialPtr& getMaterial(void) const
|
||||
{ return mMaterial; }
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
private:
|
||||
|
||||
int mLevel;
|
||||
|
||||
Ogre::SceneNode* mNode;
|
||||
|
||||
Ogre::MaterialPtr mMaterial;
|
||||
|
||||
Ogre::VertexData* mVertices;
|
||||
Ogre::IndexData* mIndices;
|
||||
|
||||
mutable bool mLightListDirty;
|
||||
|
||||
Ogre::Real mBoundingRadius;
|
||||
Ogre::AxisAlignedBox mBounds;
|
||||
Ogre::Vector3 mCenter;
|
||||
};
|
@ -1,413 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2009 Jacob Essex, Nicolay Korslund
|
||||
WWW: http://openmw.sourceforge.net/
|
||||
|
||||
This file (cpp_terrain.cpp) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
const int CELL_WIDTH = 8192;
|
||||
|
||||
SceneNode *g_rootTerrainNode;
|
||||
int g_alphaSize;
|
||||
|
||||
struct MeshInfo;
|
||||
struct AlphaInfo;
|
||||
|
||||
// D functions
|
||||
extern "C"
|
||||
{
|
||||
void d_terr_superman();
|
||||
void d_terr_terrainUpdate();
|
||||
|
||||
char *d_terr_getTexName(int32_t);
|
||||
|
||||
void d_terr_fillVertexBuffer(const MeshInfo*,float*,uint64_t);
|
||||
void d_terr_fillIndexBuffer(const MeshInfo*,uint16_t*,uint64_t);
|
||||
AlphaInfo *d_terr_getAlphaInfo(const MeshInfo*,int32_t);
|
||||
|
||||
void d_terr_fillAlphaBuffer(const AlphaInfo*,uint8_t*,uint64_t);
|
||||
|
||||
int32_t d_terr_getAlphaSize();
|
||||
}
|
||||
|
||||
// Info about a submesh. This is a clone of the struct defined in
|
||||
// archive.d. TODO: Make sure the D and C++ structs are of the same
|
||||
// size and alignment.
|
||||
struct MeshInfo
|
||||
{
|
||||
// Bounding box info
|
||||
float minHeight, maxHeight;
|
||||
float worldWidth;
|
||||
|
||||
// Vertex and index numbers
|
||||
int32_t vertRows, vertCols;
|
||||
|
||||
// Height offset to apply to all vertices
|
||||
float heightOffset;
|
||||
|
||||
// Size and offset of the vertex buffer
|
||||
int64_t vertBufSize, vertBufOffset;
|
||||
|
||||
// Texture name. Index to the string table.
|
||||
int32_t texName;
|
||||
|
||||
// Number and offset of AlphaInfo blocks
|
||||
int32_t alphaNum;
|
||||
uint64_t alphaOffset;
|
||||
|
||||
inline void fillVertexBuffer(float *buffer, uint64_t size) const
|
||||
{
|
||||
d_terr_fillVertexBuffer(this, buffer, size);
|
||||
}
|
||||
|
||||
inline void fillIndexBuffer(uint16_t *buffer, uint64_t size) const
|
||||
{
|
||||
d_terr_fillIndexBuffer(this, buffer, size);
|
||||
}
|
||||
|
||||
inline char* getTexName() const
|
||||
{
|
||||
return d_terr_getTexName(texName);
|
||||
}
|
||||
|
||||
inline AlphaInfo *getAlphaInfo(int tnum) const
|
||||
{
|
||||
return d_terr_getAlphaInfo(this, tnum);
|
||||
}
|
||||
};
|
||||
|
||||
// Info about an alpha map belonging to a mesh
|
||||
struct AlphaInfo
|
||||
{
|
||||
// Position of the actual image data
|
||||
uint64_t bufSize, bufOffset;
|
||||
|
||||
// The texture name for this layer. The actual string is stored in
|
||||
// the archive's string buffer.
|
||||
int32_t texName;
|
||||
int32_t alphaName;
|
||||
|
||||
inline char* getTexName() const
|
||||
{
|
||||
return d_terr_getTexName(texName);
|
||||
}
|
||||
|
||||
inline char* getAlphaName() const
|
||||
{
|
||||
return d_terr_getTexName(alphaName);
|
||||
}
|
||||
|
||||
inline void fillAlphaBuffer(uint8_t *buffer, uint64_t size) const
|
||||
{
|
||||
return d_terr_fillAlphaBuffer(this, buffer, size);
|
||||
}
|
||||
};
|
||||
|
||||
#include "cpp_baseland.cpp"
|
||||
#include "cpp_mesh.cpp"
|
||||
|
||||
BaseLand *g_baseLand;
|
||||
|
||||
class TerrainFrameListener : public FrameListener
|
||||
{
|
||||
protected:
|
||||
bool frameEnded(const FrameEvent& evt)
|
||||
{
|
||||
TRACE("Terrain frame");
|
||||
d_terr_terrainUpdate();
|
||||
if(g_baseLand)
|
||||
g_baseLand->update();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Renders a material into a texture
|
||||
Ogre::TexturePtr getRenderedTexture(Ogre::MaterialPtr mp,
|
||||
const std::string& name,
|
||||
int texSize, Ogre::PixelFormat tt)
|
||||
{
|
||||
Ogre::CompositorPtr cp = Ogre::CompositorManager::getSingleton().
|
||||
create("Rtt_Comp",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
|
||||
Ogre::CompositionTargetPass* ctp = cp->createTechnique()->getOutputTargetPass();
|
||||
Ogre::CompositionPass* cpass = ctp->createPass();
|
||||
cpass->setType(Ogre::CompositionPass::PT_RENDERQUAD);
|
||||
cpass->setMaterial(mp);
|
||||
|
||||
// Create the destination texture
|
||||
Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().
|
||||
createManual(name + "_T",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D,
|
||||
texSize,
|
||||
texSize,
|
||||
0,
|
||||
tt,
|
||||
Ogre::TU_RENDERTARGET
|
||||
);
|
||||
|
||||
Ogre::RenderTexture* renderTexture = texture->getBuffer()->getRenderTarget();
|
||||
Ogre::Viewport* vp = renderTexture->addViewport(mCamera);
|
||||
|
||||
Ogre::CompositorManager::getSingleton().addCompositor(vp, "Rtt_Comp");
|
||||
Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp,"Rtt_Comp", true);
|
||||
|
||||
renderTexture->update();
|
||||
|
||||
// Call the OGRE renderer.
|
||||
Ogre::Root::getSingleton().renderOneFrame();
|
||||
|
||||
Ogre::CompositorManager::getSingleton().removeCompositor(vp, "Rtt_Comp");
|
||||
Ogre::CompositorManager::getSingleton().remove(cp->getHandle());
|
||||
|
||||
renderTexture->removeAllViewports();
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
// These are used between some functions below. Kinda messy. Since
|
||||
// these are GLOBAL instances, they are terminated at program
|
||||
// exit. However, OGRE itself is terminated before that, so we have to
|
||||
// make sure we have no 'active' shared pointers after OGRE is
|
||||
// finished (otherwise we get a segfault at exit.)
|
||||
std::list<Ogre::ResourcePtr> createdResources;
|
||||
Ogre::HardwarePixelBuffer *pixelBuffer;
|
||||
MaterialPtr mat;
|
||||
|
||||
// Functions called from D
|
||||
extern "C"
|
||||
{
|
||||
SceneNode* terr_createChildNode(float x, float y,
|
||||
SceneNode *parent)
|
||||
{
|
||||
Ogre::Vector3 pos(x,y,0);
|
||||
if(parent == NULL)
|
||||
parent = g_rootTerrainNode;
|
||||
|
||||
assert(parent);
|
||||
return parent->createChildSceneNode(pos);
|
||||
}
|
||||
|
||||
void terr_destroyNode(SceneNode *node)
|
||||
{
|
||||
node->removeAndDestroyAllChildren();
|
||||
mSceneMgr->destroySceneNode(node);
|
||||
}
|
||||
|
||||
// TODO: We could make allocation a little more refined than new and
|
||||
// delete. But that's true for everything here. A freelist based
|
||||
// approach is best in most of these cases, as we have continuous
|
||||
// allocation/deallocation of fixed-size structs.
|
||||
Ogre::AxisAlignedBox *terr_makeBounds(float minHeight, float maxHeight,
|
||||
float width, SceneNode* node)
|
||||
{
|
||||
TRACE("terr_makeBounds");
|
||||
AxisAlignedBox *mBounds = new AxisAlignedBox;
|
||||
|
||||
assert(maxHeight >= minHeight);
|
||||
|
||||
mBounds->setExtents(0,0,minHeight,
|
||||
width,width,maxHeight);
|
||||
|
||||
// Transform the box to world coordinates, so it can be compared
|
||||
// with the camera later.
|
||||
mBounds->transformAffine(node->_getFullTransform());
|
||||
|
||||
return mBounds;
|
||||
}
|
||||
|
||||
void terr_killBounds(AxisAlignedBox *bounds)
|
||||
{
|
||||
TRACE("terr_killBounds");
|
||||
delete bounds;
|
||||
}
|
||||
|
||||
float terr_getSqCamDist(AxisAlignedBox *mBounds)
|
||||
{
|
||||
TRACE("terr_getSqCamDist");
|
||||
Ogre::Vector3 cpos = mCamera->getDerivedPosition();
|
||||
Ogre::Vector3 diff(0, 0, 0);
|
||||
diff.makeFloor(cpos - mBounds->getMinimum() );
|
||||
diff.makeCeil(cpos - mBounds->getMaximum() );
|
||||
return diff.squaredLength();
|
||||
}
|
||||
|
||||
TerrainMesh *terr_makeMesh(SceneNode *parent,
|
||||
MeshInfo *info,
|
||||
int level, float scale)
|
||||
{
|
||||
return new TerrainMesh(parent, *info, level, scale);
|
||||
}
|
||||
|
||||
void terr_killMesh(TerrainMesh *mesh)
|
||||
{
|
||||
TRACE("terr_killMesh");
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
// Set up the rendering system
|
||||
void terr_setupRendering()
|
||||
{
|
||||
TRACE("terr_setupRendering()");
|
||||
// Make sure the C++ sizes match the D sizes, since the structs
|
||||
// are shared between the two.
|
||||
assert(sizeof(MeshInfo) == 14*4);
|
||||
assert(sizeof(AlphaInfo) == 6*4);
|
||||
|
||||
// Add the terrain directory as a resource location. TODO: Get the
|
||||
// name from D.
|
||||
ResourceGroupManager::getSingleton().
|
||||
addResourceLocation("cache/terrain/", "FileSystem", "General");
|
||||
|
||||
// Enter superman mode
|
||||
mCamera->setFarClipDistance(40*CELL_WIDTH);
|
||||
//ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
|
||||
d_terr_superman();
|
||||
|
||||
// Create a root scene node first. The 'root' node is rotated to
|
||||
// match the MW coordinate system
|
||||
g_rootTerrainNode = mwRoot->createChildSceneNode("TERRAIN_ROOT");
|
||||
|
||||
// Add the base land. This is the ground beneath the actual
|
||||
// terrain mesh that makes the terrain look infinite.
|
||||
//g_baseLand = new BaseLand();
|
||||
|
||||
g_alphaSize = d_terr_getAlphaSize();
|
||||
|
||||
// Add the frame listener
|
||||
mRoot->addFrameListener(new TerrainFrameListener);
|
||||
}
|
||||
|
||||
// The next four functions are called in the function genLevel2Map()
|
||||
// only. This is very top-down-programming-ish and a bit messy, but
|
||||
// that's what I get for mixing C++ and D like this.
|
||||
void terr_makeLandMaterial(const char* name, float scale)
|
||||
{
|
||||
// Get a new material
|
||||
mat = Ogre::MaterialManager::getSingleton().
|
||||
create(name,
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
|
||||
// Put the default texture in the bottom 'layer', so that we don't
|
||||
// end up seeing through the landscape.
|
||||
Ogre::Pass* np = mat->getTechnique(0)->getPass(0);
|
||||
np->setLightingEnabled(false);
|
||||
np->createTextureUnitState("_land_default.dds")
|
||||
->setTextureScale(scale,scale);
|
||||
}
|
||||
|
||||
uint8_t *terr_makeAlphaLayer(const char* name, int32_t width)
|
||||
{
|
||||
// Create alpha map for this texture.
|
||||
Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton().
|
||||
createManual(name,
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D,
|
||||
width, width,
|
||||
1,0, // depth, mipmaps
|
||||
Ogre::PF_A8, // One-channel alpha
|
||||
Ogre::TU_STATIC_WRITE_ONLY);
|
||||
|
||||
createdResources.push_back(texPtr);
|
||||
|
||||
pixelBuffer = texPtr->getBuffer().get();
|
||||
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
|
||||
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
||||
|
||||
return static_cast<Ogre::uint8*>(pixelBox.data);
|
||||
}
|
||||
|
||||
void terr_closeAlpha(const char *alphaName,
|
||||
const char *texName, float scale)
|
||||
{
|
||||
// Close the alpha pixel buffer opened in the previous function
|
||||
pixelBuffer->unlock();
|
||||
|
||||
// Create a pass containing the alpha map
|
||||
Pass *np = mat->getTechnique(0)->createPass();
|
||||
np->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
|
||||
np->setLightingEnabled(false);
|
||||
np->setDepthFunction(Ogre::CMPF_EQUAL);
|
||||
Ogre::TextureUnitState* tus = np->createTextureUnitState(alphaName);
|
||||
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
||||
|
||||
// Set various blending options
|
||||
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_TEXTURE);
|
||||
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_TEXTURE);
|
||||
tus->setIsAlpha(true);
|
||||
|
||||
// Add the terrain texture to the pass and scale it.
|
||||
tus = np->createTextureUnitState(texName);
|
||||
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_CURRENT);
|
||||
tus->setTextureScale(scale, scale);
|
||||
}
|
||||
|
||||
// Clean up after the above functions, render the material to
|
||||
// texture and save the data in outdata and in the file outname.
|
||||
void terr_cleanupAlpha(const char *outname,
|
||||
void *outData, int32_t toSize)
|
||||
{
|
||||
TexturePtr tex1 = getRenderedTexture(mat,outname,
|
||||
toSize,Ogre::PF_R8G8B8);
|
||||
|
||||
// Blit the texture into the given memory buffer
|
||||
PixelBox pb = PixelBox(toSize, toSize, 1, PF_R8G8B8);
|
||||
pb.data = outData;
|
||||
tex1->getBuffer()->blitToMemory(pb);
|
||||
|
||||
// Clean up
|
||||
TextureManager::getSingleton().remove(tex1->getHandle());
|
||||
const std::list<Ogre::ResourcePtr>::const_iterator iend = createdResources.end();
|
||||
for ( std::list<Ogre::ResourcePtr>::const_iterator itr = createdResources.begin();
|
||||
itr != iend;
|
||||
++itr)
|
||||
(*itr)->getCreator()->remove((*itr)->getHandle());
|
||||
createdResources.clear();
|
||||
|
||||
MaterialManager::getSingleton().remove(mat->getHandle());
|
||||
mat.setNull();
|
||||
}
|
||||
|
||||
void terr_resize(void* srcPtr, void* dstPtr, int32_t fromW, int32_t toW)
|
||||
{
|
||||
// Create pixelboxes
|
||||
PixelBox src = PixelBox(fromW, fromW, 1, PF_R8G8B8);
|
||||
PixelBox dst = PixelBox(toW, toW, 1, PF_R8G8B8);
|
||||
|
||||
src.data = srcPtr;
|
||||
dst.data = dstPtr;
|
||||
|
||||
// Resize the image. The nearest neighbour filter makes sure
|
||||
// there is no blurring.
|
||||
Image::scale(src, dst, Ogre::Image::FILTER_NEAREST);
|
||||
}
|
||||
|
||||
void terr_saveImage(void *data, int32_t width, const char* name)
|
||||
{
|
||||
Image img;
|
||||
img.loadDynamicImage((uchar*)data, width, width, PF_R8G8B8);
|
||||
img.save(name);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user