Merge remote-tracking branch 'upstream/master'

actorid
mrcheko 11 years ago
commit a11a6b616b

@ -186,8 +186,6 @@ if (WIN32)
add_definitions(-DSDL_MAIN_HANDLED)
else (WIN32)
set(PLATFORM_INCLUDE_DIR "")
find_path (UUID_INCLUDE_DIR uuid/uuid.h)
include_directories(${UUID_INCLUDE_DIR})
endif (WIN32)
if (MSVC10)
set(PLATFORM_INCLUDE_DIR "")
@ -239,7 +237,6 @@ include_directories("."
${MYGUI_INCLUDE_DIRS}
${MYGUI_PLATFORM_INCLUDE_DIRS}
${OPENAL_INCLUDE_DIR}
${UUID_INCLUDE_DIR}
${LIBDIR}
)
@ -409,46 +406,6 @@ IF(NOT WIN32 AND NOT APPLE)
# Install resources
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources")
INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources")
IF (DPKG_PROGRAM)
## Debian Specific
IF(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.git")
EXEC_PROGRAM("git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "describe" OUTPUT_VARIABLE GIT_VERSION )
STRING(REGEX REPLACE "openmw-" "" VERSION_STRING "${GIT_VERSION}")
EXEC_PROGRAM("git" ARGS "config --get user.name" OUTPUT_VARIABLE GIT_NAME )
EXEC_PROGRAM("git" ARGS "config --get user.email" OUTPUT_VARIABLE GIT_EMAIL)
SET(PACKAGE_MAINTAINER "${GIT_NAME} <${GIT_EMAIL}>")
ELSE()
SET(VERSION_STRING "${OPENMW_VERSION}")
SET(PACKAGE_MAINTAINER "unknown")
ENDIF()
SET(CPACK_GENERATOR "DEB")
SET(CPACK_PACKAGE_NAME "openmw")
SET(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://openmw.org")
SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PACKAGE_MAINTAINER}")
SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A reimplementation of The Elder Scrolls III: Morrowind
OpenMW is a reimplementation of the Bethesda Game Studios game The Elder Scrolls III: Morrowind.
Data files from the original game is required to run it.")
SET(CPACK_DEBIAN_PACKAGE_NAME "openmw")
SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}")
SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW opencs;OpenCS bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter")
SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)")
SET(CPACK_DEBIAN_PACKAGE_SECTION "Games")
STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE)
EXECUTE_PROCESS(
COMMAND ${DPKG_PROGRAM} --print-architecture
OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME_LOWERCASE}_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
INCLUDE(CPack)
ENDIF(DPKG_PROGRAM)
ENDIF(NOT WIN32 AND NOT APPLE)
if(WIN32)

@ -680,7 +680,7 @@ std::string creatureFlags(int flags)
if (flags & ESM::Creature::Walks) properties += "Walks ";
if (flags & ESM::Creature::Swims) properties += "Swims ";
if (flags & ESM::Creature::Flies) properties += "Flies ";
if (flags & ESM::Creature::Biped) properties += "Biped ";
if (flags & ESM::Creature::Bipedal) properties += "Bipedal ";
if (flags & ESM::Creature::Respawn) properties += "Respawn ";
if (flags & ESM::Creature::Weapon) properties += "Weapon ";
if (flags & ESM::Creature::Skeleton) properties += "Skeleton ";
@ -691,7 +691,7 @@ std::string creatureFlags(int flags)
ESM::Creature::Walks|
ESM::Creature::Swims|
ESM::Creature::Flies|
ESM::Creature::Biped|
ESM::Creature::Bipedal|
ESM::Creature::Respawn|
ESM::Creature::Weapon|
ESM::Creature::Skeleton|

@ -623,6 +623,17 @@ MwIniImporter::MwIniImporter()
"Moons:Masser Fade Out Finish",
"Moons:Script Color",
// blood
"Blood:Model 0",
"Blood:Model 1",
"Blood:Model 2",
"Blood:Texture 0",
"Blood:Texture 1",
"Blood:Texture 2",
"Blood:Texture Name 0",
"Blood:Texture Name 1",
"Blood:Texture Name 2",
0
};

@ -181,7 +181,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
unsigned int mFlag;
} sCreatureFlagTable[] =
{
{ Columns::ColumnId_Biped, ESM::Creature::Biped },
{ Columns::ColumnId_Biped, ESM::Creature::Bipedal },
{ Columns::ColumnId_HasWeapon, ESM::Creature::Weapon },
{ Columns::ColumnId_NoMovement, ESM::Creature::None },
{ Columns::ColumnId_Swims, ESM::Creature::Swims },

@ -20,7 +20,7 @@ add_openmw_dir (mwrender
renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation
actors objects renderinginterface localmap occlusionquery water shadows
characterpreview globalmap videoplayer ripplesimulation refraction
terrainstorage renderconst
terrainstorage renderconst effectmanager
)
add_openmw_dir (mwinput

@ -463,6 +463,9 @@ namespace MWBase
/// Spawn a random creature from a levelled list next to the player
virtual void spawnRandomCreature(const std::string& creatureList) = 0;
/// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0;
};
}

