1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-01 20:15:33 +00:00

Merge remote branch 'zini/master' into gui-windows

This commit is contained in:
Jan Borsodi 2010-10-23 01:13:11 +02:00
commit 38b434771a
40 changed files with 2919 additions and 121 deletions

View file

@ -38,7 +38,7 @@ PROJECT_NUMBER =
# If a relative path is entered, it will be relative to the location
# where doxygen was started. If left blank the current directory will be used.
OUTPUT_DIRECTORY = ../Doxygen
OUTPUT_DIRECTORY = Doxygen
# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
# 4096 sub-directories (in 2 levels) under the output directory of each output
@ -573,10 +573,9 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = ../apps
../components
../libs
../docs
INPUT = apps \
components \
libs \
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

1542
Docs/DoxyfilePages Normal file

File diff suppressed because it is too large Load diff

3
apps/doc.hpp Normal file
View file

@ -0,0 +1,3 @@
// Note: This is not a regular source file.
/// \defgroup apps Applications

View file

@ -184,11 +184,14 @@ set(GAMECLASS_HEADER
source_group(apps\\openmw\\mwclass FILES ${GAMECLASS} ${GAMECLASS_HEADER})
set(GAMEMECHANICS
mwmechanics/mechanicsmanager.cpp)
mwmechanics/mechanicsmanager.cpp
mwmechanics/magiceffects.cpp
)
set(GAMEMECHANICS_HEADER
mwmechanics/mechanicsmanager.hpp
mwmechanics/stat.hpp
mwmechanics/creaturestats.hpp
mwmechanics/magiceffects.hpp
)
source_group(apps\\openmw\\mwmechanics FILES ${GAMEMECHANICS} ${GAMEMECHANICS_HEADER})

44
apps/openmw/doc.hpp Normal file
View file

@ -0,0 +1,44 @@
// Note: This is not a regular source file.
/// \ingroup apps
/// \defgroup openmw OpenMW
/// \namespace OMW
/// \ingroup openmw
/// \brief Integration of OpenMW-subsystems
/// \namespace MWDialogue
/// \ingroup openmw
/// \brief NPC dialogues
/// \namespace MWMechanics
/// \ingroup openmw
/// \brief Game mechanics and NPC-AI
/// \namespace MWSound
/// \ingroup openmw
/// \brief Sound & music
/// \namespace MWGUI
/// \ingroup openmw
/// \brief HUD and windows
/// \namespace MWRender
/// \ingroup openmw
/// \brief Rendering via Ogre
/// \namespace MWWorld
/// \ingroup openmw
/// \brief World data
/// \namespace MWClass
/// \ingroup openmw
/// \brief Workaround for non-OOP design of the record system
/// \namespace MWInput
/// \ingroup openmw
/// \brief User input and character controls
/// \namespace MWScript
/// \ingroup openmw
/// \brief MW-specific script extentions and integration of the script system into OpenMW

View file

@ -117,6 +117,7 @@ OMW::Engine::Engine()
, mVerboseScripts (false)
, mNewGame (false)
, mUseSound (true)
, mCompileAll (false)
, mScriptManager (0)
, mScriptContext (0)
, mGuiManager (0)
@ -278,9 +279,20 @@ void OMW::Engine::go()
// load cell
ESM::Position pos;
pos.pos[0] = pos.pos[1] = pos.pos[2] = 0;
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
mEnvironment.mWorld->changeCell (mCellName, pos);
pos.pos[2] = 0;
if (const ESM::Cell *exterior = mEnvironment.mWorld->getExterior (mCellName))
{
mEnvironment.mWorld->indexToPosition (exterior->data.gridX, exterior->data.gridY,
pos.pos[0], pos.pos[1], true);
mEnvironment.mWorld->changeToExteriorCell (pos);
}
else
{
pos.pos[0] = pos.pos[1] = 0;
mEnvironment.mWorld->changeCell (mCellName, pos);
}
// Sets up the input system
MWInput::MWInputManager input(mOgre, mEnvironment.mWorld->getPlayerPos(),
@ -305,6 +317,29 @@ void OMW::Engine::go()
std::cout << " Music Error: " << e.what() << "\n";
}
// scripts
if (mCompileAll)
{
typedef ESMS::ScriptListT<ESM::Script>::MapType Container;
Container scripts = mEnvironment.mWorld->getStore().scripts.list;
int count = 0;
int success = 0;
for (Container::const_iterator iter (scripts.begin()); iter!=scripts.end(); ++iter, ++count)
if (mScriptManager->compile (iter->first))
++success;
if (count)
std::cout
<< "compiled " << success << " of " << count << " scripts ("
<< 100*static_cast<double> (success)/count
<< "%)"
<< std::endl;
}
// Start the main rendering loop
mOgre.start();
@ -345,3 +380,8 @@ void OMW::Engine::activate()
interpreterContext.executeActivation();
}
}
void OMW::Engine::setCompileAll (bool all)
{
mCompileAll = all;
}

View file

@ -60,6 +60,7 @@ namespace OMW
bool mVerboseScripts;
bool mNewGame;
bool mUseSound;
bool mCompileAll;
MWWorld::Environment mEnvironment;
MWScript::ScriptManager *mScriptManager;
@ -127,6 +128,9 @@ namespace OMW
/// Activate the focussed object.
void activate();
/// Compile all scripts (excludign dialogue scripts) at startup?
void setCompileAll (bool all);
};
}

View file

