diff --git a/CHANGELOG.md b/CHANGELOG.md index 197ad2b7a..94c603519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Feature #4859: Make water reflections more configurable Feature #4887: Add openmw command option to set initial random seed Feature #4890: Make Distant Terrain configurable + Feature #4958: Support eight blood types Feature #4962: Add casting animations for magic items Feature #4968: Scalable UI widget skins Task #4686: Upgrade media decoder to a more current FFmpeg API diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index bee2a3565..43d0a05ed 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -704,29 +704,25 @@ std::string creatureFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; - if (flags & ESM::Creature::None) properties += "All "; + if (flags & ESM::Creature::Base) properties += "Base "; 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::Bipedal) properties += "Bipedal "; if (flags & ESM::Creature::Respawn) properties += "Respawn "; if (flags & ESM::Creature::Weapon) properties += "Weapon "; - if (flags & ESM::Creature::Skeleton) properties += "Skeleton "; - if (flags & ESM::Creature::Metal) properties += "Metal "; if (flags & ESM::Creature::Essential) properties += "Essential "; - int unused = (0xFFFFFFFF ^ - (ESM::Creature::None| + int unused = (0xFF ^ + (ESM::Creature::Base| ESM::Creature::Walks| ESM::Creature::Swims| ESM::Creature::Flies| ESM::Creature::Bipedal| ESM::Creature::Respawn| ESM::Creature::Weapon| - ESM::Creature::Skeleton| - ESM::Creature::Metal| ESM::Creature::Essential)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += str(boost::format("(0x%02X)") % flags); return properties; } @@ -828,33 +824,21 @@ std::string npcFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; - // Mythicmods and the ESM component differ. Mythicmods says - // 0x8=None and 0x10=AutoCalc, while our code previously defined - // 0x8 as AutoCalc. The former seems to be correct. All Bethesda - // records have bit 0x8 set. Previously, suspiciously large portion - // of females had autocalc turned off. - if (flags & 0x00000008) properties += "Unknown "; + if (flags & ESM::NPC::Base) properties += "Base "; if (flags & ESM::NPC::Autocalc) properties += "Autocalc "; if (flags & ESM::NPC::Female) properties += "Female "; if (flags & ESM::NPC::Respawn) properties += "Respawn "; if (flags & ESM::NPC::Essential) properties += "Essential "; - // These two flags do not appear on any NPCs and may have been - // confused with the flags for creatures. - if (flags & ESM::NPC::Skeleton) properties += "Skeleton "; - if (flags & ESM::NPC::Metal) properties += "Metal "; // Whether corpses persist is a bit that is unaccounted for, - // however the only unknown bit occurs on ALL records, and - // relatively few NPCs have this bit set. - int unused = (0xFFFFFFFF ^ - (0x00000008| + // however relatively few NPCs have this bit set. + int unused = (0xFF ^ + (ESM::NPC::Base| ESM::NPC::Autocalc| ESM::NPC::Female| ESM::NPC::Respawn| - ESM::NPC::Essential| - ESM::NPC::Skeleton| - ESM::NPC::Metal)); + ESM::NPC::Essential)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += str(boost::format("(0x%02X)") % flags); return properties; } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index c5153dadb..2149d1772 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -618,7 +618,8 @@ void Record::print() std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Flags: " << creatureFlags(mData.mFlags) << std::endl; + std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl; + std::cout << " Blood Type: " << mData.mBloodType+1 << std::endl; std::cout << " Original: " << mData.mOriginal << std::endl; std::cout << " Scale: " << mData.mScale << std::endl; @@ -1022,7 +1023,9 @@ void Record::print() std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mFaction.empty()) std::cout << " Faction: " << mData.mFaction << std::endl; - std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl; + std::cout << " Flags: " << npcFlags((int)mData.mFlags) << std::endl; + if (mData.mBloodType != 0) + std::cout << " Blood Type: " << mData.mBloodType+1 << std::endl; if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 5b229dc92..600b4917a 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -230,9 +230,19 @@ MwIniImporter::MwIniImporter() "Blood:Texture 0", "Blood:Texture 1", "Blood:Texture 2", + "Blood:Texture 3", + "Blood:Texture 4", + "Blood:Texture 5", + "Blood:Texture 6", + "Blood:Texture 7", "Blood:Texture Name 0", "Blood:Texture Name 1", "Blood:Texture Name 2", + "Blood:Texture Name 3", + "Blood:Texture Name 4", + "Blood:Texture Name 5", + "Blood:Texture Name 6", + "Blood:Texture Name 7", // movies "Movies:Company Logo", @@ -624,17 +634,6 @@ 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", - // werewolf (Bloodmoon) "General:Werewolf FOV", diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index f357264b8..249b43252 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -1,5 +1,6 @@ #include "columns.hpp" +#include #include #include "universalid.hpp" @@ -573,11 +574,6 @@ namespace "Book", "Scroll", 0 }; - static const char *sBloodType[] = - { - "Default (Red)", "Skeleton Blood (White)", "Metal Blood (Golden)", 0 - }; - static const char *sEmitterType[] = { "", "Flickering", "Flickering (Slow)", "Pulsing", "Pulsing (Slow)", 0 @@ -613,7 +609,6 @@ namespace case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; case CSMWorld::Columns::ColumnId_BookType: return sBookType; - case CSMWorld::Columns::ColumnId_BloodType: return sBloodType; case CSMWorld::Columns::ColumnId_EmitterType: return sEmitterType; default: return 0; @@ -633,6 +628,15 @@ std::vector>CSMWorld::Columns::getEnums (ColumnId col if (const char **table = getEnumNames (column)) for (int i=0; table[i]; ++i) enums.emplace_back(i, table[i]); + else if (column==ColumnId_BloodType) + { + for (int i=0; i<8; i++) + { + const std::string& bloodName = Fallback::Map::getString("Blood_Texture_Name_" + std::to_string(i)); + if (!bloodName.empty()) + enums.emplace_back(i, bloodName); + } + } else if (column==ColumnId_RecordType) { enums.emplace_back(UniversalId::Type_None, ""); // none diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 1948a95cb..3c9faa211 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -491,17 +491,7 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column == mColumns.mBloodType) - { - int mask = ESM::Creature::Skeleton | ESM::Creature::Metal; - - if ((record.get().mFlags & mask) == ESM::Creature::Skeleton) - return 1; - - if ((record.get().mFlags & mask) == ESM::Creature::Metal) - return 2; - - return 0; - } + return record.get().mBloodType; std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -527,16 +517,7 @@ void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdDa else if (column==mColumns.mOriginal) creature.mOriginal = value.toString().toUtf8().constData(); else if (column == mColumns.mBloodType) - { - int mask = ~(ESM::Creature::Skeleton | ESM::Creature::Metal); - - if (value.toInt() == 1) - creature.mFlags = (creature.mFlags & mask) | ESM::Creature::Skeleton; - else if (value.toInt() == 2) - creature.mFlags = (creature.mFlags & mask) | ESM::Creature::Metal; - else - creature.mFlags = creature.mFlags & mask; - } + creature.mBloodType = value.toInt(); else { std::map::const_iterator iter = @@ -797,17 +778,7 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column == mColumns.mBloodType) - { - int mask = ESM::NPC::Skeleton | ESM::NPC::Metal; - - if ((record.get().mFlags & mask) == ESM::NPC::Skeleton) - return 1; - - if ((record.get().mFlags & mask) == ESM::NPC::Metal) - return 2; - - return 0; - } + return record.get().mBloodType; if (column == mColumns.mGender) { @@ -846,16 +817,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d else if (column==mColumns.mHead) npc.mHead = value.toString().toUtf8().constData(); else if (column == mColumns.mBloodType) - { - int mask = ~(ESM::NPC::Skeleton | ESM::NPC::Metal); - - if (value.toInt() == 1) - npc.mFlags = (npc.mFlags & mask) | ESM::NPC::Skeleton; - else if (value.toInt() == 2) - npc.mFlags = (npc.mFlags & mask) | ESM::NPC::Metal; - else - npc.mFlags = npc.mFlags & mask; - } + npc.mBloodType = value.toInt(); else if (column == mColumns.mGender) { // Implemented this way to allow additional gender types in the future. diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 305402b80..9ea103a36 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -766,13 +766,7 @@ namespace MWClass int Creature::getBloodTexture(const MWWorld::ConstPtr &ptr) const { - int flags = ptr.get()->mBase->mFlags; - - if (flags & ESM::Creature::Skeleton) - return 1; - if (flags & ESM::Creature::Metal) - return 2; - return 0; + return ptr.get()->mBase->mBloodType; } void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 19ad8c66b..5f9640653 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1286,13 +1286,7 @@ namespace MWClass int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - if (ref->mBase->mFlags & ESM::NPC::Skeleton) - return 1; - if (ref->mBase->mFlags & ESM::NPC::Metal) - return 2; - return 0; + return ptr.get()->mBase->mBloodType; } void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ce9284774..1aac737c3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3585,7 +3585,11 @@ namespace MWWorld return; std::string texture = Fallback::Map::getString("Blood_Texture_" + std::to_string(ptr.getClass().getBloodTexture(ptr))); + if (texture.empty()) + texture = Fallback::Map::getString("Blood_Texture_0"); + std::string model = "meshes\\" + Fallback::Map::getString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))); // [0, 2] + mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 17f03c69b..52138e223 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -55,7 +55,10 @@ namespace ESM { hasNpdt = true; break; case ESM::FourCC<'F','L','A','G'>::value: - esm.getHT(mFlags); + int flags; + esm.getHT(flags); + mFlags = flags & 0xFF; + mBloodType = ((flags >> 8) & 0xFF) >> 2; hasFlags = true; break; case ESM::FourCC<'X','S','C','L'>::value: @@ -121,7 +124,7 @@ namespace ESM { esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); esm.writeHNT("NPDT", mData, 96); - esm.writeHNT("FLAG", mFlags); + esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); if (mScale != 1.0) { esm.writeHNT("XSCL", mScale); } @@ -144,6 +147,7 @@ namespace ESM { mData.mCombat = mData.mMagic = mData.mStealth = 0; for (int i=0; i<6; ++i) mData.mAttack[i] = 0; mData.mGold = 0; + mBloodType = 0; mFlags = 0; mScale = 1.f; mModel.clear(); diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index be6a72b8d..0ab09ee12 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -28,20 +28,14 @@ struct Creature // Default is 0x48? enum Flags { - // 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, // ?? This flag appears set for every creature in Morrowind.esm - Essential = 0x080, - - // Blood types - Skeleton = 0x400, - Metal = 0x800 + Bipedal = 0x01, + Respawn = 0x02, + Weapon = 0x04, // Has weapon and shield + Base = 0x08, // This flag is set for every actor in Bethesda ESMs + Swims = 0x10, + Flies = 0x20, // Don't know what happens if several + Walks = 0x40, // of these are set + Essential = 0x80 }; enum Type @@ -79,7 +73,8 @@ struct Creature NPDTstruct mData; - int mFlags; + int mBloodType; + unsigned char mFlags; bool mPersistent; diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index db6b6d31b..b68aa6375 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -86,7 +86,10 @@ namespace ESM break; case ESM::FourCC<'F','L','A','G'>::value: hasFlags = true; - esm.getHT(mFlags); + int flags; + esm.getHT(flags); + mFlags = flags & 0xFF; + mBloodType = ((flags >> 8) & 0xFF) >> 2; break; case ESM::FourCC<'N','P','C','S'>::value: mSpells.add(esm); @@ -160,7 +163,7 @@ namespace ESM esm.writeHNT("NPDT", npdt12, 12); } - esm.writeHNT("FLAG", mFlags); + esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); mInventory.save(esm); mSpells.save(esm); @@ -186,6 +189,7 @@ namespace ESM { mNpdtType = NPC_DEFAULT; blankNpdt(); + mBloodType = 0; mFlags = 0; mInventory.mList.clear(); mSpells.mList.clear(); diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 746913008..ac3f1a343 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -56,12 +56,11 @@ struct NPC enum Flags { - Female = 0x0001, - Essential = 0x0002, - Respawn = 0x0004, - Autocalc = 0x0010, - Skeleton = 0x0400, // Skeleton blood effect (white) - Metal = 0x0800 // Metal blood effect (golden?) + Female = 0x01, + Essential = 0x02, + Respawn = 0x04, + Base = 0x08, + Autocalc = 0x10 }; enum NpcType @@ -114,7 +113,8 @@ struct NPC int getFactionRank() const; /// wrapper for mNpdt*, -1 = no rank - int mFlags; + int mBloodType; + unsigned char mFlags; bool mPersistent;