@ -69,6 +69,9 @@ namespace MWClass
fMaxFlySpeed = gmst.find("fMaxFlySpeed");
fSwimRunBase = gmst.find("fSwimRunBase");
fSwimRunAthleticsMult = gmst.find("fSwimRunAthleticsMult");
fKnockDownMult = gmst.find("fKnockDownMult");
iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
inited = true;
}
@ -173,6 +176,62 @@ namespace MWClass
void Creature::hit(const MWWorld::Ptr& ptr, int type) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref =
ptr.get<ESM::Creature>();
// TODO: where is the distance defined?
std::pair<MWWorld::Ptr, Ogre::Vector3> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, 100);
if (result.first.isEmpty())
return; // Didn't hit anything
MWWorld::Ptr victim = result.first;
if (!victim.getClass().isActor())
return; // Can't hit non-actors
Ogre::Vector3 hitPosition = result.second;
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
float hitchance = ref->mBase->mData.mCombat +
(stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
(stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
hitchance *= stats.getFatigueTerm();
hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
hitchance -= otherstats.getEvasion();
if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
{
victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false);
return;
}
int min,max;
switch (type)
{
case 0:
min = ref->mBase->mData.mAttack[0];
max = ref->mBase->mData.mAttack[1];
break;
case 1:
min = ref->mBase->mData.mAttack[2];
max = ref->mBase->mData.mAttack[3];
break;
case 2:
default:
min = ref->mBase->mData.mAttack[4];
max = ref->mBase->mData.mAttack[5];
break;
}
float damage = min + (max - min) * ::rand()/(RAND_MAX+1.0);
// TODO: do not do this if the attack is blocked
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
victim.getClass().onHit(victim, damage, true, MWWorld::Ptr(), ptr, true);
}
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
@ -199,6 +258,19 @@ namespace MWClass
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
}
// Check for knockdown
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
* iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt();
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
{
getCreatureStats(ptr).setKnockedDown(true);
}
else
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
if(ishealth)
{
if(damage > 0.0f)
@ -301,7 +373,7 @@ namespace MWClass
const MWBase::World *world = MWBase::Environment::get().getWorld();
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
const float normalizedEncumbrance = 0; //getEncumbrance(ptr) / getCapacity(ptr);
const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);
@ -530,6 +602,17 @@ namespace MWClass
}
}
int Creature::getBloodTexture(const MWWorld::Ptr &ptr) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
if (ref->mBase->mFlags & ESM::Creature::Skeleton)
return 1;
if (ref->mBase->mFlags & ESM::Creature::Metal)
return 2;
return 0;
}
const ESM::GameSetting* Creature::fMinWalkSpeedCreature;
const ESM::GameSetting* Creature::fMaxWalkSpeedCreature;
const ESM::GameSetting *Creature::fEncumberedMoveEffect;
@ -540,5 +623,8 @@ namespace MWClass
const ESM::GameSetting *Creature::fMaxFlySpeed;
const ESM::GameSetting *Creature::fSwimRunBase;
const ESM::GameSetting *Creature::fSwimRunAthleticsMult;
const ESM::GameSetting *Creature::fKnockDownMult;
const ESM::GameSetting *Creature::iKnockDownOddsMult;
const ESM::GameSetting *Creature::iKnockDownOddsBase;
}

@ -24,6 +24,10 @@ namespace MWClass
static const ESM::GameSetting *fMaxFlySpeed;
static const ESM::GameSetting *fSwimRunBase;
static const ESM::GameSetting *fSwimRunAthleticsMult;
static const ESM::GameSetting *fKnockDownMult;
static const ESM::GameSetting *iKnockDownOddsMult;
static const ESM::GameSetting *iKnockDownOddsBase;
public:
@ -111,6 +115,9 @@ namespace MWClass
virtual bool isFlying (const MWWorld::Ptr &ptr) const;
virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const;
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
virtual int getBloodTexture (const MWWorld::Ptr& ptr) const;
};
}