@ -33,7 +33,7 @@ using namespace std;
bool parseOptions (int argc, char**argv, OMW::Engine& engine)
{
// Create a local alias for brevity
namespace bpo = boost::program_options;
namespace bpo = boost::program_options;
bpo::options_description desc (
"Syntax: openmw <options>\nAllowed options");
@ -43,23 +43,24 @@ bool parseOptions (int argc, char**argv, OMW::Engine& engine)
("data", bpo::value<std::string>()->default_value ("data"),
"set data directory")
("start", bpo::value<std::string>()->default_value ("Beshara"),
"set initial cell (only interior cells supported at the moment")
"set initial cell")
("master", bpo::value<std::string>()->default_value ("Morrowind"),
"master file")
( "debug", "debug mode" )
( "nosound", "disable all sound" )
( "script-verbose", "verbose script output" )
( "new-game", "activate char gen/new game mechanics" )
( "script-all", "compile all scripts (excluding dialogue scripts) at startup")
;
bpo::variables_map variables;
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
std::string configFilePath(macBundlePath() + "/Contents/MacOS/openmw.cfg");
std::ifstream configFile (configFilePath.c_str());
#else
std::ifstream configFile ("openmw.cfg");
#endif
#endif
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).options(desc).allow_unregistered().run();
@ -78,19 +79,22 @@ bool parseOptions (int argc, char**argv, OMW::Engine& engine)
engine.setDataDir (variables["data"].as<std::string>());
engine.setCell (variables["start"].as<std::string>());
engine.addMaster (variables["master"].as<std::string>());
if (variables.count ("debug"))
engine.enableDebugMode();
if (variables.count ("nosound"))
engine.disableSound();
if (variables.count ("script-verbose"))
engine.enableVerboseScripts();
if (variables.count ("new-game"))
engine.setNewGame();
if (variables.count ("script-all"))
engine.setCompileAll (true);
return true;
}
@ -100,7 +104,7 @@ int main(int argc, char**argv)
try
{
OMW::Engine engine;
if (parseOptions (argc, argv, engine))
{
engine.go();
@ -111,7 +115,7 @@ int main(int argc, char**argv)
cout << "\nERROR: " << e.what() << endl;
return 1;
}
return 0;
}

View file