@ -454,8 +454,10 @@ namespace MWClass
float dist = 100.0f * (!weapon.isEmpty() ?
weapon.get<ESM::Weapon>()->mBase->mData.mReach :
gmst.find("fHandToHandReach")->getFloat());
// TODO: Use second to work out the hit angle and where to spawn the blood effect
MWWorld::Ptr victim = world->getHitContact(ptr, dist).first;
// TODO: Use second to work out the hit angle
std::pair<MWWorld::Ptr, Ogre::Vector3> result = world->getHitContact(ptr, dist);
MWWorld::Ptr victim = result.first;
Ogre::Vector3 hitPosition = result.second;
if(victim.isEmpty()) // Didn't hit anything
return;
@ -602,6 +604,10 @@ namespace MWClass
}
}
// TODO: do not do this if the attack is blocked
if (healthdmg)
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
}
@ -775,10 +781,10 @@ namespace MWClass
}
if(getCreatureStats(ptr).isDead())
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
if(getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak))
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
if(get(ptr).getCreatureStats(ptr).isHostile())
return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak))
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
}
@ -1216,6 +1222,17 @@ namespace MWClass
return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified();
}
int Npc::getBloodTexture(const MWWorld::Ptr &ptr) const
{
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
if (ref->mBase->mFlags & ESM::NPC::Skeleton)
return 1;
if (ref->mBase->mFlags & ESM::NPC::Metal)
return 2;
return 0;
}
const ESM::GameSetting *Npc::fMinWalkSpeed;
const ESM::GameSetting *Npc::fMaxWalkSpeed;
const ESM::GameSetting *Npc::fEncumberedMoveEffect;

@ -142,6 +142,9 @@ namespace MWClass
virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const;
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
virtual int getBloodTexture (const MWWorld::Ptr& ptr) const;
virtual bool isActor() const {
return true;
}

@ -267,7 +267,8 @@ namespace MWGui
else if ((mode == GM_Container) || (mode == GM_Inventory))
{
// pick up object
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object);
if (!object.isEmpty())
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object);
}
}
}

@ -301,6 +301,12 @@ namespace MWGui
if (type == Type_Magic)
{
std::string spellId = button->getChildAt(0)->getUserString("Spell");
// Make sure the player still has this spell
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
if (!spells.hasSpell(spellId))
return;
store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
}

@ -109,7 +109,10 @@ void MWMechanics::AiSequence::stack (const AiPackage& package)
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); it++)
{
if(mPackages.front()->getPriority() <= package.getPriority())
{
mPackages.insert(it,package.clone());
return;
}
}
if(mPackages.empty())

@ -6,6 +6,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwmechanics/npcstats.hpp"
#include <OgreVector3.h>
@ -185,6 +186,14 @@ namespace MWMechanics
playIdle(actor, mPlayedIdle);
mChooseAction = false;
mIdleNow = true;
// Play idle voiced dialogue entries randomly
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
float chance = store.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
// TODO: do not show subtitle messagebox if player is too far away? or do not say at all?
if (roll < chance)
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
}

@ -479,9 +479,47 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr)
mPtr = ptr;
}
bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak)
bool CharacterController::updateCreatureState()
{
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
const MWWorld::Class &cls = mPtr.getClass();
CreatureStats &stats = cls.getCreatureStats(mPtr);
if(stats.getAttackingOrSpell())
{
if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None)
{
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
switch (stats.getAttackType())
{
case CreatureStats::AT_Chop:
mCurrentWeapon = "attack1";
break;
case CreatureStats::AT_Slash:
mCurrentWeapon = "attack2";
break;
case CreatureStats::AT_Thrust:
mCurrentWeapon = "attack3";
break;
}
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
1, "start", "stop",
0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack;
}
}
bool animPlaying = mAnimation->getInfo(mCurrentWeapon);
if (!animPlaying)
mUpperBodyState = UpperCharState_Nothing;
return false;
}
bool CharacterController::updateNpcState(bool inwater, bool isrunning)
{
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
NpcStats &stats = cls.getNpcStats(mPtr);
WeaponType weaptype = WeapType_None;
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
@ -583,8 +621,10 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
// This has to be done at the start of the casting animation,
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
if (mPtr.getRefData().getHandle() == "player")
stats.getSpells().setSelectedSpell(MWBase::Environment::get().getWindowManager()->getSelectedSpell());
{
std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
stats.getSpells().setSelectedSpell(selectedSpell);
}
std::string spellid = stats.getSpells().getSelectedSpell();
if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr))
@ -1089,7 +1129,9 @@ void CharacterController::update(float duration)
}
if(cls.isNpc())
forcestateupdate = updateNpcState(onground, inwater, isrunning, sneak) || forcestateupdate;
forcestateupdate = updateNpcState(inwater, isrunning) || forcestateupdate;
else
forcestateupdate = updateCreatureState() || forcestateupdate;
refreshCurrentAnims(idlestate, movestate, forcestateupdate);

@ -170,7 +170,8 @@ class CharacterController
void clearAnimQueue();
bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak);
bool updateNpcState(bool inwater, bool isrunning);
bool updateCreatureState();
void updateVisibility();

@ -116,9 +116,9 @@ namespace MWMechanics
enum AttackType
{
AT_Chop,
AT_Slash,
AT_Thrust,
AT_Chop
AT_Thrust
};
void setAttackType(int attackType) { mAttackType = attackType; }
int getAttackType() { return mAttackType; }

@ -44,6 +44,8 @@ namespace MWMechanics
TIterator end() const;
bool hasSpell(const std::string& spell) { return mSpells.find(Misc::StringUtils::lowerCase(spell)) != mSpells.end(); }
void add (const std::string& spell);
///< Adding a spell that is already listed in *this is a no-op.

@ -30,7 +30,7 @@
namespace MWRender
{
Ogre::Real Animation::AnimationValue::getValue() const
Ogre::Real Animation::AnimationTime::getValue() const
{
AnimStateMap::const_iterator iter = mAnimation->mStates.find(mAnimationName);
if(iter != mAnimation->mStates.end())
@ -38,16 +38,16 @@ Ogre::Real Animation::AnimationValue::getValue() const
return 0.0f;
}
void Animation::AnimationValue::setValue(Ogre::Real)
void Animation::AnimationTime::setValue(Ogre::Real)
{
}
Ogre::Real Animation::EffectAnimationValue::getValue() const
Ogre::Real Animation::EffectAnimationTime::getValue() const
{
return mTime;
}
void Animation::EffectAnimationValue::setValue(Ogre::Real)
void Animation::EffectAnimationTime::setValue(Ogre::Real)
{
}
@ -60,10 +60,10 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node)
, mNonAccumRoot(NULL)
, mNonAccumCtrl(NULL)
, mAccumulate(0.0f)
, mNullAnimationValuePtr(OGRE_NEW NullAnimationValue)
, mNullAnimationTimePtr(OGRE_NEW NullAnimationTime)
{
for(size_t i = 0;i < sNumGroups;i++)
mAnimationValuePtr[i].bind(OGRE_NEW AnimationValue(this));
mAnimationTimePtr[i].bind(OGRE_NEW AnimationTime(this));
}
Animation::~Animation()
@ -139,7 +139,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly)
for(size_t i = 0;i < mObjectRoot->mControllers.size();i++)
{
if(mObjectRoot->mControllers[i].getSource().isNull())
mObjectRoot->mControllers[i].setSource(mAnimationValuePtr[0]);
mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]);
}
}
@ -286,7 +286,7 @@ void Animation::addAnimSource(const std::string &model)
}
}
ctrls[i].setSource(mAnimationValuePtr[grp]);
ctrls[i].setSource(mAnimationTimePtr[grp]);
grpctrls[grp].push_back(ctrls[i]);
}
}
@ -296,7 +296,7 @@ void Animation::clearAnimSources()
mStates.clear();
for(size_t i = 0;i < sNumGroups;i++)
mAnimationValuePtr[i]->setAnimName(std::string());
mAnimationTimePtr[i]->setAnimName(std::string());
mNonAccumCtrl = NULL;
@ -660,13 +660,22 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
else if(evt.compare(off, len, "unequip detach") == 0)
showWeapons(false);
else if(evt.compare(off, len, "chop hit") == 0)
MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Chop);
mPtr.getClass().hit(mPtr, MWMechanics::CreatureStats::AT_Chop);
else if(evt.compare(off, len, "slash hit") == 0)
MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Slash);
mPtr.getClass().hit(mPtr, MWMechanics::CreatureStats::AT_Slash);
else if(evt.compare(off, len, "thrust hit") == 0)
MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust);
mPtr.getClass().hit(mPtr, MWMechanics::CreatureStats::AT_Thrust);
else if(evt.compare(off, len, "hit") == 0)
MWWorld::Class::get(mPtr).hit(mPtr);
{
if (groupname == "attack1")
mPtr.getClass().hit(mPtr, MWMechanics::CreatureStats::AT_Chop);
else if (groupname == "attack2")
mPtr.getClass().hit(mPtr, MWMechanics::CreatureStats::AT_Slash);
else if (groupname == "attack3")
mPtr.getClass().hit(mPtr, MWMechanics::CreatureStats::AT_Thrust);
else
mPtr.getClass().hit(mPtr);
}
else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
MWBase::Environment::get().getWorld()->castSpell(mPtr);
@ -789,7 +798,7 @@ void Animation::resetActiveGroups()
active = state;
}
mAnimationValuePtr[grp]->setAnimName((active == mStates.end()) ?
mAnimationTimePtr[grp]->setAnimName((active == mStates.end()) ?
std::string() : active->first);
}
mNonAccumCtrl = NULL;
@ -797,7 +806,7 @@ void Animation::resetActiveGroups()
if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f))
return;
AnimStateMap::const_iterator state = mStates.find(mAnimationValuePtr[0]->getAnimName());
AnimStateMap::const_iterator state = mStates.find(mAnimationTimePtr[0]->getAnimName());
if(state == mStates.end())
return;
@ -869,13 +878,13 @@ Ogre::Vector3 Animation::runAnimation(float duration)
targetTime = state.mTime + timepassed;
if(textkey == textkeys.end() || textkey->first > targetTime)
{
if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName())
if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName())
updatePosition(state.mTime, targetTime, movement);
state.mTime = std::min(targetTime, state.mStopTime);
}
else
{
if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName())
if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName())
updatePosition(state.mTime, textkey->first, movement);
state.mTime = textkey->first;
}
@ -926,7 +935,7 @@ Ogre::Vector3 Animation::runAnimation(float duration)
// Apply group controllers
for(size_t grp = 0;grp < sNumGroups;grp++)
{
const std::string &name = mAnimationValuePtr[grp]->getAnimName();
const std::string &name = mAnimationTimePtr[grp]->getAnimName();
if(!name.empty() && (stateiter=mStates.find(name)) != mStates.end())
{
const Ogre::SharedPtr<AnimSource> &src = stateiter->second.mSource;
@ -1042,6 +1051,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con
else
params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model);
// TODO: turn off shadow casting
setRenderProperties(params.mObjects, RV_Misc,
RQG_Main, RQG_Alpha, 0.f, false, NULL);
@ -1052,7 +1062,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con
for(size_t i = 0;i < params.mObjects->mControllers.size();i++)
{
if(params.mObjects->mControllers[i].getSource().isNull())
params.mObjects->mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue()));
params.mObjects->mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationTime> (new EffectAnimationTime()));
}
if (!texture.empty())
@ -1110,7 +1120,7 @@ void Animation::updateEffects(float duration)
NifOgre::ObjectScenePtr objects = it->mObjects;
for(size_t i = 0; i < objects->mControllers.size() ;i++)
{
EffectAnimationValue* value = dynamic_cast<EffectAnimationValue*>(objects->mControllers[i].getSource().get());
EffectAnimationTime* value = dynamic_cast<EffectAnimationTime*>(objects->mControllers[i].getSource().get());
if (value)
value->addTime(duration);
@ -1125,7 +1135,7 @@ void Animation::updateEffects(float duration)
float remainder = objects->mControllers[0].getSource()->getValue() - objects->mMaxControllerLength;
for(size_t i = 0; i < objects->mControllers.size() ;i++)
{
EffectAnimationValue* value = dynamic_cast<EffectAnimationValue*>(objects->mControllers[i].getSource().get());
EffectAnimationTime* value = dynamic_cast<EffectAnimationTime*>(objects->mControllers[i].getSource().get());
if (value)
value->resetTime(remainder);
}

@ -32,14 +32,14 @@ protected:
/* This is the number of *discrete* groups. */
static const size_t sNumGroups = 4;
class AnimationValue : public Ogre::ControllerValue<Ogre::Real>
class AnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
Animation *mAnimation;
std::string mAnimationName;
public:
AnimationValue(Animation *anim)
AnimationTime(Animation *anim)
: mAnimation(anim)
{ }
@ -52,12 +52,12 @@ protected:
virtual void setValue(Ogre::Real value);
};
class EffectAnimationValue : public Ogre::ControllerValue<Ogre::Real>
class EffectAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
float mTime;
public:
EffectAnimationValue() : mTime(0) { }
EffectAnimationTime() : mTime(0) { }
void addTime(float time) { mTime += time; }
void resetTime(float value) { mTime = value; }
@ -67,7 +67,7 @@ protected:
class NullAnimationValue : public Ogre::ControllerValue<Ogre::Real>
class NullAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
public:
virtual Ogre::Real getValue() const
@ -134,8 +134,8 @@ protected:
AnimStateMap mStates;
Ogre::SharedPtr<AnimationValue> mAnimationValuePtr[sNumGroups];
Ogre::SharedPtr<NullAnimationValue> mNullAnimationValuePtr;
Ogre::SharedPtr<AnimationTime> mAnimationTimePtr[sNumGroups];
Ogre::SharedPtr<NullAnimationTime> mNullAnimationTimePtr;
ObjectAttachMap mAttachedObjects;
@ -189,16 +189,18 @@ protected:
/** Adds an additional light to the given object list using the specified ESM record. */
void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light);
static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue,
Ogre::uint8 transqueue, Ogre::Real dist=0.0f,
bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL);
void clearAnimSources();
// TODO: Should not be here
Ogre::Vector3 getEnchantmentColor(MWWorld::Ptr item);
public:
// FIXME: Move outside of this class
static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue,
Ogre::uint8 transqueue, Ogre::Real dist=0.0f,
bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL);
Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node);
virtual ~Animation();