@ -560,13 +560,19 @@ void WindowManager::onClassChoice(MyGUI::WidgetPtr, int _index)
}
}
void WindowManager::showClassQuestionDialog()
namespace MWGui
{
struct Step
{
const char* text;
const char* buttons[3];
};
}
void WindowManager::showClassQuestionDialog()
{
static boost::array<Step, 2> steps = { {
{"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.",
{"Use herbs from your pack to put it to sleep?",

View file

@ -1,7 +1,11 @@
#ifndef GAME_MWMECHANICS_CREATURESTATS_H
#define GAME_MWMECHANICS_CREATURESTATS_H
#include <set>
#include <string>
#include "stat.hpp"
#include "magiceffects.hpp"
namespace MWMechanics
{
@ -10,6 +14,8 @@ namespace MWMechanics
Stat<int> mAttributes[8];
DynamicStat<int> mDynamic[3]; // health, magicka, fatigue
int mLevel;
std::set<std::string> mAbilities;
MagicEffects mMagicEffects;
};
}

View file

@ -0,0 +1,118 @@
#include "magiceffects.hpp"
#include <stdexcept>
#include <components/esm/defs.hpp>
namespace MWMechanics
{
EffectKey::EffectKey() : mId (0), mArg (-1) {}
EffectKey::EffectKey (const ESM::ENAMstruct& effect)
{
mId = effect.effectID;
mArg = -1;
if (effect.skill!=-1)
mArg = effect.skill;
if (effect.attribute!=-1)
{
if (mArg!=-1)
throw std::runtime_error (
"magic effect can't have both a skill and an attribute argument");
mArg = effect.attribute;
}
}
bool operator< (const EffectKey& left, const EffectKey& right)
{
if (left.mId<right.mId)
return true;
if (left.mId>right.mId)
return false;
return left.mArg<right.mArg;
}
EffectParam::EffectParam() : mMagnitude (0) {}
EffectParam& EffectParam::operator+= (const EffectParam& param)
{
mMagnitude += param.mMagnitude;
return *this;
}
EffectParam& EffectParam::operator-= (const EffectParam& param)
{
mMagnitude -= param.mMagnitude;
return *this;
}
void MagicEffects::add (const EffectKey& key, const EffectParam& param)
{
Collection::iterator iter = mCollection.find (key);
if (iter==mCollection.end())
{
mCollection.insert (std::make_pair (key, param));
}
else
{
iter->second += param;
}
}
EffectParam MagicEffects::get (const EffectKey& key) const
{
Collection::const_iterator iter = mCollection.find (key);
if (iter==mCollection.end())
{
return EffectParam();
}
else
{
return iter->second;
}
}
MagicEffects MagicEffects::diff (const MagicEffects& prev, const MagicEffects& now)
{
MagicEffects result;
// adding/changing
for (Collection::const_iterator iter (now.Begin()); iter!=now.End(); ++iter)
{
Collection::const_iterator other = prev.mCollection.find (iter->first);
if (other==prev.End())
{
// adding
result.add (iter->first, iter->second);
}
else
{
// changing
result.add (iter->first, iter->second - other->second);
}
}
// removing
for (Collection::const_iterator iter (prev.Begin()); iter!=prev.End(); ++iter)
{
Collection::const_iterator other = now.mCollection.find (iter->first);
if (other==prev.End())
{
result.add (iter->first, EffectParam() - iter->second);
}
}
return result;
}
}

View file

@ -0,0 +1,77 @@
#ifndef GAME_MWMECHANICS_MAGICEFFECTS_H
#define GAME_MWMECHANICS_MAGICEFFECTS_H
#include <map>
namespace ESM
{
struct ENAMstruct;
}
namespace MWMechanics
{
struct EffectKey
{
int mId;
int mArg; // skill or ability
EffectKey();
EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {}
EffectKey (const ESM::ENAMstruct& effect);
};
bool operator< (const EffectKey& left, const EffectKey& right);
struct EffectParam
{
int mMagnitude;
EffectParam();
EffectParam& operator+= (const EffectParam& param);
EffectParam& operator-= (const EffectParam& param);
};
inline EffectParam operator+ (const EffectParam& left, const EffectParam& right)
{
EffectParam param (left);
return param += right;
}
inline EffectParam operator- (const EffectParam& left, const EffectParam& right)
{
EffectParam param (left);
return param -= right;
}
/// \brief Effects currently affecting a NPC or creature
class MagicEffects
{
public:
typedef std::map<EffectKey, EffectParam> Collection;
private:
Collection mCollection;
public:
Collection::const_iterator Begin() const { return mCollection.begin(); }
Collection::const_iterator End() const { return mCollection.end(); }
void add (const EffectKey& key, const EffectParam& param);
EffectParam get (const EffectKey& key) const;
///< This function can safely be used for keys that are not present.
static MagicEffects diff (const MagicEffects& prev, const MagicEffects& now);
///< Return changes from \a prev to \a now.
};
}
#endif

View file

@ -22,63 +22,123 @@ namespace MWMechanics
// reset
creatureStats.mLevel = player->npdt52.level;
creatureStats.mAbilities.clear();
creatureStats.mMagicEffects = MagicEffects();
for (int i=0; i<27; ++i)
npcStats.mSkill[i].setBase (player->npdt52.skills[i]);
// race
const ESM::Race *race =
mEnvironment.mWorld->getStore().races.find (mEnvironment.mWorld->getPlayerPos().getRace());
bool male = mEnvironment.mWorld->getPlayerPos().isMale();
for (int i=0; i<8; ++i)
if (mRaceSelected)
{
const ESM::Race::MaleFemale *attribute = 0;
switch (i)
const ESM::Race *race =
mEnvironment.mWorld->getStore().races.find (
mEnvironment.mWorld->getPlayerPos().getRace());
bool male = mEnvironment.mWorld->getPlayerPos().isMale();
for (int i=0; i<8; ++i)
{
case 0: attribute = &race->data.strength; break;
case 1: attribute = &race->data.intelligence; break;
case 2: attribute = &race->data.willpower; break;
case 3: attribute = &race->data.agility; break;
case 4: attribute = &race->data.speed; break;
case 5: attribute = &race->data.endurance; break;
case 6: attribute = &race->data.personality; break;
case 7: attribute = &race->data.luck; break;
const ESM::Race::MaleFemale *attribute = 0;
switch (i)
{
case 0: attribute = &race->data.strength; break;
case 1: attribute = &race->data.intelligence; break;
case 2: attribute = &race->data.willpower; break;
case 3: attribute = &race->data.agility; break;
case 4: attribute = &race->data.speed; break;
case 5: attribute = &race->data.endurance; break;
case 6: attribute = &race->data.personality; break;
case 7: attribute = &race->data.luck; break;
}
creatureStats.mAttributes[i].setBase (
static_cast<int> (male ? attribute->male : attribute->female));
}
creatureStats.mAttributes[i].setBase (
static_cast<int> (male ? attribute->male : attribute->female));
}
for (int i=0; i<7; ++i)
{
int index = race->data.bonus[i].skill;
if (index>=0 && index<27)
for (int i=0; i<7; ++i)
{
npcStats.mSkill[i].setBase (
npcStats.mSkill[i].getBase() + race->data.bonus[i].bonus);
int index = race->data.bonus[i].skill;
if (index>=0 && index<27)
{
npcStats.mSkill[index].setBase (
npcStats.mSkill[index].getBase() + race->data.bonus[i].bonus);
}
}
for (std::vector<std::string>::const_iterator iter (race->powers.list.begin());
iter!=race->powers.list.end(); ++iter)
{
insertSpell (*iter, ptr);
}
}
// birthsign
// class
const ESM::Class& class_ = mEnvironment.mWorld->getPlayerPos().getClass();
for (int i=0; i<2; ++i)
if (!mEnvironment.mWorld->getPlayerPos().getBirthsign().empty())
{
int attribute = class_.data.attribute[i];
if (attribute>=0 && attribute<8)
const ESM::BirthSign *sign =
mEnvironment.mWorld->getStore().birthSigns.find (
mEnvironment.mWorld->getPlayerPos().getBirthsign());
for (std::vector<std::string>::const_iterator iter (sign->powers.list.begin());
iter!=sign->powers.list.end(); ++iter)
{
creatureStats.mAttributes[attribute].setBase (
creatureStats.mAttributes[attribute].getBase() + 10);
insertSpell (*iter, ptr);
}
}
// class
if (mClassSelected)
{
const ESM::Class& class_ = mEnvironment.mWorld->getPlayerPos().getClass();
for (int i=0; i<2; ++i)
{
int attribute = class_.data.attribute[i];
if (attribute>=0 && attribute<8)
{
creatureStats.mAttributes[attribute].setBase (
creatureStats.mAttributes[attribute].getBase() + 10);
}
}
for (int i=0; i<2; ++i)
{
int bonus = i==0 ? 10 : 25;
for (int i2=0; i2<5; ++i2)
{
int index = class_.data.skills[i2][i];
if (index>=0 && index<27)
{
npcStats.mSkill[index].setBase (
npcStats.mSkill[index].getBase() + bonus);
}
}
}
typedef ESMS::IndexListT<ESM::Skill>::MapType ContainerType;
const ContainerType& skills = mEnvironment.mWorld->getStore().skills.list;
for (ContainerType::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter)
{
if (iter->second.data.specialization==class_.data.specialization)
{
int index = iter->first;
if (index>=0 && index<27)
{
npcStats.mSkill[index].setBase (
npcStats.mSkill[index].getBase() + 5);
}
}
}
}
// magic effects
adjustMagicEffects (ptr);
// calculate dynamic stats
int strength = creatureStats.mAttributes[0].getBase();
@ -87,17 +147,81 @@ namespace MWMechanics
int agility = creatureStats.mAttributes[3].getBase();
int endurance = creatureStats.mAttributes[5].getBase();
double magickaFactor = creatureStats.mMagicEffects.get (EffectKey (84)).mMagnitude*0.1 + 0.5;
creatureStats.mDynamic[0].setBase (static_cast<int> (0.5 * (strength + endurance)));
// TODO: calculate factor
creatureStats.mDynamic[1].setBase (static_cast<int> (intelligence + 1 * intelligence));
creatureStats.mDynamic[1].setBase (static_cast<int> (intelligence +
magickaFactor * intelligence));
creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance);
for (int i=0; i<3; ++i)
creatureStats.mDynamic[i].setCurrent (creatureStats.mDynamic[i].getModified());
}
void MechanicsManager::insertSpell (const std::string& id, MWWorld::Ptr& creature)
{
MWMechanics::CreatureStats& creatureStats =
MWWorld::Class::get (creature).getCreatureStats (creature);
const ESM::Spell *spell = mEnvironment.mWorld->getStore().spells.find (id);
switch (spell->data.type)
{
case ESM::Spell::ST_Ability:
if (creatureStats.mAbilities.find (id)==creatureStats.mAbilities.end())
{
creatureStats.mAbilities.insert (id);
}
break;
// TODO ST_SPELL, ST_Blight, ST_Disease, ST_Curse, ST_Power
default:
std::cout
<< "adding unsupported spell type (" << spell->data.type
<< ") to creature: " << id << std::endl;
}
}
void MechanicsManager::adjustMagicEffects (MWWorld::Ptr& creature)
{
MWMechanics::CreatureStats& creatureStats =
MWWorld::Class::get (creature).getCreatureStats (creature);
MagicEffects now;
for (std::set<std::string>::const_iterator iter (creatureStats.mAbilities.begin());
iter!=creatureStats.mAbilities.end(); ++iter)
{
const ESM::Spell *spell = mEnvironment.mWorld->getStore().spells.find (*iter);
for (std::vector<ESM::ENAMstruct>::const_iterator iter = spell->effects.list.begin();
iter!=spell->effects.list.end(); ++iter)
{
if (iter->range==0) // self
{
EffectParam param;
param.mMagnitude = iter->magnMax; // TODO calculate magnitude
now.add (EffectKey (*iter), param);
}
}
}
// TODO add effects from other spell types, active spells and equipment
MagicEffects diff = MagicEffects::diff (creatureStats.mMagicEffects, now);
creatureStats.mMagicEffects = now;
// TODO apply diff to other stats
}
MechanicsManager::MechanicsManager (MWWorld::Environment& environment)
: mEnvironment (environment), mUpdatePlayer (true)
: mEnvironment (environment), mUpdatePlayer (true), mClassSelected (false),
mRaceSelected (false)
{
buildPlayer();
}
@ -237,6 +361,7 @@ namespace MWMechanics
{
mEnvironment.mWorld->getPlayerPos().setGender (male);
mEnvironment.mWorld->getPlayerPos().setRace (race);
mRaceSelected = true;
buildPlayer();
mUpdatePlayer = true;
}
@ -250,6 +375,7 @@ namespace MWMechanics
void MechanicsManager::setPlayerClass (const std::string& id)
{
mEnvironment.mWorld->getPlayerPos().setClass (*mEnvironment.mWorld->getStore().classes.find (id));
mClassSelected = true;
buildPlayer();
mUpdatePlayer = true;
}
@ -257,6 +383,7 @@ namespace MWMechanics
void MechanicsManager::setPlayerClass (const ESM::Class& class_)
{
mEnvironment.mWorld->getPlayerPos().setClass (class_);
mClassSelected = true;
buildPlayer();
mUpdatePlayer = true;
}

View file

@ -23,11 +23,17 @@ namespace MWMechanics
CreatureStats mWatchedCreature;
NpcStats mWatchedNpc;
bool mUpdatePlayer;
bool mClassSelected;
bool mRaceSelected;
void buildPlayer();
///< build player according to stored class/race/birthsign information. Will
/// default to the values of the ESM::NPC object, if no explicit information is given.
void insertSpell (const std::string& id, MWWorld::Ptr& creature);
void adjustMagicEffects (MWWorld::Ptr& creature);
public:
MechanicsManager (MWWorld::Environment& environment);

View file

@ -13,7 +13,7 @@ namespace Interpreter
namespace MWScript
{
/// \brief stats-related script functionality (creatures and NPCs)
/// \brief Container-related script functionality (chests, NPCs, creatures)
namespace Container
{
void registerExtensions (Compiler::Extensions& extensions);

View file

@ -95,4 +95,10 @@ op 0x2000085-0x200008b: Disable Controls
op 0x200008c: Unlock
op 0x200008d: Unlock, explicit reference
op 0x200008e: COE
opcodes 0x200008f-0x3ffffff unused
op 0x200008e-0x20000a8: GetSkill
op 0x20000a9-0x20000c3: GetSkill, explicit reference
op 0x20000c4-0x20000de: SetSkill
op 0x20000df-0x20000f9: SetSkill, explicit reference
op 0x20000fa-0x2000114: ModSkill
op 0x2000115-0x200012f: ModSKill, explicit reference
opcodes 0x2000130-0x3ffffff unused

View file

@ -27,7 +27,7 @@ namespace Interpreter
}
namespace MWScript
{
{
class ScriptManager
{
Compiler::StreamErrorHandler mErrorHandler;
@ -35,19 +35,21 @@ namespace MWScript
bool mVerbose;
Compiler::Context& mCompilerContext;
Compiler::FileParser mParser;
std::map<std::string, std::vector<Interpreter::Type_Code> > mScripts;
bool compile (const std::string& name);
public:
ScriptManager (const ESMS::ESMStore& store, bool verbose,
Compiler::Context& compilerContext);
void run (const std::string& name, Interpreter::Context& interpreterContext);
///< Run the script with the given name (compile first, if not compiled yet)
bool compile (const std::string& name);
///< Compile script with the given namen
/// \return Success?
};
};
#endif

View file

@ -468,6 +468,160 @@ namespace MWScript
}
};
class OpGetSkill : public Interpreter::Opcode0
{
int mIndex;
public:
OpGetSkill (int index) : mIndex (index) {}
virtual void execute (Interpreter::Runtime& runtime)
{
MWScript::InterpreterContext& context
= static_cast<MWScript::InterpreterContext&> (runtime.getContext());
MWWorld::Ptr ptr = context.getReference();
Interpreter::Type_Integer value =
MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex].
getModified();
runtime.push (value);
}
};
class OpGetSkillExplicit : public Interpreter::Opcode0
{
int mIndex;
public:
OpGetSkillExplicit (int index) : mIndex (index) {}
virtual void execute (Interpreter::Runtime& runtime)
{
MWScript::InterpreterContext& context
= static_cast<MWScript::InterpreterContext&> (runtime.getContext());
std::string id = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWWorld::Ptr ptr = context.getWorld().getPtr (id, false);
Interpreter::Type_Integer value =
MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex].
getModified();
runtime.push (value);
}
};
class OpSetSkill : public Interpreter::Opcode0
{
int mIndex;
public:
OpSetSkill (int index) : mIndex (index) {}
virtual void execute (Interpreter::Runtime& runtime)
{
MWScript::InterpreterContext& context
= static_cast<MWScript::InterpreterContext&> (runtime.getContext());
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
MWWorld::Ptr ptr = context.getReference();
MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex].
setModified (value, 0);
}
};
class OpSetSkillExplicit : public Interpreter::Opcode0
{
int mIndex;
public:
OpSetSkillExplicit (int index) : mIndex (index) {}
virtual void execute (Interpreter::Runtime& runtime)
{
MWScript::InterpreterContext& context
= static_cast<MWScript::InterpreterContext&> (runtime.getContext());
std::string id = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
MWWorld::Ptr ptr = context.getWorld().getPtr (id, false);
MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex].
setModified (value, 0);
}
};
class OpModSkill : public Interpreter::Opcode0
{
int mIndex;
public:
OpModSkill (int index) : mIndex (index) {}
virtual void execute (Interpreter::Runtime& runtime)
{
MWScript::InterpreterContext& context
= static_cast<MWScript::InterpreterContext&> (runtime.getContext());
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
MWWorld::Ptr ptr = context.getReference();
value += MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex].
getModified();
MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex].
setModified (value, 0, 100);
}
};
class OpModSkillExplicit : public Interpreter::Opcode0
{
int mIndex;
public:
OpModSkillExplicit (int index) : mIndex (index) {}
virtual void execute (Interpreter::Runtime& runtime)
{
MWScript::InterpreterContext& context
= static_cast<MWScript::InterpreterContext&> (runtime.getContext());
std::string id = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
MWWorld::Ptr ptr = context.getWorld().getPtr (id, false);
value +=
MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex].
getModified();
MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex].
setModified (value, 0, 100);
}
};
const int numberOfAttributes = 8;
const int opcodeGetAttribute = 0x2000027;
@ -490,6 +644,15 @@ namespace MWScript
const int opcodeGetDynamicGetRatio = 0x200006f;
const int opcodeGetDynamicGetRatioExplicit = 0x2000072;
const int numberOfSkills = 27;
const int opcodeGetSkill = 0x200008e;
const int opcodeGetSkillExplicit = 0x20000a9;
const int opcodeSetSkill = 0x20000c4;
const int opcodeSetSkillExplicit = 0x20000df;
const int opcodeModSkill = 0x20000fa;
const int opcodeModSkillExplicit = 0x2000115;
void registerExtensions (Compiler::Extensions& extensions)
{
static const char *attributes[numberOfAttributes] =
@ -503,6 +666,16 @@ namespace MWScript
"health", "magicka", "fatigue"
};
static const char *skills[numberOfSkills] =
{
"block", "armorer", "mediumarmor", "heavyarmor", "bluntweapon",
"longblade", "axe", "spear", "athletics", "enchant", "destruction",
"alteration", "illusion", "conjuration", "mysticism",
"restoration", "alchemy", "unarmored", "security", "sneak",
"acrobatics", "lightarmor", "shortblade", "marksman",
"merchantile", "speechcraft", "handtohand"
};
std::string get ("get");
std::string set ("set");
std::string mod ("mod");
@ -537,7 +710,18 @@ namespace MWScript
extensions.registerFunction (get + dynamics[i] + getRatio, 'f', "",
opcodeGetDynamicGetRatio+i, opcodeGetDynamicGetRatioExplicit+i);
}
for (int i=0; i<numberOfSkills; ++i)
{
extensions.registerFunction (get + skills[i], 'l', "",
opcodeGetSkill+i, opcodeGetSkillExplicit+i);
extensions.registerInstruction (set + skills[i], "l",
opcodeSetSkill+i, opcodeSetSkillExplicit+i);
extensions.registerInstruction (mod + skills[i], "l",
opcodeModSkill+i, opcodeModSkillExplicit+i);
}
}
@ -581,6 +765,18 @@ namespace MWScript
interpreter.installSegment5 (opcodeGetDynamicGetRatioExplicit+i,
new OpGetDynamicGetRatioExplicit (i));
}
for (int i=0; i<numberOfSkills; ++i)
{
interpreter.installSegment5 (opcodeGetSkill+i, new OpGetSkill (i));
interpreter.installSegment5 (opcodeGetSkillExplicit+i, new OpGetSkillExplicit (i));
interpreter.installSegment5 (opcodeSetSkill+i, new OpSetSkill (i));
interpreter.installSegment5 (opcodeSetSkillExplicit+i, new OpSetSkillExplicit (i));
interpreter.installSegment5 (opcodeModSkill+i, new OpModSkill (i));
interpreter.installSegment5 (opcodeModSkillExplicit+i, new OpModSkillExplicit (i));
}
}
}
}