@ -24,7 +24,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
setObjectRoot(model, false);
setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
if((ref->mBase->mFlags&ESM::Creature::Biped))
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
addAnimSource("meshes\\base_anim.nif");
addAnimSource(model);
}

@ -0,0 +1,117 @@
#include "effectmanager.hpp"
#include <OgreSceneManager.h>
#include <OgreParticleSystem.h>
#include "animation.hpp"
#include "renderconst.hpp"
namespace MWRender
{
class EffectAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
float mTime;
public:
EffectAnimationTime() : mTime(0) { }
void addTime(float time) { mTime += time; }
virtual Ogre::Real getValue() const { return mTime; }
virtual void setValue(Ogre::Real value) {}
};
EffectManager::EffectManager(Ogre::SceneManager *sceneMgr)
: mSceneMgr(sceneMgr)
{
}
void EffectManager::addEffect(const std::string &model, std::string textureOverride, const Ogre::Vector3 &worldPosition)
{
Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition);
// fix texture extension to .dds
if (textureOverride.size() > 4)
{
textureOverride[textureOverride.size()-3] = 'd';
textureOverride[textureOverride.size()-2] = 'd';
textureOverride[textureOverride.size()-1] = 's';
}
NifOgre::ObjectScenePtr scene = NifOgre::Loader::createObjects(sceneNode, model);
// TODO: turn off shadow casting
MWRender::Animation::setRenderProperties(scene, RV_Misc,
RQG_Main, RQG_Alpha, 0.f, false, NULL);
for(size_t i = 0;i < scene->mControllers.size();i++)
{
if(scene->mControllers[i].getSource().isNull())
scene->mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationTime> (new EffectAnimationTime()));
}
if (!textureOverride.empty())
{
for(size_t i = 0;i < scene->mParticles.size(); ++i)
{
Ogre::ParticleSystem* partSys = scene->mParticles[i];
Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(partSys);
for (int t=0; t<mat->getNumTechniques(); ++t)
{
Ogre::Technique* tech = mat->getTechnique(t);
for (int p=0; p<tech->getNumPasses(); ++p)
{
Ogre::Pass* pass = tech->getPass(p);
for (int tex=0; tex<pass->getNumTextureUnitStates(); ++tex)
{
Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex);
tus->setTextureName("textures\\" + textureOverride);
}
}
}
}
}
mEffects.push_back(std::make_pair(sceneNode, scene));
}
void EffectManager::update(float dt)
{
for (std::vector<std::pair<Ogre::SceneNode*, NifOgre::ObjectScenePtr> >::iterator it = mEffects.begin(); it != mEffects.end(); )
{
NifOgre::ObjectScenePtr objects = it->second;
for(size_t i = 0; i < objects->mControllers.size() ;i++)
{
EffectAnimationTime* value = dynamic_cast<EffectAnimationTime*>(objects->mControllers[i].getSource().get());
if (value)
value->addTime(dt);
objects->mControllers[i].update();
}
// Finished playing?
if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength)
{
Ogre::SceneNode* node = it->first;
it = mEffects.erase(it);
mSceneMgr->destroySceneNode(node);
continue;
}
++it;
}
}
void EffectManager::clear()
{
for (std::vector<std::pair<Ogre::SceneNode*, NifOgre::ObjectScenePtr> >::iterator it = mEffects.begin(); it != mEffects.end(); )
{
Ogre::SceneNode* node = it->first;
it = mEffects.erase(it);
mSceneMgr->destroySceneNode(node);
}
}
}

@ -0,0 +1,31 @@
#ifndef OPENMW_MWRENDER_EFFECTMANAGER_H
#define OPENMW_MWRENDER_EFFECTMANAGER_H
#include <components/nifogre/ogrenifloader.hpp>
namespace MWRender
{
// Note: effects attached to another object should be managed by MWRender::Animation::addEffect.
// This class manages "free" effects, i.e. attached to a dedicated scene node in the world.
class EffectManager
{
public:
EffectManager(Ogre::SceneManager* sceneMgr);
~EffectManager() { clear(); }
/// Add an effect. When it's finished playing, it will be removed automatically.
void addEffect (const std::string& model, std::string textureOverride, const Ogre::Vector3& worldPosition);
void update(float dt);
/// Remove all effects
void clear();
private:
std::vector<std::pair<Ogre::SceneNode*, NifOgre::ObjectScenePtr> > mEffects;
Ogre::SceneManager* mSceneMgr;
};
}
#endif

@ -61,8 +61,9 @@ std::string getVampireHead(const std::string& race, bool female)
namespace MWRender
{
float SayAnimationValue::getValue() const
float HeadAnimationTime::getValue() const
{
// TODO: Handle eye blinking (time is in the text keys)
if (MWBase::Environment::get().getSoundManager()->sayDone(mReference))
return 0;
else
@ -124,7 +125,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v
{
mNpc = mPtr.get<ESM::NPC>()->mBase;
mSayAnimationValue = Ogre::SharedPtr<SayAnimationValue>(new SayAnimationValue(mPtr));
mHeadAnimationTime = Ogre::SharedPtr<HeadAnimationTime>(new HeadAnimationTime(mPtr));
for(size_t i = 0;i < ESM::PRT_Count;i++)
{
@ -595,10 +596,10 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
{
if(ctrl->getSource().isNull())
{
ctrl->setSource(mNullAnimationValuePtr);
ctrl->setSource(mNullAnimationTimePtr);
if (type == ESM::PRT_Head)
ctrl->setSource(mSayAnimationValue);
ctrl->setSource(mHeadAnimationTime);
}
}

@ -13,12 +13,12 @@ namespace ESM
namespace MWRender
{
class SayAnimationValue : public Ogre::ControllerValue<Ogre::Real>
class HeadAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
MWWorld::Ptr mReference;
public:
SayAnimationValue(MWWorld::Ptr reference) : mReference(reference) {}
HeadAnimationTime(MWWorld::Ptr reference) : mReference(reference) {}
virtual Ogre::Real getValue() const;
virtual void setValue(Ogre::Real value)
@ -70,7 +70,7 @@ private:
Ogre::Vector3 mFirstPersonOffset;
Ogre::SharedPtr<SayAnimationValue> mSayAnimationValue;
Ogre::SharedPtr<HeadAnimationTime> mHeadAnimationTime;
float mAlpha;

@ -41,6 +41,7 @@
#include "globalmap.hpp"
#include "videoplayer.hpp"
#include "terrainstorage.hpp"
#include "effectmanager.hpp"
using namespace MWRender;
using namespace Ogre;
@ -57,9 +58,11 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
, mSunEnabled(0)
, mPhysicsEngine(engine)
, mTerrain(NULL)
, mEffectManager(NULL)
{
mActors = new MWRender::Actors(mRendering, this);
mObjects = new MWRender::Objects(mRendering);
mEffectManager = new EffectManager(mRendering.getScene());
// select best shader mode
bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos);
bool glES = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL ES") != std::string::npos);
@ -193,6 +196,7 @@ RenderingManager::~RenderingManager ()
delete mVideoPlayer;
delete mActors;
delete mObjects;
delete mEffectManager;
delete mFactory;
}
@ -374,6 +378,8 @@ void RenderingManager::update (float duration, bool paused)
if(paused)
return;
mEffectManager->update(duration);
mActors->update (mRendering.getCamera());
mPlayerAnimation->preRender(mRendering.getCamera());
mObjects->update (duration, mRendering.getCamera());
@ -675,14 +681,14 @@ Shadows* RenderingManager::getShadows()
void RenderingManager::switchToInterior()
{
// causes light flicker in opengl when moving..
//mRendering.getScene()->setCameraRelativeRendering(false);
// TODO: also do this when switching worldspace
mEffectManager->clear();
}
void RenderingManager::switchToExterior()
{
// causes light flicker in opengl when moving..
//mRendering.getScene()->setCameraRelativeRendering(true);
// TODO: also do this when switching worldspace
mEffectManager->clear();
}
Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds)
@ -1020,4 +1026,9 @@ float RenderingManager::getCameraDistance() const
return mCamera->getCameraDistance();
}
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition)
{
mEffectManager->addEffect(model, texture, worldPosition);
}
} // namespace