30
components/doc.hpp Normal file
View file

@ -0,0 +1,30 @@
// Note: This is not a regular source file.
/// \defgroup components Components
/// \namespace ESMS
/// \ingroup components
/// \brief ESM/ESP record store
/// \namespace ESM
/// \ingroup components
/// \brief ESM/ESP records
/// \namespace FileFinder
/// \ingroup components
/// \brief Linux/Windows-path resolving
/// \namespace ToUTF
/// \ingroup components
/// \brief Text encoding
/// \namespace Compiler
/// \ingroup components
/// \brief script compiler
/// \namespace Interpreter
/// \ingroup components
/// \brief script interpreter
// TODO put nif and nifogre in different namespaces (or merge them)
// TODO put other components into namespaces

View file

@ -68,7 +68,7 @@ struct ENAMstruct
// Which skills/attributes are affected (for restore/drain spells
// etc.)
char skill, attribute; // -1 if N/A
signed char skill, attribute; // -1 if N/A
// Other spell parameters
int range; // 0 - self, 1 - touch, 2 - target (RangeType enum)
@ -84,7 +84,7 @@ struct EffectList
void load(ESMReader &esm)
{
ENAMstruct s;
ENAMstruct s;
while(esm.isNextSub("ENAM"))
{
esm.getHT(s, 24);

View file

@ -215,6 +215,7 @@ struct Cell
else ref.teleport = false;
// Integer, despite the name suggesting otherwise
ref.lockLevel = 0;
esm.getHNOT(ref.lockLevel, "FLTV");
ref.key = esm.getHNOString("KNAM");
ref.trap = esm.getHNOString("TNAM");

View file

@ -25,8 +25,9 @@ struct Land
{
// Get the grid location
esm.getSubNameIs("INTV");
esm.getT(X);
esm.getT(Y);
esm.getSubHeaderIs(8);
esm.getT<int>(X);
esm.getT<int>(Y);
esm.getHNT(flags, "DATA");

View file

@ -23,12 +23,11 @@ namespace ESM {
struct LandTexture
{
std::string name, texture;
std::string id, texture;
int index;
void load(ESMReader &esm)
{
name = esm.getHNString("NAME");
esm.getHNT(index, "INTV");
texture = esm.getHNString("DATA");
}

View file

@ -160,6 +160,79 @@ namespace ESMS
int getSize() { return list.size(); }
};
/* Land textures are indexed by an integer number
*/
struct LTexList : RecList
{
// TODO: For multiple ESM/ESP files we need one list per file.
std::vector<LandTexture> ltex;
int count;
LTexList() : count(0)
{
// More than enough to hold Morrowind.esm.
ltex.reserve(128);
}
int getSize() { return count; }
void load(ESMReader &esm, const std::string &id)
{
LandTexture lt;
lt.load(esm);
lt.id = id;
// Make sure we have room for the structure
if(lt.index + 1 > (int)ltex.size())
ltex.resize(lt.index+1);
// Store it
ltex[lt.index] = lt;
}
};
/* Landscapes are indexed by the X,Y coordinates of the exterior
cell they belong to.
*/
struct LandList : RecList
{
// Map containing all landscapes
typedef std::map<int, Land*> LandsCol;
typedef std::map<int, LandsCol> Lands;
Lands lands;
int count;
LandList() : count(0) {}
int getSize() { return count; }
// Find land for the given coordinates. Return null if no data.
const Land *search(int x, int y) const
{
Lands::const_iterator it = lands.find(x);
if(it==lands.end())
return NULL;
LandsCol::const_iterator it2 = it->second.find(y);
if(it2 == it->second.end())
return NULL;
return it2->second;
}
void load(ESMReader &esm, const std::string &id)
{
count++;
// Create the structure and load it. This actually skips the
// landscape data and remembers the file position for later.
Land *land = new Land;
land->load(esm);
// Store the structure
lands[land->X][land->Y] = land;
}
};
// Cells aren't simply indexed by name. Exterior cells are treated
// separately.
// TODO: case handling (cell names are case-insensitive, but they are also showen to the
@ -254,8 +327,6 @@ namespace ESMS
void load(ESMReader &esm, const std::string &id)
{
using namespace std;
count++;
// All cells have a name record, even nameless exterior cells.

View file

@ -69,10 +69,10 @@ namespace ESMS
// Lists that need special rules
CellList cells;
RecIDListT<GameSetting> gameSettings;
//RecListT<Land> lands;
//RecListT<LandTexture> landTexts;
LandList lands;
LTexList landTexts;
ScriptListT<Script> scripts;
IndexListT<MagicEffect> magicEffects;
ScriptListT<Script> scripts;
IndexListT<Skill> skills;
//RecListT<PathGrid> pathgrids;
@ -115,12 +115,13 @@ namespace ESMS
recLists[REC_GLOB] = &globals;
recLists[REC_GMST] = &gameSettings;
recLists[REC_INGR] = &ingreds;
//recLists[REC_LAND] = &lands;
recLists[REC_LAND] = &lands;
recLists[REC_LEVC] = &creatureLists;
recLists[REC_LEVI] = &itemLists;
recLists[REC_LIGH] = &lights;
recLists[REC_LOCK] = &lockpicks;
//recLists[REC_LTEX] = &landTexts;
recLists[REC_LTEX] = &landTexts;
//recLists[REC_MGEF] = &magicEffects;
recLists[REC_MISC] = &miscItems;
recLists[REC_NPC_] = &npcs;
recLists[REC_NPCC] = &npcChange;

1
components/terrain/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
old

View file

@ -0,0 +1,48 @@
#include "esm_land_factory.hpp"
// The first one already includes the others implicitly, but it
// doesn't hurt to be explicit.
#include "../esm_store/store.hpp"
#include "../esm/esm_reader.hpp"
#include "../esm/loadland.hpp"
using namespace Terrain;
static class ESMLandStream : public Mangle::Stream
{
public:
ESMLandStream(ESM::Land *l, ESM::ESMReader &r)
{
}
};
bool ESMLandFactory::has(int x, int y)
{
return store.landscapes.has(x,y);
}
LandDataPtr get(int x, int y, LandInfo &info)
{
assert(has(x,y));
// Set up the info
info.grid = LGT_Quadratic;
info.data = LDT_Float;
const float SIZE = 8192; // CHECK
info.xsize = SIZE;
info.ysize = SIZE;
info.numx = 65;
info.numy = 65;
info.xoffset = SIZE*x;
info.yoffset = SIZE*y;
// Get the Land struct from store
ESM::Land* land = store.landscapes.find(x,y);
assert(land->hasData);
// Create a stream for the data and return it.
LandDataPtr ptr(new ESMLandStream(land, reader));
return ptr;
}

View file

@ -0,0 +1,41 @@
#ifndef TERRAIN_ESM_LAND_FACTORY_H
#define TERRAIN_ESM_LAND_FACTORY_H
#include "land_factory.hpp"
namespace ESMS
{
struct ESMStore;
}
namespace ESM
{
class ESMReader;
}
namespace Terrain
{
/*
Land factory that loads data from ESM files.
*/
class ESMLandFactory
{
ESMS::ESMStore &store;
ESM::ESMReader &reader;
public:
// Initialize the land factory. Note that refrences to the given
// store and reader are stored in the class, so the given objects
// must be valid for a long as you plan to use this factory.
ESMLandFactory(ESMS::ESMStore &st, ESM::ESMReader &rd)
: store(st), reader(rd) {}
// True if this factory has any data for the given grid cell.
bool has(int x, int y);
// Return stream to data for this cell. Additional data about the
// landscape is returned through the LandInfo struct.
LandDataPtr get(int x, int y, LandInfo &info);
};
}
#endif

View file

@ -0,0 +1,38 @@
#ifndef TERRAIN_HEIGHTMAP_H
#define TERRAIN_HEIGHTMAP_H
/*
Generic interface for a structure holding heightmap data.
A HeightMap returns information about landscape data in the form of
a regular grid of float heights.
*/
namespace Terrain
{
struct HeightMap
{
// Get height from grid position, counted from 0 to getNumX/Y().
virtual float getHeight(int x, int y) = 0;
// Get heigth from vertex index, assumed to be y*getNumX() + x.
virtual float getHeight(int index) = 0;
virtual float getMinX() = 0;
virtual float getMaxX() = 0;
virtual float getMinY() = 0;
virtual float getMaxY() = 0;
virtual int getNumX() = 0;
virtual int getNumY() = 0;
// True if the given coordinate is within the grid
bool isWithin(float x, float y)
{
return
x >= getMinX() && x < getMaxX() &&
y >= getMinY() && y < getMaxY();
}
};
}
#endif

View file

@ -0,0 +1,72 @@
#ifndef TERRAIN_HEIGHTMAPBUF_H
#define TERRAIN_HEIGHTMAPBUF_H
/*
A HeightMap implementation that stores heigths in a buffer.
*/
#include "heightmap.hpp"
#include "land_factory.hpp"
#include <vector>
#include <assert.h>
namespace Terrain
{
class HeightMapBuffer : public HeightMap
{
std::vector<float> buf;
float beginX, sizeX, endX;
float beginY, sizeY, endY;
int numX, numY;
public:
void load(LandDataPtr data, const LandInfo &info)
{
// We don't support other kinds of grid data yet.
assert(info.grid == LGT_Quadratic);
assert(info.data == LDT_Float);
// Set up internal data
beginX = info.xoffset;
sizeX = info.xsize;
endX = beginX+sizeX;
numX = info.numx;
beginY = info.yoffset;
sizeY = info.ysize;
endY = beginY+sizeY;
numY = info.numy;
// Prepare the buffer and load it
buf.resize(numX*numY);
data.read(&buf[0], buf.size()*sizeof(float));
}
// Functions inherited from HeightMap:
float getHeight(int x, int y)
{
assert(x>=0 && x<numX);
assert(y>=0 && y<numY);
return getHeight(x + y*numX);
}
float getHeight(int index)
{
assert(index >= 0 && index < buf.size());
return buf[index];
}
float getMinX() { return beginX; }
float getMaxX() { return endX; }
float getMinY() { return beginY; }
float getMaxY() { return endY; }
int getNumX() { return numX; }
int getNumY() { return numY; }
};
}
#endif

View file

@ -0,0 +1,55 @@
#ifndef TERRAIN_LAND_FACTORY_H
#define TERRAIN_LAND_FACTORY_H
#include <mangle/stream/stream.hpp>
namespace Terrain
{
enum LandInfoGridType
{
LGT_Quadratic
};
enum LandInfoDataType
{
LDT_Float
};
struct LandInfo
{
// Type information
LandInfoGridType grid;
LandInfoDataType data;
// Landscape size and number of vertices. Note that xsize and
// ysize may be negative, signaling a flipped landscape in that
// direction.
float xsize, ysize;
int numx, numy;
// World offset along the same x/y axes. Whether these are set or
// used depends on the client implementation.
float xoffset, yoffset;
};
/* We use normal streams for data. This allows us to just pass (for
example) a file stream as height map input later, with no extra
fuzz.
*/
typedef Mangle::Stream::StreamPtr LandDataPtr;
/*
Factory class that provides streams to land data cells. Each
"cell" has a unique integer coordinate in the plane.
*/
struct LandFactory
{
// True if this factory has any data for the given grid cell.
virtual bool has(int x, int y) = 0;
// Return stream to data for this cell. Additional data about the
// landscape is returned through the LandInfo struct.
virtual LandDataPtr get(int x, int y, LandInfo &info) = 0;
};
}
#endif

1
components/terrain/tests/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*_test

View file

@ -0,0 +1,14 @@
GCC=g++
all: triangle_test esm_test
LIB_INC=-I../../../libs/
triangle_test: triangle_test.cpp
$(GCC) $^ -o $@
esm_test: esm_test.cpp
$(GCC) $^ -o $@ $(LIB_INC)
clean:
rm *_test

View file

@ -0,0 +1,10 @@
#include <iostream>
using namespace std;
#include "../esm_land_factory.hpp"
int main()
{
cout << "under development\n";
return 0;
}

View file

@ -0,0 +1 @@
under development

View file

@ -0,0 +1,55 @@
Cell types:
\ / \ /
/ \ / \
\ / \ /
/ \ / \
Full index list:
0
6
5
0
1
6
1
2
6
6
2
7
2
8
7
2
3
8
3
4
8
8
4
9
5
6
10
10
6
11
6
12
11
6
7
12
7
8
12
12
8
13
8
14
13
8
9
14

View file

@ -0,0 +1,18 @@
#!/bin/bash
make || exit
mkdir -p output
PROGS=*_test
for a in $PROGS; do
if [ -f "output/$a.out" ]; then
echo "Running $a:"
./$a | diff output/$a.out -
else
echo "Creating $a.out"
./$a > "output/$a.out"
git add "output/$a.out"
fi
done

View file

@ -0,0 +1,93 @@
#include <iostream>
using namespace std;
#include "../triangulator.hpp"
const int X = 4;
const int Y = 4;
typedef Terrain::Triangulator<short,X,Y> Triangles4x4;
int main()
{
Triangles4x4 t;
cout << "Cell types:\n";
for(int y=0;y<Y;y++)
{
for(int x=0;x<X;x++)
{
if(t.cellType(x,y)) cout << "/ ";
else cout << "\\ ";
}
cout << endl;
}
cout << endl;
cout << "Full index list:\n";
for(int i=0; i<X*Y*3; i++)
cout << t.getData()[i] << endl;
return 0;
}
/* Code we might add later:
// Get the vertex indices belonging to a given triangle
void getTriangle(int trinum, Index &p1, Index &p2, Index &p3)
{
assert(trinum >= 0 && trinum < TriNum);
trinum *= 3;
p1 = array[trinum++];
p2 = array[trinum++];
p3 = array[trinum];
}
/*
Get height interpolation weights for a given grid square. The
input is the grid square number (x,y) and the relative position
within that square (xrel,yrel = [0.0..1.0].) The weights are
returned as three vertex index + weight factor pairs.
A more user-friendly version for HeightMap structs is given
below.
* /
void getWeights(int x, int y, float xrel, float yrel,
Index &p1, float w1,
Index &p2, float w2,
Index &p3, float w3)
{
// Find cell index
int index = y*SizeX + x;
// First triangle in cell
index *= 2;
// The rest depends on how the cell is triangulated
if(cellType(x,y))
{
}
else
{
// Cell is divided as \ from 0,0 to 1,1
if(xrel < yrel)
{
// Bottom left triangle.
// Order is (0,0),(1,1),(0,1).
getTriangle(index, p1,p2,p3);
}
else
{
// Top right triangle
// Order is (0,0),(1,0),(1,1).
getTriangle(index+1, p1,p2,p3);
}
}
}
*/

View file

@ -0,0 +1,104 @@
#ifndef TERRAIN_TRIANGULATOR_H
#define TERRAIN_TRIANGULATOR_H
/*
The triangulator is a simple math helper class, used for dividing a
regular square grid into alternating set of triangles. It divides a
grid like this:
+----+----+
| | |
| | |
+----+----+
| | |
| | |
+----+----+
into this:
+----+----+
| \ 2|3 / |
|1 \ | / 4|
+----+----+
|5 / | \ 8|
| / 6|7 \ |
+----+----+
Since the triangulation information is typically the same for all
terrains of the same size, once instance can usually be shared.
*/
#include <assert.h>
namespace Terrain
{
// Index number type, number of grid cells (not vertices) in X and Y
// directions.
template <typename Index, int SizeX, int SizeY>
class Triangulator
{
// Number of triangles
static const int TriNum = SizeX * SizeY * 2;
// 3 indices per triangle
Index array[TriNum * 3];
public:
// Get raw triangle data pointer. Typically used for creating
// meshes.
const Index *getData() { return array; }
// Return whether a given cell is divided as / (true) or \
// (false).
static bool cellType(int x, int y)
{
assert(x >= 0 && x < SizeX);
assert(y >= 0 && y < SizeY);
bool even = (x & 1) == 1;
if((y & 1) == 1) even = !even;
return even;
}
// Constructor sets up the index buffer
Triangulator()
{
int index = 0;
for ( int y = 0; y < SizeX; y++ )
for ( int x = 0; x < SizeY; x++ )
{
// Get vertex indices
Index line1 = y*(SizeX+1) + x;
Index line2 = line1 + SizeX+1;
if(cellType(x,y))
{
// Top left
array[index++] = line1;
array[index++] = line1 + 1;
array[index++] = line2;
// Bottom right
array[index++] = line2;
array[index++] = line1 + 1;
array[index++] = line2 + 1;
}
else
{
// Bottom left
array[index++] = line1;
array[index++] = line2 + 1;
array[index++] = line2;
// Top right
array[index++] = line1;
array[index++] = line1 + 1;
array[index++] = line2 + 1;
}
}
assert(index == TriNum*3);
}
};
} // Namespace
#endif

View file

@ -352,46 +352,7 @@ void genIndexData()
cache.addVertexBuffer(lev,vertList);
}
// Pregenerate triangle indices
int size = 64*64*6;
auto indList = new ushort[size];
int index = 0;
bool flag = false;
for ( int y = 0; y < 64; y++ )
{
for ( int x = 0; x < 64; x++ )
{
int line1 = y*65 + x;
int line2 = (y+1)*65 + x;
if ( flag )
{
indList[index++] = line1;
indList[index++] = line1 + 1;
indList[index++] = line2;
indList[index++] = line2;
indList[index++] = line1 + 1;
indList[index++] = line2 + 1;
}
else
{
indList[index++] = line1;
indList[index++] = line2 + 1;
indList[index++] = line2;
indList[index++] = line1;
indList[index++] = line1 + 1;
indList[index++] = line2 + 1;
}
flag = !flag; //flip tris for next time
}
flag = !flag; //flip tries for next row
}
assert(index == indList.length);
cache.setIndexBuffer(indList);
// index stuff already ported
}
void genLevel(int level, int X, int Y, ref GenLevelResult result,