@ -21,10 +21,7 @@
namespace Ogre
{
class SceneManager;
class SceneNode;
class Quaternion;
class Vector3;
}
namespace MWWorld
@ -51,6 +48,7 @@ namespace MWRender
class GlobalMap;
class VideoPlayer;
class Animation;
class EffectManager;
class RenderingManager: private RenderingInterface, public Ogre::RenderTargetListener, public OEngine::Render::WindowSizeListener
{
@ -209,6 +207,8 @@ public:
void stopVideo();
void frameStarted(float dt, bool paused);
void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition);
protected:
virtual void windowResized(int x, int y);
@ -239,6 +239,8 @@ private:
MWRender::Objects* mObjects;
MWRender::Actors* mActors;
MWRender::EffectManager* mEffectManager;
MWRender::NpcAnimation *mPlayerAnimation;
// 0 normal, 1 more bright, 2 max

@ -248,14 +248,13 @@ namespace MWSound
return;
try
{
// The range values are not tested
float basevol = volumeFromType(Play_TypeVoice);
std::string filePath = "Sound/"+filename;
const ESM::Position &pos = ptr.getRefData().getPosition();
const Ogre::Vector3 objpos(pos.pos);
MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f,
20.0f, 12750.0f, Play_Normal|Play_TypeVoice, 0);
20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0);
mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound"));
}
catch(std::exception &e)

@ -363,4 +363,8 @@ namespace MWWorld
throw std::runtime_error("class does not support skills");
}
int Class::getBloodTexture (const MWWorld::Ptr& ptr) const
{
throw std::runtime_error("class does not support gore");
}
}

@ -278,6 +278,9 @@ namespace MWWorld
virtual bool isKey (const MWWorld::Ptr& ptr) const { return false; }
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
virtual int getBloodTexture (const MWWorld::Ptr& ptr) const;
virtual Ptr
copyToCell(const Ptr &ptr, CellStore &cell) const;

@ -149,7 +149,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
item.getCellRef().mPos.pos[1] = 0;
item.getCellRef().mPos.pos[2] = 0;
if (setOwner)
if (setOwner && actorPtr.getClass().isActor())
item.getCellRef().mOwner = actorPtr.getCellRef().mRefID;
std::string script = MWWorld::Class::get(item).getScript(item);

@ -108,7 +108,7 @@ namespace MWWorld
}
static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time,
bool isFlying, float waterlevel, OEngine::Physic::PhysicEngine *engine)
bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine)
{
const ESM::Position &refpos = ptr.getRefData().getPosition();
Ogre::Vector3 position(refpos.pos);
@ -229,7 +229,10 @@ namespace MWWorld
physicActor->setInertialForce(Ogre::Vector3(0.0f));
else
{
inertia.z += time*-627.2f;
float diff = time*-627.2f;
if (inertia.z < 0)
diff *= slowFall;
inertia.z += diff;
physicActor->setInertialForce(inertia);
}
physicActor->setOnGround(isOnGround);
@ -577,9 +580,10 @@ namespace MWWorld
float oldHeight = iter->first.getRefData().getPosition().pos[2];
const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects();
bool waterCollision = false;
if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects()
.get(ESM::MagicEffect::WaterWalking).mMagnitude
if (effects.get(ESM::MagicEffect::WaterWalking).mMagnitude
&& cell->hasWater()
&& !world->isUnderwater(iter->first.getCell(),
Ogre::Vector3(iter->first.getRefData().getPosition().pos)))
@ -592,9 +596,12 @@ namespace MWWorld
if (waterCollision)
mEngine->dynamicsWorld->addCollisionObject(&object);
// 100 points of slowfall reduce gravity by 90% (this is just a guess)
float slowFall = 1-std::min(std::max(0.f, (effects.get(ESM::MagicEffect::SlowFall).mMagnitude / 100.f) * 0.9f), 0.9f);
Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum,
world->isFlying(iter->first),
waterlevel, mEngine);
waterlevel, slowFall, mEngine);
if (waterCollision)
mEngine->dynamicsWorld->removeCollisionObject(&object);

@ -2282,7 +2282,8 @@ namespace MWWorld
void World::breakInvisibility(const Ptr &actor)
{
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
if (actor.getClass().isNpc())
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
}
bool World::isDark() const
@ -2610,4 +2611,31 @@ namespace MWWorld
safePlaceObject(ref.getPtr(),*cell,ipos);
}
}
void World::spawnBloodEffect(const Ptr &ptr, const Vector3 &worldPosition)
{
int type = ptr.getClass().getBloodTexture(ptr);
std::string texture;
switch (type)
{
case 2:
texture = getFallback()->getFallbackString("Blood_Texture_2");
break;
case 1:
texture = getFallback()->getFallbackString("Blood_Texture_1");
break;
case 0:
default:
texture = getFallback()->getFallbackString("Blood_Texture_0");
break;
}
std::stringstream modelName;
modelName << "Blood_Model_";
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 3; // [0, 2]
modelName << roll;
std::string model = "meshes\\" + getFallback()->getFallbackString(modelName.str());
mRendering->spawnEffect(model, texture, worldPosition);
}
}

@ -549,6 +549,9 @@ namespace MWWorld
/// Spawn a random creature from a levelled list next to the player
virtual void spawnRandomCreature(const std::string& creatureList);
/// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition);
};
}

@ -25,16 +25,20 @@ struct Creature
// Default is 0x48?
enum Flags
{
Biped = 0x001,
Respawn = 0x002,
Weapon = 0x004, // Has weapon and shield
None = 0x008, // ??
// Movement types
Bipedal = 0x001,
Swims = 0x010,
Flies = 0x020, // Don't know what happens if several
Walks = 0x040, // of these are set
Respawn = 0x002,
Weapon = 0x004, // Has weapon and shield
None = 0x008, // ??
Essential = 0x080,
Skeleton = 0x400, // Does not have normal blood
Metal = 0x800 // Has 'golden' blood
// Blood types
Skeleton = 0x400,
Metal = 0x800
};
enum Type

Loading…
Cancel
Save