From ba3ae448d4f69cb802b28dac31e28bd092af7fc0 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 30 Jan 2022 11:07:39 +0100 Subject: [PATCH 01/48] Initial import of esm4 by cc9cii --- components/CMakeLists.txt | 12 +- components/esm/common.cpp | 15 + components/esm/common.hpp | 57 ++ components/esm/reader.cpp | 114 +++ components/esm/reader.hpp | 60 ++ components/esm4/acti.hpp | 70 ++ components/esm4/actor.hpp | 163 +++++ components/esm4/common.cpp | 100 +++ components/esm4/common.hpp | 939 +++++++++++++++++++++++++ components/esm4/dialogue.hpp | 46 ++ components/esm4/effect.hpp | 56 ++ components/esm4/formid.cpp | 78 ++ components/esm4/formid.hpp | 42 ++ components/esm4/inventory.hpp | 55 ++ components/esm4/lighting.hpp | 79 +++ components/esm4/loadachr.cpp | 124 ++++ components/esm4/loadachr.hpp | 70 ++ components/esm4/loadacre.cpp | 106 +++ components/esm4/loadacre.hpp | 67 ++ components/esm4/loadacti.cpp | 103 +++ components/esm4/loadalch.cpp | 121 ++++ components/esm4/loadalch.hpp | 88 +++ components/esm4/loadaloc.cpp | 172 +++++ components/esm4/loadaloc.hpp | 88 +++ components/esm4/loadammo.cpp | 133 ++++ components/esm4/loadammo.hpp | 84 +++ components/esm4/loadanio.cpp | 80 +++ components/esm4/loadanio.hpp | 63 ++ components/esm4/loadappa.cpp | 106 +++ components/esm4/loadappa.hpp | 75 ++ components/esm4/loadarma.cpp | 151 ++++ components/esm4/loadarma.hpp | 70 ++ components/esm4/loadarmo.cpp | 218 ++++++ components/esm4/loadarmo.hpp | 196 ++++++ components/esm4/loadaspc.cpp | 94 +++ components/esm4/loadaspc.hpp | 66 ++ components/esm4/loadbook.cpp | 121 ++++ components/esm4/loadbook.hpp | 114 +++ components/esm4/loadbptd.cpp | 113 +++ components/esm4/loadbptd.hpp | 128 ++++ components/esm4/loadcell.cpp | 269 +++++++ components/esm4/loadcell.hpp | 109 +++ components/esm4/loadclas.cpp | 84 +++ components/esm4/loadclas.hpp | 66 ++ components/esm4/loadclfm.cpp | 93 +++ components/esm4/loadclfm.hpp | 70 ++ components/esm4/loadclot.cpp | 106 +++ components/esm4/loadclot.hpp | 83 +++ components/esm4/loadcont.cpp | 107 +++ components/esm4/loadcont.hpp | 71 ++ components/esm4/loadcrea.cpp | 232 ++++++ components/esm4/loadcrea.hpp | 151 ++++ components/esm4/loaddial.cpp | 125 ++++ components/esm4/loaddial.hpp | 70 ++ components/esm4/loaddobj.cpp | 128 ++++ components/esm4/loaddobj.hpp | 100 +++ components/esm4/loaddoor.cpp | 93 +++ components/esm4/loaddoor.hpp | 76 ++ components/esm4/loadeyes.cpp | 74 ++ components/esm4/loadeyes.hpp | 68 ++ components/esm4/loadflor.cpp | 89 +++ components/esm4/loadflor.hpp | 78 ++ components/esm4/loadflst.cpp | 81 +++ components/esm4/loadflst.hpp | 60 ++ components/esm4/loadfurn.cpp | 98 +++ components/esm4/loadfurn.hpp | 64 ++ components/esm4/loadglob.cpp | 82 +++ components/esm4/loadglob.hpp | 60 ++ components/esm4/loadgras.cpp | 78 ++ components/esm4/loadgras.hpp | 98 +++ components/esm4/loadgrup.hpp | 158 +++++ components/esm4/loadhair.cpp | 84 +++ components/esm4/loadhair.hpp | 71 ++ components/esm4/loadhdpt.cpp | 104 +++ components/esm4/loadhdpt.hpp | 66 ++ components/esm4/loadidle.cpp | 84 +++ components/esm4/loadidle.hpp | 62 ++ components/esm4/loadidlm.cpp | 103 +++ components/esm4/loadidlm.hpp | 63 ++ components/esm4/loadimod.cpp | 89 +++ components/esm4/loadimod.hpp | 59 ++ components/esm4/loadinfo.cpp | 222 ++++++ components/esm4/loadinfo.hpp | 90 +++ components/esm4/loadingr.cpp | 133 ++++ components/esm4/loadingr.hpp | 83 +++ components/esm4/loadkeym.cpp | 93 +++ components/esm4/loadkeym.hpp | 77 ++ components/esm4/loadland.cpp | 255 +++++++ components/esm4/loadland.hpp | 136 ++++ components/esm4/loadlgtm.cpp | 105 +++ components/esm4/loadlgtm.hpp | 63 ++ components/esm4/loadligh.cpp | 120 ++++ components/esm4/loadligh.hpp | 101 +++ components/esm4/loadltex.cpp | 107 +++ components/esm4/loadltex.hpp | 75 ++ components/esm4/loadlvlc.cpp | 130 ++++ components/esm4/loadlvlc.hpp | 71 ++ components/esm4/loadlvli.cpp | 142 ++++ components/esm4/loadlvli.hpp | 72 ++ components/esm4/loadlvln.cpp | 114 +++ components/esm4/loadlvln.hpp | 69 ++ components/esm4/loadmato.cpp | 76 ++ components/esm4/loadmato.hpp | 58 ++ components/esm4/loadmisc.cpp | 95 +++ components/esm4/loadmisc.hpp | 77 ++ components/esm4/loadmset.cpp | 115 +++ components/esm4/loadmset.hpp | 99 +++ components/esm4/loadmstt.cpp | 87 +++ components/esm4/loadmstt.hpp | 60 ++ components/esm4/loadmusc.cpp | 86 +++ components/esm4/loadmusc.hpp | 60 ++ components/esm4/loadnavi.cpp | 373 ++++++++++ components/esm4/loadnavi.hpp | 117 +++ components/esm4/loadnavm.cpp | 270 +++++++ components/esm4/loadnavm.hpp | 112 +++ components/esm4/loadnote.cpp | 86 +++ components/esm4/loadnote.hpp | 60 ++ components/esm4/loadnpc.cpp | 333 +++++++++ components/esm4/loadnpc.hpp | 230 ++++++ components/esm4/loadotft.cpp | 84 +++ components/esm4/loadotft.hpp | 60 ++ components/esm4/loadpack.cpp | 200 ++++++ components/esm4/loadpack.hpp | 110 +++ components/esm4/loadpgrd.cpp | 177 +++++ components/esm4/loadpgrd.hpp | 96 +++ components/esm4/loadpgre.cpp | 105 +++ components/esm4/loadpgre.hpp | 59 ++ components/esm4/loadpwat.cpp | 82 +++ components/esm4/loadpwat.hpp | 59 ++ components/esm4/loadqust.cpp | 179 +++++ components/esm4/loadqust.hpp | 84 +++ components/esm4/loadrace.cpp | 715 +++++++++++++++++++ components/esm4/loadrace.hpp | 174 +++++ components/esm4/loadrefr.cpp | 349 +++++++++ components/esm4/loadrefr.hpp | 119 ++++ components/esm4/loadregn.cpp | 164 +++++ components/esm4/loadregn.hpp | 98 +++ components/esm4/loadroad.cpp | 123 ++++ components/esm4/loadroad.hpp | 89 +++ components/esm4/loadsbsp.cpp | 78 ++ components/esm4/loadsbsp.hpp | 64 ++ components/esm4/loadscol.cpp | 84 +++ components/esm4/loadscol.hpp | 59 ++ components/esm4/loadscpt.cpp | 173 +++++ components/esm4/loadscpt.hpp | 59 ++ components/esm4/loadscrl.cpp | 99 +++ components/esm4/loadscrl.hpp | 68 ++ components/esm4/loadsgst.cpp | 118 ++++ components/esm4/loadsgst.hpp | 75 ++ components/esm4/loadslgm.cpp | 91 +++ components/esm4/loadslgm.hpp | 76 ++ components/esm4/loadsndr.cpp | 94 +++ components/esm4/loadsndr.hpp | 86 +++ components/esm4/loadsoun.cpp | 95 +++ components/esm4/loadsoun.hpp | 101 +++ components/esm4/loadstat.cpp | 101 +++ components/esm4/loadstat.hpp | 62 ++ components/esm4/loadtact.cpp | 89 +++ components/esm4/loadtact.hpp | 76 ++ components/esm4/loadterm.cpp | 101 +++ components/esm4/loadterm.hpp | 66 ++ components/esm4/loadtes4.cpp | 113 +++ components/esm4/loadtes4.hpp | 69 ++ components/esm4/loadtree.cpp | 85 +++ components/esm4/loadtree.hpp | 62 ++ components/esm4/loadtxst.cpp | 92 +++ components/esm4/loadtxst.hpp | 66 ++ components/esm4/loadweap.cpp | 188 +++++ components/esm4/loadweap.hpp | 96 +++ components/esm4/loadwrld.cpp | 205 ++++++ components/esm4/loadwrld.hpp | 137 ++++ components/esm4/reader.cpp | 639 +++++++++++++++++ components/esm4/reader.hpp | 298 ++++++++ components/esm4/records.hpp | 74 ++ components/esm4/reference.hpp | 68 ++ components/esm4/script.hpp | 384 ++++++++++ components/to_utf8/to_utf8.cpp | 40 +- components/to_utf8/to_utf8.hpp | 3 + components/translation/translation.cpp | 11 +- 179 files changed, 20848 insertions(+), 18 deletions(-) create mode 100644 components/esm/common.cpp create mode 100644 components/esm/common.hpp create mode 100644 components/esm/reader.cpp create mode 100644 components/esm/reader.hpp create mode 100644 components/esm4/acti.hpp create mode 100644 components/esm4/actor.hpp create mode 100644 components/esm4/common.cpp create mode 100644 components/esm4/common.hpp create mode 100644 components/esm4/dialogue.hpp create mode 100644 components/esm4/effect.hpp create mode 100644 components/esm4/formid.cpp create mode 100644 components/esm4/formid.hpp create mode 100644 components/esm4/inventory.hpp create mode 100644 components/esm4/lighting.hpp create mode 100644 components/esm4/loadachr.cpp create mode 100644 components/esm4/loadachr.hpp create mode 100644 components/esm4/loadacre.cpp create mode 100644 components/esm4/loadacre.hpp create mode 100644 components/esm4/loadacti.cpp create mode 100644 components/esm4/loadalch.cpp create mode 100644 components/esm4/loadalch.hpp create mode 100644 components/esm4/loadaloc.cpp create mode 100644 components/esm4/loadaloc.hpp create mode 100644 components/esm4/loadammo.cpp create mode 100644 components/esm4/loadammo.hpp create mode 100644 components/esm4/loadanio.cpp create mode 100644 components/esm4/loadanio.hpp create mode 100644 components/esm4/loadappa.cpp create mode 100644 components/esm4/loadappa.hpp create mode 100644 components/esm4/loadarma.cpp create mode 100644 components/esm4/loadarma.hpp create mode 100644 components/esm4/loadarmo.cpp create mode 100644 components/esm4/loadarmo.hpp create mode 100644 components/esm4/loadaspc.cpp create mode 100644 components/esm4/loadaspc.hpp create mode 100644 components/esm4/loadbook.cpp create mode 100644 components/esm4/loadbook.hpp create mode 100644 components/esm4/loadbptd.cpp create mode 100644 components/esm4/loadbptd.hpp create mode 100644 components/esm4/loadcell.cpp create mode 100644 components/esm4/loadcell.hpp create mode 100644 components/esm4/loadclas.cpp create mode 100644 components/esm4/loadclas.hpp create mode 100644 components/esm4/loadclfm.cpp create mode 100644 components/esm4/loadclfm.hpp create mode 100644 components/esm4/loadclot.cpp create mode 100644 components/esm4/loadclot.hpp create mode 100644 components/esm4/loadcont.cpp create mode 100644 components/esm4/loadcont.hpp create mode 100644 components/esm4/loadcrea.cpp create mode 100644 components/esm4/loadcrea.hpp create mode 100644 components/esm4/loaddial.cpp create mode 100644 components/esm4/loaddial.hpp create mode 100644 components/esm4/loaddobj.cpp create mode 100644 components/esm4/loaddobj.hpp create mode 100644 components/esm4/loaddoor.cpp create mode 100644 components/esm4/loaddoor.hpp create mode 100644 components/esm4/loadeyes.cpp create mode 100644 components/esm4/loadeyes.hpp create mode 100644 components/esm4/loadflor.cpp create mode 100644 components/esm4/loadflor.hpp create mode 100644 components/esm4/loadflst.cpp create mode 100644 components/esm4/loadflst.hpp create mode 100644 components/esm4/loadfurn.cpp create mode 100644 components/esm4/loadfurn.hpp create mode 100644 components/esm4/loadglob.cpp create mode 100644 components/esm4/loadglob.hpp create mode 100644 components/esm4/loadgras.cpp create mode 100644 components/esm4/loadgras.hpp create mode 100644 components/esm4/loadgrup.hpp create mode 100644 components/esm4/loadhair.cpp create mode 100644 components/esm4/loadhair.hpp create mode 100644 components/esm4/loadhdpt.cpp create mode 100644 components/esm4/loadhdpt.hpp create mode 100644 components/esm4/loadidle.cpp create mode 100644 components/esm4/loadidle.hpp create mode 100644 components/esm4/loadidlm.cpp create mode 100644 components/esm4/loadidlm.hpp create mode 100644 components/esm4/loadimod.cpp create mode 100644 components/esm4/loadimod.hpp create mode 100644 components/esm4/loadinfo.cpp create mode 100644 components/esm4/loadinfo.hpp create mode 100644 components/esm4/loadingr.cpp create mode 100644 components/esm4/loadingr.hpp create mode 100644 components/esm4/loadkeym.cpp create mode 100644 components/esm4/loadkeym.hpp create mode 100644 components/esm4/loadland.cpp create mode 100644 components/esm4/loadland.hpp create mode 100644 components/esm4/loadlgtm.cpp create mode 100644 components/esm4/loadlgtm.hpp create mode 100644 components/esm4/loadligh.cpp create mode 100644 components/esm4/loadligh.hpp create mode 100644 components/esm4/loadltex.cpp create mode 100644 components/esm4/loadltex.hpp create mode 100644 components/esm4/loadlvlc.cpp create mode 100644 components/esm4/loadlvlc.hpp create mode 100644 components/esm4/loadlvli.cpp create mode 100644 components/esm4/loadlvli.hpp create mode 100644 components/esm4/loadlvln.cpp create mode 100644 components/esm4/loadlvln.hpp create mode 100644 components/esm4/loadmato.cpp create mode 100644 components/esm4/loadmato.hpp create mode 100644 components/esm4/loadmisc.cpp create mode 100644 components/esm4/loadmisc.hpp create mode 100644 components/esm4/loadmset.cpp create mode 100644 components/esm4/loadmset.hpp create mode 100644 components/esm4/loadmstt.cpp create mode 100644 components/esm4/loadmstt.hpp create mode 100644 components/esm4/loadmusc.cpp create mode 100644 components/esm4/loadmusc.hpp create mode 100644 components/esm4/loadnavi.cpp create mode 100644 components/esm4/loadnavi.hpp create mode 100644 components/esm4/loadnavm.cpp create mode 100644 components/esm4/loadnavm.hpp create mode 100644 components/esm4/loadnote.cpp create mode 100644 components/esm4/loadnote.hpp create mode 100644 components/esm4/loadnpc.cpp create mode 100644 components/esm4/loadnpc.hpp create mode 100644 components/esm4/loadotft.cpp create mode 100644 components/esm4/loadotft.hpp create mode 100644 components/esm4/loadpack.cpp create mode 100644 components/esm4/loadpack.hpp create mode 100644 components/esm4/loadpgrd.cpp create mode 100644 components/esm4/loadpgrd.hpp create mode 100644 components/esm4/loadpgre.cpp create mode 100644 components/esm4/loadpgre.hpp create mode 100644 components/esm4/loadpwat.cpp create mode 100644 components/esm4/loadpwat.hpp create mode 100644 components/esm4/loadqust.cpp create mode 100644 components/esm4/loadqust.hpp create mode 100644 components/esm4/loadrace.cpp create mode 100644 components/esm4/loadrace.hpp create mode 100644 components/esm4/loadrefr.cpp create mode 100644 components/esm4/loadrefr.hpp create mode 100644 components/esm4/loadregn.cpp create mode 100644 components/esm4/loadregn.hpp create mode 100644 components/esm4/loadroad.cpp create mode 100644 components/esm4/loadroad.hpp create mode 100644 components/esm4/loadsbsp.cpp create mode 100644 components/esm4/loadsbsp.hpp create mode 100644 components/esm4/loadscol.cpp create mode 100644 components/esm4/loadscol.hpp create mode 100644 components/esm4/loadscpt.cpp create mode 100644 components/esm4/loadscpt.hpp create mode 100644 components/esm4/loadscrl.cpp create mode 100644 components/esm4/loadscrl.hpp create mode 100644 components/esm4/loadsgst.cpp create mode 100644 components/esm4/loadsgst.hpp create mode 100644 components/esm4/loadslgm.cpp create mode 100644 components/esm4/loadslgm.hpp create mode 100644 components/esm4/loadsndr.cpp create mode 100644 components/esm4/loadsndr.hpp create mode 100644 components/esm4/loadsoun.cpp create mode 100644 components/esm4/loadsoun.hpp create mode 100644 components/esm4/loadstat.cpp create mode 100644 components/esm4/loadstat.hpp create mode 100644 components/esm4/loadtact.cpp create mode 100644 components/esm4/loadtact.hpp create mode 100644 components/esm4/loadterm.cpp create mode 100644 components/esm4/loadterm.hpp create mode 100644 components/esm4/loadtes4.cpp create mode 100644 components/esm4/loadtes4.hpp create mode 100644 components/esm4/loadtree.cpp create mode 100644 components/esm4/loadtree.hpp create mode 100644 components/esm4/loadtxst.cpp create mode 100644 components/esm4/loadtxst.hpp create mode 100644 components/esm4/loadweap.cpp create mode 100644 components/esm4/loadweap.hpp create mode 100644 components/esm4/loadwrld.cpp create mode 100644 components/esm4/loadwrld.hpp create mode 100644 components/esm4/reader.cpp create mode 100644 components/esm4/reader.hpp create mode 100644 components/esm4/records.hpp create mode 100644 components/esm4/reference.hpp create mode 100644 components/esm4/script.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2d986660bc..3a7b1a43b8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -76,7 +76,7 @@ add_component_dir (to_utf8 to_utf8 ) -add_component_dir(esm attr defs esmcommon records util luascripts) +add_component_dir(esm attr common defs esmcommon reader records util luascripts) add_component_dir (esm3 esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell @@ -94,6 +94,16 @@ add_component_dir (esm3terrain storage ) +add_component_dir (esm4 + loadachr loadacre loadacti loadalch loadaloc loadammo loadanio loadappa loadarma loadarmo loadaspc loadbook + loadbptd loadcell loadclas loadclfm loadclot common loadcont loadcrea loaddial loaddobj loaddoor loadeyes + loadflor loadflst formid loadfurn loadglob loadgras loadhair loadhdpt loadidle loadidlm loadimod loadinfo + loadingr loadkeym loadland loadlgtm loadligh loadltex loadlvlc loadlvli loadlvln loadmato loadmisc loadmset + loadmstt loadmusc loadnavi loadnavm loadnote loadnpc loadotft loadpack loadpgrd loadpgre loadpwat loadqust + loadrace loadrefr loadregn loadroad loadsbsp loadscol loadscpt loadscrl loadsgst loadslgm loadsndr + loadsoun loadstat loadtact loadterm loadtes4 loadtree loadtxst loadweap loadwrld reader +) + add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread compression osguservalues errorMarker color diff --git a/components/esm/common.cpp b/components/esm/common.cpp new file mode 100644 index 0000000000..d04033edef --- /dev/null +++ b/components/esm/common.cpp @@ -0,0 +1,15 @@ +#include "sstream" + +namespace ESM +{ + std::string printName(const std::uint32_t typeId) + { + unsigned char typeName[4]; + typeName[0] = typeId & 0xff; + typeName[1] = (typeId >> 8) & 0xff; + typeName[2] = (typeId >> 16) & 0xff; + typeName[3] = (typeId >> 24) & 0xff; + + return std::string((char*)typeName, 4); + } +} diff --git a/components/esm/common.hpp b/components/esm/common.hpp new file mode 100644 index 0000000000..3a8894e321 --- /dev/null +++ b/components/esm/common.hpp @@ -0,0 +1,57 @@ +#ifndef COMPONENT_ESM_COMMON_H +#define COMPONENT_ESM_COMMON_H + +#include +#include + +namespace ESM +{ +#pragma pack(push, 1) + union ESMVersion + { + float f; + std::uint32_t ui; + }; + + union TypeId + { + std::uint32_t value; + char name[4]; // record type in ascii + }; +#pragma pack(pop) + + enum ESMVersions + { + VER_120 = 0x3f99999a, // TES3 + VER_130 = 0x3fa66666, // TES3 + VER_080 = 0x3f4ccccd, // TES4 + VER_100 = 0x3f800000, // TES4 + VER_132 = 0x3fa8f5c3, // FONV Courier's Stash, DeadMoney + VER_133 = 0x3faa3d71, // FONV HonestHearts + VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues + VER_094 = 0x3f70a3d7, // TES5/FO3 + VER_170 = 0x3fd9999a // TES5 + }; + + // Defines another files (esm or esp) that this file depends upon. + struct MasterData + { + std::string name; + std::uint64_t size; + }; + + enum VarType + { + VT_Unknown = 0, + VT_None, + VT_Short, // stored as a float, kinda + VT_Int, + VT_Long, // stored as a float + VT_Float, + VT_String + }; + + std::string printName(const std::uint32_t typeId); +} + +#endif // COMPONENT_ESM_COMMON_H diff --git a/components/esm/reader.cpp b/components/esm/reader.cpp new file mode 100644 index 0000000000..1f22b95b47 --- /dev/null +++ b/components/esm/reader.cpp @@ -0,0 +1,114 @@ +#include "reader.hpp" + +//#ifdef NDEBUG +//#undef NDEBUG +//#endif + +#include +#include + +#include + +#include "components/esm3/esmreader.hpp" +#include "components/esm4/reader.hpp" + +namespace ESM +{ + Reader* Reader::getReader(const std::string &filename) + { + Files::IStreamPtr esmStream(Files::openConstrainedFileStream (filename.c_str ())); + + std::uint32_t modVer = 0; // get the first 4 bytes of the record header only + esmStream->read((char*)&modVer, sizeof(modVer)); + if (esmStream->gcount() == sizeof(modVer)) + { + esmStream->seekg(0); + + if (modVer == ESM4::REC_TES4) + { + return new ESM4::Reader(esmStream, filename); + } + else + { + //return new ESM3::ESMReader(esmStream, filename); + } + } + + throw std::runtime_error("Unknown file format"); + } + + bool Reader::getStringImpl(std::string& str, std::size_t size, + Files::IStreamPtr filestream, ToUTF8::Utf8Encoder* encoder, bool hasNull) + { + std::size_t newSize = size; + + if (encoder) + { + if (!hasNull) + newSize += 1; // Utf8Encoder::getLength() expects a null terminator + + std::string tmp; + tmp.resize(newSize); + filestream->read(&tmp[0], size); + if ((std::size_t)filestream->gcount() == size) + { + if (hasNull) + { + assert (tmp[newSize - 1] == '\0' + && "ESM4::Reader::getString string is not terminated with a null"); + } + else + { + // NOTE: script text of some mods have terminating null; + // unfortunately we just have to deal with it + //if (tmp[newSize - 2] != '\0') + tmp[newSize - 1] = '\0'; // for Utf8Encoder::getLength() + } + + encoder->toUtf8(tmp, str, newSize - 1); + // NOTE: the encoder converts “keep” as “keep†which increases the length, + // which results in below resize() truncating the string + if (str.size() == newSize) // ascii + str.resize(newSize - 1); // don't want the null terminator + else + { + // this is a horrible hack but fortunately there's only a few like this + std::string tmp2(str.data()); + str.swap(tmp2); + } + + return true; + } + } + else + { + if (hasNull) + newSize -= 1; // don't read the null terminator yet + + str.resize(newSize); // assumed C++11 + filestream->read(&str[0], newSize); + if ((std::size_t)filestream->gcount() == newSize) + { + if (hasNull) + { + char ch; + filestream->read(&ch, 1); // read the null terminator + assert (ch == '\0' + && "ESM4::Reader::getString string is not terminated with a null"); + } +#if 0 + else + { + // NOTE: normal ESMs don't but omwsave has locals or spells with null terminator + assert (str[newSize - 1] != '\0' + && "ESM4::Reader::getString string is unexpectedly terminated with a null"); + } +#endif + return true; + } + } + + str.clear(); + return false; // FIXME: throw instead? + } +} diff --git a/components/esm/reader.hpp b/components/esm/reader.hpp new file mode 100644 index 0000000000..a5e3dd516b --- /dev/null +++ b/components/esm/reader.hpp @@ -0,0 +1,60 @@ +#ifndef COMPONENT_ESM_READER_H +#define COMPONENT_ESM_READER_H + +#include + +#include +#include + +#include "common.hpp" // MasterData + +namespace ToUTF8 +{ + class Utf8Encoder; +} + +namespace ESM +{ + class Reader + { + std::vector* mGlobalReaderList; + + public: + virtual ~Reader() {} + + static Reader* getReader(const std::string& filename); + + void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} + std::vector *getGlobalReaderList() {return mGlobalReaderList;} + + virtual inline bool isEsm4() const = 0; + + virtual inline bool hasMoreRecs() const = 0; + + virtual inline void setEncoder(ToUTF8::Utf8Encoder* encoder) = 0; + + // used to check for dependencies e.g. CS::Editor::run() + virtual inline const std::vector& getGameFiles() const = 0; + + // used by ContentSelector::ContentModel::addFiles() + virtual inline const std::string getAuthor() const = 0; + virtual inline const std::string getDesc() const = 0; + virtual inline int getFormat() const = 0; + + virtual inline std::string getFileName() const = 0; + + // used by CSMWorld::Data::startLoading() and getTotalRecords() for loading progress bar + virtual inline int getRecordCount() const = 0; + + virtual void setModIndex(std::uint32_t index) = 0; + + // used by CSMWorld::Data::getTotalRecords() + virtual void close() = 0; + + protected: + bool getStringImpl(std::string& str, std::size_t size, + Files::IStreamPtr filestream, ToUTF8::Utf8Encoder* encoder, bool hasNull = false); + }; +} + +#endif // COMPONENT_ESM_READER_H diff --git a/components/esm4/acti.hpp b/components/esm4/acti.hpp new file mode 100644 index 0000000000..4ec3fe1e5e --- /dev/null +++ b/components/esm4/acti.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACTI_H +#define ESM4_ACTI_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Activator + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + FormId mScriptId; + FormId mLoopingSound; // SOUN + FormId mActivationSound; // SOUN + + float mBoundRadius; + + FormId mRadioTemplate; // SOUN + FormId mRadioStation; // TACT + + std::string mActivationPrompt; + + Activator(); + virtual ~Activator(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACTI_H diff --git a/components/esm4/actor.hpp b/components/esm4/actor.hpp new file mode 100644 index 0000000000..ae56a49564 --- /dev/null +++ b/components/esm4/actor.hpp @@ -0,0 +1,163 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACTOR_H +#define ESM4_ACTOR_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ +#pragma pack(push, 1) + struct AIData // NPC_, CREA + { + std::uint8_t aggression; + std::uint8_t confidence; + std::uint8_t energyLevel; + std::uint8_t responsibility; + std::uint32_t aiFlags; + std::uint8_t trainSkill; + std::uint8_t trainLevel; + std::uint16_t unknown; + }; + + struct AttributeValues + { + std::uint8_t strength; + std::uint8_t intelligence; + std::uint8_t willpower; + std::uint8_t agility; + std::uint8_t speed; + std::uint8_t endurance; + std::uint8_t personality; + std::uint8_t luck; + }; + + struct ACBS_TES4 + { + std::uint32_t flags; + std::uint16_t baseSpell; + std::uint16_t fatigue; + std::uint16_t barterGold; + std::int16_t levelOrOffset; + std::uint16_t calcMin; + std::uint16_t calcMax; + std::uint32_t padding1; + std::uint32_t padding2; + }; + + struct ACBS_FO3 + { + std::uint32_t flags; + std::uint16_t fatigue; + std::uint16_t barterGold; + std::int16_t levelOrMult; + std::uint16_t calcMinlevel; + std::uint16_t calcMaxlevel; + std::uint16_t speedMultiplier; + float karma; + std::int16_t dispositionBase; + std::uint16_t templateFlags; + }; + + struct ACBS_TES5 + { + std::uint32_t flags; + std::uint16_t magickaOffset; + std::uint16_t staminaOffset; + std::uint16_t levelOrMult; // TODO: check if int16_t + std::uint16_t calcMinlevel; + std::uint16_t calcMaxlevel; + std::uint16_t speedMultiplier; + std::uint16_t dispositionBase; // TODO: check if int16_t + std::uint16_t templateFlags; + std::uint16_t healthOffset; + std::uint16_t bleedoutOverride; + }; + + union ActorBaseConfig + { + ACBS_TES4 tes4; + ACBS_FO3 fo3; + ACBS_TES5 tes5; + }; + + struct ActorFaction + { + FormId faction; + std::int8_t rank; + std::uint8_t unknown1; + std::uint8_t unknown2; + std::uint8_t unknown3; + }; +#pragma pack(pop) + + struct BodyTemplate // TES5 + { + // 0x00000001 - Head + // 0x00000002 - Hair + // 0x00000004 - Body + // 0x00000008 - Hands + // 0x00000010 - Forearms + // 0x00000020 - Amulet + // 0x00000040 - Ring + // 0x00000080 - Feet + // 0x00000100 - Calves + // 0x00000200 - Shield + // 0x00000400 - Tail + // 0x00000800 - Long Hair + // 0x00001000 - Circlet + // 0x00002000 - Ears + // 0x00004000 - Body AddOn 3 + // 0x00008000 - Body AddOn 4 + // 0x00010000 - Body AddOn 5 + // 0x00020000 - Body AddOn 6 + // 0x00040000 - Body AddOn 7 + // 0x00080000 - Body AddOn 8 + // 0x00100000 - Decapitate Head + // 0x00200000 - Decapitate + // 0x00400000 - Body AddOn 9 + // 0x00800000 - Body AddOn 10 + // 0x01000000 - Body AddOn 11 + // 0x02000000 - Body AddOn 12 + // 0x04000000 - Body AddOn 13 + // 0x08000000 - Body AddOn 14 + // 0x10000000 - Body AddOn 15 + // 0x20000000 - Body AddOn 16 + // 0x40000000 - Body AddOn 17 + // 0x80000000 - FX01 + std::uint32_t bodyPart; + std::uint8_t flags; + std::uint8_t unknown1; // probably padding + std::uint8_t unknown2; // probably padding + std::uint8_t unknown3; // probably padding + std::uint32_t type; // 0 = light, 1 = heavy, 2 = none (cloth?) + }; +} + +#endif // ESM4_ACTOR_H diff --git a/components/esm4/common.cpp b/components/esm4/common.cpp new file mode 100644 index 0000000000..752660fd85 --- /dev/null +++ b/components/esm4/common.cpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2015-2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "common.hpp" + +#include +#include +#include + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + const char *sGroupType[] = + { + "Record Type", "World Child", "Interior Cell", "Interior Sub Cell", "Exterior Cell", + "Exterior Sub Cell", "Cell Child", "Topic Child", "Cell Persistent Child", + "Cell Temporary Child", "Cell Visible Dist Child", "Unknown" + }; + + std::string printLabel(const GroupLabel& label, const std::uint32_t type) + { + std::ostringstream ss; + ss << std::string(sGroupType[std::min(type, (uint32_t)11)]); // avoid out of range + + switch (type) + { + case ESM4::Grp_RecordType: + { + ss << ": " << std::string((char*)label.recordType, 4); + break; + } + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + { + //short x, y; + //y = label & 0xff; + //x = (label >> 16) & 0xff; + ss << ": grid (x, y) " << std::dec << label.grid[1] << ", " << label.grid[0]; + + break; + } + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + { + ss << ": block 0x" << std::hex << label.value; + break; + } + case ESM4::Grp_WorldChild: + case ESM4::Grp_CellChild: + case ESM4::Grp_TopicChild: + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + { + ss << ": FormId 0x" << formIdToString(label.value); + break; + } + default: + break; + } + + return ss.str(); + } + + void gridToString(std::int16_t x, std::int16_t y, std::string& str) + { + char buf[6+6+2+1]; // longest signed 16 bit number is 6 characters (-32768) + int res = snprintf(buf, 6+6+2+1, "#%d %d", x, y); + if (res > 0 && res < 6+6+2+1) + str.assign(buf); + else + throw std::runtime_error("possible buffer overflow while converting grid"); + } +} diff --git a/components/esm4/common.hpp b/components/esm4/common.hpp new file mode 100644 index 0000000000..2f769c28c9 --- /dev/null +++ b/components/esm4/common.hpp @@ -0,0 +1,939 @@ +/* + Copyright (C) 2015-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + MKTAG macro was adapated from ScummVM. + +*/ +#ifndef ESM4_COMMON_H +#define ESM4_COMMON_H + +#include +#include + +#include "formid.hpp" + +// From ScummVM's endianness.h but for little endian +#ifndef MKTAG +#define MKTAG(a0,a1,a2,a3) ((std::uint32_t)((a0) | ((a1) << 8) | ((a2) << 16) | ((a3) << 24))) +#endif + +namespace ESM4 +{ + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format + enum RecordTypes + { + REC_AACT = MKTAG('A','A','C','T'), // Action + REC_ACHR = MKTAG('A','C','H','R'), // Actor Reference + REC_ACTI = MKTAG('A','C','T','I'), // Activator + REC_ADDN = MKTAG('A','D','D','N'), // Addon Node + REC_ALCH = MKTAG('A','L','C','H'), // Potion + REC_AMMO = MKTAG('A','M','M','O'), // Ammo + REC_ANIO = MKTAG('A','N','I','O'), // Animated Object + REC_APPA = MKTAG('A','P','P','A'), // Apparatus (probably unused) + REC_ARMA = MKTAG('A','R','M','A'), // Armature (Model) + REC_ARMO = MKTAG('A','R','M','O'), // Armor + REC_ARTO = MKTAG('A','R','T','O'), // Art Object + REC_ASPC = MKTAG('A','S','P','C'), // Acoustic Space + REC_ASTP = MKTAG('A','S','T','P'), // Association Type + REC_AVIF = MKTAG('A','V','I','F'), // Actor Values/Perk Tree Graphics + REC_BOOK = MKTAG('B','O','O','K'), // Book + REC_BPTD = MKTAG('B','P','T','D'), // Body Part Data + REC_CAMS = MKTAG('C','A','M','S'), // Camera Shot + REC_CELL = MKTAG('C','E','L','L'), // Cell + REC_CLAS = MKTAG('C','L','A','S'), // Class + REC_CLFM = MKTAG('C','L','F','M'), // Color + REC_CLMT = MKTAG('C','L','M','T'), // Climate + REC_CLOT = MKTAG('C','L','O','T'), // Clothing + REC_COBJ = MKTAG('C','O','B','J'), // Constructible Object (recipes) + REC_COLL = MKTAG('C','O','L','L'), // Collision Layer + REC_CONT = MKTAG('C','O','N','T'), // Container + REC_CPTH = MKTAG('C','P','T','H'), // Camera Path + REC_CREA = MKTAG('C','R','E','A'), // Creature + REC_CSTY = MKTAG('C','S','T','Y'), // Combat Style + REC_DEBR = MKTAG('D','E','B','R'), // Debris + REC_DIAL = MKTAG('D','I','A','L'), // Dialog Topic + REC_DLBR = MKTAG('D','L','B','R'), // Dialog Branch + REC_DLVW = MKTAG('D','L','V','W'), // Dialog View + REC_DOBJ = MKTAG('D','O','B','J'), // Default Object Manager + REC_DOOR = MKTAG('D','O','O','R'), // Door + REC_DUAL = MKTAG('D','U','A','L'), // Dual Cast Data (possibly unused) + //REC_ECZN = MKTAG('E','C','Z','N'), // Encounter Zone + REC_EFSH = MKTAG('E','F','S','H'), // Effect Shader + REC_ENCH = MKTAG('E','N','C','H'), // Enchantment + REC_EQUP = MKTAG('E','Q','U','P'), // Equip Slot (flag-type values) + REC_EXPL = MKTAG('E','X','P','L'), // Explosion + REC_EYES = MKTAG('E','Y','E','S'), // Eyes + REC_FACT = MKTAG('F','A','C','T'), // Faction + REC_FLOR = MKTAG('F','L','O','R'), // Flora + REC_FLST = MKTAG('F','L','S','T'), // Form List (non-levelled list) + REC_FSTP = MKTAG('F','S','T','P'), // Footstep + REC_FSTS = MKTAG('F','S','T','S'), // Footstep Set + REC_FURN = MKTAG('F','U','R','N'), // Furniture + REC_GLOB = MKTAG('G','L','O','B'), // Global Variable + REC_GMST = MKTAG('G','M','S','T'), // Game Setting + REC_GRAS = MKTAG('G','R','A','S'), // Grass + REC_GRUP = MKTAG('G','R','U','P'), // Form Group + REC_HAIR = MKTAG('H','A','I','R'), // Hair + //REC_HAZD = MKTAG('H','A','Z','D'), // Hazard + REC_HDPT = MKTAG('H','D','P','T'), // Head Part + REC_IDLE = MKTAG('I','D','L','E'), // Idle Animation + REC_IDLM = MKTAG('I','D','L','M'), // Idle Marker + REC_IMAD = MKTAG('I','M','A','D'), // Image Space Modifier + REC_IMGS = MKTAG('I','M','G','S'), // Image Space + REC_INFO = MKTAG('I','N','F','O'), // Dialog Topic Info + REC_INGR = MKTAG('I','N','G','R'), // Ingredient + REC_IPCT = MKTAG('I','P','C','T'), // Impact Data + REC_IPDS = MKTAG('I','P','D','S'), // Impact Data Set + REC_KEYM = MKTAG('K','E','Y','M'), // Key + REC_KYWD = MKTAG('K','Y','W','D'), // Keyword + REC_LAND = MKTAG('L','A','N','D'), // Land + REC_LCRT = MKTAG('L','C','R','T'), // Location Reference Type + REC_LCTN = MKTAG('L','C','T','N'), // Location + REC_LGTM = MKTAG('L','G','T','M'), // Lighting Template + REC_LIGH = MKTAG('L','I','G','H'), // Light + REC_LSCR = MKTAG('L','S','C','R'), // Load Screen + REC_LTEX = MKTAG('L','T','E','X'), // Land Texture + REC_LVLC = MKTAG('L','V','L','C'), // Leveled Creature + REC_LVLI = MKTAG('L','V','L','I'), // Leveled Item + REC_LVLN = MKTAG('L','V','L','N'), // Leveled Actor + REC_LVSP = MKTAG('L','V','S','P'), // Leveled Spell + REC_MATO = MKTAG('M','A','T','O'), // Material Object + REC_MATT = MKTAG('M','A','T','T'), // Material Type + REC_MESG = MKTAG('M','E','S','G'), // Message + REC_MGEF = MKTAG('M','G','E','F'), // Magic Effect + REC_MISC = MKTAG('M','I','S','C'), // Misc. Object + REC_MOVT = MKTAG('M','O','V','T'), // Movement Type + REC_MSTT = MKTAG('M','S','T','T'), // Movable Static + REC_MUSC = MKTAG('M','U','S','C'), // Music Type + REC_MUST = MKTAG('M','U','S','T'), // Music Track + REC_NAVI = MKTAG('N','A','V','I'), // Navigation (master data) + REC_NAVM = MKTAG('N','A','V','M'), // Nav Mesh + REC_NOTE = MKTAG('N','O','T','E'), // Note + REC_NPC_ = MKTAG('N','P','C','_'), // Actor (NPC, Creature) + REC_OTFT = MKTAG('O','T','F','T'), // Outfit + REC_PACK = MKTAG('P','A','C','K'), // AI Package + REC_PERK = MKTAG('P','E','R','K'), // Perk + REC_PGRE = MKTAG('P','G','R','E'), // Placed grenade + REC_PHZD = MKTAG('P','H','Z','D'), // Placed hazard + REC_PROJ = MKTAG('P','R','O','J'), // Projectile + REC_QUST = MKTAG('Q','U','S','T'), // Quest + REC_RACE = MKTAG('R','A','C','E'), // Race / Creature type + REC_REFR = MKTAG('R','E','F','R'), // Object Reference + REC_REGN = MKTAG('R','E','G','N'), // Region (Audio/Weather) + REC_RELA = MKTAG('R','E','L','A'), // Relationship + REC_REVB = MKTAG('R','E','V','B'), // Reverb Parameters + REC_RFCT = MKTAG('R','F','C','T'), // Visual Effect + REC_SBSP = MKTAG('S','B','S','P'), // Subspace (TES4 only?) + REC_SCEN = MKTAG('S','C','E','N'), // Scene + REC_SCPT = MKTAG('S','C','P','T'), // Script + REC_SCRL = MKTAG('S','C','R','L'), // Scroll + REC_SGST = MKTAG('S','G','S','T'), // Sigil Stone + REC_SHOU = MKTAG('S','H','O','U'), // Shout + REC_SLGM = MKTAG('S','L','G','M'), // Soul Gem + REC_SMBN = MKTAG('S','M','B','N'), // Story Manager Branch Node + REC_SMEN = MKTAG('S','M','E','N'), // Story Manager Event Node + REC_SMQN = MKTAG('S','M','Q','N'), // Story Manager Quest Node + REC_SNCT = MKTAG('S','N','C','T'), // Sound Category + REC_SNDR = MKTAG('S','N','D','R'), // Sound Reference + REC_SOPM = MKTAG('S','O','P','M'), // Sound Output Model + REC_SOUN = MKTAG('S','O','U','N'), // Sound + REC_SPEL = MKTAG('S','P','E','L'), // Spell + REC_SPGD = MKTAG('S','P','G','D'), // Shader Particle Geometry + REC_STAT = MKTAG('S','T','A','T'), // Static + REC_TACT = MKTAG('T','A','C','T'), // Talking Activator + REC_TERM = MKTAG('T','E','R','M'), // Terminal + REC_TES4 = MKTAG('T','E','S','4'), // Plugin info + REC_TREE = MKTAG('T','R','E','E'), // Tree + REC_TXST = MKTAG('T','X','S','T'), // Texture Set + REC_VTYP = MKTAG('V','T','Y','P'), // Voice Type + REC_WATR = MKTAG('W','A','T','R'), // Water Type + REC_WEAP = MKTAG('W','E','A','P'), // Weapon + REC_WOOP = MKTAG('W','O','O','P'), // Word Of Power + REC_WRLD = MKTAG('W','R','L','D'), // World Space + REC_WTHR = MKTAG('W','T','H','R'), // Weather + REC_ACRE = MKTAG('A','C','R','E'), // Placed Creature (TES4 only?) + REC_PGRD = MKTAG('P','G','R','D'), // Pathgrid (TES4 only?) + REC_ROAD = MKTAG('R','O','A','D'), // Road (TES4 only?) + REC_IMOD = MKTAG('I','M','O','D'), // Item Mod + REC_PWAT = MKTAG('P','W','A','T'), // Placeable Water + REC_SCOL = MKTAG('S','C','O','L'), // Static Collection + REC_CCRD = MKTAG('C','C','R','D'), // Caravan Card + REC_CMNY = MKTAG('C','M','N','Y'), // Caravan Money + REC_ALOC = MKTAG('A','L','O','C'), // Audio Location Controller + REC_MSET = MKTAG('M','S','E','T') // Media Set + }; + + enum SubRecordTypes + { + SUB_HEDR = MKTAG('H','E','D','R'), + SUB_CNAM = MKTAG('C','N','A','M'), + SUB_SNAM = MKTAG('S','N','A','M'), // TES4 only? + SUB_MAST = MKTAG('M','A','S','T'), + SUB_DATA = MKTAG('D','A','T','A'), + SUB_ONAM = MKTAG('O','N','A','M'), + SUB_INTV = MKTAG('I','N','T','V'), + SUB_INCC = MKTAG('I','N','C','C'), + SUB_OFST = MKTAG('O','F','S','T'), // TES4 only? + SUB_DELE = MKTAG('D','E','L','E'), // TES4 only? + + SUB_DNAM = MKTAG('D','N','A','M'), + SUB_EDID = MKTAG('E','D','I','D'), + SUB_FULL = MKTAG('F','U','L','L'), + SUB_LTMP = MKTAG('L','T','M','P'), + SUB_MHDT = MKTAG('M','H','D','T'), + SUB_MNAM = MKTAG('M','N','A','M'), + SUB_MODL = MKTAG('M','O','D','L'), + SUB_NAM0 = MKTAG('N','A','M','0'), + SUB_NAM2 = MKTAG('N','A','M','2'), + SUB_NAM3 = MKTAG('N','A','M','3'), + SUB_NAM4 = MKTAG('N','A','M','4'), + SUB_NAM9 = MKTAG('N','A','M','9'), + SUB_NAMA = MKTAG('N','A','M','A'), + SUB_PNAM = MKTAG('P','N','A','M'), + SUB_RNAM = MKTAG('R','N','A','M'), + SUB_TNAM = MKTAG('T','N','A','M'), + SUB_UNAM = MKTAG('U','N','A','M'), + SUB_WCTR = MKTAG('W','C','T','R'), + SUB_WNAM = MKTAG('W','N','A','M'), + SUB_XEZN = MKTAG('X','E','Z','N'), + SUB_XLCN = MKTAG('X','L','C','N'), + SUB_XXXX = MKTAG('X','X','X','X'), + SUB_ZNAM = MKTAG('Z','N','A','M'), + SUB_MODT = MKTAG('M','O','D','T'), + SUB_ICON = MKTAG('I','C','O','N'), // TES4 only? + + SUB_NVER = MKTAG('N','V','E','R'), + SUB_NVMI = MKTAG('N','V','M','I'), + SUB_NVPP = MKTAG('N','V','P','P'), + SUB_NVSI = MKTAG('N','V','S','I'), + + SUB_NVNM = MKTAG('N','V','N','M'), + SUB_NNAM = MKTAG('N','N','A','M'), + + SUB_XCLC = MKTAG('X','C','L','C'), + SUB_XCLL = MKTAG('X','C','L','L'), + SUB_TVDT = MKTAG('T','V','D','T'), + SUB_XCGD = MKTAG('X','C','G','D'), + SUB_LNAM = MKTAG('L','N','A','M'), + SUB_XCLW = MKTAG('X','C','L','W'), + SUB_XNAM = MKTAG('X','N','A','M'), + SUB_XCLR = MKTAG('X','C','L','R'), + SUB_XWCS = MKTAG('X','W','C','S'), + SUB_XWCN = MKTAG('X','W','C','N'), + SUB_XWCU = MKTAG('X','W','C','U'), + SUB_XCWT = MKTAG('X','C','W','T'), + SUB_XOWN = MKTAG('X','O','W','N'), + SUB_XILL = MKTAG('X','I','L','L'), + SUB_XWEM = MKTAG('X','W','E','M'), + SUB_XCCM = MKTAG('X','C','C','M'), + SUB_XCAS = MKTAG('X','C','A','S'), + SUB_XCMO = MKTAG('X','C','M','O'), + SUB_XCIM = MKTAG('X','C','I','M'), + SUB_XCMT = MKTAG('X','C','M','T'), // TES4 only? + SUB_XRNK = MKTAG('X','R','N','K'), // TES4 only? + SUB_XGLB = MKTAG('X','G','L','B'), // TES4 only? + + SUB_VNML = MKTAG('V','N','M','L'), + SUB_VHGT = MKTAG('V','H','G','T'), + SUB_VCLR = MKTAG('V','C','L','R'), + SUA_BTXT = MKTAG('B','T','X','T'), + SUB_ATXT = MKTAG('A','T','X','T'), + SUB_VTXT = MKTAG('V','T','X','T'), + SUB_VTEX = MKTAG('V','T','E','X'), + + SUB_HNAM = MKTAG('H','N','A','M'), + SUB_GNAM = MKTAG('G','N','A','M'), + + SUB_RCLR = MKTAG('R','C','L','R'), + SUB_RPLI = MKTAG('R','P','L','I'), + SUB_RPLD = MKTAG('R','P','L','D'), + SUB_RDAT = MKTAG('R','D','A','T'), + SUB_RDMD = MKTAG('R','D','M','D'), // TES4 only? + SUB_RDSD = MKTAG('R','D','S','D'), // TES4 only? + SUB_RDGS = MKTAG('R','D','G','S'), // TES4 only? + SUB_RDMO = MKTAG('R','D','M','O'), + SUB_RDSA = MKTAG('R','D','S','A'), + SUB_RDWT = MKTAG('R','D','W','T'), + SUB_RDOT = MKTAG('R','D','O','T'), + SUB_RDMP = MKTAG('R','D','M','P'), + + SUB_MODB = MKTAG('M','O','D','B'), + SUB_OBND = MKTAG('O','B','N','D'), + SUB_MODS = MKTAG('M','O','D','S'), + + SUB_NAME = MKTAG('N','A','M','E'), + SUB_XMRK = MKTAG('X','M','R','K'), + SUB_FNAM = MKTAG('F','N','A','M'), + SUB_XSCL = MKTAG('X','S','C','L'), + SUB_XTEL = MKTAG('X','T','E','L'), + SUB_XTRG = MKTAG('X','T','R','G'), + SUB_XSED = MKTAG('X','S','E','D'), + SUB_XLOD = MKTAG('X','L','O','D'), + SUB_XPCI = MKTAG('X','P','C','I'), + SUB_XLOC = MKTAG('X','L','O','C'), + SUB_XESP = MKTAG('X','E','S','P'), + SUB_XLCM = MKTAG('X','L','C','M'), + SUB_XRTM = MKTAG('X','R','T','M'), + SUB_XACT = MKTAG('X','A','C','T'), + SUB_XCNT = MKTAG('X','C','N','T'), + SUB_VMAD = MKTAG('V','M','A','D'), + SUB_XPRM = MKTAG('X','P','R','M'), + SUB_XMBO = MKTAG('X','M','B','O'), + SUB_XPOD = MKTAG('X','P','O','D'), + SUB_XRMR = MKTAG('X','R','M','R'), + SUB_INAM = MKTAG('I','N','A','M'), + SUB_SCHR = MKTAG('S','C','H','R'), + SUB_XLRM = MKTAG('X','L','R','M'), + SUB_XRGD = MKTAG('X','R','G','D'), + SUB_XRDS = MKTAG('X','R','D','S'), + SUB_XEMI = MKTAG('X','E','M','I'), + SUB_XLIG = MKTAG('X','L','I','G'), + SUB_XALP = MKTAG('X','A','L','P'), + SUB_XNDP = MKTAG('X','N','D','P'), + SUB_XAPD = MKTAG('X','A','P','D'), + SUB_XAPR = MKTAG('X','A','P','R'), + SUB_XLIB = MKTAG('X','L','I','B'), + SUB_XLKR = MKTAG('X','L','K','R'), + SUB_XLRT = MKTAG('X','L','R','T'), + SUB_XCVL = MKTAG('X','C','V','L'), + SUB_XCVR = MKTAG('X','C','V','R'), + SUB_XCZA = MKTAG('X','C','Z','A'), + SUB_XCZC = MKTAG('X','C','Z','C'), + SUB_XFVC = MKTAG('X','F','V','C'), + SUB_XHTW = MKTAG('X','H','T','W'), + SUB_XIS2 = MKTAG('X','I','S','2'), + SUB_XMBR = MKTAG('X','M','B','R'), + SUB_XCCP = MKTAG('X','C','C','P'), + SUB_XPWR = MKTAG('X','P','W','R'), + SUB_XTRI = MKTAG('X','T','R','I'), + SUB_XATR = MKTAG('X','A','T','R'), + SUB_XPRD = MKTAG('X','P','R','D'), + SUB_XPPA = MKTAG('X','P','P','A'), + SUB_PDTO = MKTAG('P','D','T','O'), + SUB_XLRL = MKTAG('X','L','R','L'), + + SUB_QNAM = MKTAG('Q','N','A','M'), + SUB_COCT = MKTAG('C','O','C','T'), + SUB_COED = MKTAG('C','O','E','D'), + SUB_CNTO = MKTAG('C','N','T','O'), + SUB_SCRI = MKTAG('S','C','R','I'), + + SUB_BNAM = MKTAG('B','N','A','M'), + + SUB_BMDT = MKTAG('B','M','D','T'), + SUB_MOD2 = MKTAG('M','O','D','2'), + SUB_MOD3 = MKTAG('M','O','D','3'), + SUB_MOD4 = MKTAG('M','O','D','4'), + SUB_MO2B = MKTAG('M','O','2','B'), + SUB_MO3B = MKTAG('M','O','3','B'), + SUB_MO4B = MKTAG('M','O','4','B'), + SUB_MO2T = MKTAG('M','O','2','T'), + SUB_MO3T = MKTAG('M','O','3','T'), + SUB_MO4T = MKTAG('M','O','4','T'), + SUB_ANAM = MKTAG('A','N','A','M'), + SUB_ENAM = MKTAG('E','N','A','M'), + SUB_ICO2 = MKTAG('I','C','O','2'), + + SUB_ACBS = MKTAG('A','C','B','S'), + SUB_SPLO = MKTAG('S','P','L','O'), + SUB_AIDT = MKTAG('A','I','D','T'), + SUB_PKID = MKTAG('P','K','I','D'), + SUB_HCLR = MKTAG('H','C','L','R'), + SUB_FGGS = MKTAG('F','G','G','S'), + SUB_FGGA = MKTAG('F','G','G','A'), + SUB_FGTS = MKTAG('F','G','T','S'), + SUB_KFFZ = MKTAG('K','F','F','Z'), + + SUB_PFIG = MKTAG('P','F','I','G'), + SUB_PFPC = MKTAG('P','F','P','C'), + + SUB_XHRS = MKTAG('X','H','R','S'), + SUB_XMRC = MKTAG('X','M','R','C'), + + SUB_SNDD = MKTAG('S','N','D','D'), + SUB_SNDX = MKTAG('S','N','D','X'), + + SUB_DESC = MKTAG('D','E','S','C'), + + SUB_ENIT = MKTAG('E','N','I','T'), + SUB_EFID = MKTAG('E','F','I','D'), + SUB_EFIT = MKTAG('E','F','I','T'), + SUB_SCIT = MKTAG('S','C','I','T'), + + SUB_SOUL = MKTAG('S','O','U','L'), + SUB_SLCP = MKTAG('S','L','C','P'), + + SUB_CSCR = MKTAG('C','S','C','R'), + SUB_CSDI = MKTAG('C','S','D','I'), + SUB_CSDC = MKTAG('C','S','D','C'), + SUB_NIFZ = MKTAG('N','I','F','Z'), + SUB_CSDT = MKTAG('C','S','D','T'), + SUB_NAM1 = MKTAG('N','A','M','1'), + SUB_NIFT = MKTAG('N','I','F','T'), + + SUB_LVLD = MKTAG('L','V','L','D'), + SUB_LVLF = MKTAG('L','V','L','F'), + SUB_LVLO = MKTAG('L','V','L','O'), + + SUB_BODT = MKTAG('B','O','D','T'), + SUB_YNAM = MKTAG('Y','N','A','M'), + SUB_DEST = MKTAG('D','E','S','T'), + SUB_DMDL = MKTAG('D','M','D','L'), + SUB_DMDS = MKTAG('D','M','D','S'), + SUB_DMDT = MKTAG('D','M','D','T'), + SUB_DSTD = MKTAG('D','S','T','D'), + SUB_DSTF = MKTAG('D','S','T','F'), + SUB_KNAM = MKTAG('K','N','A','M'), + SUB_KSIZ = MKTAG('K','S','I','Z'), + SUB_KWDA = MKTAG('K','W','D','A'), + SUB_VNAM = MKTAG('V','N','A','M'), + SUB_SDSC = MKTAG('S','D','S','C'), + SUB_MO2S = MKTAG('M','O','2','S'), + SUB_MO4S = MKTAG('M','O','4','S'), + SUB_BOD2 = MKTAG('B','O','D','2'), + SUB_BAMT = MKTAG('B','A','M','T'), + SUB_BIDS = MKTAG('B','I','D','S'), + SUB_ETYP = MKTAG('E','T','Y','P'), + SUB_BMCT = MKTAG('B','M','C','T'), + SUB_MICO = MKTAG('M','I','C','O'), + SUB_MIC2 = MKTAG('M','I','C','2'), + SUB_EAMT = MKTAG('E','A','M','T'), + SUB_EITM = MKTAG('E','I','T','M'), + + SUB_SCTX = MKTAG('S','C','T','X'), + SUB_XLTW = MKTAG('X','L','T','W'), + SUB_XMBP = MKTAG('X','M','B','P'), + SUB_XOCP = MKTAG('X','O','C','P'), + SUB_XRGB = MKTAG('X','R','G','B'), + SUB_XSPC = MKTAG('X','S','P','C'), + SUB_XTNM = MKTAG('X','T','N','M'), + SUB_ATKR = MKTAG('A','T','K','R'), + SUB_CRIF = MKTAG('C','R','I','F'), + SUB_DOFT = MKTAG('D','O','F','T'), + SUB_DPLT = MKTAG('D','P','L','T'), + SUB_ECOR = MKTAG('E','C','O','R'), + SUB_ATKD = MKTAG('A','T','K','D'), + SUB_ATKE = MKTAG('A','T','K','E'), + SUB_FTST = MKTAG('F','T','S','T'), + SUB_HCLF = MKTAG('H','C','L','F'), + SUB_NAM5 = MKTAG('N','A','M','5'), + SUB_NAM6 = MKTAG('N','A','M','6'), + SUB_NAM7 = MKTAG('N','A','M','7'), + SUB_NAM8 = MKTAG('N','A','M','8'), + SUB_PRKR = MKTAG('P','R','K','R'), + SUB_PRKZ = MKTAG('P','R','K','Z'), + SUB_SOFT = MKTAG('S','O','F','T'), + SUB_SPCT = MKTAG('S','P','C','T'), + SUB_TINC = MKTAG('T','I','N','C'), + SUB_TIAS = MKTAG('T','I','A','S'), + SUB_TINI = MKTAG('T','I','N','I'), + SUB_TINV = MKTAG('T','I','N','V'), + SUB_TPLT = MKTAG('T','P','L','T'), + SUB_VTCK = MKTAG('V','T','C','K'), + SUB_SHRT = MKTAG('S','H','R','T'), + SUB_SPOR = MKTAG('S','P','O','R'), + SUB_XHOR = MKTAG('X','H','O','R'), + SUB_CTDA = MKTAG('C','T','D','A'), + SUB_CRDT = MKTAG('C','R','D','T'), + SUB_FNMK = MKTAG('F','N','M','K'), + SUB_FNPR = MKTAG('F','N','P','R'), + SUB_WBDT = MKTAG('W','B','D','T'), + SUB_QUAL = MKTAG('Q','U','A','L'), + SUB_INDX = MKTAG('I','N','D','X'), + SUB_ATTR = MKTAG('A','T','T','R'), + SUB_MTNM = MKTAG('M','T','N','M'), + SUB_UNES = MKTAG('U','N','E','S'), + SUB_TIND = MKTAG('T','I','N','D'), + SUB_TINL = MKTAG('T','I','N','L'), + SUB_TINP = MKTAG('T','I','N','P'), + SUB_TINT = MKTAG('T','I','N','T'), + SUB_TIRS = MKTAG('T','I','R','S'), + SUB_PHWT = MKTAG('P','H','W','T'), + SUB_AHCF = MKTAG('A','H','C','F'), + SUB_AHCM = MKTAG('A','H','C','M'), + SUB_HEAD = MKTAG('H','E','A','D'), + SUB_MPAI = MKTAG('M','P','A','I'), + SUB_MPAV = MKTAG('M','P','A','V'), + SUB_DFTF = MKTAG('D','F','T','F'), + SUB_DFTM = MKTAG('D','F','T','M'), + SUB_FLMV = MKTAG('F','L','M','V'), + SUB_FTSF = MKTAG('F','T','S','F'), + SUB_FTSM = MKTAG('F','T','S','M'), + SUB_MTYP = MKTAG('M','T','Y','P'), + SUB_PHTN = MKTAG('P','H','T','N'), + SUB_RNMV = MKTAG('R','N','M','V'), + SUB_RPRF = MKTAG('R','P','R','F'), + SUB_RPRM = MKTAG('R','P','R','M'), + SUB_SNMV = MKTAG('S','N','M','V'), + SUB_SPED = MKTAG('S','P','E','D'), + SUB_SWMV = MKTAG('S','W','M','V'), + SUB_WKMV = MKTAG('W','K','M','V'), + SUB_LLCT = MKTAG('L','L','C','T'), + SUB_IDLF = MKTAG('I','D','L','F'), + SUB_IDLA = MKTAG('I','D','L','A'), + SUB_IDLC = MKTAG('I','D','L','C'), + SUB_IDLT = MKTAG('I','D','L','T'), + SUB_DODT = MKTAG('D','O','D','T'), + SUB_TX00 = MKTAG('T','X','0','0'), + SUB_TX01 = MKTAG('T','X','0','1'), + SUB_TX02 = MKTAG('T','X','0','2'), + SUB_TX03 = MKTAG('T','X','0','3'), + SUB_TX04 = MKTAG('T','X','0','4'), + SUB_TX05 = MKTAG('T','X','0','5'), + SUB_TX06 = MKTAG('T','X','0','6'), + SUB_TX07 = MKTAG('T','X','0','7'), + SUB_BPND = MKTAG('B','P','N','D'), + SUB_BPTN = MKTAG('B','P','T','N'), + SUB_BPNN = MKTAG('B','P','N','N'), + SUB_BPNT = MKTAG('B','P','N','T'), + SUB_BPNI = MKTAG('B','P','N','I'), + SUB_RAGA = MKTAG('R','A','G','A'), + + SUB_QSTI = MKTAG('Q','S','T','I'), + SUB_QSTR = MKTAG('Q','S','T','R'), + SUB_QSDT = MKTAG('Q','S','D','T'), + SUB_SCDA = MKTAG('S','C','D','A'), + SUB_SCRO = MKTAG('S','C','R','O'), + SUB_QSTA = MKTAG('Q','S','T','A'), + SUB_CTDT = MKTAG('C','T','D','T'), + SUB_SCHD = MKTAG('S','C','H','D'), + SUB_TCLF = MKTAG('T','C','L','F'), + SUB_TCLT = MKTAG('T','C','L','T'), + SUB_TRDT = MKTAG('T','R','D','T'), + SUB_TPIC = MKTAG('T','P','I','C'), + + SUB_PKDT = MKTAG('P','K','D','T'), + SUB_PSDT = MKTAG('P','S','D','T'), + SUB_PLDT = MKTAG('P','L','D','T'), + SUB_PTDT = MKTAG('P','T','D','T'), + SUB_PGRP = MKTAG('P','G','R','P'), + SUB_PGRR = MKTAG('P','G','R','R'), + SUB_PGRI = MKTAG('P','G','R','I'), + SUB_PGRL = MKTAG('P','G','R','L'), + SUB_PGAG = MKTAG('P','G','A','G'), + SUB_FLTV = MKTAG('F','L','T','V'), + + SUB_XHLT = MKTAG('X','H','L','T'), // Unofficial Oblivion Patch + SUB_XCHG = MKTAG('X','C','H','G'), // thievery.exp + + SUB_ITXT = MKTAG('I','T','X','T'), + SUB_MO5T = MKTAG('M','O','5','T'), + SUB_MOD5 = MKTAG('M','O','D','5'), + SUB_MDOB = MKTAG('M','D','O','B'), + SUB_SPIT = MKTAG('S','P','I','T'), + SUB_PTDA = MKTAG('P','T','D','A'), // TES5 + SUB_PFOR = MKTAG('P','F','O','R'), // TES5 + SUB_PFO2 = MKTAG('P','F','O','2'), // TES5 + SUB_PRCB = MKTAG('P','R','C','B'), // TES5 + SUB_PKCU = MKTAG('P','K','C','U'), // TES5 + SUB_PKC2 = MKTAG('P','K','C','2'), // TES5 + SUB_CITC = MKTAG('C','I','T','C'), // TES5 + SUB_CIS1 = MKTAG('C','I','S','1'), // TES5 + SUB_CIS2 = MKTAG('C','I','S','2'), // TES5 + SUB_TIFC = MKTAG('T','I','F','C'), // TES5 + SUB_ALCA = MKTAG('A','L','C','A'), // TES5 + SUB_ALCL = MKTAG('A','L','C','L'), // TES5 + SUB_ALCO = MKTAG('A','L','C','O'), // TES5 + SUB_ALDN = MKTAG('A','L','D','N'), // TES5 + SUB_ALEA = MKTAG('A','L','E','A'), // TES5 + SUB_ALED = MKTAG('A','L','E','D'), // TES5 + SUB_ALEQ = MKTAG('A','L','E','Q'), // TES5 + SUB_ALFA = MKTAG('A','L','F','A'), // TES5 + SUB_ALFC = MKTAG('A','L','F','C'), // TES5 + SUB_ALFD = MKTAG('A','L','F','D'), // TES5 + SUB_ALFE = MKTAG('A','L','F','E'), // TES5 + SUB_ALFI = MKTAG('A','L','F','I'), // TES5 + SUB_ALFL = MKTAG('A','L','F','L'), // TES5 + SUB_ALFR = MKTAG('A','L','F','R'), // TES5 + SUB_ALID = MKTAG('A','L','I','D'), // TES5 + SUB_ALLS = MKTAG('A','L','L','S'), // TES5 + SUB_ALNA = MKTAG('A','L','N','A'), // TES5 + SUB_ALNT = MKTAG('A','L','N','T'), // TES5 + SUB_ALPC = MKTAG('A','L','P','C'), // TES5 + SUB_ALRT = MKTAG('A','L','R','T'), // TES5 + SUB_ALSP = MKTAG('A','L','S','P'), // TES5 + SUB_ALST = MKTAG('A','L','S','T'), // TES5 + SUB_ALUA = MKTAG('A','L','U','A'), // TES5 + SUB_FLTR = MKTAG('F','L','T','R'), // TES5 + SUB_QTGL = MKTAG('Q','T','G','L'), // TES5 + SUB_TWAT = MKTAG('T','W','A','T'), // TES5 + SUB_XIBS = MKTAG('X','I','B','S'), // FO3 + SUB_REPL = MKTAG('R','E','P','L'), // FO3 + SUB_BIPL = MKTAG('B','I','P','L'), // FO3 + SUB_MODD = MKTAG('M','O','D','D'), // FO3 + SUB_MOSD = MKTAG('M','O','S','D'), // FO3 + SUB_MO3S = MKTAG('M','O','3','S'), // FO3 + SUB_XCET = MKTAG('X','C','E','T'), // FO3 + SUB_LVLG = MKTAG('L','V','L','G'), // FO3 + SUB_NVCI = MKTAG('N','V','C','I'), // FO3 + SUB_NVVX = MKTAG('N','V','V','X'), // FO3 + SUB_NVTR = MKTAG('N','V','T','R'), // FO3 + SUB_NVCA = MKTAG('N','V','C','A'), // FO3 + SUB_NVDP = MKTAG('N','V','D','P'), // FO3 + SUB_NVGD = MKTAG('N','V','G','D'), // FO3 + SUB_NVEX = MKTAG('N','V','E','X'), // FO3 + SUB_XHLP = MKTAG('X','H','L','P'), // FO3 + SUB_XRDO = MKTAG('X','R','D','O'), // FO3 + SUB_XAMT = MKTAG('X','A','M','T'), // FO3 + SUB_XAMC = MKTAG('X','A','M','C'), // FO3 + SUB_XRAD = MKTAG('X','R','A','D'), // FO3 + SUB_XORD = MKTAG('X','O','R','D'), // FO3 + SUB_XCLP = MKTAG('X','C','L','P'), // FO3 + SUB_NEXT = MKTAG('N','E','X','T'), // FO3 + SUB_QOBJ = MKTAG('Q','O','B','J'), // FO3 + SUB_POBA = MKTAG('P','O','B','A'), // FO3 + SUB_POCA = MKTAG('P','O','C','A'), // FO3 + SUB_POEA = MKTAG('P','O','E','A'), // FO3 + SUB_PKDD = MKTAG('P','K','D','D'), // FO3 + SUB_PKD2 = MKTAG('P','K','D','2'), // FO3 + SUB_PKPT = MKTAG('P','K','P','T'), // FO3 + SUB_PKED = MKTAG('P','K','E','D'), // FO3 + SUB_PKE2 = MKTAG('P','K','E','2'), // FO3 + SUB_PKAM = MKTAG('P','K','A','M'), // FO3 + SUB_PUID = MKTAG('P','U','I','D'), // FO3 + SUB_PKW3 = MKTAG('P','K','W','3'), // FO3 + SUB_PTD2 = MKTAG('P','T','D','2'), // FO3 + SUB_PLD2 = MKTAG('P','L','D','2'), // FO3 + SUB_PKFD = MKTAG('P','K','F','D'), // FO3 + SUB_IDLB = MKTAG('I','D','L','B'), // FO3 + SUB_XDCR = MKTAG('X','D','C','R'), // FO3 + SUB_DALC = MKTAG('D','A','L','C'), // FO3 + SUB_IMPS = MKTAG('I','M','P','S'), // FO3 Anchorage + SUB_IMPF = MKTAG('I','M','P','F'), // FO3 Anchorage + + SUB_XATO = MKTAG('X','A','T','O'), // FONV + SUB_INFC = MKTAG('I','N','F','C'), // FONV + SUB_INFX = MKTAG('I','N','F','X'), // FONV + SUB_TDUM = MKTAG('T','D','U','M'), // FONV + SUB_TCFU = MKTAG('T','C','F','U'), // FONV + SUB_DAT2 = MKTAG('D','A','T','2'), // FONV + SUB_RCIL = MKTAG('R','C','I','L'), // FONV + SUB_MMRK = MKTAG('M','M','R','K'), // FONV + SUB_SCRV = MKTAG('S','C','R','V'), // FONV + SUB_SCVR = MKTAG('S','C','V','R'), // FONV + SUB_SLSD = MKTAG('S','L','S','D'), // FONV + SUB_XSRF = MKTAG('X','S','R','F'), // FONV + SUB_XSRD = MKTAG('X','S','R','D'), // FONV + SUB_WMI1 = MKTAG('W','M','I','1'), // FONV + SUB_RDID = MKTAG('R','D','I','D'), // FONV + SUB_RDSB = MKTAG('R','D','S','B'), // FONV + SUB_RDSI = MKTAG('R','D','S','I'), // FONV + SUB_BRUS = MKTAG('B','R','U','S'), // FONV + SUB_VATS = MKTAG('V','A','T','S'), // FONV + SUB_VANM = MKTAG('V','A','N','M'), // FONV + SUB_MWD1 = MKTAG('M','W','D','1'), // FONV + SUB_MWD2 = MKTAG('M','W','D','2'), // FONV + SUB_MWD3 = MKTAG('M','W','D','3'), // FONV + SUB_MWD4 = MKTAG('M','W','D','4'), // FONV + SUB_MWD5 = MKTAG('M','W','D','5'), // FONV + SUB_MWD6 = MKTAG('M','W','D','6'), // FONV + SUB_MWD7 = MKTAG('M','W','D','7'), // FONV + SUB_WMI2 = MKTAG('W','M','I','2'), // FONV + SUB_WMI3 = MKTAG('W','M','I','3'), // FONV + SUB_WMS1 = MKTAG('W','M','S','1'), // FONV + SUB_WMS2 = MKTAG('W','M','S','2'), // FONV + SUB_WNM1 = MKTAG('W','N','M','1'), // FONV + SUB_WNM2 = MKTAG('W','N','M','2'), // FONV + SUB_WNM3 = MKTAG('W','N','M','3'), // FONV + SUB_WNM4 = MKTAG('W','N','M','4'), // FONV + SUB_WNM5 = MKTAG('W','N','M','5'), // FONV + SUB_WNM6 = MKTAG('W','N','M','6'), // FONV + SUB_WNM7 = MKTAG('W','N','M','7'), // FONV + SUB_JNAM = MKTAG('J','N','A','M'), // FONV + SUB_EFSD = MKTAG('E','F','S','D'), // FONV DeadMoney + }; + + enum MagicEffectID + { + // Alteration + EFI_BRDN = MKTAG('B','R','D','N'), + EFI_FTHR = MKTAG('F','T','H','R'), + EFI_FISH = MKTAG('F','I','S','H'), + EFI_FRSH = MKTAG('F','R','S','H'), + EFI_OPEN = MKTAG('O','P','N','N'), + EFI_SHLD = MKTAG('S','H','L','D'), + EFI_LISH = MKTAG('L','I','S','H'), + EFI_WABR = MKTAG('W','A','B','R'), + EFI_WAWA = MKTAG('W','A','W','A'), + + // Conjuration + EFI_BABO = MKTAG('B','A','B','O'), // Bound Boots + EFI_BACU = MKTAG('B','A','C','U'), // Bound Cuirass + EFI_BAGA = MKTAG('B','A','G','A'), // Bound Gauntlets + EFI_BAGR = MKTAG('B','A','G','R'), // Bound Greaves + EFI_BAHE = MKTAG('B','A','H','E'), // Bound Helmet + EFI_BASH = MKTAG('B','A','S','H'), // Bound Shield + EFI_BWAX = MKTAG('B','W','A','X'), // Bound Axe + EFI_BWBO = MKTAG('B','W','B','O'), // Bound Bow + EFI_BWDA = MKTAG('B','W','D','A'), // Bound Dagger + EFI_BWMA = MKTAG('B','W','M','A'), // Bound Mace + EFI_BWSW = MKTAG('B','W','S','W'), // Bound Sword + EFI_Z001 = MKTAG('Z','0','0','1'), // Summon Rufio's Ghost + EFI_Z002 = MKTAG('Z','0','0','2'), // Summon Ancestor Guardian + EFI_Z003 = MKTAG('Z','0','0','3'), // Summon Spiderling + EFI_Z005 = MKTAG('Z','0','0','5'), // Summon Bear + EFI_ZCLA = MKTAG('Z','C','L','A'), // Summon Clannfear + EFI_ZDAE = MKTAG('Z','D','A','E'), // Summon Daedroth + EFI_ZDRE = MKTAG('Z','D','R','E'), // Summon Dremora + EFI_ZDRL = MKTAG('Z','D','R','L'), // Summon Dremora Lord + EFI_ZFIA = MKTAG('Z','F','I','A'), // Summon Flame Atronach + EFI_ZFRA = MKTAG('Z','F','R','A'), // Summon Frost Atronach + EFI_ZGHO = MKTAG('Z','G','H','O'), // Summon Ghost + EFI_ZHDZ = MKTAG('Z','H','D','Z'), // Summon Headless Zombie + EFI_ZLIC = MKTAG('Z','L','I','C'), // Summon Lich + EFI_ZSCA = MKTAG('Z','S','C','A'), // Summon Scamp + EFI_ZSKE = MKTAG('Z','S','K','E'), // Summon Skeleton + EFI_ZSKA = MKTAG('Z','S','K','A'), // Summon Skeleton Guardian + EFI_ZSKH = MKTAG('Z','S','K','H'), // Summon Skeleton Hero + EFI_ZSKC = MKTAG('Z','S','K','C'), // Summon Skeleton Champion + EFI_ZSPD = MKTAG('Z','S','P','D'), // Summon Spider Daedra + EFI_ZSTA = MKTAG('Z','S','T','A'), // Summon Storm Atronach + EFI_ZWRA = MKTAG('Z','W','R','A'), // Summon Faded Wraith + EFI_ZWRL = MKTAG('Z','W','R','L'), // Summon Gloom Wraith + EFI_ZXIV = MKTAG('Z','X','I','V'), // Summon Xivilai + EFI_ZZOM = MKTAG('Z','Z','O','M'), // Summon Zombie + EFI_TURN = MKTAG('T','U','R','N'), // Turn Undead + + // Destruction + EFI_DGAT = MKTAG('D','G','A','T'), // Damage Attribute + EFI_DGFA = MKTAG('D','G','F','A'), // Damage Fatigue + EFI_DGHE = MKTAG('D','G','H','E'), // Damage Health + EFI_DGSP = MKTAG('D','G','S','P'), // Damage Magicka + EFI_DIAR = MKTAG('D','I','A','R'), // Disintegrate Armor + EFI_DIWE = MKTAG('D','I','W','E'), // Disintegrate Weapon + EFI_DRAT = MKTAG('D','R','A','T'), // Drain Attribute + EFI_DRFA = MKTAG('D','R','F','A'), // Drain Fatigue + EFI_DRHE = MKTAG('D','R','H','E'), // Drain Health + EFI_DRSP = MKTAG('D','R','S','P'), // Drain Magicka + EFI_DRSK = MKTAG('D','R','S','K'), // Drain Skill + EFI_FIDG = MKTAG('F','I','D','G'), // Fire Damage + EFI_FRDG = MKTAG('F','R','D','G'), // Frost Damage + EFI_SHDG = MKTAG('S','H','D','G'), // Shock Damage + EFI_WKDI = MKTAG('W','K','D','I'), // Weakness to Disease + EFI_WKFI = MKTAG('W','K','F','I'), // Weakness to Fire + EFI_WKFR = MKTAG('W','K','F','R'), // Weakness to Frost + EFI_WKMA = MKTAG('W','K','M','A'), // Weakness to Magic + EFI_WKNW = MKTAG('W','K','N','W'), // Weakness to Normal Weapons + EFI_WKPO = MKTAG('W','K','P','O'), // Weakness to Poison + EFI_WKSH = MKTAG('W','K','S','H'), // Weakness to Shock + + // Illusion + EFI_CALM = MKTAG('C','A','L','M'), // Calm + EFI_CHML = MKTAG('C','H','M','L'), // Chameleon + EFI_CHRM = MKTAG('C','H','R','M'), // Charm + EFI_COCR = MKTAG('C','O','C','R'), // Command Creature + EFI_COHU = MKTAG('C','O','H','U'), // Command Humanoid + EFI_DEMO = MKTAG('D','E','M','O'), // Demoralize + EFI_FRNZ = MKTAG('F','R','N','Z'), // Frenzy + EFI_INVI = MKTAG('I','N','V','I'), // Invisibility + EFI_LGHT = MKTAG('L','G','H','T'), // Light + EFI_NEYE = MKTAG('N','E','Y','E'), // Night-Eye + EFI_PARA = MKTAG('P','A','R','A'), // Paralyze + EFI_RALY = MKTAG('R','A','L','Y'), // Rally + EFI_SLNC = MKTAG('S','L','N','C'), // Silence + + // Mysticism + EFI_DTCT = MKTAG('D','T','C','T'), // Detect Life + EFI_DSPL = MKTAG('D','S','P','L'), // Dispel + EFI_REDG = MKTAG('R','E','D','G'), // Reflect Damage + EFI_RFLC = MKTAG('R','F','L','C'), // Reflect Spell + EFI_STRP = MKTAG('S','T','R','P'), // Soul Trap + EFI_SABS = MKTAG('S','A','B','S'), // Spell Absorption + EFI_TELE = MKTAG('T','E','L','E'), // Telekinesis + + // Restoration + EFI_ABAT = MKTAG('A','B','A','T'), // Absorb Attribute + EFI_ABFA = MKTAG('A','B','F','A'), // Absorb Fatigue + EFI_ABHe = MKTAG('A','B','H','e'), // Absorb Health + EFI_ABSP = MKTAG('A','B','S','P'), // Absorb Magicka + EFI_ABSK = MKTAG('A','B','S','K'), // Absorb Skill + EFI_1400 = MKTAG('1','4','0','0'), // Cure Disease + EFI_CUPA = MKTAG('C','U','P','A'), // Cure Paralysis + EFI_CUPO = MKTAG('C','U','P','O'), // Cure Poison + EFI_FOAT = MKTAG('F','O','A','T'), // Fortify Attribute + EFI_FOFA = MKTAG('F','O','F','A'), // Fortify Fatigue + EFI_FOHE = MKTAG('F','O','H','E'), // Fortify Health + EFI_FOSP = MKTAG('F','O','S','P'), // Fortify Magicka + EFI_FOSK = MKTAG('F','O','S','K'), // Fortify Skill + EFI_RSDI = MKTAG('R','S','D','I'), // Resist Disease + EFI_RSFI = MKTAG('R','S','F','I'), // Resist Fire + EFI_RSFR = MKTAG('R','S','F','R'), // Resist Frost + EFI_RSMA = MKTAG('R','S','M','A'), // Resist Magic + EFI_RSNW = MKTAG('R','S','N','W'), // Resist Normal Weapons + EFI_RSPA = MKTAG('R','S','P','A'), // Resist Paralysis + EFI_RSPO = MKTAG('R','S','P','O'), // Resist Poison + EFI_RSSH = MKTAG('R','S','S','H'), // Resist Shock + EFI_REAT = MKTAG('R','E','A','T'), // Restore Attribute + EFI_REFA = MKTAG('R','E','F','A'), // Restore Fatigue + EFI_REHE = MKTAG('R','E','H','E'), // Restore Health + EFI_RESP = MKTAG('R','E','S','P'), // Restore Magicka + + // Effects + EFI_LOCK = MKTAG('L','O','C','K'), // Lock Lock + EFI_SEFF = MKTAG('S','E','F','F'), // Script Effect + EFI_Z020 = MKTAG('Z','0','2','0'), // Summon 20 Extra + EFI_MYHL = MKTAG('M','Y','H','L'), // Summon Mythic Dawn Helmet + EFI_MYTH = MKTAG('M','Y','T','H'), // Summon Mythic Dawn Armor + EFI_REAN = MKTAG('R','E','A','N'), // Reanimate + EFI_DISE = MKTAG('D','I','S','E'), // Disease Info + EFI_POSN = MKTAG('P','O','S','N'), // Poison Info + EFI_DUMY = MKTAG('D','U','M','Y'), // Mehrunes Dagon Custom Effect + EFI_STMA = MKTAG('S','T','M','A'), // Stunted Magicka + EFI_SUDG = MKTAG('S','U','D','G'), // Sun Damage + EFI_VAMP = MKTAG('V','A','M','P'), // Vampirism + EFI_DARK = MKTAG('D','A','R','K'), // Darkness + EFI_RSWD = MKTAG('R','S','W','D') // Resist Water Damage + }; + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Groups + enum GroupType + { + Grp_RecordType = 0, + Grp_WorldChild = 1, + Grp_InteriorCell = 2, + Grp_InteriorSubCell = 3, + Grp_ExteriorCell = 4, + Grp_ExteriorSubCell = 5, + Grp_CellChild = 6, + Grp_TopicChild = 7, + Grp_CellPersistentChild = 8, + Grp_CellTemporaryChild = 9, + Grp_CellVisibleDistChild = 10 + }; + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Records + enum RecordFlag + { + Rec_ESM = 0x00000001, // (TES4 record only) Master (ESM) file. + Rec_Deleted = 0x00000020, // Deleted + Rec_Constant = 0x00000040, // Constant + Rec_HiddenLMap = 0x00000040, // (REFR) Hidden From Local Map (Needs Confirmation: Related to shields) + Rec_Localized = 0x00000080, // (TES4 record only) Is localized. This will make Skyrim load the + // .STRINGS, .DLSTRINGS, and .ILSTRINGS files associated with the mod. + // If this flag is not set, lstrings are treated as zstrings. + Rec_FireOff = 0x00000080, // (PHZD) Turn off fire + Rec_UpdateAnim = 0x00000100, // Must Update Anims + Rec_NoAccess = 0x00000100, // (REFR) Inaccessible + Rec_Hidden = 0x00000200, // (REFR) Hidden from local map + Rec_StartDead = 0x00000200, // (ACHR) Starts dead /(REFR) MotionBlurCastsShadows + Rec_Persistent = 0x00000400, // Quest item / Persistent reference + Rec_DispMenu = 0x00000400, // (LSCR) Displays in Main Menu + Rec_Disabled = 0x00000800, // Initially disabled + Rec_Ignored = 0x00001000, // Ignored + Rec_VisDistant = 0x00008000, // Visible when distant + Rec_RandAnim = 0x00010000, // (ACTI) Random Animation Start + Rec_Danger = 0x00020000, // (ACTI) Dangerous / Off limits (Interior cell) + // Dangerous Can't be set withough Ignore Object Interaction + Rec_Compressed = 0x00040000, // Data is compressed + Rec_CanNotWait = 0x00080000, // Can't wait + Rec_IgnoreObj = 0x00100000, // (ACTI) Ignore Object Interaction + // Ignore Object Interaction Sets Dangerous Automatically + Rec_Marker = 0x00800000, // Is Marker + Rec_Obstacle = 0x02000000, // (ACTI) Obstacle / (REFR) No AI Acquire + Rec_NavMFilter = 0x04000000, // NavMesh Gen - Filter + Rec_NavMBBox = 0x08000000, // NavMesh Gen - Bounding Box + Rec_ExitToTalk = 0x10000000, // (FURN) Must Exit to Talk + Rec_Refected = 0x10000000, // (REFR) Reflected By Auto Water + Rec_ChildUse = 0x20000000, // (FURN/IDLM) Child Can Use + Rec_NoHavok = 0x20000000, // (REFR) Don't Havok Settle + Rec_NavMGround = 0x40000000, // NavMesh Gen - Ground + Rec_NoRespawn = 0x40000000, // (REFR) NoRespawn + Rec_MultiBound = 0x80000000 // (REFR) MultiBound + }; + +#pragma pack(push, 1) + // NOTE: the label field of a group is not reliable (http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format) + union GroupLabel + { + std::uint32_t value; // formId, blockNo or raw int representation of type + char recordType[4]; // record type in ascii + std::int16_t grid[2]; // grid y, x (note the reverse order) + }; + + struct GroupTypeHeader + { + std::uint32_t typeId; + std::uint32_t groupSize; // includes the 24 bytes (20 for TES4) of header (i.e. this struct) + GroupLabel label; // format based on type + std::int32_t type; + std::uint16_t stamp; // & 0xff for day, & 0xff00 for months since Dec 2002 (i.e. 1 = Jan 2003) + std::uint16_t unknown; + std::uint16_t version; // not in TES4 + std::uint16_t unknown2; // not in TES4 + }; + + struct RecordTypeHeader + { + std::uint32_t typeId; + std::uint32_t dataSize; // does *not* include 24 bytes (20 for TES4) of header + std::uint32_t flags; + FormId id; + std::uint32_t revision; + std::uint16_t version; // not in TES4 + std::uint16_t unknown; // not in TES4 + }; + + union RecordHeader + { + struct GroupTypeHeader group; + struct RecordTypeHeader record; + }; + + struct SubRecordHeader + { + std::uint32_t typeId; + std::uint16_t dataSize; + }; + + // Grid, CellGrid and Vertex are shared by NVMI(NAVI) and NVNM(NAVM) + + struct Grid + { + std::int16_t x; + std::int16_t y; + }; + + union CellGrid + { + FormId cellId; + Grid grid; + }; + + struct Vertex + { + float x; + float y; + float z; + }; +#pragma pack(pop) + + // For pretty printing GroupHeader labels + std::string printLabel(const GroupLabel& label, const std::uint32_t type); + + void gridToString(std::int16_t x, std::int16_t y, std::string& str); +} + +#endif // ESM4_COMMON_H diff --git a/components/esm4/dialogue.hpp b/components/esm4/dialogue.hpp new file mode 100644 index 0000000000..e77d192901 --- /dev/null +++ b/components/esm4/dialogue.hpp @@ -0,0 +1,46 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_DIALOGUE_H +#define ESM4_DIALOGUE_H + +namespace ESM4 +{ + enum DialType + { + DTYP_Topic = 0, + DTYP_Conversation = 1, + DTYP_Combat = 2, + DTYP_Persuation = 3, + DTYP_Detection = 4, + DTYP_Service = 5, + DTYP_Miscellaneous = 6, + // below FO3/FONV + DTYP_Radio = 7 + }; +} + +#endif // ESM4_DIALOGUE_H diff --git a/components/esm4/effect.hpp b/components/esm4/effect.hpp new file mode 100644 index 0000000000..8580a478dc --- /dev/null +++ b/components/esm4/effect.hpp @@ -0,0 +1,56 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_EFFECT_H +#define ESM4_EFFECT_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ +#pragma pack(push, 1) + union EFI_Label + { + std::uint32_t value; + char effect[4]; + }; + + struct ScriptEffect + { + FormId formId; // Script effect (Magic effect must be SEFF) + std::int32_t school; // Magic school. See Magic schools for more information. + EFI_Label visualEffect; // Visual effect name or 0x00000000 if None + std::uint8_t flags; // 0x01 = Hostile + std::uint8_t unknown1; + std::uint8_t unknown2; + std::uint8_t unknown3; + }; +#pragma pack(pop) +} + +#endif // ESM4_EFFECT_H diff --git a/components/esm4/formid.cpp b/components/esm4/formid.cpp new file mode 100644 index 0000000000..f5493fd0ca --- /dev/null +++ b/components/esm4/formid.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#include "formid.hpp" + +#include +#include +#include +#include // strtol +#include // LONG_MIN, LONG_MAX for gcc + +#include + +namespace ESM4 +{ + void formIdToString(FormId formId, std::string& str) + { + char buf[8+1]; + int res = snprintf(buf, 8+1, "%08X", formId); + if (res > 0 && res < 8+1) + str.assign(buf); + else + throw std::runtime_error("Possible buffer overflow while converting formId"); + } + + std::string formIdToString(FormId formId) + { + std::string str; + formIdToString(formId, str); + return str; + } + + bool isFormId(const std::string& str, FormId *id) + { + if (str.size() != 8) + return false; + + char *tmp; + errno = 0; + unsigned long val = strtol(str.c_str(), &tmp, 16); + + if (tmp == str.c_str() || *tmp != '\0' + || ((val == (unsigned long)LONG_MIN || val == (unsigned long)LONG_MAX) && errno == ERANGE)) + return false; + + if (id != nullptr) + *id = static_cast(val); + + return true; + } + + FormId stringToFormId(const std::string& str) + { + if (str.size() != 8) + throw std::out_of_range("StringToFormId: incorrect string size"); + + return static_cast(std::stoul(str, nullptr, 16)); + } +} diff --git a/components/esm4/formid.hpp b/components/esm4/formid.hpp new file mode 100644 index 0000000000..6bb2c475e3 --- /dev/null +++ b/components/esm4/formid.hpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2016 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#ifndef ESM4_FORMID_H +#define ESM4_FORMID_H + +#include +#include + +namespace ESM4 +{ + typedef std::uint32_t FormId; + + void formIdToString(FormId formId, std::string& str); + + std::string formIdToString(FormId formId); + + bool isFormId(const std::string& str, FormId *id = nullptr); + + FormId stringToFormId(const std::string& str); +} + +#endif // ESM4_FORMID_H diff --git a/components/esm4/inventory.hpp b/components/esm4/inventory.hpp new file mode 100644 index 0000000000..31181ce33c --- /dev/null +++ b/components/esm4/inventory.hpp @@ -0,0 +1,55 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_INVENTORY_H +#define ESM4_INVENTORY_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ +#pragma pack(push, 1) + // LVLC, LVLI + struct LVLO + { + std::int16_t level; + std::uint16_t unknown; // sometimes missing + FormId item; + std::int16_t count; + std::uint16_t unknown2; // sometimes missing + }; + + struct InventoryItem // NPC_, CREA, CONT + { + FormId item; + std::uint32_t count; + }; +#pragma pack(pop) +} + +#endif // ESM4_INVENTORY_H diff --git a/components/esm4/lighting.hpp b/components/esm4/lighting.hpp new file mode 100644 index 0000000000..42e76eceee --- /dev/null +++ b/components/esm4/lighting.hpp @@ -0,0 +1,79 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LIGHTING_H +#define ESM4_LIGHTING_H + +#include + +namespace ESM4 +{ +#pragma pack(push, 1) + // guesses only for TES4 + struct Lighting + { // | Aichan Prison values + std::uint32_t ambient; // | 16 17 19 00 (RGBA) + std::uint32_t directional; // | 00 00 00 00 (RGBA) + std::uint32_t fogColor; // | 1D 1B 16 00 (RGBA) + float fogNear; // Fog Near | 00 00 00 00 = 0.f + float fogFar; // Fog Far | 00 80 3B 45 = 3000.f + std::int32_t rotationXY; // rotation xy | 00 00 00 00 = 0 + std::int32_t rotationZ; // rotation z | 00 00 00 00 = 0 + float fogDirFade; // Fog dir fade | 00 00 80 3F = 1.f + float fogClipDist; // Fog clip dist | 00 80 3B 45 = 3000.f + float fogPower; + }; + + struct Lighting_TES5 + { + std::uint32_t ambient; + std::uint32_t directional; + std::uint32_t fogColor; + float fogNear; + float fogFar; + std::int32_t rotationXY; + std::int32_t rotationZ; + float fogDirFade; + float fogClipDist; + float fogPower; + std::uint32_t unknown1; + std::uint32_t unknown2; + std::uint32_t unknown3; + std::uint32_t unknown4; + std::uint32_t unknown5; + std::uint32_t unknown6; + std::uint32_t unknown7; + std::uint32_t unknown8; + std::uint32_t fogColorFar; + float fogMax; + float LightFadeStart; + float LightFadeEnd; + std::uint32_t padding; + }; +#pragma pack(pop) +} + +#endif // ESM4_LIGHTING_H diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp new file mode 100644 index 0000000000..fe03fcb59e --- /dev/null +++ b/components/esm4/loadachr.cpp @@ -0,0 +1,124 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadachr.hpp" + +#include +//#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ActorCharacter::ActorCharacter() : mFormId(0), mFlags(0), mBaseObj(0), + mScale(1.f), mOwner(0), mGlobal(0), mInitiallyDisabled(false) +{ + mEditorId.clear(); + mFullName.clear(); + + mEsp.parent = 0; + mEsp.flags = 0; +} + +ESM4::ActorCharacter::~ActorCharacter() +{ +} + +void ESM4::ActorCharacter::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currCell(); // NOTE: only for persistent achr? (aren't they all persistent?) + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break; + case ESM4::SUB_DATA: reader.get(mPlacement); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XOWN: reader.get(mOwner); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + break; + } + case ESM4::SUB_XRGD: // ragdoll + case ESM4::SUB_XRGB: // ragdoll biped + { + //std::cout << "ACHR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XHRS: // horse formId + case ESM4::SUB_XMRC: // merchant container formId + // TES5 + case ESM4::SUB_XAPD: // activation parent + case ESM4::SUB_XAPR: // active parent + case ESM4::SUB_XEZN: // encounter zone + case ESM4::SUB_XHOR: + case ESM4::SUB_XLCM: // levelled creature + case ESM4::SUB_XLCN: // location + case ESM4::SUB_XLKR: // location route? + case ESM4::SUB_XLRT: // location type + // + case ESM4::SUB_XPRD: + case ESM4::SUB_XPPA: + case ESM4::SUB_INAM: + case ESM4::SUB_PDTO: + // + case ESM4::SUB_XIS2: + case ESM4::SUB_XPCI: // formId + case ESM4::SUB_XLOD: + case ESM4::SUB_VMAD: + case ESM4::SUB_XLRL: // Unofficial Skyrim Patch + case ESM4::SUB_XRDS: // FO3 + case ESM4::SUB_XIBS: // FO3 + case ESM4::SUB_SCHR: // FO3 + case ESM4::SUB_TNAM: // FO3 + case ESM4::SUB_XATO: // FONV + { + //std::cout << "ACHR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACHR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ActorCharacter::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ActorCharacter::blank() +//{ +//} diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp new file mode 100644 index 0000000000..a8656ea926 --- /dev/null +++ b/components/esm4/loadachr.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACHR_H +#define ESM4_ACHR_H + +#include + +#include "reference.hpp" // FormId, Placement, EnableParent + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ActorCharacter + { + FormId mParent; // cell formId, from the loading sequence + // NOTE: for exterior cells it will be the dummy cell FormId + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mBaseObj; + + Placement mPlacement; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + + bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) + + EnableParent mEsp; + + ActorCharacter(); + virtual ~ActorCharacter(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACHR_H diff --git a/components/esm4/loadacre.cpp b/components/esm4/loadacre.cpp new file mode 100644 index 0000000000..3510986d3c --- /dev/null +++ b/components/esm4/loadacre.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadacre.hpp" + +#include +//#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ActorCreature::ActorCreature() : mFormId(0), mFlags(0), mBaseObj(0), mScale(1.f), + mOwner(0), mGlobal(0), mFactionRank(0), mInitiallyDisabled(false) +{ + mEditorId.clear(); + + mEsp.parent = 0; + mEsp.flags = 0; +} + +ESM4::ActorCreature::~ActorCreature() +{ +} + +void ESM4::ActorCreature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break; + case ESM4::SUB_DATA: reader.get(mPlacement); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + break; + } + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.get(mGlobal); break; // FIXME: formId? + case ESM4::SUB_XRNK: reader.get(mFactionRank); break; + case ESM4::SUB_XRGD: // ragdoll + case ESM4::SUB_XRGB: // ragdoll biped + { + // seems to occur only for dead bodies, e.g. DeadMuffy, DeadDogVicious + //std::cout << "ACRE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XLKR: // FO3 + case ESM4::SUB_XLCM: // FO3 + case ESM4::SUB_XEZN: // FO3 + case ESM4::SUB_XMRC: // FO3 + case ESM4::SUB_XAPD: // FO3 + case ESM4::SUB_XAPR: // FO3 + case ESM4::SUB_XRDS: // FO3 + case ESM4::SUB_XPRD: // FO3 + case ESM4::SUB_XATO: // FONV + { + //std::cout << "ACRE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACRE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ActorCreature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ActorCreature::blank() +//{ +//} diff --git a/components/esm4/loadacre.hpp b/components/esm4/loadacre.hpp new file mode 100644 index 0000000000..26cab34a4e --- /dev/null +++ b/components/esm4/loadacre.hpp @@ -0,0 +1,67 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACRE_H +#define ESM4_ACRE_H + +#include + +#include "reference.hpp" // FormId, Placement, EnableParent + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ActorCreature + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + FormId mBaseObj; + + Placement mPlacement; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + std::uint32_t mFactionRank; + + bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) + + EnableParent mEsp; + + ActorCreature(); + virtual ~ActorCreature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACRE_H diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp new file mode 100644 index 0000000000..abbe901014 --- /dev/null +++ b/components/esm4/loadacti.cpp @@ -0,0 +1,103 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "acti.hpp" + +#include +#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Activator::Activator() : mFormId(0), mFlags(0), mScriptId(0), mLoopingSound(0), mActivationSound(0), + mBoundRadius(0.f), mRadioTemplate(0), mRadioStation(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mActivationPrompt.clear(); +} + +ESM4::Activator::~Activator() +{ +} + +void ESM4::Activator::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_SNAM: reader.getFormId(mLoopingSound); break; + case ESM4::SUB_VNAM: reader.getFormId(mActivationSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_INAM: reader.getFormId(mRadioTemplate); break; // FONV + case ESM4::SUB_RNAM: reader.getFormId(mRadioStation); break; + case ESM4::SUB_XATO: reader.getZString(mActivationPrompt); break; // FONV + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_DEST: + case ESM4::SUB_DMDL: + case ESM4::SUB_DMDS: + case ESM4::SUB_DMDT: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + case ESM4::SUB_FNAM: + case ESM4::SUB_KNAM: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_WNAM: + { + //std::cout << "ACTI " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACTI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Activator::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Activator::blank() +//{ +//} diff --git a/components/esm4/loadalch.cpp b/components/esm4/loadalch.cpp new file mode 100644 index 0000000000..b720b740ca --- /dev/null +++ b/components/esm4/loadalch.cpp @@ -0,0 +1,121 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadalch.hpp" + +#include +#include +//#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Potion::Potion() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mScriptId(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + mMiniIcon.clear(); + + mData.weight = 0.f; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::Potion::~Potion() +{ +} + +void ESM4::Potion::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_ENIT: + { + if (subHdr.dataSize == 8) // TES4 + { + reader.get(&mItem, 8); + mItem.withdrawl = 0; + mItem.sound = 0; + break; + } + + reader.get(mItem); + reader.adjustFormId(mItem.withdrawl); + reader.adjustFormId(mItem.sound); + break; + } + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + case ESM4::SUB_CTDA: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_ETYP: // FO3 + { + //std::cout << "ALCH " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ALCH::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Potion::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Potion::blank() +//{ +//} diff --git a/components/esm4/loadalch.hpp b/components/esm4/loadalch.hpp new file mode 100644 index 0000000000..e6680dc93b --- /dev/null +++ b/components/esm4/loadalch.hpp @@ -0,0 +1,88 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ALCH_H +#define ESM4_ALCH_H + +#include +#include + +#include "effect.hpp" // FormId, ScriptEffect + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Potion + { +#pragma pack(push, 1) + struct Data + { + float weight; + }; + + struct EnchantedItem + { + std::int32_t value; + std::uint32_t flags; + FormId withdrawl; + float chanceAddition; + FormId sound; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + std::string mMiniIcon; // inventory + + FormId mPickUpSound; + FormId mDropSound; + + FormId mScriptId; + ScriptEffect mEffect; + + float mBoundRadius; + + Data mData; + EnchantedItem mItem; + + Potion(); + virtual ~Potion(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ALCH_H diff --git a/components/esm4/loadaloc.cpp b/components/esm4/loadaloc.cpp new file mode 100644 index 0000000000..a84e56592d --- /dev/null +++ b/components/esm4/loadaloc.cpp @@ -0,0 +1,172 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadaloc.hpp" + +#include +#include +//#include // FIXME: for debugging only +//#include // FIXME: for debugging only + +//#include // FIXME + +//#include "formid.hpp" // FIXME: + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MediaLocationController::MediaLocationController() : mFormId(0), mFlags(0), + mConditionalFaction(0), mLocationDelay(0.f), mRetriggerDelay(0.f), mDayStart(0), mNightStart(0) +{ + mEditorId.clear(); + mFullName.clear(); + + std::memset(&mMediaFlags, 0, sizeof(MLC_Flags)); +} + +ESM4::MediaLocationController::~MediaLocationController() +{ +} + +void ESM4::MediaLocationController::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_GNAM: + { + FormId id; + reader.getFormId(id); + mBattleSets.push_back(id); + + break; + } + case ESM4::SUB_LNAM: + { + FormId id; + reader.getFormId(id); + mLocationSets.push_back(id); + + break; + } + case ESM4::SUB_YNAM: + { + FormId id; + reader.getFormId(id); + mEnemySets.push_back(id); + + break; + } + case ESM4::SUB_HNAM: + { + FormId id; + reader.getFormId(id); + mNeutralSets.push_back(id); + + break; + } + case ESM4::SUB_XNAM: + { + FormId id; + reader.getFormId(id); + mFriendSets.push_back(id); + + break; + } + case ESM4::SUB_ZNAM: + { + FormId id; + reader.getFormId(id); + mAllySets.push_back(id); + + break; + } + case ESM4::SUB_RNAM: reader.getFormId(mConditionalFaction); break; + case ESM4::SUB_NAM1: + { + reader.get(mMediaFlags); + std::uint8_t flags = mMediaFlags.loopingOptions; + mMediaFlags.loopingOptions = (flags & 0xF0) >> 4; + mMediaFlags.factionNotFound = flags & 0x0F; // WARN: overwriting data + break; + } + case ESM4::SUB_NAM4: reader.get(mLocationDelay); break; + case ESM4::SUB_NAM7: reader.get(mRetriggerDelay); break; + case ESM4::SUB_NAM5: reader.get(mDayStart); break; + case ESM4::SUB_NAM6: reader.get(mNightStart); break; + case ESM4::SUB_NAM2: // always 0? 4 bytes + case ESM4::SUB_NAM3: // always 0? 4 bytes + case ESM4::SUB_FNAM: // always 0? 4 bytes + { +#if 0 + boost::scoped_array mDataBuf(new unsigned char[subHdr.dataSize]); + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; + for (std::size_t i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) // looks like printable ascii char + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) // wrap around + ss << "\n"; + else if (i < subHdr.dataSize-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + //std::cout << "ALOC " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); +#endif + break; + } + default: + //std::cout << "ALOC " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::ALOC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MediaLocationController::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MediaLocationController::blank() +//{ +//} diff --git a/components/esm4/loadaloc.hpp b/components/esm4/loadaloc.hpp new file mode 100644 index 0000000000..1c2d17d11e --- /dev/null +++ b/components/esm4/loadaloc.hpp @@ -0,0 +1,88 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ALOC_H +#define ESM4_ALOC_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + +#pragma pack(push, 1) + struct MLC_Flags + { + // use day/night transition: 0 = loop, 1 = random, 2 = retrigger, 3 = none + // use defaults (6:00/23:54): 4 = loop, 5 = random, 6 = retrigger, 7 = none + std::uint8_t loopingOptions; + // 0 = neutral, 1 = enemy, 2 = ally, 3 = friend, 4 = location, 5 = none + std::uint8_t factionNotFound; // WARN: overwriting whatever is in this + std::uint16_t unknown; // padding? + }; +#pragma pack(pop) + + struct MediaLocationController + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + + std::vector mBattleSets; + std::vector mLocationSets; + std::vector mEnemySets; + std::vector mNeutralSets; + std::vector mFriendSets; + std::vector mAllySets; + + MLC_Flags mMediaFlags; + + FormId mConditionalFaction; + + float mLocationDelay; + float mRetriggerDelay; + + std::uint32_t mDayStart; + std::uint32_t mNightStart; + + MediaLocationController(); + virtual ~MediaLocationController(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ALOC_H diff --git a/components/esm4/loadammo.cpp b/components/esm4/loadammo.cpp new file mode 100644 index 0000000000..7ccf8e1582 --- /dev/null +++ b/components/esm4/loadammo.cpp @@ -0,0 +1,133 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadammo.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Ammunition::Ammunition() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + mMiniIcon.clear(); +} + +ESM4::Ammunition::~Ammunition() +{ +} + +void ESM4::Ammunition::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 16) // FO3 has 13 bytes even though VER_094 + { + FormId projectile; + reader.get(projectile); // FIXME: add to mData + reader.get(mData.flags); + reader.get(mData.weight); + float damageInFloat; + reader.get(damageInFloat); // FIXME: add to mData + } + else if (isFONV || subHdr.dataSize == 13) + { + reader.get(mData.speed); + std::uint8_t flags; + reader.get(flags); + mData.flags = flags; + static std::uint8_t dummy; + reader.get(dummy); + reader.get(dummy); + reader.get(dummy); + reader.get(mData.value); + reader.get(mData.clipRounds); + } + else // TES4 + { + reader.get(mData.speed); + reader.get(mData.flags); + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.damage); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_ONAM: // FO3 + case ESM4::SUB_DAT2: // FONV + case ESM4::SUB_QNAM: // FONV + case ESM4::SUB_RCIL: // FONV + case ESM4::SUB_SCRI: // FONV + { + //std::cout << "AMMO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::AMMO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Ammunition::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Ammunition::blank() +//{ +//} diff --git a/components/esm4/loadammo.hpp b/components/esm4/loadammo.hpp new file mode 100644 index 0000000000..8ff9cd9390 --- /dev/null +++ b/components/esm4/loadammo.hpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_AMMO_H +#define ESM4_AMMO_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Ammunition + { + struct Data // FIXME: TES5 projectile, damage (float) + { + float speed; + std::uint32_t flags; + std::uint32_t value; // gold + float weight; + std::uint16_t damage; + std::uint8_t clipRounds; // only in FO3/FONV + + Data() : speed(0.f), flags(0), value(0), weight(0.f), damage(0), clipRounds(0) {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + std::string mIcon; // inventory + std::string mMiniIcon; // inventory + + FormId mPickUpSound; + FormId mDropSound; + + float mBoundRadius; + + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Ammunition(); + virtual ~Ammunition(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_AMMO_H diff --git a/components/esm4/loadanio.cpp b/components/esm4/loadanio.cpp new file mode 100644 index 0000000000..b6b97a1a47 --- /dev/null +++ b/components/esm4/loadanio.cpp @@ -0,0 +1,80 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadanio.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::AnimObject::AnimObject() : mFormId(0), mFlags(0), mBoundRadius(0.f), mIdleAnim(0) +{ + mEditorId.clear(); + mModel.clear(); + mUnloadEvent.clear(); +} + +ESM4::AnimObject::~AnimObject() +{ +} + +void ESM4::AnimObject::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_BNAM: reader.getZString(mUnloadEvent); break; + case ESM4::SUB_DATA: reader.getFormId(mIdleAnim); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: // TES5 only + case ESM4::SUB_MODS: // TES5 only + { + //std::cout << "ANIO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ANIO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::AnimObject::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::AnimObject::blank() +//{ +//} diff --git a/components/esm4/loadanio.hpp b/components/esm4/loadanio.hpp new file mode 100644 index 0000000000..4259abe7b3 --- /dev/null +++ b/components/esm4/loadanio.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ANIO_H +#define ESM4_ANIO_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct AnimObject + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + FormId mIdleAnim; // only in TES4 + std::string mUnloadEvent; // only in TES5 + + AnimObject(); + virtual ~AnimObject(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ANIO_H diff --git a/components/esm4/loadappa.cpp b/components/esm4/loadappa.cpp new file mode 100644 index 0000000000..7ad7635927 --- /dev/null +++ b/components/esm4/loadappa.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadappa.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Apparatus::Apparatus() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + + mData.type = 0; + mData.value = 0; + mData.weight = 0.f; + mData.quality = 0.f; +} + +ESM4::Apparatus::~Apparatus() +{ +} + +void ESM4::Apparatus::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + { + reader.get(mData.value); + reader.get(mData.weight); + } + else + { + reader.get(mData.type); + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.quality); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_QUAL: + { + //std::cout << "APPA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::APPA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Apparatus::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Apparatus::blank() +//{ +//} diff --git a/components/esm4/loadappa.hpp b/components/esm4/loadappa.hpp new file mode 100644 index 0000000000..1723c969ee --- /dev/null +++ b/components/esm4/loadappa.hpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_APPA_H +#define ESM4_APPA_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Apparatus + { + struct Data + { + std::uint8_t type; // 0 = Mortar and Pestle, 1 = Alembic, 2 = Calcinator, 3 = Retort + std::uint32_t value; // gold + float weight; + float quality; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + std::string mIcon; // inventory + + float mBoundRadius; + + FormId mScriptId; + + Data mData; + + Apparatus(); + virtual ~Apparatus(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_APPA_H diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp new file mode 100644 index 0000000000..8b29000bb3 --- /dev/null +++ b/components/esm4/loadarma.cpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadarma.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ArmorAddon::ArmorAddon() : mFormId(0), mFlags(0), mTextureMale(0), mTextureFemale(0), + mRacePrimary(0) +{ + mEditorId.clear(); + + mModelMale.clear(); + mModelFemale.clear(); +} + +ESM4::ArmorAddon::~ArmorAddon() +{ +} + +void ESM4::ArmorAddon::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MOD2: reader.getZString(mModelMale); break; + case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; + case ESM4::SUB_MOD4: + case ESM4::SUB_MOD5: + { + std::string model; + reader.getZString(model); + + //std::cout << mEditorId << " " << ESM::printName(subHdr.typeId) << " " << model << std::endl; + + break; + } + case ESM4::SUB_NAM0: reader.getFormId(mTextureMale); break; + case ESM4::SUB_NAM1: reader.getFormId(mTextureFemale); break; + case ESM4::SUB_RNAM: reader.getFormId(mRacePrimary); break; + case ESM4::SUB_MODL: + { + if ((esmVer == ESM::VER_094 || esmVer == ESM::VER_170) && subHdr.dataSize == 4) // TES5 + { + FormId formId; + reader.getFormId(formId); + mRaces.push_back(formId); + } + else + reader.skipSubRecordData(); // FIXME: this should be mModelMale for FO3/FONV + + break; + } + case ESM4::SUB_BODT: // body template + { + reader.get(mBodyTemplate.bodyPart); + reader.get(mBodyTemplate.flags); + reader.get(mBodyTemplate.unknown1); // probably padding + reader.get(mBodyTemplate.unknown2); // probably padding + reader.get(mBodyTemplate.unknown3); // probably padding + reader.get(mBodyTemplate.type); + + break; + } + case ESM4::SUB_BOD2: // TES5 + { + reader.get(mBodyTemplate.bodyPart); + mBodyTemplate.flags = 0; + mBodyTemplate.unknown1 = 0; // probably padding + mBodyTemplate.unknown2 = 0; // probably padding + mBodyTemplate.unknown3 = 0; // probably padding + reader.get(mBodyTemplate.type); + + break; + } + case ESM4::SUB_DNAM: + case ESM4::SUB_MO2T: // FIXME: should group with MOD2 + case ESM4::SUB_MO2S: // FIXME: should group with MOD2 + case ESM4::SUB_MO3T: // FIXME: should group with MOD3 + case ESM4::SUB_MO3S: // FIXME: should group with MOD3 + case ESM4::SUB_MOSD: // FO3 // FIXME: should group with MOD3 + case ESM4::SUB_MO4T: // FIXME: should group with MOD4 + case ESM4::SUB_MO4S: // FIXME: should group with MOD4 + case ESM4::SUB_MO5T: + case ESM4::SUB_NAM2: // txst formid male + case ESM4::SUB_NAM3: // txst formid female + case ESM4::SUB_SNDD: // footset sound formid + case ESM4::SUB_BMDT: // FO3 + case ESM4::SUB_DATA: // FO3 + case ESM4::SUB_ETYP: // FO3 + case ESM4::SUB_FULL: // FO3 + case ESM4::SUB_ICO2: // FO3 // female + case ESM4::SUB_ICON: // FO3 // male + case ESM4::SUB_MODT: // FO3 // FIXME: should group with MODL + case ESM4::SUB_MODS: // FO3 // FIXME: should group with MODL + case ESM4::SUB_MODD: // FO3 // FIXME: should group with MODL + case ESM4::SUB_OBND: // FO3 + { + //std::cout << "ARMA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ARMA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ArmorAddon::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ArmorAddon::blank() +//{ +//} diff --git a/components/esm4/loadarma.hpp b/components/esm4/loadarma.hpp new file mode 100644 index 0000000000..59c4550b7f --- /dev/null +++ b/components/esm4/loadarma.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ARMA_H +#define ESM4_ARMA_H + +#include +#include +#include + +#include "formid.hpp" +#include "actor.hpp" // BodyTemplate + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ArmorAddon + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::string mModelMale; + std::string mModelFemale; + + FormId mTextureMale; + FormId mTextureFemale; + + FormId mRacePrimary; + std::vector mRaces; // TES5 only + + BodyTemplate mBodyTemplate; // TES5 + + ArmorAddon(); + virtual ~ArmorAddon(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ARMA_H diff --git a/components/esm4/loadarmo.cpp b/components/esm4/loadarmo.cpp new file mode 100644 index 0000000000..bc0765c869 --- /dev/null +++ b/components/esm4/loadarmo.cpp @@ -0,0 +1,218 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadarmo.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Armor::Armor() : mFormId(0), mFlags(0), mIsTES4(false), mIsFO3(false), mIsFONV(false), + mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), + mArmorFlags(0), mGeneralFlags(0), mScriptId(0), mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModelMale.clear(); + mModelMaleWorld.clear(); + mModelFemale.clear(); + mModelFemaleWorld.clear(); + mText.clear(); + mIconMale.clear(); + mMiniIconMale.clear(); + mIconFemale.clear(); + mMiniIconFemale.clear(); + + mData.armor = 0; + mData.value = 0; + mData.health = 0; + mData.weight = 0.f; +} + +ESM4::Armor::~Armor() +{ +} + +void ESM4::Armor::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 8) // FO3 has 12 bytes even though VER_094 + { + reader.get(mData.value); + reader.get(mData.weight); + mIsFO3 = true; + } + else if (mIsFONV || subHdr.dataSize == 12) + { + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + } + else + { + reader.get(mData); // TES4 + mIsTES4 = true; + } + + break; + } + case ESM4::SUB_MODL: // seems only for Dawnguard/Dragonborn? + { + //if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + if (subHdr.dataSize == 4) // FO3 has zstring even though VER_094 + { + FormId formId; + reader.getFormId(formId); + + mAddOns.push_back(formId); + } + else + { + if (!reader.getZString(mModelMale)) + throw std::runtime_error ("ARMO MODL data read error"); + } + + break; + } + case ESM4::SUB_MOD2: reader.getZString(mModelMaleWorld);break; + case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; + case ESM4::SUB_MOD4: reader.getZString(mModelFemaleWorld); break; + case ESM4::SUB_ICON: reader.getZString(mIconMale); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIconMale); break; + case ESM4::SUB_ICO2: reader.getZString(mIconFemale); break; + case ESM4::SUB_MIC2: reader.getZString(mMiniIconFemale); break; + case ESM4::SUB_BMDT: + { + if (subHdr.dataSize == 8) // FO3 + { + reader.get(mArmorFlags); + reader.get(mGeneralFlags); + mGeneralFlags &= 0x000000ff; + mGeneralFlags |= TYPE_FO3; + } + else // TES4 + { + reader.get(mArmorFlags); + mGeneralFlags = (mArmorFlags & 0x00ff0000) >> 16; + mGeneralFlags |= TYPE_TES4; + } + break; + } + case ESM4::SUB_BODT: + { + reader.get(mArmorFlags); + uint32_t flags = 0; + if (subHdr.dataSize == 12) + reader.get(flags); + reader.get(mGeneralFlags); // skill + mGeneralFlags &= 0x0000000f; // 0 (light), 1 (heavy) or 2 (none) + if (subHdr.dataSize == 12) + mGeneralFlags |= (flags & 0x0000000f) << 3; + mGeneralFlags |= TYPE_TES5; + break; + } + case ESM4::SUB_BOD2: + { + reader.get(mArmorFlags); + reader.get(mGeneralFlags); + mGeneralFlags &= 0x0000000f; // 0 (light), 1 (heavy) or 2 (none) + mGeneralFlags |= TYPE_TES5; + break; + } + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MO2B: + case ESM4::SUB_MO3B: + case ESM4::SUB_MO4B: + case ESM4::SUB_MO2T: + case ESM4::SUB_MO2S: + case ESM4::SUB_MO3T: + case ESM4::SUB_MO4T: + case ESM4::SUB_MO4S: + case ESM4::SUB_OBND: + case ESM4::SUB_RNAM: // race formid + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_TNAM: + case ESM4::SUB_DNAM: + case ESM4::SUB_BAMT: + case ESM4::SUB_BIDS: + case ESM4::SUB_ETYP: + case ESM4::SUB_BMCT: + case ESM4::SUB_EAMT: + case ESM4::SUB_EITM: + case ESM4::SUB_VMAD: + case ESM4::SUB_REPL: // FO3 + case ESM4::SUB_BIPL: // FO3 + case ESM4::SUB_MODD: // FO3 + case ESM4::SUB_MOSD: // FO3 + case ESM4::SUB_MODS: // FO3 + case ESM4::SUB_MO3S: // FO3 + case ESM4::SUB_BNAM: // FONV + case ESM4::SUB_SNAM: // FONV + { + //std::cout << "ARMO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ARMO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //if ((mArmorFlags&0xffff) == 0x02) // only hair + //std::cout << "only hair " << mEditorId << std::endl; +} + +//void ESM4::Armor::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Armor::blank() +//{ +//} diff --git a/components/esm4/loadarmo.hpp b/components/esm4/loadarmo.hpp new file mode 100644 index 0000000000..f17521f507 --- /dev/null +++ b/components/esm4/loadarmo.hpp @@ -0,0 +1,196 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ARMO_H +#define ESM4_ARMO_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Armor + { + // WARN: TES4 Armorflags still has the general flags high bits + enum ArmorFlags + { + TES4_Head = 0x00000001, + TES4_Hair = 0x00000002, + TES4_UpperBody = 0x00000004, + TES4_LowerBody = 0x00000008, + TES4_Hands = 0x00000010, + TES4_Feet = 0x00000020, + TES4_RightRing = 0x00000040, + TES4_LeftRing = 0x00000080, + TES4_Amulet = 0x00000100, + TES4_Weapon = 0x00000200, + TES4_BackWeapon = 0x00000400, + TES4_SideWeapon = 0x00000800, + TES4_Quiver = 0x00001000, + TES4_Shield = 0x00002000, + TES4_Torch = 0x00004000, + TES4_Tail = 0x00008000, + // + FO3_Head = 0x00000001, + FO3_Hair = 0x00000002, + FO3_UpperBody = 0x00000004, + FO3_LeftHand = 0x00000008, + FO3_RightHand = 0x00000010, + FO3_Weapon = 0x00000020, + FO3_PipBoy = 0x00000040, + FO3_Backpack = 0x00000080, + FO3_Necklace = 0x00000100, + FO3_Headband = 0x00000200, + FO3_Hat = 0x00000400, + FO3_EyeGlasses = 0x00000800, + FO3_NoseRing = 0x00001000, + FO3_Earrings = 0x00002000, + FO3_Mask = 0x00004000, + FO3_Choker = 0x00008000, + FO3_MouthObject = 0x00010000, + FO3_BodyAddOn1 = 0x00020000, + FO3_BodyAddOn2 = 0x00040000, + FO3_BodyAddOn3 = 0x00080000, + // + TES5_Head = 0x00000001, + TES5_Hair = 0x00000002, + TES5_Body = 0x00000004, + TES5_Hands = 0x00000008, + TES5_Forearms = 0x00000010, + TES5_Amulet = 0x00000020, + TES5_Ring = 0x00000040, + TES5_Feet = 0x00000080, + TES5_Calves = 0x00000100, + TES5_Shield = 0x00000200, + TES5_Tail = 0x00000400, + TES5_LongHair = 0x00000800, + TES5_Circlet = 0x00001000, + TES5_Ears = 0x00002000, + TES5_BodyAddOn3 = 0x00004000, + TES5_BodyAddOn4 = 0x00008000, + TES5_BodyAddOn5 = 0x00010000, + TES5_BodyAddOn6 = 0x00020000, + TES5_BodyAddOn7 = 0x00040000, + TES5_BodyAddOn8 = 0x00080000, + TES5_DecapHead = 0x00100000, + TES5_Decapitate = 0x00200000, + TES5_BodyAddOn9 = 0x00400000, + TES5_BodyAddOn10 = 0x00800000, + TES5_BodyAddOn11 = 0x01000000, + TES5_BodyAddOn12 = 0x02000000, + TES5_BodyAddOn13 = 0x04000000, + TES5_BodyAddOn14 = 0x08000000, + TES5_BodyAddOn15 = 0x10000000, + TES5_BodyAddOn16 = 0x20000000, + TES5_BodyAddOn17 = 0x40000000, + TES5_FX01 = 0x80000000 + }; + + enum GeneralFlags + { + TYPE_TES4 = 0x1000, + TYPE_FO3 = 0x2000, + TYPE_TES5 = 0x3000, + TYPE_FONV = 0x4000, + // + TES4_HideRings = 0x0001, + TES4_HideAmulet = 0x0002, + TES4_NonPlayable = 0x0040, + TES4_HeavyArmor = 0x0080, + // + FO3_PowerArmor = 0x0020, + FO3_NonPlayable = 0x0040, + FO3_HeavyArmor = 0x0080, + // + TES5_LightArmor = 0x0000, + TES5_HeavyArmor = 0x0001, + TES5_None = 0x0002, + TES5_ModVoice = 0x0004, // note bit shift + TES5_NonPlayable = 0x0040 // note bit shift + }; + +#pragma pack(push, 1) + struct Data + { + std::uint16_t armor; // only in TES4? + std::uint32_t value; + std::uint32_t health; // not in TES5? + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + bool mIsTES4; // TODO: check that these match the general flags + bool mIsFO3; + bool mIsFONV; + + std::string mEditorId; + std::string mFullName; + std::string mModelMale; + std::string mModelMaleWorld; + std::string mModelFemale; + std::string mModelFemaleWorld; + std::string mText; + std::string mIconMale; + std::string mMiniIconMale; + std::string mIconFemale; + std::string mMiniIconFemale; + + FormId mPickUpSound; + FormId mDropSound; + + std::string mModel; // FIXME: for OpenCS + + float mBoundRadius; + + std::uint32_t mArmorFlags; + std::uint32_t mGeneralFlags; + FormId mScriptId; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + std::vector mAddOns; // TES5 ARMA + Data mData; + + Armor(); + virtual ~Armor(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ARMO_H diff --git a/components/esm4/loadaspc.cpp b/components/esm4/loadaspc.cpp new file mode 100644 index 0000000000..a4cb6f3939 --- /dev/null +++ b/components/esm4/loadaspc.cpp @@ -0,0 +1,94 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadaspc.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::AcousticSpace::AcousticSpace() : mFormId(0), mFlags(0), mEnvironmentType(0), mSoundRegion(0), + mIsInterior(0) +{ + mEditorId.clear(); +} + +ESM4::AcousticSpace::~AcousticSpace() +{ +} + +void ESM4::AcousticSpace::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_ANAM: reader.get(mEnvironmentType); break; + case ESM4::SUB_SNAM: + { + FormId id; + reader.getFormId(id); + mAmbientLoopSounds.push_back(id); + break; + } + case ESM4::SUB_RDAT: reader.getFormId(mSoundRegion); break; + case ESM4::SUB_INAM: reader.get(mIsInterior); break; + case ESM4::SUB_WNAM: // usually 0 for FONV (maybe # of close Actors for Walla to trigger) + { + std::uint32_t dummy; + reader.get(dummy); + //std::cout << "WNAM " << mEditorId << " " << dummy << std::endl; + break; + } + case ESM4::SUB_BNAM: // TES5 reverb formid + case ESM4::SUB_OBND: + { + //std::cout << "ASPC " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ASPC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::AcousticSpace::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::AcousticSpace::blank() +//{ +//} diff --git a/components/esm4/loadaspc.hpp b/components/esm4/loadaspc.hpp new file mode 100644 index 0000000000..3ab1a8484d --- /dev/null +++ b/components/esm4/loadaspc.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ASPC_H +#define ESM4_ASPC_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct AcousticSpace + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::uint32_t mEnvironmentType; + + // 0 Dawn (5:00 start), 1 Afternoon (8:00), 2 Dusk (18:00), 3 Night (20:00) + std::vector mAmbientLoopSounds; + FormId mSoundRegion; + + std::uint32_t mIsInterior; // if true only use mAmbientLoopSounds[0] + + AcousticSpace(); + virtual ~AcousticSpace(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ASPC_H diff --git a/components/esm4/loadbook.cpp b/components/esm4/loadbook.cpp new file mode 100644 index 0000000000..0ffc69ef0e --- /dev/null +++ b/components/esm4/loadbook.cpp @@ -0,0 +1,121 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadbook.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Book::Book() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), + mEnchantmentPoints(0), mEnchantment(0), mPickUpSound(0), mDropSound(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + + mData.flags = 0; + mData.type = 0; + mData.bookSkill = 0; + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Book::~Book() +{ +} + +void ESM4::Book::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + //std::uint32_t esmVer = reader.esmVersion(); // currently unused + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_DATA: + { + reader.get(mData.flags); + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 16) // FO3 has 10 bytes even though VER_094 + { + static std::uint8_t dummy; + reader.get(mData.type); + reader.get(dummy); + reader.get(dummy); + reader.get(mData.teaches); + } + else + { + reader.get(mData.bookSkill); + } + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; // TODO: does this exist? + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_CNAM: + case ESM4::SUB_INAM: + case ESM4::SUB_VMAD: + { + //std::cout << "BOOK " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::BOOK::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Book::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Book::blank() +//{ +//} diff --git a/components/esm4/loadbook.hpp b/components/esm4/loadbook.hpp new file mode 100644 index 0000000000..c5c1a3529f --- /dev/null +++ b/components/esm4/loadbook.hpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_BOOK_H +#define ESM4_BOOK_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Book + { + enum Flags + { + Flag_Scroll = 0x0001, + Flag_NoTake = 0x0002 + }; + + enum BookSkill // for TES4 only + { + BookSkill_None = -1, + BookSkill_Armorer = 0, + BookSkill_Athletics = 1, + BookSkill_Blade = 2, + BookSkill_Block = 3, + BookSkill_Blunt = 4, + BookSkill_HandToHand = 5, + BookSkill_HeavyArmor = 6, + BookSkill_Alchemy = 7, + BookSkill_Alteration = 8, + BookSkill_Conjuration = 9, + BookSkill_Destruction = 10, + BookSkill_Illusion = 11, + BookSkill_Mysticism = 12, + BookSkill_Restoration = 13, + BookSkill_Acrobatics = 14, + BookSkill_LightArmor = 15, + BookSkill_Marksman = 16, + BookSkill_Mercantile = 17, + BookSkill_Security = 18, + BookSkill_Sneak = 19, + BookSkill_Speechcraft = 20 + }; + + struct Data + { + std::uint8_t flags; + std::uint8_t type; // TES5 only + std::uint32_t teaches; // TES5 only + std::int8_t bookSkill; // not in TES5 + std::uint32_t value; + float weight; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + std::string mText; + FormId mScriptId; + std::string mIcon; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + FormId mPickUpSound; + FormId mDropSound; + + Book(); + virtual ~Book(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_BOOK_H diff --git a/components/esm4/loadbptd.cpp b/components/esm4/loadbptd.cpp new file mode 100644 index 0000000000..6c6c1bc26c --- /dev/null +++ b/components/esm4/loadbptd.cpp @@ -0,0 +1,113 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadbptd.hpp" + +#include +#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +void ESM4::BodyPartData::BodyPart::clear() +{ + mPartName.clear(); + mNodeName.clear(); + mVATSTarget.clear(); + mIKStartNode.clear(); + std::memset(&mData, 0, sizeof(BPND)); + mLimbReplacementModel.clear(); + mGoreEffectsTarget.clear(); +} + +ESM4::BodyPartData::BodyPartData() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::BodyPartData::~BodyPartData() +{ +} + +void ESM4::BodyPartData::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + BodyPart bodyPart; + bodyPart.clear(); + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_BPTN: reader.getLocalizedString(bodyPart.mPartName); break; + case ESM4::SUB_BPNN: reader.getZString(bodyPart.mNodeName); break; + case ESM4::SUB_BPNT: reader.getZString(bodyPart.mVATSTarget); break; + case ESM4::SUB_BPNI: reader.getZString(bodyPart.mIKStartNode); break; + case ESM4::SUB_BPND: reader.get(bodyPart.mData); break; + case ESM4::SUB_NAM1: reader.getZString(bodyPart.mLimbReplacementModel); break; + case ESM4::SUB_NAM4: // FIXME: assumed occurs last + { + reader.getZString(bodyPart.mGoreEffectsTarget); // target bone + + mBodyParts.push_back(bodyPart); // FIXME: possible without copying? + + bodyPart.clear(); + break; + } + case ESM4::SUB_NAM5: + case ESM4::SUB_RAGA: // ragdoll + case ESM4::SUB_MODS: + case ESM4::SUB_MODT: + { + //std::cout << "BPTD " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::BPTD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + + //if (mEditorId == "DefaultBodyPartData") + //std::cout << "BPTD: " << mEditorId << std::endl; // FIXME: testing only +} + +//void ESM4::BodyPartData::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::BodyPartData::blank() +//{ +//} diff --git a/components/esm4/loadbptd.hpp b/components/esm4/loadbptd.hpp new file mode 100644 index 0000000000..0a29cd22cb --- /dev/null +++ b/components/esm4/loadbptd.hpp @@ -0,0 +1,128 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_BPTD_H +#define ESM4_BPTD_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct BodyPartData + { +#pragma pack(push, 1) + struct BPND + { + float damageMult; + + // Severable + // IK Data + // IK Data - Biped Data + // Explodable + // IK Data - Is Head + // IK Data - Headtracking + // To Hit Chance - Absolute + std::uint8_t flags; + + // Torso + // Head + // Eye + // LookAt + // Fly Grab + // Saddle + std::uint8_t partType; + + std::uint8_t healthPercent; + std::int8_t actorValue; //(Actor Values) + std::uint8_t toHitChance; + + std::uint8_t explExplosionChance; // % + std::uint16_t explDebrisCount; + FormId explDebris; + FormId explExplosion; + float trackingMaxAngle; + float explDebrisScale; + + std::int32_t sevDebrisCount; + FormId sevDebris; + FormId sevExplosion; + float sevDebrisScale; + + //Struct - Gore Effects Positioning + float transX; + float transY; + float transZ; + float rotX; + float rotY; + float rotZ; + + FormId sevImpactDataSet; + FormId explImpactDataSet; + uint8_t sevDecalCount; + uint8_t explDecalCount; + uint16_t Unknown; + float limbReplacementScale; + }; +#pragma pack(pop) + + struct BodyPart + { + std::string mPartName; + std::string mNodeName; + std::string mVATSTarget; + std::string mIKStartNode; + BPND mData; + std::string mLimbReplacementModel; + std::string mGoreEffectsTarget; + + void clear(); + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + std::vector mBodyParts; + + BodyPartData(); + virtual ~BodyPartData(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_BPTD_H diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp new file mode 100644 index 0000000000..eb1f864b92 --- /dev/null +++ b/components/esm4/loadcell.cpp @@ -0,0 +1,269 @@ +/* + Copyright (C) 2015-2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadcell.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include +#include // FLT_MAX for gcc + +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Cell::Cell() : mParent(0), mFormId(0), mFlags(0), mCellFlags(0), mX(0), mY(0), mOwner(0), + mGlobal(0), mClimate(0), mWater(0), mWaterHeight(0.f), + mLightingTemplate(0), mLightingTemplateFlags(0), mMusic(0), mAcousticSpace(0), + mMusicType(0), mPreloaded(false) +{ + mEditorId.clear(); + mFullName.clear(); + + mLighting.ambient = 0; + mLighting.directional = 0; + mLighting.fogColor = 0; + mLighting.fogNear = 0.f; + mLighting.fogFar = 0.f; + mLighting.rotationXY = 0; + mLighting.rotationZ = 0; + mLighting.fogDirFade = 0.f; + mLighting.fogClipDist = 0.f; + mLighting.fogPower = FLT_MAX; // hack way to detect TES4 + + mRegions.clear(); +} + +ESM4::Cell::~Cell() +{ +} + +void ESM4::Cell::init(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currWorld(); + + reader.clearCellGrid(); // clear until XCLC FIXME: somehow do this automatically? + + // Sometimes cell 0,0 does not have an XCLC sub record (e.g. ToddLand 000009BF) + // To workaround this issue put a default value if group is "exterior sub cell" and its + // grid from label is "0 0". Note the reversed X/Y order (no matter since they're both 0 + // anyway). + if (reader.grp().type == ESM4::Grp_ExteriorSubCell + && reader.grp().label.grid[1] == 0 && reader.grp().label.grid[0] == 0) + { + ESM4::CellGrid currCellGrid; + currCellGrid.grid.x = 0; + currCellGrid.grid.y = 0; + reader.setCurrCellGrid(currCellGrid); // side effect: sets mCellGridValid true + } +} + +// TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) +// +// But, for external cells we may be scanning the whole record since we don't know if there is +// going to be an EDID subrecord. And the vast majority of cells are these kinds. +// +// So perhaps some testing needs to be done to see if scanning and skipping takes +// longer/shorter/same as loading the subrecords. +bool ESM4::Cell::preload(ESM4::Reader& reader) +{ + if (!mPreloaded) + load(reader); + + mPreloaded = true; + return true; +} + +void ESM4::Cell::load(ESM4::Reader& reader) +{ + if (mPreloaded) + return; + + // WARN: we need to call setCurrCell (and maybe setCurrCellGrid?) again before loading + // cell child groups if we are loading them after restoring the context + // (may be easier to update the context before saving?) + init(reader); + reader.setCurrCell(mFormId); // save for LAND (and other children) to access later + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: + { + if (!reader.getZString(mEditorId)) + throw std::runtime_error ("CELL EDID data read error"); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "CELL Editor ID: " << mEditorId << std::endl; +#endif + break; + } + case ESM4::SUB_XCLC: + { + //(X, Y) grid location of the cell followed by flags. Always in + //exterior cells and never in interior cells. + // + // int32 - X + // int32 - Y + // uint32 - flags (high bits look random) + // + // 0x1 - Force Hide Land Quad 1 + // 0x2 - Force Hide Land Quad 2 + // 0x4 - Force Hide Land Quad 3 + // 0x8 - Force Hide Land Quad 4 + uint32_t flags; + reader.get(mX); + reader.get(mY); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "CELL group " << ESM4::printLabel(reader.grp().label, reader.grp().type) << std::endl; + std::cout << padding << "CELL formId " << std::hex << reader.hdr().record.id << std::endl; + std::cout << padding << "CELL X " << std::dec << mX << ", Y " << mY << std::endl; +#endif + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + if (subHdr.dataSize == 12) + reader.get(flags); // not in Obvlivion, nor FO3/FONV + + // Remember cell grid for later (loading LAND, NAVM which should be CELL temporary children) + // Note that grids only apply for external cells. For interior cells use the cell's formid. + ESM4::CellGrid currCell; + currCell.grid.x = (int16_t)mX; + currCell.grid.y = (int16_t)mY; + reader.setCurrCellGrid(currCell); + + break; + } + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + if (subHdr.dataSize == 2) + reader.get(mCellFlags); + else + { + assert(subHdr.dataSize == 1 && "CELL unexpected DATA flag size"); + reader.get(&mCellFlags, sizeof(std::uint8_t)); + } + else + { + reader.get((std::uint8_t&)mCellFlags); // 8 bits in Obvlivion + } +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "flags: " << std::hex << mCellFlags << std::endl; +#endif + break; + } + case ESM4::SUB_XCLR: // for exterior cells + { + mRegions.resize(subHdr.dataSize/sizeof(FormId)); + for (std::vector::iterator it = mRegions.begin(); it != mRegions.end(); ++it) + { + reader.getFormId(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "region: " << std::hex << *it << std::endl; +#endif + } + break; + } + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.getFormId(mGlobal); break; // Oblivion only? + case ESM4::SUB_XCCM: reader.getFormId(mClimate); break; + case ESM4::SUB_XCWT: reader.getFormId(mWater); break; + case ESM4::SUB_XCLW: reader.get(mWaterHeight); break; + case ESM4::SUB_XCLL: + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + { + if (subHdr.dataSize == 40) // FO3/FONV + reader.get(mLighting); + else if (subHdr.dataSize == 92) // TES5 + { + reader.get(mLighting); + reader.skipSubRecordData(52); // FIXME + } + else + reader.skipSubRecordData(); + } + else + reader.get(&mLighting, 36); // TES4 + + break; + } + case ESM4::SUB_XCMT: reader.get(mMusicType); break; // Oblivion only? + case ESM4::SUB_LTMP: reader.getFormId(mLightingTemplate); break; + case ESM4::SUB_LNAM: reader.get(mLightingTemplateFlags); break; // seems to always follow LTMP + case ESM4::SUB_XCMO: reader.getFormId(mMusic); break; + case ESM4::SUB_XCAS: reader.getFormId(mAcousticSpace); break; + case ESM4::SUB_TVDT: + case ESM4::SUB_MHDT: + case ESM4::SUB_XCGD: + case ESM4::SUB_XNAM: + case ESM4::SUB_XLCN: + case ESM4::SUB_XWCS: + case ESM4::SUB_XWCU: + case ESM4::SUB_XWCN: + case ESM4::SUB_XCIM: + case ESM4::SUB_XEZN: + case ESM4::SUB_XWEM: + case ESM4::SUB_XILL: + case ESM4::SUB_XRNK: // Oblivion only? + case ESM4::SUB_XCET: // FO3 + case ESM4::SUB_IMPF: // FO3 Zeta + { + //std::cout << "CELL " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CELL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Cell::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Cell::blank() +{ +} diff --git a/components/esm4/loadcell.hpp b/components/esm4/loadcell.hpp new file mode 100644 index 0000000000..70dc94e73a --- /dev/null +++ b/components/esm4/loadcell.hpp @@ -0,0 +1,109 @@ +/* + Copyright (C) 2015-2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CELL_H +#define ESM4_CELL_H + +#include +#include +#include + +#include "formid.hpp" +#include "lighting.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + struct ReaderContext; + struct CellGroup; + typedef std::uint32_t FormId; + + enum CellFlags // TES4 TES5 + { // ----------------------- ------------------------------------ + CELL_Interior = 0x0001, // Can't travel from here Interior + CELL_HasWater = 0x0002, // Has water (Int) Has Water (Int) + CELL_NoTravel = 0x0004, // not Can't Travel From Here(Int only) + CELL_HideLand = 0x0008, // Force hide land (Ext) No LOD Water + // Oblivion interior (Int) + CELL_Public = 0x0020, // Public place Public Area + CELL_HandChgd = 0x0040, // Hand changed Hand Changed + CELL_QuasiExt = 0x0080, // Behave like exterior Show Sky + CELL_SkyLight = 0x0100 // Use Sky Lighting + }; + + // Unlike TES3, multiple cells can have the same exterior co-ordinates. + // The cells need to be organised under world spaces. + struct Cell + { + FormId mParent; // world formId (for grouping cells), from the loading sequence + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::uint16_t mCellFlags; // TES5 can also be 8 bits + + std::int32_t mX; + std::int32_t mY; + + FormId mOwner; + FormId mGlobal; + FormId mClimate; + FormId mWater; + float mWaterHeight; + + std::vector mRegions; + Lighting mLighting; + + FormId mLightingTemplate; // FO3/FONV + std::uint32_t mLightingTemplateFlags; // FO3/FONV + + FormId mMusic; // FO3/FONV + FormId mAcousticSpace; // FO3/FONV + // TES4: 0 = default, 1 = public, 2 = dungeon + // FO3/FONV have more types (not sure how they are used, however) + std::uint8_t mMusicType; + + CellGroup *mCellGroup; + + Cell(); + virtual ~Cell(); + + void init(ESM4::Reader& reader); // common setup for both preload() and load() + + bool mPreloaded; + bool preload(ESM4::Reader& reader); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_CELL_H diff --git a/components/esm4/loadclas.cpp b/components/esm4/loadclas.cpp new file mode 100644 index 0000000000..2948976709 --- /dev/null +++ b/components/esm4/loadclas.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadclas.hpp" + +//#ifdef NDEBUG // FIXME: debugging only +//#undef NDEBUG +//#endif + +//#include +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Class::Class() +{ + mEditorId.clear(); + mFullName.clear(); + mDesc.clear(); + mIcon.clear(); +} + +ESM4::Class::~Class() +{ +} + +void ESM4::Class::load(ESM4::Reader& reader) +{ + //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mDesc); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: + { + //std::cout << "CLAS " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CLAS::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Class::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Class::blank() +//{ +//} diff --git a/components/esm4/loadclas.hpp b/components/esm4/loadclas.hpp new file mode 100644 index 0000000000..83e8372f4a --- /dev/null +++ b/components/esm4/loadclas.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CLAS_H +#define ESM4_CLAS_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Class + { + struct Data + { + std::uint32_t attr; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mDesc; + std::string mIcon; + Data mData; + + Class(); + ~Class(); + + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& reader) const; + + //void blank(); + }; +} + +#endif // ESM4_CLAS_H diff --git a/components/esm4/loadclfm.cpp b/components/esm4/loadclfm.cpp new file mode 100644 index 0000000000..b926cb93fd --- /dev/null +++ b/components/esm4/loadclfm.cpp @@ -0,0 +1,93 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadclfm.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Colour::Colour() : mFormId(0), mFlags(0), mPlayable(0) +{ + mEditorId.clear(); + mFullName.clear(); + + mColour.red = 0; + mColour.green = 0; + mColour.blue = 0; + mColour.custom = 0; +} + +ESM4::Colour::~Colour() +{ +} + +void ESM4::Colour::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_CNAM: + { + reader.get(mColour.red); + reader.get(mColour.green); + reader.get(mColour.blue); + reader.get(mColour.custom); + + break; + } + case ESM4::SUB_FNAM: + { + reader.get(mPlayable); + + break; + } + default: + //std::cout << "CLFM " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::CLFM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Colour::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Colour::blank() +//{ +//} diff --git a/components/esm4/loadclfm.hpp b/components/esm4/loadclfm.hpp new file mode 100644 index 0000000000..2a18e42cd6 --- /dev/null +++ b/components/esm4/loadclfm.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CLFM_H +#define ESM4_CLFM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + // FIXME: duplicate with Npc + struct ColourRGB + { + std::uint8_t red; + std::uint8_t green; + std::uint8_t blue; + std::uint8_t custom; // alpha? + }; + + struct Colour + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + + ColourRGB mColour; + std::uint32_t mPlayable; + + Colour(); + virtual ~Colour(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CLFM_H diff --git a/components/esm4/loadclot.cpp b/components/esm4/loadclot.cpp new file mode 100644 index 0000000000..da6473c393 --- /dev/null +++ b/components/esm4/loadclot.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadclot.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Clothing::Clothing() : mFormId(0), mFlags(0), mBoundRadius(0.f), mClothingFlags(0), + mScriptId(0), mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModelMale.clear(); + mModelMaleWorld.clear(); + mModelFemale.clear(); + mModelFemaleWorld.clear(); + mIconMale.clear(); + mIconFemale.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Clothing::~Clothing() +{ +} + +void ESM4::Clothing::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_BMDT: reader.get(mClothingFlags); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODL: reader.getZString(mModelMale); break; + case ESM4::SUB_MOD2: reader.getZString(mModelMaleWorld); break; + case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; + case ESM4::SUB_MOD4: reader.getZString(mModelFemaleWorld); break; + case ESM4::SUB_ICON: reader.getZString(mIconMale); break; + case ESM4::SUB_ICO2: reader.getZString(mIconFemale); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MO2B: + case ESM4::SUB_MO3B: + case ESM4::SUB_MO4B: + case ESM4::SUB_MO2T: + case ESM4::SUB_MO3T: + case ESM4::SUB_MO4T: + { + //std::cout << "CLOT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CLOT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //if ((mClothingFlags&0xffff) == 0x02) // only hair + //std::cout << "only hair " << mEditorId << std::endl; +} + +//void ESM4::Clothing::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Clothing::blank() +//{ +//} diff --git a/components/esm4/loadclot.hpp b/components/esm4/loadclot.hpp new file mode 100644 index 0000000000..06e019137b --- /dev/null +++ b/components/esm4/loadclot.hpp @@ -0,0 +1,83 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CLOT_H +#define ESM4_CLOT_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Clothing + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModelMale; + std::string mModelMaleWorld; + std::string mModelFemale; + std::string mModelFemaleWorld; + std::string mIconMale; // texture + std::string mIconFemale; // texture + + std::string mModel; // FIXME: for OpenCS + + float mBoundRadius; + + std::uint32_t mClothingFlags; // see Armor::ArmorFlags for the values + FormId mScriptId; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Clothing(); + virtual ~Clothing(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CLOT_H diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp new file mode 100644 index 0000000000..06366c88cc --- /dev/null +++ b/components/esm4/loadcont.cpp @@ -0,0 +1,107 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadcont.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Container::Container() : mFormId(0), mFlags(0), mBoundRadius(0.f), mDataFlags(0), mWeight(0.f), + mOpenSound(0), mCloseSound(0), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Container::~Container() +{ +} + +void ESM4::Container::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + reader.get(mDataFlags); + reader.get(mWeight); + break; + } + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SNAM: reader.getFormId(mOpenSound); break; + case ESM4::SUB_QNAM: reader.getFormId(mCloseSound); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: // TES5 only + case ESM4::SUB_VMAD: // TES5 only + case ESM4::SUB_OBND: // TES5 only + case ESM4::SUB_COCT: // TES5 only + case ESM4::SUB_COED: // TES5 only + case ESM4::SUB_DEST: // FONV + case ESM4::SUB_DSTD: // FONV + case ESM4::SUB_DSTF: // FONV + case ESM4::SUB_DMDL: // FONV + case ESM4::SUB_DMDT: // FONV + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "CONT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CONT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Container::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Container::blank() +//{ +//} diff --git a/components/esm4/loadcont.hpp b/components/esm4/loadcont.hpp new file mode 100644 index 0000000000..e059d9a361 --- /dev/null +++ b/components/esm4/loadcont.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CONT_H +#define ESM4_CONT_H + +#include +#include +#include + +#include "formid.hpp" +#include "inventory.hpp" // InventoryItem + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Container + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + unsigned char mDataFlags; + float mWeight; + + FormId mOpenSound; + FormId mCloseSound; + FormId mScriptId; // TES4 only + + std::vector mInventory; + + Container(); + virtual ~Container(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CONT_H diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp new file mode 100644 index 0000000000..5fdd414256 --- /dev/null +++ b/components/esm4/loadcrea.cpp @@ -0,0 +1,232 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadcrea.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Creature::Creature() : mFormId(0), mFlags(0), mDeathItem(0), mScriptId(0), mCombatStyle(0), + mSoundBase(0), mSound(0), mSoundChance(0), mBaseScale(0.f), + mTurningSpeed(0.f), mFootWeight(0.f), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mBloodSpray.clear(); + mBloodDecal.clear(); + + mAIData.aggression = 0; + mAIData.confidence = 0; + mAIData.energyLevel = 0; + mAIData.responsibility = 0; + mAIData.aiFlags = 0; + mAIData.trainSkill = 0; + mAIData.trainLevel = 0; + + std::memset(&mData, 0, sizeof(Data)); +} + +ESM4::Creature::~Creature() +{ +} + +void ESM4::Creature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_SPLO: + { + FormId id; + reader.getFormId(id); + mSpell.push_back(id); + break; + } + case ESM4::SUB_PKID: + { + FormId id; + reader.getFormId(id); + mAIPackages.push_back(id); + break; + } + case ESM4::SUB_SNAM: + { + reader.get(mFaction); + reader.adjustFormId(mFaction.faction); + break; + } + case ESM4::SUB_INAM: reader.getFormId(mDeathItem); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_AIDT: + { + if (subHdr.dataSize == 20) // FO3 + reader.skipSubRecordData(); + else + reader.get(mAIData); // 12 bytes + break; + } + case ESM4::SUB_ACBS: + { + //if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) + if (subHdr.dataSize == 24) + reader.get(mBaseConfig); + else + reader.get(&mBaseConfig, 16); // TES4 + break; + } + case ESM4::SUB_DATA: + { + if (subHdr.dataSize == 17) // FO3 + reader.skipSubRecordData(); + else + reader.get(mData); + break; + } + case ESM4::SUB_ZNAM: reader.getFormId(mCombatStyle); break; + case ESM4::SUB_CSCR: reader.getFormId(mSoundBase); break; + case ESM4::SUB_CSDI: reader.getFormId(mSound); break; + case ESM4::SUB_CSDC: reader.get(mSoundChance); break; + case ESM4::SUB_BNAM: reader.get(mBaseScale); break; + case ESM4::SUB_TNAM: reader.get(mTurningSpeed); break; + case ESM4::SUB_WNAM: reader.get(mFootWeight); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_NAM0: reader.getZString(mBloodSpray); break; + case ESM4::SUB_NAM1: reader.getZString(mBloodDecal); break; + case ESM4::SUB_NIFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("CREA NIFZ data read error"); + + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mNif.push_back(file); + + break; + } + case ESM4::SUB_NIFT: + { + if (subHdr.dataSize != 4) // FIXME: FO3 + { + reader.skipSubRecordData(); + break; + } + + assert(subHdr.dataSize == 4 && "CREA NIFT datasize error"); + std::uint32_t nift; + reader.get(nift); + if (nift) + std::cout << "CREA NIFT " << mFormId << ", non-zero " << nift << std::endl; + break; + } + case ESM4::SUB_KFFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("CREA KFFZ data read error"); + + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mKf.push_back(file); + + break; + } + case ESM4::SUB_TPLT: reader.get(mBaseTemplate); break; // FO3 + case ESM4::SUB_PNAM: // FO3/FONV/TES5 + { + FormId bodyPart; + reader.get(bodyPart); + mBodyParts.push_back(bodyPart); + + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_RNAM: + case ESM4::SUB_CSDT: + case ESM4::SUB_OBND: // FO3 + case ESM4::SUB_EAMT: // FO3 + case ESM4::SUB_VTCK: // FO3 + case ESM4::SUB_NAM4: // FO3 + case ESM4::SUB_NAM5: // FO3 + case ESM4::SUB_CNAM: // FO3 + case ESM4::SUB_LNAM: // FO3 + case ESM4::SUB_EITM: // FO3 + case ESM4::SUB_DEST: // FO3 + case ESM4::SUB_DSTD: // FO3 + case ESM4::SUB_DSTF: // FO3 + case ESM4::SUB_DMDL: // FO3 + case ESM4::SUB_DMDT: // FO3 + case ESM4::SUB_COED: // FO3 + { + //std::cout << "CREA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CREA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Creature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Creature::blank() +//{ +//} diff --git a/components/esm4/loadcrea.hpp b/components/esm4/loadcrea.hpp new file mode 100644 index 0000000000..c2889d1713 --- /dev/null +++ b/components/esm4/loadcrea.hpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CREA_H +#define ESM4_CREA_H + +#include +#include +#include + +#include "actor.hpp" +#include "inventory.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Creature + { + enum ACBS_TES4 + { + TES4_Essential = 0x000002, + TES4_WeapAndShield = 0x000004, + TES4_Respawn = 0x000008, + TES4_PCLevelOffset = 0x000080, + TES4_NoLowLevelProc = 0x000200, + TES4_NoHead = 0x008000, // different meaning to npc_ + TES4_NoRightArm = 0x010000, + TES4_NoLeftArm = 0x020000, + TES4_NoCombatWater = 0x040000, + TES4_NoShadow = 0x080000, + TES4_NoCorpseCheck = 0x100000 // opposite of npc_ + }; + + enum ACBS_FO3 + { + FO3_Biped = 0x00000001, + FO3_Essential = 0x00000002, + FO3_Weap_Shield = 0x00000004, + FO3_Respawn = 0x00000008, + FO3_CanSwim = 0x00000010, + FO3_CanFly = 0x00000020, + FO3_CanWalk = 0x00000040, + FO3_PCLevelMult = 0x00000080, + FO3_NoLowLevelProc = 0x00000200, + FO3_NoBloodSpray = 0x00000800, + FO3_NoBloodDecal = 0x00001000, + FO3_NoHead = 0x00008000, + FO3_NoRightArm = 0x00010000, + FO3_NoLeftArm = 0x00020000, + FO3_NoWaterCombat = 0x00040000, + FO3_NoShadow = 0x00080000, + FO3_NoVATSMelee = 0x00100000, + FO3_AllowPCDialog = 0x00200000, + FO3_NoOpenDoors = 0x00400000, + FO3_Immobile = 0x00800000, + FO3_TiltFrontBack = 0x01000000, + FO3_TiltLeftRight = 0x02000000, + FO3_NoKnockdown = 0x04000000, + FO3_NotPushable = 0x08000000, + FO3_AllowPickpoket = 0x10000000, + FO3_IsGhost = 0x20000000, + FO3_NoRotateHead = 0x40000000, + FO3_Invulnerable = 0x80000000 + }; + +#pragma pack(push, 1) + struct Data + { + std::uint8_t unknown; + std::uint8_t combat; + std::uint8_t magic; + std::uint8_t stealth; + std::uint16_t soul; + std::uint16_t health; + std::uint16_t unknown2; + std::uint16_t damage; + AttributeValues attribs; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + FormId mDeathItem; + std::vector mSpell; + FormId mScriptId; + + AIData mAIData; + std::vector mAIPackages; + ActorBaseConfig mBaseConfig; + ActorFaction mFaction; + Data mData; + FormId mCombatStyle; + FormId mSoundBase; + FormId mSound; + std::uint8_t mSoundChance; + float mBaseScale; + float mTurningSpeed; + float mFootWeight; + std::string mBloodSpray; + std::string mBloodDecal; + + float mBoundRadius; + std::vector mNif; // NIF filenames, get directory from mModel + std::vector mKf; + + std::vector mInventory; + + FormId mBaseTemplate; // FO3/FONV + std::vector mBodyParts; // FO3/FONV + + Creature(); + virtual ~Creature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CREA_H diff --git a/components/esm4/loaddial.cpp b/components/esm4/loaddial.cpp new file mode 100644 index 0000000000..716da441d5 --- /dev/null +++ b/components/esm4/loaddial.cpp @@ -0,0 +1,125 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loaddial.hpp" + +#include +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Dialogue::Dialogue() : mFormId(0), mFlags(0), mDoAllBeforeRepeat(false), + mDialType(0), mDialFlags(0), mPriority(0.f) +{ + mEditorId.clear(); + mTopicName.clear(); + + mTextDumb.clear(); // FIXME: temp name +} + +ESM4::Dialogue::~Dialogue() +{ +} + +void ESM4::Dialogue::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mTopicName); break; + case ESM4::SUB_QSTI: + { + FormId questId; + reader.getFormId(questId); + mQuests.push_back(questId); + + break; + } + case ESM4::SUB_QSTR: // Seems never used in TES4 + { + FormId questRem; + reader.getFormId(questRem); + mQuestsRemoved.push_back(questRem); + + break; + } + case ESM4::SUB_DATA: + { + if (subHdr.dataSize == 4) // TES5 + { + std::uint8_t dummy; + reader.get(dummy); + if (dummy != 0) + mDoAllBeforeRepeat = true; + } + + reader.get(mDialType); // TES4/FO3/FONV/TES5 + + if (subHdr.dataSize >= 2) // FO3/FONV/TES5 + reader.get(mDialFlags); + + if (subHdr.dataSize >= 3) // TES5 + reader.skipSubRecordData(1); // unknown + + break; + } + case ESM4::SUB_PNAM: reader.get(mPriority); break; // FO3/FONV + case ESM4::SUB_TDUM: reader.getZString(mTextDumb); break; // FONV + case ESM4::SUB_SCRI: + case ESM4::SUB_INFC: // FONV info connection + case ESM4::SUB_INFX: // FONV info index + case ESM4::SUB_QNAM: // TES5 + case ESM4::SUB_BNAM: // TES5 + case ESM4::SUB_SNAM: // TES5 + case ESM4::SUB_TIFC: // TES5 + { + //std::cout << "DIAL " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::DIAL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Dialogue::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Dialogue::blank() +//{ +//} diff --git a/components/esm4/loaddial.hpp b/components/esm4/loaddial.hpp new file mode 100644 index 0000000000..ef44c841fc --- /dev/null +++ b/components/esm4/loaddial.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_DIAL_H +#define ESM4_DIAL_H + +#include +#include +#include + +#include "formid.hpp" +#include "dialogue.hpp" // DialType + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Dialogue + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::vector mQuests; + std::vector mQuestsRemoved; // FONV only? + std::string mTopicName; + + std::string mTextDumb; // FIXME: temp name + + bool mDoAllBeforeRepeat; // TES5 only + std::uint8_t mDialType; // DialType + std::uint8_t mDialFlags; // FO3/FONV: 0x1 rumours, 0x2 top-level + + float mPriority; + + Dialogue(); + virtual ~Dialogue(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_DIAL_H diff --git a/components/esm4/loaddobj.cpp b/components/esm4/loaddobj.cpp new file mode 100644 index 0000000000..307674a5db --- /dev/null +++ b/components/esm4/loaddobj.cpp @@ -0,0 +1,128 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loaddobj.hpp" + +#include +#include +//#include // FIXME: for debugging only + +//#include "formid.hpp" + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::DefaultObj::DefaultObj() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + std::memset(&mData, 0, sizeof(Defaults)); +} + +ESM4::DefaultObj::~DefaultObj() +{ +} + +void ESM4::DefaultObj::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; // "DefaultObjectManager" + case ESM4::SUB_DATA: + { + reader.getFormId(mData.stimpack); + reader.getFormId(mData.superStimpack); + reader.getFormId(mData.radX); + reader.getFormId(mData.radAway); + reader.getFormId(mData.morphine); + reader.getFormId(mData.perkParalysis); + reader.getFormId(mData.playerFaction); + reader.getFormId(mData.mysteriousStrangerNPC); + reader.getFormId(mData.mysteriousStrangerFaction); + reader.getFormId(mData.defaultMusic); + reader.getFormId(mData.battleMusic); + reader.getFormId(mData.deathMusic); + reader.getFormId(mData.successMusic); + reader.getFormId(mData.levelUpMusic); + reader.getFormId(mData.playerVoiceMale); + reader.getFormId(mData.playerVoiceMaleChild); + reader.getFormId(mData.playerVoiceFemale); + reader.getFormId(mData.playerVoiceFemaleChild); + reader.getFormId(mData.eatPackageDefaultFood); + reader.getFormId(mData.everyActorAbility); + reader.getFormId(mData.drugWearsOffImageSpace); + // below FONV only + if (subHdr.dataSize == 136) // FONV 136/4 = 34 formid + { + reader.getFormId(mData.doctorsBag); + reader.getFormId(mData.missFortuneNPC); + reader.getFormId(mData.missFortuneFaction); + reader.getFormId(mData.meltdownExplosion); + reader.getFormId(mData.unarmedForwardPA); + reader.getFormId(mData.unarmedBackwardPA); + reader.getFormId(mData.unarmedLeftPA); + reader.getFormId(mData.unarmedRightPA); + reader.getFormId(mData.unarmedCrouchPA); + reader.getFormId(mData.unarmedCounterPA); + reader.getFormId(mData.spotterEffect); + reader.getFormId(mData.itemDetectedEfect); + reader.getFormId(mData.cateyeMobileEffectNYI); + } + + break; + } + case ESM4::SUB_DNAM: + { + //std::cout << "DOBJ " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + //std::cout << "DOBJ " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::DOBJ::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::DefaultObj::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::DefaultObj::blank() +//{ +//} diff --git a/components/esm4/loaddobj.hpp b/components/esm4/loaddobj.hpp new file mode 100644 index 0000000000..6d708fcbbc --- /dev/null +++ b/components/esm4/loaddobj.hpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_DOBJ_H +#define ESM4_DOBJ_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Defaults + { + FormId stimpack; + FormId superStimpack; + FormId radX; + FormId radAway; + FormId morphine; + FormId perkParalysis; + FormId playerFaction; + FormId mysteriousStrangerNPC; + FormId mysteriousStrangerFaction; + FormId defaultMusic; + FormId battleMusic; + FormId deathMusic; + FormId successMusic; + FormId levelUpMusic; + FormId playerVoiceMale; + FormId playerVoiceMaleChild; + FormId playerVoiceFemale; + FormId playerVoiceFemaleChild; + FormId eatPackageDefaultFood; + FormId everyActorAbility; + FormId drugWearsOffImageSpace; + // below FONV only + FormId doctorsBag; + FormId missFortuneNPC; + FormId missFortuneFaction; + FormId meltdownExplosion; + FormId unarmedForwardPA; + FormId unarmedBackwardPA; + FormId unarmedLeftPA; + FormId unarmedRightPA; + FormId unarmedCrouchPA; + FormId unarmedCounterPA; + FormId spotterEffect; + FormId itemDetectedEfect; + FormId cateyeMobileEffectNYI; + }; + + struct DefaultObj + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + Defaults mData; + + DefaultObj(); + virtual ~DefaultObj(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_DOBJ_H diff --git a/components/esm4/loaddoor.cpp b/components/esm4/loaddoor.cpp new file mode 100644 index 0000000000..45ac1f0b6c --- /dev/null +++ b/components/esm4/loaddoor.cpp @@ -0,0 +1,93 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loaddoor.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Door::Door() : mFormId(0), mFlags(0), mBoundRadius(0.f), mDoorFlags(0), mScriptId(0), + mOpenSound(0), mCloseSound(0), mLoopSound(0), mRandomTeleport(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Door::~Door() +{ +} + +void ESM4::Door::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_SNAM: reader.getFormId(mOpenSound); break; + case ESM4::SUB_ANAM: reader.getFormId(mCloseSound); break; + case ESM4::SUB_BNAM: reader.getFormId(mLoopSound); break; + case ESM4::SUB_FNAM: reader.get(mDoorFlags); break; + case ESM4::SUB_TNAM: reader.getFormId(mRandomTeleport); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + case ESM4::SUB_DEST: // FO3 + case ESM4::SUB_DSTD: // FO3 + case ESM4::SUB_DSTF: // FO3 + case ESM4::SUB_DMDL: // FO3 + case ESM4::SUB_DMDT: // FO3 + { + //std::cout << "DOOR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::DOOR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Door::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Door::blank() +//{ +//} diff --git a/components/esm4/loaddoor.hpp b/components/esm4/loaddoor.hpp new file mode 100644 index 0000000000..0a332339d5 --- /dev/null +++ b/components/esm4/loaddoor.hpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_DOOR_H +#define ESM4_DOOR_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Door + { + enum Flags + { + Flag_OblivionGate = 0x01, + Flag_AutomaticDoor = 0x02, + Flag_Hidden = 0x04, + Flag_MinimalUse = 0x08 + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + std::uint8_t mDoorFlags; + FormId mScriptId; + FormId mOpenSound; // SNDR for TES5, SOUN for others + FormId mCloseSound; // SNDR for TES5, SOUN for others + FormId mLoopSound; + FormId mRandomTeleport; + + Door(); + virtual ~Door(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_DOOR_H diff --git a/components/esm4/loadeyes.cpp b/components/esm4/loadeyes.cpp new file mode 100644 index 0000000000..059bee6c37 --- /dev/null +++ b/components/esm4/loadeyes.cpp @@ -0,0 +1,74 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadeyes.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Eyes::Eyes() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mIcon.clear(); + + mData.flags = 0; +} + +ESM4::Eyes::~Eyes() +{ +} + +void ESM4::Eyes::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + default: + throw std::runtime_error("ESM4::EYES::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Eyes::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Eyes::blank() +//{ +//} diff --git a/components/esm4/loadeyes.hpp b/components/esm4/loadeyes.hpp new file mode 100644 index 0000000000..814e2a9647 --- /dev/null +++ b/components/esm4/loadeyes.hpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_EYES_H +#define ESM4_EYES_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Eyes + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = playable? + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mIcon; // texture + + Data mData; + + Eyes(); + virtual ~Eyes(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_EYES_H diff --git a/components/esm4/loadflor.cpp b/components/esm4/loadflor.cpp new file mode 100644 index 0000000000..fab6c3f0ed --- /dev/null +++ b/components/esm4/loadflor.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadflor.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Flora::Flora() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mIngredient(0), + mSound(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Flora::~Flora() +{ +} + +void ESM4::Flora::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_PFIG: reader.getFormId(mIngredient); break; + case ESM4::SUB_PFPC: reader.get(mPercentHarvest); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_FNAM: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_RNAM: + case ESM4::SUB_VMAD: + { + //std::cout << "FLOR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::FLOR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Flora::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Flora::blank() +//{ +//} diff --git a/components/esm4/loadflor.hpp b/components/esm4/loadflor.hpp new file mode 100644 index 0000000000..2f9e22d548 --- /dev/null +++ b/components/esm4/loadflor.hpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_FLOR_H +#define ESM4_FLOR_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Flora + { +#pragma pack(push, 1) + struct Production + { + std::uint8_t spring; + std::uint8_t summer; + std::uint8_t autumn; + std::uint8_t winter; + + Production() : spring(0), summer(0), autumn(0), winter(0) {} + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + FormId mScriptId; + FormId mIngredient; + FormId mSound; + Production mPercentHarvest; + + Flora(); + virtual ~Flora(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_FLOR_H diff --git a/components/esm4/loadflst.cpp b/components/esm4/loadflst.cpp new file mode 100644 index 0000000000..32b23ff284 --- /dev/null +++ b/components/esm4/loadflst.cpp @@ -0,0 +1,81 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadflst.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::FormIdList::FormIdList() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::FormIdList::~FormIdList() +{ +} + +void ESM4::FormIdList::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_LNAM: + { + FormId formId; + reader.getFormId(formId); + + mObjects.push_back(formId); + + break; + } + default: + //std::cout << "FLST " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::FLST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //std::cout << "flst " << mEditorId << " " << mObjects.size() << std::endl; // FIXME +} + +//void ESM4::FormIdList::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::FormIdList::blank() +//{ +//} diff --git a/components/esm4/loadflst.hpp b/components/esm4/loadflst.hpp new file mode 100644 index 0000000000..6fd381aa44 --- /dev/null +++ b/components/esm4/loadflst.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_FLST_H +#define ESM4_FLST_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct FormIdList + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::vector mObjects; + + FormIdList(); + virtual ~FormIdList(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_FLST_H diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp new file mode 100644 index 0000000000..6fba8bc3ce --- /dev/null +++ b/components/esm4/loadfurn.cpp @@ -0,0 +1,98 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadfurn.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Furniture::Furniture() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), + mActiveMarkerFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Furniture::~Furniture() +{ +} + +void ESM4::Furniture::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MNAM: reader.get(mActiveMarkerFlags); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_DEST: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + case ESM4::SUB_ENAM: + case ESM4::SUB_FNAM: + case ESM4::SUB_FNMK: + case ESM4::SUB_FNPR: + case ESM4::SUB_KNAM: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MODS: + case ESM4::SUB_NAM0: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_WBDT: + case ESM4::SUB_XMRK: + { + //std::cout << "FURN " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::FURN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Furniture::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Furniture::blank() +//{ +//} diff --git a/components/esm4/loadfurn.hpp b/components/esm4/loadfurn.hpp new file mode 100644 index 0000000000..89da61ebf9 --- /dev/null +++ b/components/esm4/loadfurn.hpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_FURN_H +#define ESM4_FURN_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Furniture + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + FormId mScriptId; + std::uint32_t mActiveMarkerFlags; + + Furniture(); + virtual ~Furniture(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_FURN_H diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp new file mode 100644 index 0000000000..6a2fa8b00a --- /dev/null +++ b/components/esm4/loadglob.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadglob.hpp" + +#include +#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::GlobalVariable::GlobalVariable() : mFormId(0), mFlags(0), mType(0), mValue(0.f) +{ + mEditorId.clear(); +} + +ESM4::GlobalVariable::~GlobalVariable() +{ +} + +void ESM4::GlobalVariable::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FNAM: reader.get(mType); break; + case ESM4::SUB_FLTV: reader.get(mValue); break; + case ESM4::SUB_FULL: + case ESM4::SUB_MODL: + case ESM4::SUB_MODB: + case ESM4::SUB_ICON: + case ESM4::SUB_DATA: + case ESM4::SUB_OBND: // TES5 + case ESM4::SUB_VMAD: // TES5 + { + //std::cout << "GLOB " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::GLOB::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::GlobalVariable::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::GlobalVariable::blank() +//{ +//} diff --git a/components/esm4/loadglob.hpp b/components/esm4/loadglob.hpp new file mode 100644 index 0000000000..15cf4a07ac --- /dev/null +++ b/components/esm4/loadglob.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_GLOB_H +#define ESM4_GLOB_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct GlobalVariable + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::uint8_t mType; + float mValue; + + GlobalVariable(); + virtual ~GlobalVariable(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_GLOB_H diff --git a/components/esm4/loadgras.cpp b/components/esm4/loadgras.cpp new file mode 100644 index 0000000000..3818654fc1 --- /dev/null +++ b/components/esm4/loadgras.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadgras.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Grass::Grass() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Grass::~Grass() +{ +} + +void ESM4::Grass::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + { + //std::cout << "GRAS " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::GRAS::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Grass::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Grass::blank() +//{ +//} diff --git a/components/esm4/loadgras.hpp b/components/esm4/loadgras.hpp new file mode 100644 index 0000000000..d7754d03d9 --- /dev/null +++ b/components/esm4/loadgras.hpp @@ -0,0 +1,98 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_GRAS_H +#define ESM4_GRAS_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Grass + { +#pragma pack(push, 1) + // unused fields are probably packing + struct Data + { + std::uint8_t density; + std::uint8_t minSlope; + std::uint8_t maxSlope; + std::uint8_t unused; + std::uint16_t distanceFromWater; + std::uint16_t unused2; + /* + 1 Above - At Least + 2 Above - At Most + 3 Below - At Least + 4 Below - At Most + 5 Either - At Least + 6 Either - At Most + 7 Either - At Most Above + 8 Either - At Most Below + */ + std::uint32_t waterDistApplication; + float positionRange; + float heightRange; + float colorRange; + float wavePeriod; + /* + 0x01 Vertex Lighting + 0x02 Uniform Scaling + 0x04 Fit to Slope + */ + std::uint8_t flags; + std::uint8_t unused3; + std::uint16_t unused4; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + Data mData; + + Grass(); + virtual ~Grass(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_GRAS_H diff --git a/components/esm4/loadgrup.hpp b/components/esm4/loadgrup.hpp new file mode 100644 index 0000000000..8eb53a0e58 --- /dev/null +++ b/components/esm4/loadgrup.hpp @@ -0,0 +1,158 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_GRUP_H +#define ESM4_GRUP_H + +#include +#include + +#include "common.hpp" // GroupLabel + +namespace ESM4 +{ + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format#Hierarchical_Top_Groups + // + // Type | Info | + // ------+--------------------------------------+------------------- + // 2 | Interior Cell Block | + // 3 | Interior Cell Sub-Block | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // | | + // 0 | Top (Type) | + // R | WRLD | + // 1 | World Children | + // R | ROAD | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // 4 | Exterior World Block | + // 5 | Exterior World Sub-block | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | LAND | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // + struct WorldGroup + { + FormId mWorld; // WRLD record for this group + + // occurs only after World Child (type 1) + // since GRUP label may not be reliable, need to keep the formid of the current WRLD in + // the reader's context + FormId mRoad; + + std::vector mCells; // FIXME should this be CellGroup* instead? + + WorldGroup() : mWorld(0), mRoad(0) {} + }; + + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format/CELL + // + // The block and subblock groups for an interior cell are determined by the last two decimal + // digits of the lower 3 bytes of the cell form ID (the modindex is not included in the + // calculation). For example, for form ID 0x000CF2=3314, the block is 4 and the subblock is 1. + // + // The block and subblock groups for an exterior cell are determined by the X-Y coordinates of + // the cell. Each block contains 16 subblocks (4x4) and each subblock contains 64 cells (8x8). + // So each block contains 1024 cells (32x32). + // + // NOTE: There may be many CELL records in one subblock + struct CellGroup + { + FormId mCell; // CELL record for this cell group + int mCellModIndex; // from which file to get the CELL record (e.g. may have been updated) + + // For retrieving parent group size (for lazy loading or skipping) and sub-block number / grid + // NOTE: There can be more than one file that adds/modifies records to this cell group + // + // Use Case 1: To quickly get only the visble when distant records: + // + // - Find the FormId of the CELL (maybe WRLD/X/Y grid lookup or from XTEL of a REFR) + // - search a map of CELL FormId to CellGroup + // - load CELL and its child groups (or load the visible distant only, or whatever) + // + // Use Case 2: Scan the files but don't load CELL or cell group + // + // - Load referenceables and other records up front, updating them as required + // - Don't load CELL, LAND, PGRD or ROAD (keep FormId's and file index, and file + // context then skip the rest of the group) + // + std::vector mHeaders; // FIXME: is this needed? + + // FIXME: should these be pairs? i.e. so that we know from which file + // the formid came (it may have been updated by a mod) + // but does it matter? the record itself keeps track of whether it is base, + // added or modified anyway + // FIXME: should these be maps? e.g. std::map + // or vector for storage with a corresponding map of index? + + // cache (modindex adjusted) formId's of children + // FIXME: also need file index + file context of all those that has type 8 GRUP + GroupTypeHeader mHdrPersist; + std::vector mPersistent; // REFR, ACHR, ACRE + std::vector mdelPersistent; + + // FIXME: also need file index + file context of all those that has type 10 GRUP + GroupTypeHeader mHdrVisDist; + std::vector mVisibleDist; // REFR, ACHR, ACRE + std::vector mdelVisibleDist; + + // FIXME: also need file index + file context of all those that has type 9 GRUP + GroupTypeHeader mHdrTemp; + FormId mLand; // if present, assume only one LAND per exterior CELL + FormId mPgrd; // if present, seems to be the first record after LAND in Temp Cell Child GRUP + std::vector mTemporary; // REFR, ACHR, ACRE + std::vector mdelTemporary; + + // need to keep modindex and context for lazy loading (of all the files that contribute + // to this group) + + CellGroup() : mCell(0), mLand(0), mPgrd(0) {} + }; +} + +#endif // ESM4_GRUP_H diff --git a/components/esm4/loadhair.cpp b/components/esm4/loadhair.cpp new file mode 100644 index 0000000000..661c333cbd --- /dev/null +++ b/components/esm4/loadhair.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadhair.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Hair::Hair() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.flags = 0; +} + +ESM4::Hair::~Hair() +{ +} + +void ESM4::Hair::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + { + //std::cout << "HAIR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::HAIR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Hair::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Hair::blank() +//{ +//} diff --git a/components/esm4/loadhair.hpp b/components/esm4/loadhair.hpp new file mode 100644 index 0000000000..c9a4fc2756 --- /dev/null +++ b/components/esm4/loadhair.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_HAIR +#define ESM4_HAIR + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Hair + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = not playable, 0x02 = not male, 0x04 = not female, ?? = fixed + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; // mesh + std::string mIcon; // texture + + float mBoundRadius; + + Data mData; + + Hair(); + virtual ~Hair(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_HAIR diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp new file mode 100644 index 0000000000..f188890246 --- /dev/null +++ b/components/esm4/loadhdpt.cpp @@ -0,0 +1,104 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadhdpt.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::HeadPart::HeadPart() : mFormId(0), mFlags(0), mData(0), mAdditionalPart(0), mBaseTexture(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mTriFile.resize(3); +} + +ESM4::HeadPart::~HeadPart() +{ +} + +void ESM4::HeadPart::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t type; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_HNAM: reader.getFormId(mAdditionalPart); break; + case ESM4::SUB_NAM0: // TES5 + { + reader.get(type); + + break; + } + case ESM4::SUB_NAM1: // TES5 + { + std::string file; + reader.getZString(file); + + // FIXME: check type >= 0 && type < 3 + mTriFile[type] = std::move(file); + + break; + } + case ESM4::SUB_TNAM: reader.getFormId(mBaseTexture); break; + case ESM4::SUB_PNAM: + case ESM4::SUB_MODS: + case ESM4::SUB_MODT: + case ESM4::SUB_RNAM: + { + //std::cout << "HDPT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::HDPT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::HeadPart::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::HeadPart::blank() +//{ +//} diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp new file mode 100644 index 0000000000..d93411bb7e --- /dev/null +++ b/components/esm4/loadhdpt.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_HDPT_H +#define ESM4_HDPT_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct HeadPart + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + std::uint8_t mData; + + FormId mAdditionalPart; + + std::vector mTriFile; + FormId mBaseTexture; + + HeadPart(); + virtual ~HeadPart(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_HDPT_H diff --git a/components/esm4/loadidle.cpp b/components/esm4/loadidle.cpp new file mode 100644 index 0000000000..3d45b76e82 --- /dev/null +++ b/components/esm4/loadidle.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadidle.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::IdleAnimation::IdleAnimation() : mFormId(0), mFlags(0), mParent(0), mPrevious(0) +{ + mEditorId.clear(); + mCollision.clear(); + mEvent.clear(); +} + +ESM4::IdleAnimation::~IdleAnimation() +{ +} + +void ESM4::IdleAnimation::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_DNAM: reader.getZString(mCollision); break; + case ESM4::SUB_ENAM: reader.getZString(mEvent); break; + case ESM4::SUB_ANAM: + { + reader.get(mParent); + reader.get(mPrevious); + break; + } + case ESM4::SUB_CTDA: // formId + case ESM4::SUB_DATA: // formId + { + //std::cout << "IDLE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::IDLE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::IdleAnimation::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::IdleAnimation::blank() +//{ +//} diff --git a/components/esm4/loadidle.hpp b/components/esm4/loadidle.hpp new file mode 100644 index 0000000000..cadd056122 --- /dev/null +++ b/components/esm4/loadidle.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_IDLE_H +#define ESM4_IDLE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct IdleAnimation + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mCollision; + std::string mEvent; + + FormId mParent; // IDLE or AACT + FormId mPrevious; + + IdleAnimation(); + virtual ~IdleAnimation(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_IDLE_H diff --git a/components/esm4/loadidlm.cpp b/components/esm4/loadidlm.cpp new file mode 100644 index 0000000000..ba823df300 --- /dev/null +++ b/components/esm4/loadidlm.cpp @@ -0,0 +1,103 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadidlm.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::IdleMarker::IdleMarker() : mFormId(0), mFlags(0), mIdleFlags(0), mIdleCount(0), mIdleTimer(0.f), mIdleAnim(0) +{ + mEditorId.clear(); +} + +ESM4::IdleMarker::~IdleMarker() +{ +} + +void ESM4::IdleMarker::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_IDLF: reader.get(mIdleFlags); break; + case ESM4::SUB_IDLC: + { + if (subHdr.dataSize != 1) // FO3 can have 4? + { + reader.skipSubRecordData(); + break; + } + + reader.get(mIdleCount); + break; + } + case ESM4::SUB_IDLT: reader.get(mIdleTimer); break; + case ESM4::SUB_IDLA: + { + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + if (esmVer == ESM::VER_094 || isFONV) // FO3? 4 or 8 bytes + { + reader.skipSubRecordData(); + break; + } + + mIdleAnim.resize(mIdleCount); + for (unsigned int i = 0; i < static_cast(mIdleCount); ++i) + reader.get(mIdleAnim.at(i)); + break; + } + case ESM4::SUB_OBND: // object bounds + { + //std::cout << "IDLM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::IDLM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::IdleMarker::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::IdleMarker::blank() +//{ +//} diff --git a/components/esm4/loadidlm.hpp b/components/esm4/loadidlm.hpp new file mode 100644 index 0000000000..78d03a7024 --- /dev/null +++ b/components/esm4/loadidlm.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_IDLM_H +#define ESM4_IDLM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct IdleMarker + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + std::uint8_t mIdleFlags; + std::uint8_t mIdleCount; + float mIdleTimer; + std::vector mIdleAnim; + + IdleMarker(); + virtual ~IdleMarker(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_IDLM_H diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp new file mode 100644 index 0000000000..9950c59b7b --- /dev/null +++ b/components/esm4/loadimod.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadimod.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ItemMod::ItemMod() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::ItemMod::~ItemMod() +{ +} + +void ESM4::ItemMod::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_OBND: + case ESM4::SUB_FULL: + case ESM4::SUB_MODL: + case ESM4::SUB_ICON: + case ESM4::SUB_MICO: + case ESM4::SUB_SCRI: + case ESM4::SUB_DESC: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_DATA: + { + //std::cout << "IMOD " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "IMOD " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::IMOD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ItemMod::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ItemMod::blank() +//{ +//} diff --git a/components/esm4/loadimod.hpp b/components/esm4/loadimod.hpp new file mode 100644 index 0000000000..a72fbe472d --- /dev/null +++ b/components/esm4/loadimod.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_IMOD_H +#define ESM4_IMOD_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ItemMod + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + ItemMod(); + virtual ~ItemMod(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_IMOD_H diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp new file mode 100644 index 0000000000..a68774112b --- /dev/null +++ b/components/esm4/loadinfo.cpp @@ -0,0 +1,222 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadinfo.hpp" + +#include +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::DialogInfo::DialogInfo() : mFormId(0), mFlags(0), mQuest(0), mSound(0), + mDialType(0), mNextSpeaker(0), mInfoFlags(0), mParam3(0) +{ + std::memset(&mResponseData, 0, sizeof(TargetResponseData)); + mResponse.clear(); + mNotes.clear(); + mEdits.clear(); + + std::memset(&mTargetCondition, 0, sizeof(TargetCondition)); + + std::memset(&mScript.scriptHeader, 0, sizeof(ScriptHeader)); + mScript.scriptSource.clear(); + mScript.globReference = 0; +} + +ESM4::DialogInfo::~DialogInfo() +{ +} + +void ESM4::DialogInfo::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code + + static ScriptLocalVariableData localVar; + bool ignore = false; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_QSTI: reader.getFormId(mQuest); break; // FormId quest id + case ESM4::SUB_SNDD: reader.getFormId(mSound); break; // FO3 (not used in FONV?) + case ESM4::SUB_TRDT: + { + if (subHdr.dataSize == 16) // TES4 + reader.get(&mResponseData, 16); + else if (subHdr.dataSize == 20) // FO3 + reader.get(&mResponseData, 20); + else // FO3/FONV + { + reader.get(mResponseData); + if (mResponseData.sound) + reader.adjustFormId(mResponseData.sound); + } + + break; + } + case ESM4::SUB_NAM1: reader.getZString(mResponse); break; // response text + case ESM4::SUB_NAM2: reader.getZString(mNotes); break; // actor notes + case ESM4::SUB_NAM3: reader.getZString(mEdits); break; // not in TES4 + case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + { + if (subHdr.dataSize == 24) // TES4 + reader.get(&mTargetCondition, 24); + else if (subHdr.dataSize == 20) // FO3 + reader.get(&mTargetCondition, 20); + else if (subHdr.dataSize == 28) + { + reader.get(mTargetCondition); // FO3/FONV + if (mTargetCondition.reference) + reader.adjustFormId(mTargetCondition.reference); + } + else // TES5 + { + reader.get(&mTargetCondition, 20); + if (subHdr.dataSize == 36) + reader.getFormId(mParam3); + reader.get(mTargetCondition.runOn); + reader.get(mTargetCondition.reference); + if (mTargetCondition.reference) + reader.adjustFormId(mTargetCondition.reference); + reader.skipSubRecordData(4); // unknown + } + + break; + } + case ESM4::SUB_SCHR: + { + if (!ignore) + reader.get(mScript.scriptHeader); + else + reader.skipSubRecordData(); // TODO: does the second one ever used? + + break; + } + case ESM4::SUB_SCDA: reader.skipSubRecordData(); break; // compiled script data + case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); break; + case ESM4::SUB_SCRO: reader.getFormId(mScript.globReference); break; + case ESM4::SUB_SLSD: + { + localVar.clear(); + reader.get(localVar.index); + reader.get(localVar.unknown1); + reader.get(localVar.unknown2); + reader.get(localVar.unknown3); + reader.get(localVar.type); + reader.get(localVar.unknown4); + // WARN: assumes SCVR will follow immediately + + break; + } + case ESM4::SUB_SCVR: // assumed always pair with SLSD + { + reader.getZString(localVar.variableName); + + mScript.localVarData.push_back(localVar); + + break; + } + case ESM4::SUB_SCRV: + { + std::uint32_t index; + reader.get(index); + + mScript.localRefVarIndex.push_back(index); + + break; + } + case ESM4::SUB_NEXT: // FO3/FONV marker for next script header + { + ignore = true; + + break; + } + case ESM4::SUB_DATA: // always 3 for TES4 ? + { + if (subHdr.dataSize == 4) // FO3/FONV + { + reader.get(mDialType); + reader.get(mNextSpeaker); + reader.get(mInfoFlags); + } + else + reader.skipSubRecordData(); // FIXME + break; + } + case ESM4::SUB_NAME: // FormId add topic (not always present) + case ESM4::SUB_CTDT: // older version of CTDA? 20 bytes + case ESM4::SUB_SCHD: // 28 bytes + case ESM4::SUB_TCLT: // FormId choice + case ESM4::SUB_TCLF: // FormId + case ESM4::SUB_PNAM: // TES4 DLC + case ESM4::SUB_TPIC: // TES4 DLC + case ESM4::SUB_ANAM: // FO3 speaker formid + case ESM4::SUB_DNAM: // FO3 speech challenge + case ESM4::SUB_KNAM: // FO3 formid + case ESM4::SUB_LNAM: // FONV + case ESM4::SUB_TCFU: // FONV + case ESM4::SUB_TIFC: // TES5 + case ESM4::SUB_TWAT: // TES5 + case ESM4::SUB_CIS2: // TES5 + case ESM4::SUB_CNAM: // TES5 + case ESM4::SUB_ENAM: // TES5 + case ESM4::SUB_EDID: // TES5 + case ESM4::SUB_VMAD: // TES5 + case ESM4::SUB_BNAM: // TES5 + case ESM4::SUB_SNAM: // TES5 + case ESM4::SUB_ONAM: // TES5 + case ESM4::SUB_QNAM: // TES5 for mScript + case ESM4::SUB_RNAM: // TES5 + { + //std::cout << "INFO " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "INFO " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::INFO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::DialogInfo::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::DialogInfo::blank() +//{ +//} diff --git a/components/esm4/loadinfo.hpp b/components/esm4/loadinfo.hpp new file mode 100644 index 0000000000..be7090cd32 --- /dev/null +++ b/components/esm4/loadinfo.hpp @@ -0,0 +1,90 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_INFO_H +#define ESM4_INFO_H + +#include +#include + +#include "formid.hpp" +#include "script.hpp" // TargetCondition +#include "dialogue.hpp" // DialType + +namespace ESM4 +{ + class Reader; + class Writer; + + enum InfoFlag + { + INFO_Goodbye = 0x0001, + INFO_Random = 0x0002, + INFO_SayOnce = 0x0004, + INFO_RunImmediately = 0x0008, + INFO_InfoRefusal = 0x0010, + INFO_RandomEnd = 0x0020, + INFO_RunForRumors = 0x0040, + INFO_SpeechChallenge = 0x0080, + INFO_SayOnceADay = 0x0100, + INFO_AlwaysDarken = 0x0200 + }; + + struct DialogInfo + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; // FIXME: no such record for INFO, but keep here to avoid extra work for now + + FormId mQuest; + FormId mSound; // unused? + + TargetResponseData mResponseData; + std::string mResponse; + std::string mNotes; + std::string mEdits; + + std::uint8_t mDialType; // DialType + std::uint8_t mNextSpeaker; + std::uint16_t mInfoFlags; // see above enum + + TargetCondition mTargetCondition; + FormId mParam3; // TES5 only + + ScriptDefinition mScript; // FIXME: ignoring the second one after the NEXT sub-record + + DialogInfo(); + virtual ~DialogInfo(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_INFO_H diff --git a/components/esm4/loadingr.cpp b/components/esm4/loadingr.cpp new file mode 100644 index 0000000000..03994dbfb4 --- /dev/null +++ b/components/esm4/loadingr.cpp @@ -0,0 +1,133 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadingr.hpp" + +#include +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Ingredient::Ingredient() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; + mEnchantment.value = 0; + mEnchantment.flags = 0; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::Ingredient::~Ingredient() +{ +} + +void ESM4::Ingredient::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (mFullName.empty()) + { + reader.getLocalizedString(mFullName); break; + } + else // in TES4 subsequent FULL records are script effect names + { + // FIXME: should be part of a struct? + std::string scriptEffectName; + if (!reader.getZString(scriptEffectName)) + throw std::runtime_error ("INGR FULL data read error"); + + mScriptEffect.push_back(scriptEffectName); + + break; + } + } + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 8) // FO3 is size 4 even though VER_094 + reader.get(mData); + else + reader.get(mData.weight); + + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ENIT: reader.get(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: // Dragonborn only? + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + case ESM4::SUB_OBND: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_VMAD: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_ETYP: // FO3 + { + //std::cout << "INGR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::INGR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Ingredient::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Ingredient::blank() +//{ +//} diff --git a/components/esm4/loadingr.hpp b/components/esm4/loadingr.hpp new file mode 100644 index 0000000000..44312eb24b --- /dev/null +++ b/components/esm4/loadingr.hpp @@ -0,0 +1,83 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_INGR_H +#define ESM4_INGR_H + +#include +#include + +#include "effect.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Ingredient + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; + float weight; + }; + + struct ENIT + { + std::uint32_t value; + std::uint32_t flags; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + std::vector mScriptEffect; // FIXME: prob. should be in a struct + FormId mScriptId; + ScriptEffect mEffect; + ENIT mEnchantment; + + Data mData; + + Ingredient(); + virtual ~Ingredient(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_INGR_H diff --git a/components/esm4/loadkeym.cpp b/components/esm4/loadkeym.cpp new file mode 100644 index 0000000000..a5aba621af --- /dev/null +++ b/components/esm4/loadkeym.cpp @@ -0,0 +1,93 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadkeym.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Key::Key() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + mMiniIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Key::~Key() +{ +} + +void ESM4::Key::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + { + //std::cout << "KEYM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::KEYM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Key::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Key::blank() +//{ +//} diff --git a/components/esm4/loadkeym.hpp b/components/esm4/loadkeym.hpp new file mode 100644 index 0000000000..d59b3826d5 --- /dev/null +++ b/components/esm4/loadkeym.hpp @@ -0,0 +1,77 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_KEYM_H +#define ESM4_KEYM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Key + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + std::string mMiniIcon; // inventory + + FormId mPickUpSound; + FormId mDropSound; + + float mBoundRadius; + FormId mScriptId; + + Data mData; + + Key(); + virtual ~Key(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_KEYM_H diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp new file mode 100644 index 0000000000..2bb260da33 --- /dev/null +++ b/components/esm4/loadland.cpp @@ -0,0 +1,255 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadland.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include + +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Land::Land() : mFormId(0), mFlags(0), mLandFlags(0), mDataTypes(0) +{ + for (int i = 0; i < 4; ++i) + { + mTextures[i].base.formId = 0; + mTextures[i].base.quadrant = 0; + mTextures[i].base.unknown1 = 0; + mTextures[i].base.unknown2 = 0; + } +} + +ESM4::Land::~Land() +{ +} + +// overlap north +// +// 32 +// 31 +// 30 +// overlap . +// west . +// . +// 2 +// 1 +// 0 +// 0 1 2 ... 30 31 32 +// +// overlap south +// +void ESM4::Land::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + TxtLayer layer; + std::int8_t currentAddQuad = -1; // for VTXT following ATXT + + //std::map uniqueTextures; // FIXME: for temp testing only + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_DATA: + { + reader.get(mLandFlags); + break; + } + case ESM4::SUB_VNML: // vertex normals, 33x33x(1+1+1) = 3267 + { + reader.get(mVertNorm); + mDataTypes |= LAND_VNML; + break; + } + case ESM4::SUB_VHGT: // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 + { +#if 0 + reader.get(mHeightMap.heightOffset); + reader.get(mHeightMap.gradientData); + reader.get(mHeightMap.unknown1); + reader.get(mHeightMap.unknown2); +#endif + reader.get(mHeightMap); + mDataTypes |= LAND_VHGT; + break; + } + case ESM4::SUB_VCLR: // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 + { + reader.get(mVertColr); + mDataTypes |= LAND_VCLR; + break; + } + case ESM4::SUA_BTXT: + { + BTXT base; + if (reader.getExact(base)) + { + assert(base.quadrant < 4 && base.quadrant >= 0 && "base texture quadrant index error"); + + reader.adjustFormId(base.formId); + mTextures[base.quadrant].base = std::move(base); +#if 0 + std::cout << "Base Texture formid: 0x" + << std::hex << mTextures[base.quadrant].base.formId + << ", quad " << std::dec << (int)base.quadrant << std::endl; +#endif + } + break; + } + case ESM4::SUB_ATXT: + { + if (currentAddQuad != -1) + { + // FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now + std::cout << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << std::endl; + mTextures[currentAddQuad].layers.push_back(layer); + } + reader.get(layer.texture); + reader.adjustFormId(layer.texture.formId); + assert(layer.texture.quadrant < 4 && layer.texture.quadrant >= 0 + && "additional texture quadrant index error"); +#if 0 + FormId txt = layer.texture.formId; + std::map::iterator lb = uniqueTextures.lower_bound(txt); + if (lb != uniqueTextures.end() && !(uniqueTextures.key_comp()(txt, lb->first))) + { + lb->second += 1; + } + else + uniqueTextures.insert(lb, std::make_pair(txt, 1)); +#endif +#if 0 + std::cout << "Additional Texture formId: 0x" + << std::hex << layer.texture.formId + << ", quad " << std::dec << (int)layer.texture.quadrant << std::endl; + std::cout << "Additional Texture layer: " + << std::dec << (int)layer.texture.layerIndex << std::endl; +#endif + currentAddQuad = layer.texture.quadrant; + break; + } + case ESM4::SUB_VTXT: + { + assert(currentAddQuad != -1 && "VTXT without ATXT found"); + + int count = (int)reader.subRecordHeader().dataSize / sizeof(ESM4::Land::VTXT); + assert((reader.subRecordHeader().dataSize % sizeof(ESM4::Land::VTXT)) == 0 + && "ESM4::LAND VTXT data size error"); + + if (count) + { + layer.data.resize(count); + std::vector::iterator it = layer.data.begin(); + for (;it != layer.data.end(); ++it) + { + reader.get(*it); + // FIXME: debug only + //std::cout << "pos: " << std::dec << (int)(*it).position << std::endl; + } + } + mTextures[currentAddQuad].layers.push_back(layer); + + // Assumed that the layers are added in the correct sequence + // FIXME: Knights.esp doesn't seem to observe this - investigate more + //assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()-1 + //&& "additional texture layer index error"); + + currentAddQuad = -1; + layer.data.clear(); + // FIXME: debug only + //std::cout << "VTXT: count " << std::dec << count << std::endl; + break; + } + case ESM4::SUB_VTEX: // only in Oblivion? + { + int count = (int)reader.subRecordHeader().dataSize / sizeof(FormId); + assert((reader.subRecordHeader().dataSize % sizeof(FormId)) == 0 + && "ESM4::LAND VTEX data size error"); + + if (count) + { + mIds.resize(count); + for (std::vector::iterator it = mIds.begin(); it != mIds.end(); ++it) + { + reader.getFormId(*it); + // FIXME: debug only + //std::cout << "VTEX: " << std::hex << *it << std::endl; + } + } + break; + } + default: + throw std::runtime_error("ESM4::LAND::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + + if (currentAddQuad != -1) + { + // FIXME: not sure if it happens here as well + std::cout << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << " quad " << (int)layer.texture.quadrant << std::endl; + mTextures[currentAddQuad].layers.push_back(layer); + } + + bool missing = false; + for (int i = 0; i < 4; ++i) + { + if (mTextures[i].base.formId == 0) + { + //std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl; + //std::cout << "layers " << mTextures[i].layers.size() << std::endl; + // NOTE: can't set the default here since FO3/FONV may have different defaults + //mTextures[i].base.formId = 0x000008C0; // TerrainHDDirt01.dds + missing = true; + } + //else + //{ + // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " base, quad " << i << std::endl; + // std::cout << "layers " << mTextures[i].layers.size() << std::endl; + //} + } + // at least one of the quadrants do not have a base texture, return without setting the flag + if (!missing) + mDataTypes |= LAND_VTEX; +} + +//void ESM4::Land::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Land::blank() +//{ +//} diff --git a/components/esm4/loadland.hpp b/components/esm4/loadland.hpp new file mode 100644 index 0000000000..6d53a99ff2 --- /dev/null +++ b/components/esm4/loadland.hpp @@ -0,0 +1,136 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LAND_H +#define ESM4_LAND_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + + struct Land + { + enum + { + LAND_VNML = 1, + LAND_VHGT = 2, + LAND_WNAM = 4, // only in TES3? + LAND_VCLR = 8, + LAND_VTEX = 16 + }; + + // number of vertices per side + static const int VERTS_PER_SIDE = 33; + + // cell terrain size in world coords + static const int REAL_SIZE = 4096; + + // total number of vertices + static const int LAND_NUM_VERTS = VERTS_PER_SIDE * VERTS_PER_SIDE; + + static const int HEIGHT_SCALE = 8; + + // number of textures per side of a land quadrant + // (for TES4 - based on vanilla observations) + static const int QUAD_TEXTURE_PER_SIDE = 6; + +#pragma pack(push,1) + struct VHGT + { + float heightOffset; + std::int8_t gradientData[VERTS_PER_SIDE * VERTS_PER_SIDE]; + std::uint16_t unknown1; + unsigned char unknown2; + }; + + struct BTXT + { + FormId formId; + std::uint8_t quadrant; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right + std::uint8_t unknown1; + std::uint16_t unknown2; + }; + + struct ATXT + { + FormId formId; + std::uint8_t quadrant; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right + std::uint8_t unknown; + std::uint16_t layerIndex; // texture layer, 0..7 + }; + + struct VTXT + { + std::uint16_t position; // 0..288 (17x17 grid) + std::uint8_t unknown1; + std::uint8_t unknown2; + float opacity; + }; +#pragma pack(pop) + + struct TxtLayer + { + ATXT texture; + std::vector data; // alpha data + }; + + struct Texture + { + BTXT base; + std::vector layers; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::uint32_t mLandFlags; // from DATA subrecord + + // FIXME: lazy loading not yet implemented + int mDataTypes; // which data types are loaded + + signed char mVertNorm[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VNML subrecord + signed char mVertColr[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VCLR subrecord + VHGT mHeightMap; + Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right + std::vector mIds; // land texture (LTEX) formids + + Land(); + virtual ~Land(); + + virtual void load(Reader& reader); + //virtual void save(Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LAND_H diff --git a/components/esm4/loadlgtm.cpp b/components/esm4/loadlgtm.cpp new file mode 100644 index 0000000000..79867d4106 --- /dev/null +++ b/components/esm4/loadlgtm.cpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadlgtm.hpp" + +#include +#include // FLT_MAX for gcc +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LightingTemplate::LightingTemplate() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + mLighting.ambient = 0; + mLighting.directional = 0; + mLighting.fogColor = 0; + mLighting.fogNear = 0.f; + mLighting.fogFar = 0.f; + mLighting.rotationXY = 0; + mLighting.rotationZ = 0; + mLighting.fogDirFade = 0.f; + mLighting.fogClipDist = 0.f; + mLighting.fogPower = FLT_MAX; // hack way to detect TES4 +} + +ESM4::LightingTemplate::~LightingTemplate() +{ +} + +void ESM4::LightingTemplate::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_DATA: + { + if (subHdr.dataSize == 36) // TES4 + reader.get(&mLighting, 36); + if (subHdr.dataSize == 40) // FO3/FONV + reader.get(mLighting); + else if (subHdr.dataSize == 92) // TES5 + { + reader.get(mLighting); + reader.skipSubRecordData(52); // FIXME + } + else + reader.skipSubRecordData(); // throw? + + break; + } + case ESM4::SUB_DALC: // TES5 + { + //std::cout << "LGTM " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LGTM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LightingTemplate::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LightingTemplate::blank() +//{ +//} diff --git a/components/esm4/loadlgtm.hpp b/components/esm4/loadlgtm.hpp new file mode 100644 index 0000000000..9724d8b16e --- /dev/null +++ b/components/esm4/loadlgtm.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_LGTM_H +#define ESM4_LGTM_H + +#include +#include + +#include "formid.hpp" +#include "lighting.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct LightingTemplate + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + Lighting mLighting; + + LightingTemplate(); + virtual ~LightingTemplate(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LGTM_H diff --git a/components/esm4/loadligh.cpp b/components/esm4/loadligh.cpp new file mode 100644 index 0000000000..bdd125c6bb --- /dev/null +++ b/components/esm4/loadligh.cpp @@ -0,0 +1,120 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadligh.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Light::Light() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mSound(0), + mFade(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); +} + +ESM4::Light::~Light() +{ +} + +void ESM4::Light::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + // FIXME: TES4 might be uint32 as well, need to check + if (isFONV || (esmVer == ESM::VER_094 && subHdr.dataSize == 32)/*FO3*/) + { + reader.get(mData.time); // uint32 + } + else + reader.get(mData.duration); // float + + reader.get(mData.radius); + reader.get(mData.colour); + reader.get(mData.flags); + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 48) + { + reader.get(mData.falloff); + reader.get(mData.FOV); + reader.get(mData.nearClip); + reader.get(mData.frequency); + reader.get(mData.intensityAmplitude); + reader.get(mData.movementAmplitude); + } + else if (subHdr.dataSize == 32) // TES4 + { + reader.get(mData.falloff); + reader.get(mData.FOV); + } + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_FNAM: reader.get(mFade); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: // Dragonborn only? + { + //std::cout << "LIGH " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LIGH::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Light::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Light::blank() +//{ +//} diff --git a/components/esm4/loadligh.hpp b/components/esm4/loadligh.hpp new file mode 100644 index 0000000000..4a0120d71a --- /dev/null +++ b/components/esm4/loadligh.hpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LIGH_H +#define ESM4_LIGH_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Light + { + struct Data + { + std::uint32_t time; // FO/FONV only + float duration; + std::uint32_t radius; + std::uint32_t colour; // RGBA + // flags: + // 0x00000001 = Dynamic + // 0x00000002 = Can be Carried + // 0x00000004 = Negative + // 0x00000008 = Flicker + // 0x00000020 = Off By Default + // 0x00000040 = Flicker Slow + // 0x00000080 = Pulse + // 0x00000100 = Pulse Slow + // 0x00000200 = Spot Light + // 0x00000400 = Spot Shadow + std::int32_t flags; + float falloff; + float FOV; + float nearClip; // TES5 only + float frequency; // TES5 only + float intensityAmplitude; // TES5 only + float movementAmplitude; // TES5 only + std::uint32_t value; // gold + float weight; + Data() : duration(-1), radius(0), colour(0), flags(0), falloff(1.f), FOV(90), + nearClip(0.f), frequency(0.f), intensityAmplitude(0.f), movementAmplitude(0.f), + value(0), weight(0.f) // FIXME: FOV in degrees or radians? + {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; + + float mBoundRadius; + + FormId mScriptId; + FormId mSound; + + float mFade; + + Data mData; + + Light(); + virtual ~Light(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LIGH_H diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp new file mode 100644 index 0000000000..577d5dc8e1 --- /dev/null +++ b/components/esm4/loadltex.cpp @@ -0,0 +1,107 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadltex.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include +//#include // FIXME: debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LandTexture::LandTexture() : mFormId(0), mFlags(0), mHavokFriction(0), mHavokRestitution(0), + mTextureSpecular(0), mGrass(0), mHavokMaterial(0), mTexture(0), + mMaterial(0) +{ + mEditorId.clear(); + mTextureFile.clear(); +} + +ESM4::LandTexture::~LandTexture() +{ +} + +void ESM4::LandTexture::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_HNAM: + { + if (isFONV) + { + reader.skipSubRecordData(); // FIXME: skip FONV for now + break; + } + + if ((reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + && subHdr.dataSize == 2) // FO3 is VER_094 but dataSize 3 + { + //assert(subHdr.dataSize == 2 && "LTEX unexpected HNAM size"); + reader.get(mHavokFriction); + reader.get(mHavokRestitution); + } + else + { + assert(subHdr.dataSize == 3 && "LTEX unexpected HNAM size"); + reader.get(mHavokMaterial); + reader.get(mHavokFriction); + reader.get(mHavokRestitution); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mTextureFile); break; // Oblivion only? + case ESM4::SUB_SNAM: reader.get(mTextureSpecular); break; + case ESM4::SUB_GNAM: reader.getFormId(mGrass); break; + case ESM4::SUB_TNAM: reader.getFormId(mTexture); break; // TES5 only + case ESM4::SUB_MNAM: reader.getFormId(mMaterial); break; // TES5 only + default: + throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LandTexture::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LandTexture::blank() +//{ +//} diff --git a/components/esm4/loadltex.hpp b/components/esm4/loadltex.hpp new file mode 100644 index 0000000000..934e7cc708 --- /dev/null +++ b/components/esm4/loadltex.hpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LTEX_H +#define ESM4_LTEX_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LandTexture + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::uint8_t mHavokFriction; + std::uint8_t mHavokRestitution; + + std::uint8_t mTextureSpecular; // default 30 + FormId mGrass; + + // ------ TES4 only ----- + + std::string mTextureFile; + std::uint8_t mHavokMaterial; + + // ------ TES5 only ----- + + FormId mTexture; + FormId mMaterial; + + // ---------------------- + + LandTexture(); + virtual ~LandTexture(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LTEX_H diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp new file mode 100644 index 0000000000..0088446277 --- /dev/null +++ b/components/esm4/loadlvlc.cpp @@ -0,0 +1,130 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadlvlc.hpp" + +#include +//#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LevelledCreature::LevelledCreature() : mFormId(0), mFlags(0), mScriptId(0), mTemplate(0), + mChanceNone(0), mLvlCreaFlags(0) +{ + mEditorId.clear(); +} + +ESM4::LevelledCreature::~LevelledCreature() +{ +} + +void ESM4::LevelledCreature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_TNAM: reader.getFormId(mTemplate); break; + case ESM4::SUB_LVLD: reader.get(mChanceNone); break; + case ESM4::SUB_LVLF: reader.get(mLvlCreaFlags); break; + case ESM4::SUB_LVLO: + { + static LVLO lvlo; + if (subHdr.dataSize != 12) + { + if (subHdr.dataSize == 8) + { + reader.get(lvlo.level); + reader.get(lvlo.item); + reader.get(lvlo.count); + //std::cout << "LVLC " << mEditorId << " LVLO lev " << lvlo.level << ", item " << lvlo.item + //<< ", count " << lvlo.count << std::endl; + // FIXME: seems to happen only once, don't add to mLvlObject + // LVLC TesKvatchCreature LVLO lev 1, item 1393819648, count 2 + // 0x0001, 0x5314 0000, 0x0002 + break; + } + else + throw std::runtime_error("ESM4::LVLC::load - " + mEditorId + " LVLO size error"); + } + else + reader.get(lvlo); + + reader.adjustFormId(lvlo.item); + mLvlObject.push_back(lvlo); + break; + } + case ESM4::SUB_OBND: // FO3 + { + //std::cout << "LVLC " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LVLC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +bool ESM4::LevelledCreature::calcAllLvlLessThanPlayer() const +{ + if (mHasLvlCreaFlags) + return (mLvlCreaFlags & 0x01) != 0; + else + return (mChanceNone & 0x80) != 0; // FIXME: 0x80 is just a guess +} + +bool ESM4::LevelledCreature::calcEachItemInCount() const +{ + if (mHasLvlCreaFlags) + return (mLvlCreaFlags & 0x02) != 0; + else + return true; // FIXME: just a guess +} + +std::int8_t ESM4::LevelledCreature::chanceNone() const +{ + if (mHasLvlCreaFlags) + return mChanceNone; + else + return (mChanceNone & 0x7f); // FIXME: 0x80 is just a guess +} + +//void ESM4::LevelledCreature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LevelledCreature::blank() +//{ +//} diff --git a/components/esm4/loadlvlc.hpp b/components/esm4/loadlvlc.hpp new file mode 100644 index 0000000000..ccd08944b5 --- /dev/null +++ b/components/esm4/loadlvlc.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LVLC_H +#define ESM4_LVLC_H + +#include +#include + +#include "formid.hpp" +#include "inventory.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LevelledCreature + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + FormId mScriptId; + FormId mTemplate; + std::int8_t mChanceNone; + + bool mHasLvlCreaFlags; + std::uint8_t mLvlCreaFlags; + + std::vector mLvlObject; + + bool calcAllLvlLessThanPlayer() const; + bool calcEachItemInCount() const; + std::int8_t chanceNone() const; + + LevelledCreature(); + virtual ~LevelledCreature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LVLC_H diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp new file mode 100644 index 0000000000..1237a0add4 --- /dev/null +++ b/components/esm4/loadlvli.cpp @@ -0,0 +1,142 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadlvli.hpp" + +#include +#include // FIXME: for debugging + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LevelledItem::LevelledItem() : mFormId(0), mFlags(0), mChanceNone(0), mHasLvlItemFlags(false), + mLvlItemFlags(0), mData(0) +{ + mEditorId.clear(); +} + +ESM4::LevelledItem::~LevelledItem() +{ +} + +void ESM4::LevelledItem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_LVLD: reader.get(mChanceNone); break; + case ESM4::SUB_LVLF: reader.get(mLvlItemFlags); mHasLvlItemFlags = true; break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_LVLO: + { + static LVLO lvlo; + if (subHdr.dataSize != 12) + { + if (subHdr.dataSize == 8) + { + reader.get(lvlo.level); + reader.get(lvlo.item); + reader.get(lvlo.count); +// std::cout << "LVLI " << mEditorId << " LVLO lev " << lvlo.level << ", item " << lvlo.item +// << ", count " << lvlo.count << std::endl; + break; + } + else + throw std::runtime_error("ESM4::LVLI::load - " + mEditorId + " LVLO size error"); + } + else + reader.get(lvlo); + + reader.adjustFormId(lvlo.item); + mLvlObject.push_back(lvlo); + break; + } + case ESM4::SUB_LLCT: + case ESM4::SUB_OBND: // FO3/FONV + case ESM4::SUB_COED: // FO3/FONV + case ESM4::SUB_LVLG: // FO3/FONV + { + + //std::cout << "LVLI " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LVLI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + + // FIXME: testing + //if (mHasLvlItemFlags && mChanceNone >= 90) + //std::cout << "LVLI " << mEditorId << " chance none " << int(mChanceNone) << std::endl; +} + +bool ESM4::LevelledItem::calcAllLvlLessThanPlayer() const +{ + if (mHasLvlItemFlags) + return (mLvlItemFlags & 0x01) != 0; + else + return (mChanceNone & 0x80) != 0; // FIXME: 0x80 is just a guess +} + +bool ESM4::LevelledItem::calcEachItemInCount() const +{ + if (mHasLvlItemFlags) + return (mLvlItemFlags & 0x02) != 0; + else + return mData != 0; +} + +std::int8_t ESM4::LevelledItem::chanceNone() const +{ + if (mHasLvlItemFlags) + return mChanceNone; + else + return (mChanceNone & 0x7f); // FIXME: 0x80 is just a guess +} + +bool ESM4::LevelledItem::useAll() const +{ + if (mHasLvlItemFlags) + return (mLvlItemFlags & 0x04) != 0; + else + return false; +} + +//void ESM4::LevelledItem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LevelledItem::blank() +//{ +//} diff --git a/components/esm4/loadlvli.hpp b/components/esm4/loadlvli.hpp new file mode 100644 index 0000000000..0964370e63 --- /dev/null +++ b/components/esm4/loadlvli.hpp @@ -0,0 +1,72 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LVLI_H +#define ESM4_LVLI_H + +#include +#include + +#include "formid.hpp" +#include "inventory.hpp" // LVLO + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LevelledItem + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::int8_t mChanceNone; + + bool mHasLvlItemFlags; + std::uint8_t mLvlItemFlags; + + std::uint8_t mData; + + std::vector mLvlObject; + + LevelledItem(); + virtual ~LevelledItem(); + + bool calcAllLvlLessThanPlayer() const; + bool calcEachItemInCount() const; + bool useAll() const; + std::int8_t chanceNone() const; + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LVLI_H diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp new file mode 100644 index 0000000000..fdd860b9d9 --- /dev/null +++ b/components/esm4/loadlvln.cpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadlvln.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LevelledNpc::LevelledNpc() : mFormId(0), mFlags(0), mChanceNone(0), mLvlActorFlags(0), mListCount(0) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::LevelledNpc::~LevelledNpc() +{ +} + +void ESM4::LevelledNpc::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + //std::uint32_t esmVer = reader.esmVersion(); // currently unused + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_LLCT: reader.get(mListCount); break; + case ESM4::SUB_LVLD: reader.get(mChanceNone); break; + case ESM4::SUB_LVLF: reader.get(mLvlActorFlags); break; + case ESM4::SUB_LVLO: + { + static LVLO lvlo; + if (subHdr.dataSize != 12) + { + if (subHdr.dataSize == 8) + { + reader.get(lvlo.level); + reader.get(lvlo.item); + reader.get(lvlo.count); + break; + } + else + throw std::runtime_error("ESM4::LVLN::load - " + mEditorId + " LVLO size error"); + } +// else if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) +// { +// std::uint32_t level; +// reader.get(level); +// lvlo.level = static_cast(level); +// reader.get(lvlo.item); +// std::uint32_t count; +// reader.get(count); +// lvlo.count = static_cast(count); +// } + else + reader.get(lvlo); + + reader.adjustFormId(lvlo.item); + mLvlObject.push_back(lvlo); + break; + } + case ESM4::SUB_COED: // owner + case ESM4::SUB_OBND: // object bounds + case ESM4::SUB_MODT: // model texture data + { + //std::cout << "LVLN " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LVLN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LevelledNpc::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LevelledNpc::blank() +//{ +//} diff --git a/components/esm4/loadlvln.hpp b/components/esm4/loadlvln.hpp new file mode 100644 index 0000000000..057ed9581d --- /dev/null +++ b/components/esm4/loadlvln.hpp @@ -0,0 +1,69 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LVLN_H +#define ESM4_LVLN_H + +#include +#include + +#include "formid.hpp" +#include "inventory.hpp" // LVLO + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LevelledNpc + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + std::int8_t mChanceNone; + std::uint8_t mLvlActorFlags; + + std::uint8_t mListCount; + std::vector mLvlObject; + + LevelledNpc(); + virtual ~LevelledNpc(); + + inline bool calcAllLvlLessThanPlayer() const { return (mLvlActorFlags & 0x01) != 0; } + inline bool calcEachItemInCount() const { return (mLvlActorFlags & 0x02) != 0; } + inline std::int8_t chanceNone() const { return mChanceNone; } + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LVLN_H diff --git a/components/esm4/loadmato.cpp b/components/esm4/loadmato.cpp new file mode 100644 index 0000000000..fdbdc66ebc --- /dev/null +++ b/components/esm4/loadmato.cpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadmato.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Material::Material() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Material::~Material() +{ +} + +void ESM4::Material::load(ESM4::Reader& reader) +{ + //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DNAM: + case ESM4::SUB_DATA: + { + //std::cout << "MATO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MATO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Material::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Material::blank() +//{ +//} diff --git a/components/esm4/loadmato.hpp b/components/esm4/loadmato.hpp new file mode 100644 index 0000000000..9b0d6c3947 --- /dev/null +++ b/components/esm4/loadmato.hpp @@ -0,0 +1,58 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MATO_H +#define ESM4_MATO_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Material + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + Material(); + virtual ~Material(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MATO_H diff --git a/components/esm4/loadmisc.cpp b/components/esm4/loadmisc.cpp new file mode 100644 index 0000000000..ee3c10d0b6 --- /dev/null +++ b/components/esm4/loadmisc.cpp @@ -0,0 +1,95 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadmisc.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MiscItem::MiscItem() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + mMiniIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::MiscItem::~MiscItem() +{ +} + +void ESM4::MiscItem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "MISC " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MISC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MiscItem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MiscItem::blank() +//{ +//} diff --git a/components/esm4/loadmisc.hpp b/components/esm4/loadmisc.hpp new file mode 100644 index 0000000000..94f09b1a23 --- /dev/null +++ b/components/esm4/loadmisc.hpp @@ -0,0 +1,77 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MISC_H +#define ESM4_MISC_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct MiscItem + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + std::string mMiniIcon; // inventory + + FormId mPickUpSound; + FormId mDropSound; + + float mBoundRadius; + FormId mScriptId; + + Data mData; + + MiscItem(); + virtual ~MiscItem(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MISC_H diff --git a/components/esm4/loadmset.cpp b/components/esm4/loadmset.cpp new file mode 100644 index 0000000000..b7859f9fa5 --- /dev/null +++ b/components/esm4/loadmset.cpp @@ -0,0 +1,115 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadmset.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MediaSet::MediaSet() : mFormId(0), mFlags(0), mSetType(-1), mEnabled(0), + mBoundaryDayOuter(0.f), mBoundaryDayMiddle(0.f), mBoundaryDayInner(0.f), + mBoundaryNightOuter(0.f), mBoundaryNightMiddle(0.f), mBoundaryNightInner(0.f), + mLevel8(0.f), mLevel9(0.f), mLevel0(0.f), mLevelA(0.f), mLevelB(0.f), mLevelC(0.f), + mTime1(0.f), mTime2(0.f), mTime3(0.f), mTime4(0.f), + mSoundIntro(0), mSoundOutro(0) +{ + mEditorId.clear(); + mFullName.clear(); + + mSet2.clear(); + mSet3.clear(); + mSet4.clear(); + mSet5.clear(); + mSet6.clear(); + mSet7.clear(); +} + +ESM4::MediaSet::~MediaSet() +{ +} + +void ESM4::MediaSet::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_NAM1: reader.get(mSetType); break; + case ESM4::SUB_PNAM: reader.get(mEnabled); break; + case ESM4::SUB_NAM2: reader.getZString(mSet2); break; + case ESM4::SUB_NAM3: reader.getZString(mSet3); break; + case ESM4::SUB_NAM4: reader.getZString(mSet4); break; + case ESM4::SUB_NAM5: reader.getZString(mSet5); break; + case ESM4::SUB_NAM6: reader.getZString(mSet6); break; + case ESM4::SUB_NAM7: reader.getZString(mSet7); break; + case ESM4::SUB_HNAM: reader.getFormId(mSoundIntro); break; + case ESM4::SUB_INAM: reader.getFormId(mSoundOutro); break; + case ESM4::SUB_NAM8: reader.get(mLevel8); break; + case ESM4::SUB_NAM9: reader.get(mLevel9); break; + case ESM4::SUB_NAM0: reader.get(mLevel0); break; + case ESM4::SUB_ANAM: reader.get(mLevelA); break; + case ESM4::SUB_BNAM: reader.get(mLevelB); break; + case ESM4::SUB_CNAM: reader.get(mLevelC); break; + case ESM4::SUB_JNAM: reader.get(mBoundaryDayOuter); break; + case ESM4::SUB_KNAM: reader.get(mBoundaryDayMiddle); break; + case ESM4::SUB_LNAM: reader.get(mBoundaryDayInner); break; + case ESM4::SUB_MNAM: reader.get(mBoundaryNightOuter); break; + case ESM4::SUB_NNAM: reader.get(mBoundaryNightMiddle); break; + case ESM4::SUB_ONAM: reader.get(mBoundaryNightInner); break; + case ESM4::SUB_DNAM: reader.get(mTime1); break; + case ESM4::SUB_ENAM: reader.get(mTime2); break; + case ESM4::SUB_FNAM: reader.get(mTime3); break; + case ESM4::SUB_GNAM: reader.get(mTime4); break; + case ESM4::SUB_DATA: + { + //std::cout << "MSET " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MSET::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MediaSet::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MediaSet::blank() +//{ +//} diff --git a/components/esm4/loadmset.hpp b/components/esm4/loadmset.hpp new file mode 100644 index 0000000000..b13c4d6f64 --- /dev/null +++ b/components/esm4/loadmset.hpp @@ -0,0 +1,99 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MSET_H +#define ESM4_MSET_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct MediaSet + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + + // -1 none, 0 battle, 1 location, 2 dungeon, 3 incidental + // Battle - intro (HNAM), loop (NAM2), outro (INAM) + // Location - day outer (NAM2), day middle (NAM3), day inner (NAM4), + // night outer (NAM5), night middle (NAM6), night inner (NAM7) + // Dungeon - intro (HNAM), battle (NAM2), explore (NAM3), suspence (NAM4), outro (INAM) + // Incidental - daytime (HNAM), nighttime (INAM) + std::int32_t mSetType; + // 0x01 day outer, 0x02 day middle, 0x04 day inner + // 0x08 night outer, 0x10 night middle, 0x20 night inner + std::uint8_t mEnabled; // for location + + float mBoundaryDayOuter; // % + float mBoundaryDayMiddle; // % + float mBoundaryDayInner; // % + float mBoundaryNightOuter; // % + float mBoundaryNightMiddle; // % + float mBoundaryNightInner; // % + + // start at 2 to reduce confusion + std::string mSet2; // NAM2 + std::string mSet3; // NAM3 + std::string mSet4; // NAM4 + std::string mSet5; // NAM5 + std::string mSet6; // NAM6 + std::string mSet7; // NAM7 + + float mLevel8; // dB + float mLevel9; // dB + float mLevel0; // dB + float mLevelA; // dB + float mLevelB; // dB + float mLevelC; // dB + + float mTime1; + float mTime2; + float mTime3; + float mTime4; + + FormId mSoundIntro; // HNAM + FormId mSoundOutro; // INAM + + MediaSet(); + virtual ~MediaSet(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MSET_H diff --git a/components/esm4/loadmstt.cpp b/components/esm4/loadmstt.cpp new file mode 100644 index 0000000000..61d6147b2f --- /dev/null +++ b/components/esm4/loadmstt.cpp @@ -0,0 +1,87 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadmstt.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MovableStatic::MovableStatic() : mFormId(0), mFlags(0), mData(0), mLoopingSound(0) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::MovableStatic::~MovableStatic() +{ +} + +void ESM4::MovableStatic::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SNAM: reader.get(mLoopingSound); break; + case ESM4::SUB_DEST: // destruction data + case ESM4::SUB_OBND: // object bounds + case ESM4::SUB_MODT: // model texture data + case ESM4::SUB_DMDL: + case ESM4::SUB_DMDT: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + case ESM4::SUB_MODS: + case ESM4::SUB_FULL: + case ESM4::SUB_MODB: + { + //std::cout << "MSTT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MSTT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MovableStatic::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MovableStatic::blank() +//{ +//} diff --git a/components/esm4/loadmstt.hpp b/components/esm4/loadmstt.hpp new file mode 100644 index 0000000000..77dc453d35 --- /dev/null +++ b/components/esm4/loadmstt.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MSTT_H +#define ESM4_MSTT_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct MovableStatic + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + std::int8_t mData; + FormId mLoopingSound; + + MovableStatic(); + virtual ~MovableStatic(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MSTT_H diff --git a/components/esm4/loadmusc.cpp b/components/esm4/loadmusc.cpp new file mode 100644 index 0000000000..903bbb5a1f --- /dev/null +++ b/components/esm4/loadmusc.cpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadmusc.hpp" + +#include +//#include // FIXME: for debugging only + +//#include "formid.hpp" + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Music::Music() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mMusicFile.clear(); +} + +ESM4::Music::~Music() +{ +} + +void ESM4::Music::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FNAM: reader.getZString(mMusicFile); + //std::cout << "music: " << /*formIdToString(mFormId)*/mEditorId << " " << mMusicFile << std::endl; + break; + case ESM4::SUB_ANAM: // FONV float (attenuation in db? loop if positive?) + case ESM4::SUB_WNAM: // TES5 + case ESM4::SUB_PNAM: // TES5 + case ESM4::SUB_TNAM: // TES5 + { + //std::cout << "MUSC " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MUSC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Music::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Music::blank() +//{ +//} diff --git a/components/esm4/loadmusc.hpp b/components/esm4/loadmusc.hpp new file mode 100644 index 0000000000..ab3889a6c0 --- /dev/null +++ b/components/esm4/loadmusc.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_MUSC_H +#define ESM4_MUSC_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Music + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mMusicFile; + + Music(); + virtual ~Music(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MUSC_H diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp new file mode 100644 index 0000000000..0655edb1e8 --- /dev/null +++ b/components/esm4/loadnavi.cpp @@ -0,0 +1,373 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadnavi.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include + +#include // FIXME: debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Navigation::Navigation() +{ + mEditorId.clear(); +} + +ESM4::Navigation::~Navigation() +{ +} + +void ESM4::Navigation::IslandInfo::load(ESM4::Reader& reader) +{ + reader.get(minX); + reader.get(minY); + reader.get(minZ); + reader.get(maxX); + reader.get(maxY); + reader.get(maxZ); + + std::uint32_t count; + reader.get(count); // countTriangle; + if (count) + { + triangles.resize(count); + //std::cout << "NVMI island triangles " << std::dec << count << std::endl; // FIXME + for (std::vector::iterator it = triangles.begin(); it != triangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countVertex; + if (count) + { + verticies.resize(count); + for (std::vector::iterator it = verticies.begin(); it != verticies.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NVMI vert " << std::dec << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; +#endif + } + } +} + +void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader) +{ + std::uint32_t count; + + reader.get(formId); + reader.get(flags); + reader.get(x); + reader.get(y); + reader.get(z); + +// FIXME: for debugging only +#if 0 + std::string padding = ""; + if (flags == ESM4::FLG_Modified) + padding.insert(0, 2, '-'); + else if (flags == ESM4::FLG_Unmodified) + padding.insert(0, 4, '.'); + + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NVMI formId: 0x" << std::hex << formId << std::endl; + std::cout << padding << "NVMI flags: " << std::hex << flags << std::endl; + std::cout << padding << "NVMI center: " << std::dec << x << ", " << y << ", " << z << std::endl; +#endif + + reader.get(flagPrefMerges); + + reader.get(count); // countMerged; + if (count) + { + //std::cout << "NVMI countMerged " << std::dec << count << std::endl; + formIdMerged.resize(count); + for (std::vector::iterator it = formIdMerged.begin(); it != formIdMerged.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countPrefMerged; + if (count) + { + //std::cout << "NVMI countPrefMerged " << std::dec << count << std::endl; + formIdPrefMerged.resize(count); + for (std::vector::iterator it = formIdPrefMerged.begin(); it != formIdPrefMerged.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countLinkedDoors; + if (count) + { + //std::cout << "NVMI countLinkedDoors " << std::dec << count << std::endl; + linkedDoors.resize(count); + for (std::vector::iterator it = linkedDoors.begin(); it != linkedDoors.end(); ++it) + { + reader.get(*it); + } + } + + unsigned char island; + reader.get(island); + if (island) + { + Navigation::IslandInfo island2; + island2.load(reader); + islandInfo.push_back(island2); // Maybe don't use a vector for just one entry? + } + else if (flags == FLG_Island) // FIXME: debug only + std::cerr << "nvmi no island but has 0x20 flag" << std::endl; + + reader.get(locationMarker); + + reader.get(worldSpaceId); + //FLG_Tamriel = 0x0000003c, // grid info follows, possibly Tamriel? + //FLG_Morrowind = 0x01380000, // grid info follows, probably Skywind + if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000) + { + reader.get(cellGrid.grid.y); // NOTE: reverse order + reader.get(cellGrid.grid.x); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == ESM4::FLG_Morrowind) + std::cout << padding << "NVMI MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; + else + std::cout << padding << "NVMI SR: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; +#endif + } + else + { + reader.get(cellGrid.cellId); + +#if 0 + if (worldSpaceId == 0) // interior + std::cout << "NVMI Interior: cellId " << std::hex << cellGrid.cellId << std::endl; + else + std::cout << "NVMI FormID: cellId " << std::hex << cellGrid.cellId << std::endl; +#endif + } +} + +// NVPP data seems to be organised this way (total is 0x64 = 100) +// +// (0) total | 0x1 | formid (index 0) | count | formid's +// (1) | count | formid's +// (2) | count | formid's +// (3) | count | formid's +// (4) | count | formid's +// (5) | count | formid's +// (6) | count | formid's +// (7) | count | formid's +// (8) | count | formid's +// (9) | count | formid's +// (10) | 0x1 | formid (index 1) | count | formid's +// (11) | count | formid's +// (12) | count | formid's +// (13) | count | formid's +// (14) | count | formid's +// (15) | count | formid's +// ... +// +// (88) | count | formid's +// (89) | count | formid's +// +// Here the pattern changes (final count is 0xa = 10) +// +// (90) | 0x1 | formid (index 9) | count | formid | index +// (91) | formid | index +// (92) | formid | index +// (93) | formid | index +// (94) | formid | index +// (95) | formid | index +// (96) | formid | index +// (97) | formid | index +// (98) | formid | index +// (99) | formid | index +// +// Note that the index values are not sequential, i.e. the first index value +// (i.e. row 90) for Update.esm is 2. +// +// Also note that there's no list of formid's following the final node (index 9) +// +// The same 10 formids seem to be used for the indices, but not necessarily +// with the same index value (but only Update.esm differs?) +// +// formid cellid X Y Editor ID other formids in same X,Y S U D D +// -------- ------ --- --- --------------------------- ---------------------------- - - - - +// 00079bbf 9639 5 -4 WhiterunExterior17 00079bc3 0 6 0 0 +// 0010377b 8ed5 6 24 DawnstarWesternMineExterior 1 1 1 1 +// 000a3f44 9577 -22 2 RoriksteadEdge 2 9 2 2 +// 00100f4b 8ea2 26 25 WinterholdExterior01 00100f4a, 00100f49 3 3 3 3 +// 00103120 bc8e 42 -22 (near Riften) 4 2 4 4 +// 00105e9a 929d -18 24 SolitudeExterior03 5 0 5 5 +// 001030cb 7178 -40 1 SalviusFarmExterior01 (east of Markarth) 6 8 6 6 +// 00098776 980b 4 -19 HelgenExterior 000cce3d 7 5 7 7 +// 000e88cc 93de -9 14 (near Morthal) 0010519e, 0010519d, 000e88d2 8 7 8 8 +// 000b87df b51d 33 5 WindhelmAttackStart05 9 4 9 9 +// +void ESM4::Navigation::load(ESM4::Reader& reader) +{ + //mFormId = reader.hdr().record.id; + //mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: // seems to be unused? + { + if (!reader.getZString(mEditorId)) + throw std::runtime_error ("NAVI EDID data read error"); + break; + } + case ESM4::SUB_NVPP: + { + std::uint32_t total; + std::uint32_t count; + reader.get(total); + if (!total) + { + reader.get(count); // throw away + break; + } + + total -= 10; // HACK + std::uint32_t node; + for (std::uint32_t i = 0; i < total; ++i) + { + std::vector preferredPaths; + reader.get(count); + if (count == 1) + { + reader.get(node); + reader.get(count); + } + if (count) + { + preferredPaths.resize(count); + for (std::vector::iterator it = preferredPaths.begin(); + it != preferredPaths.end(); ++it) + { + reader.get(*it); + } + } + mPreferredPaths.push_back(std::make_pair(node, preferredPaths)); +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", count " << count << ", i " << std::dec << i << std::endl; +#endif + } + reader.get(count); + assert(count == 1 && "expected separator"); + + reader.get(node); // HACK + std::vector preferredPaths; + mPreferredPaths.push_back(std::make_pair(node, preferredPaths)); // empty +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", count " << 0 << std::endl; +#endif + + reader.get(count); // HACK + assert(count == 10 && "expected 0xa"); + std::uint32_t index; + for (std::uint32_t i = 0; i < count; ++i) + { + reader.get(node); + reader.get(index); +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", index " << index << ", i " << std::dec << total+i << std::endl; +#endif + //std::pair::iterator, bool> res = + mPathIndexMap.insert(std::make_pair(node, index)); + // FIXME: this throws if more than one file is being loaded + //if (!res.second) + //throw std::runtime_error ("node already exists in the preferred path index map"); + } + break; + } + case ESM4::SUB_NVER: + { + std::uint32_t version; // always the same? (0x0c) + reader.get(version); // TODO: store this or use it for merging? + //std::cout << "NAVI version " << std::dec << version << std::endl; + break; + } + case ESM4::SUB_NVMI: // multiple + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + { + reader.skipSubRecordData(); // FIXME: FO3/FONV have different form of NavMeshInfo + break; + } + + //std::cout << "\nNVMI start" << std::endl; + NavMeshInfo nvmi; + nvmi.load(reader); + mNavMeshInfo.push_back (nvmi); + break; + } + case ESM4::SUB_NVSI: // from Dawnguard onwards + case ESM4::SUB_NVCI: // FO3 + { + reader.skipSubRecordData(); // FIXME: + break; + } + default: + { + throw std::runtime_error("ESM4::NAVI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + } +} + +//void ESM4::Navigation::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Navigation::blank() +//{ +//} diff --git a/components/esm4/loadnavi.hpp b/components/esm4/loadnavi.hpp new file mode 100644 index 0000000000..0db9650c4e --- /dev/null +++ b/components/esm4/loadnavi.hpp @@ -0,0 +1,117 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NAVI_H +#define ESM4_NAVI_H + +#include +#include +#include + +#include "common.hpp" // CellGrid, Vertex + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Navigation + { +#pragma pack(push,1) + struct DoorRef + { + std::uint32_t unknown; + FormId formId; + }; + + struct Triangle + { + std::uint16_t vertexIndex0; + std::uint16_t vertexIndex1; + std::uint16_t vertexIndex2; + }; +#pragma pack(pop) + + struct IslandInfo + { + float minX; + float minY; + float minZ; + float maxX; + float maxY; + float maxZ; + std::vector triangles; + std::vector verticies; + + void load(ESM4::Reader& reader); + }; + + enum Flags // NVMI island flags (not certain) + { + FLG_Island = 0x00000020, + FLG_Modified = 0x00000000, // not island + FLG_Unmodified = 0x00000040 // not island + }; + + struct NavMeshInfo + { + FormId formId; + std::uint32_t flags; + // center point of the navmesh + float x; + float y; + float z; + std::uint32_t flagPrefMerges; + std::vector formIdMerged; + std::vector formIdPrefMerged; + std::vector linkedDoors; + std::vector islandInfo; + std::uint32_t locationMarker; + FormId worldSpaceId; + CellGrid cellGrid; + + void load(ESM4::Reader& reader); + }; + + std::string mEditorId; + + std::vector mNavMeshInfo; + + std::vector > > mPreferredPaths; + + std::map mPathIndexMap; + + Navigation(); + virtual ~Navigation(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_NAVI_H diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp new file mode 100644 index 0000000000..1f3eebe434 --- /dev/null +++ b/components/esm4/loadnavm.cpp @@ -0,0 +1,270 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadnavm.hpp" + +#include +#include +#include + +#include // FIXME: debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::NavMesh::NavMesh() : mFormId(0), mFlags(0) +{ +} + +ESM4::NavMesh::~NavMesh() +{ +} + +void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader) +{ + //std::cout << "start: divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; + //"this 0x" << this << std::endl; // FIXME + + std::uint32_t count; + + reader.get(unknownNVER); + reader.get(unknownLCTN); + reader.get(worldSpaceId); + //FLG_Tamriel = 0x0000003c, // grid info follows, possibly Tamriel? + //FLG_Morrowind = 0x01380000, // grid info follows, probably Skywind + if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000) + { + // ^ + // Y | X Y Index + // | 0,0 0 + // 1 |23 0,1 1 + // 0 |01 1,0 2 + // +--- 1,1 3 + // 01 -> + // X + // + // e.g. Dagonfel X:13,14,15,16 Y:43,44,45,46 (Morrowind X:7 Y:22) + // + // Skywind: -4,-3 -2,-1 0,1 2,3 4,5 6,7 + // Morrowind: -2 -1 0 1 2 3 + // + // Formula seems to be floor(Skywind coord / 2) + // + reader.get(cellGrid.grid.y); // NOTE: reverse order + reader.get(cellGrid.grid.x); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == ESM4::FLG_Morrowind) + std::cout << padding << "NVNM MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; + else + std::cout << padding << "NVNM SR: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; +#endif + } + else + { + reader.get(cellGrid.cellId); + +#if 0 + std::string padding = ""; // FIXME + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == 0) // interior + std::cout << padding << "NVNM Interior: cellId " << std::hex << cellGrid.cellId << std::endl; + else + std::cout << padding << "NVNM FormID: cellId " << std::hex << cellGrid.cellId << std::endl; +#endif + } + + reader.get(count); // numVerticies + if (count) + { + verticies.resize(count); + for (std::vector::iterator it = verticies.begin(); it != verticies.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + //if (reader.hdr().record.id == 0x2004ecc) // FIXME + std::cout << "nvnm vert " << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; +#endif + } + } + + reader.get(count); // numTriangles; + if (count) + { + triangles.resize(count); + for (std::vector::iterator it = triangles.begin(); it != triangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // numExtConn; + if (count) + { + extConns.resize(count); + for (std::vector::iterator it = extConns.begin(); it != extConns.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + std::cout << "nvnm ext 0x" << std::hex << (*it).navMesh << std::endl; +#endif + } + } + + reader.get(count); // numDoorTriangles; + if (count) + { + doorTriangles.resize(count); + for (std::vector::iterator it = doorTriangles.begin(); it != doorTriangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // numCoverTriangles; + if (count) + { + coverTriangles.resize(count); + for (std::vector::iterator it = coverTriangles.begin(); it != coverTriangles.end(); ++it) + { + reader.get(*it); + } + } + + // abs((maxX - minX) / divisor) = Max X Distance + reader.get(divisor); // FIXME: latest over-writes old + + reader.get(maxXDist); // FIXME: update with formula + reader.get(maxYDist); + reader.get(minX); // FIXME: use std::min + reader.get(minY); + reader.get(minZ); + reader.get(maxX); + reader.get(maxY); + reader.get(maxZ); + + // FIXME: should check remaining size here + // there are divisor^2 segments, each segment is a vector of triangle indices + for (unsigned int i = 0; i < divisor*divisor; ++i) + { + reader.get(count); // NOTE: count may be zero + + std::vector indices; + indices.resize(count); + for (std::vector::iterator it = indices.begin(); it != indices.end(); ++it) + { + reader.get(*it); + } + triSegments.push_back(indices); + } + assert(triSegments.size() == divisor*divisor && "tiangle segments size is not the square of divisor"); +#if 0 + if (triSegments.size() != divisor*divisor) + std::cout << "divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; + "this 0x" << this << std::endl; +#endif +} + +void ESM4::NavMesh::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + //std::cout << "NavMesh 0x" << std::hex << this << std::endl; // FIXME + std::uint32_t subSize = 0; // for XXXX sub record + +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NAVM flags 0x" << std::hex << reader.hdr().record.flags << std::endl; + std::cout << padding << "NAVM id 0x" << std::hex << reader.hdr().record.id << std::endl; +#endif + while (reader.getSubRecordHeader()) + { + switch (reader.subRecordHeader().typeId) + { + case ESM4::SUB_NVNM: + { + NVNMstruct nvnm; + nvnm.load(reader); + mData.push_back(nvnm); // FIXME try swap + break; + } + case ESM4::SUB_ONAM: + case ESM4::SUB_PNAM: + case ESM4::SUB_NNAM: + { + if (subSize) + { + reader.skipSubRecordData(subSize); // special post XXXX + reader.updateRecordRead(subSize); // WARNING: manual update + subSize = 0; + } + else + //const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + //std::cout << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + + break; + } + case ESM4::SUB_XXXX: + { + reader.get(subSize); + break; + } + case ESM4::SUB_NVER: // FO3 + case ESM4::SUB_DATA: // FO3 + case ESM4::SUB_NVVX: // FO3 + case ESM4::SUB_NVTR: // FO3 + case ESM4::SUB_NVCA: // FO3 + case ESM4::SUB_NVDP: // FO3 + case ESM4::SUB_NVGD: // FO3 + case ESM4::SUB_NVEX: // FO3 + case ESM4::SUB_EDID: // FO3 + { + reader.skipSubRecordData(); // FIXME: + break; + } + default: + throw std::runtime_error("ESM4::NAVM::load - Unknown subrecord " + + ESM::printName(reader.subRecordHeader().typeId)); + } + } + //std::cout << "num nvnm " << std::dec << mData.size() << std::endl; // FIXME +} + +//void ESM4::NavMesh::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::NavMesh::blank() +{ +} diff --git a/components/esm4/loadnavm.hpp b/components/esm4/loadnavm.hpp new file mode 100644 index 0000000000..f5fb9b5e20 --- /dev/null +++ b/components/esm4/loadnavm.hpp @@ -0,0 +1,112 @@ +/* + Copyright (C) 2015, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NAVM_H +#define ESM4_NAVM_H + +#include +#include + +#include "common.hpp" // CellGrid, Vertex + +namespace ESM4 +{ + class Reader; + class Writer; + + struct NavMesh + { +#pragma pack(push,1) + struct Triangle + { + std::uint16_t vertexIndex0; + std::uint16_t vertexIndex1; + std::uint16_t vertexIndex2; + std::uint16_t edge0; + std::uint16_t edge1; + std::uint16_t edge2; + std::uint16_t coverMarker; + std::uint16_t coverFlags; + }; + + struct ExtConnection + { + std::uint32_t unknown; + FormId navMesh; + std::uint16_t triangleIndex; + }; + + struct DoorTriangle + { + std::uint16_t triangleIndex; + std::uint32_t unknown; + FormId doorRef; + }; +#pragma pack(pop) + + struct NVNMstruct + { + std::uint32_t unknownNVER; + std::uint32_t unknownLCTN; + FormId worldSpaceId; + CellGrid cellGrid; + std::vector verticies; + std::vector triangles; + std::vector extConns; + std::vector doorTriangles; + std::vector coverTriangles; + std::uint32_t divisor; + float maxXDist; + float maxYDist; + float minX; + float minY; + float minZ; + float maxX; + float maxY; + float maxZ; + // there are divisor^2 segments, each segment is a vector of triangle indices + std::vector > triSegments; + + void load(ESM4::Reader& esm); + }; + + std::vector mData; // Up to 4 skywind cells in one Morrowind cell + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + NavMesh(); + virtual ~NavMesh(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; + +} + +#endif // ESM4_NAVM_H diff --git a/components/esm4/loadnote.cpp b/components/esm4/loadnote.cpp new file mode 100644 index 0000000000..ee897258b7 --- /dev/null +++ b/components/esm4/loadnote.cpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadnote.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Note::Note() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); +} + +ESM4::Note::~Note() +{ +} + +void ESM4::Note::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: + case ESM4::SUB_MODB: + case ESM4::SUB_ONAM: + case ESM4::SUB_SNAM: + case ESM4::SUB_TNAM: + case ESM4::SUB_XNAM: + case ESM4::SUB_OBND: + { + //std::cout << "NOTE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::NOTE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Note::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Note::blank() +//{ +//} diff --git a/components/esm4/loadnote.hpp b/components/esm4/loadnote.hpp new file mode 100644 index 0000000000..1ba9bfec24 --- /dev/null +++ b/components/esm4/loadnote.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NOTE_H +#define ESM4_NOTE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Note + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; + + Note(); + virtual ~Note(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_NOTE_H diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp new file mode 100644 index 0000000000..e32ee6e90d --- /dev/null +++ b/components/esm4/loadnpc.cpp @@ -0,0 +1,333 @@ +/* + Copyright (C) 2016-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadnpc.hpp" + +#include +#include +#include // getline +#include // NOTE: for testing only +#include // NOTE: for testing only +#include // NOTE: for testing only + +//#include +//#include +#include + +#include "formid.hpp" // NOTE: for testing only +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Npc::Npc() : mFormId(0), mFlags(0), mIsTES4(false), mIsFONV(false), mRace(0), mClass(0), mHair(0), + mEyes(0), mHairLength(0.f), mHairColourId(0), mDeathItem(0), + mScriptId(0), mCombatStyle(0), mSoundBase(0), mSound(0), mSoundChance(0), + mFootWeight(0.f), mBoundRadius(0.f), mBaseTemplate(0), mWornArmor(0), + mDefaultOutfit(0), mSleepOutfit(0), mDefaultPkg(0), mFgRace(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mHairColour.red = 0; + mHairColour.green = 0; + mHairColour.blue = 0; + mHairColour.custom = 0; + + std::memset(&mAIData, 0, sizeof(AIData)); + std::memset(&mData, 0, sizeof(Data)); + std::memset(&mBaseConfig, 0, sizeof(ActorBaseConfig)); + std::memset(&mFaction, 0, sizeof(ActorFaction)); +} + +ESM4::Npc::~Npc() +{ +} + +void ESM4::Npc::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + mIsTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + //mIsTES5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; // WARN: FO3 is also VER_094 + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; // not for TES5, see Race + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_SPLO: + { + FormId id; + reader.getFormId(id); + mSpell.push_back(id); + break; + } + case ESM4::SUB_PKID: + { + FormId id; + reader.getFormId(id); + mAIPackages.push_back(id); + break; + } + case ESM4::SUB_SNAM: + { + reader.get(mFaction); + reader.adjustFormId(mFaction.faction); + break; + } + case ESM4::SUB_RNAM: reader.getFormId(mRace); break; + case ESM4::SUB_CNAM: reader.getFormId(mClass); break; + case ESM4::SUB_HNAM: reader.getFormId(mHair); break; // not for TES5 + case ESM4::SUB_ENAM: reader.getFormId(mEyes); break; + // + case ESM4::SUB_INAM: reader.getFormId(mDeathItem); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + // + case ESM4::SUB_AIDT: + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) + { + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + + reader.get(mAIData); // TES4 + break; + } + case ESM4::SUB_ACBS: + { + //if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) + if (subHdr.dataSize == 24) + reader.get(mBaseConfig); + else + reader.get(&mBaseConfig, 16); // TES4 + + break; + } + case ESM4::SUB_DATA: + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) + { + if (subHdr.dataSize != 0) // FIXME FO3 + reader.skipSubRecordData(); + break; // zero length + } + + reader.get(&mData, 33); // FIXME: check packing + break; + } + case ESM4::SUB_ZNAM: reader.getFormId(mCombatStyle); break; + case ESM4::SUB_CSCR: reader.getFormId(mSoundBase); break; + case ESM4::SUB_CSDI: reader.getFormId(mSound); break; + case ESM4::SUB_CSDC: reader.get(mSoundChance); break; + case ESM4::SUB_WNAM: + { + if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + reader.get(mWornArmor); + else + reader.get(mFootWeight); + break; + } + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_KFFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("NPC_ KFFZ data read error"); + + // Seems to be only below 3, and only happens 3 times while loading TES4: + // Forward_SheogorathWithCane.kf + // TurnLeft_SheogorathWithCane.kf + // TurnRight_SheogorathWithCane.kf + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mKf.push_back(file); + + break; + } + case ESM4::SUB_LNAM: reader.get(mHairLength); break; + case ESM4::SUB_HCLR: + { + reader.get(mHairColour.red); + reader.get(mHairColour.green); + reader.get(mHairColour.blue); + reader.get(mHairColour.custom); + + break; + } + case ESM4::SUB_TPLT: reader.get(mBaseTemplate); break; + case ESM4::SUB_FGGS: + { + mSymShapeModeCoefficients.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymShapeModeCoefficients.at(i)); + + break; + } + case ESM4::SUB_FGGA: + { + mAsymShapeModeCoefficients.resize(30); + for (std::size_t i = 0; i < 30; ++i) + reader.get(mAsymShapeModeCoefficients.at(i)); + + break; + } + case ESM4::SUB_FGTS: + { + mSymTextureModeCoefficients.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymTextureModeCoefficients.at(i)); + + break; + } + case ESM4::SUB_FNAM: + { + reader.get(mFgRace); + //std::cout << "race " << mEditorId << " " << mRace << std::endl; // FIXME + //std::cout << "fg race " << mEditorId << " " << mFgRace << std::endl; // FIXME + break; + } + case ESM4::SUB_PNAM: // FO3/FONV/TES5 + { + FormId headPart; + reader.getFormId(headPart); + mHeadParts.push_back(headPart); + + break; + } + case ESM4::SUB_HCLF: // TES5 hair colour + { + reader.getFormId(mHairColourId); + + break; + } + case ESM4::SUB_COCT: // TES5 + { + std::uint32_t count; + reader.get(count); + + break; + } + case ESM4::SUB_DOFT: reader.getFormId(mDefaultOutfit); break; + case ESM4::SUB_SOFT: reader.getFormId(mSleepOutfit); break; + case ESM4::SUB_DPLT: reader.getFormId(mDefaultPkg); break; // AI package list + case ESM4::SUB_DEST: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + { +#if 1 + boost::scoped_array dataBuf(new unsigned char[subHdr.dataSize]); + reader.get(&dataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; + for (std::size_t i = 0; i < subHdr.dataSize; ++i) + { + if (dataBuf[i] > 64 && dataBuf[i] < 91) // looks like printable ascii char + ss << (char)(dataBuf[i]) << " "; + else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(dataBuf[i]); + if ((i & 0x000f) == 0xf) // wrap around + ss << "\n"; + else if (i < (size_t)(subHdr.dataSize-1)) // quiesce gcc + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + //std::cout << "NPC_ " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); +#endif + break; + } + case ESM4::SUB_NAM6: // height mult + case ESM4::SUB_NAM7: // weight mult + case ESM4::SUB_ATKR: + case ESM4::SUB_CRIF: + case ESM4::SUB_CSDT: + case ESM4::SUB_DNAM: + case ESM4::SUB_ECOR: + case ESM4::SUB_ANAM: + case ESM4::SUB_ATKD: + case ESM4::SUB_ATKE: + case ESM4::SUB_FTST: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_NAM5: + case ESM4::SUB_NAM8: + case ESM4::SUB_NAM9: + case ESM4::SUB_NAMA: + case ESM4::SUB_OBND: + case ESM4::SUB_PRKR: + case ESM4::SUB_PRKZ: + case ESM4::SUB_QNAM: + case ESM4::SUB_SPCT: + case ESM4::SUB_TIAS: + case ESM4::SUB_TINC: + case ESM4::SUB_TINI: + case ESM4::SUB_TINV: + case ESM4::SUB_VMAD: + case ESM4::SUB_VTCK: + case ESM4::SUB_GNAM: + case ESM4::SUB_SHRT: + case ESM4::SUB_SPOR: + case ESM4::SUB_EAMT: // FO3 + case ESM4::SUB_NAM4: // FO3 + case ESM4::SUB_COED: // FO3 + { + //std::cout << "NPC_ " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::NPC_::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Npc::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Npc::blank() +//{ +//} diff --git a/components/esm4/loadnpc.hpp b/components/esm4/loadnpc.hpp new file mode 100644 index 0000000000..d30ca5f14f --- /dev/null +++ b/components/esm4/loadnpc.hpp @@ -0,0 +1,230 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NPC__H +#define ESM4_NPC__H + +#include +#include +#include + +#include "actor.hpp" +#include "inventory.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Npc + { + enum ACBS_TES4 + { + TES4_Female = 0x000001, + TES4_Essential = 0x000002, + TES4_Respawn = 0x000008, + TES4_AutoCalcStats = 0x000010, + TES4_PCLevelOffset = 0x000080, + TES4_NoLowLevelProc = 0x000200, + TES4_NoRumors = 0x002000, + TES4_Summonable = 0x004000, + TES4_NoPersuasion = 0x008000, // different meaning to crea + TES4_CanCorpseCheck = 0x100000 // opposite of crea + }; + + enum ACBS_FO3 + { + FO3_Female = 0x00000001, + FO3_Essential = 0x00000002, + FO3_PresetFace = 0x00000004, // Is CharGen Face Preset + FO3_Respawn = 0x00000008, + FO3_AutoCalcStats = 0x00000010, + FO3_PCLevelMult = 0x00000080, + FO3_UseTemplate = 0x00000100, + FO3_NoLowLevelProc = 0x00000200, + FO3_NoBloodSpray = 0x00000800, + FO3_NoBloodDecal = 0x00001000, + FO3_NoVATSMelee = 0x00100000, + FO3_AnyRace = 0x00400000, + FO3_AutoCalcServ = 0x00800000, + FO3_NoKnockdown = 0x04000000, + FO3_NotPushable = 0x08000000, + FO3_NoRotateHead = 0x40000000 + }; + + enum ACBS_TES5 + { + TES5_Female = 0x00000001, + TES5_Essential = 0x00000002, + TES5_PresetFace = 0x00000004, // Is CharGen Face Preset + TES5_Respawn = 0x00000008, + TES5_AutoCalcStats = 0x00000010, + TES5_Unique = 0x00000020, + TES5_NoStealth = 0x00000040, // Doesn't affect stealth meter + TES5_PCLevelMult = 0x00000080, + //TES5_Unknown = 0x00000100, // Audio template? + TES5_Protected = 0x00000800, + TES5_Summonable = 0x00004000, + TES5_NoBleeding = 0x00010000, + TES5_Owned = 0x00040000, // owned/follow? (Horses, Atronachs, NOT Shadowmere) + TES5_GenderAnim = 0x00080000, // Opposite Gender Anims + TES5_SimpleActor = 0x00100000, + TES5_LoopedScript = 0x00200000, // AAvenicci, Arcadia, Silvia, Afflicted, TortureVictims + TES5_LoopedAudio = 0x10000000, // AAvenicci, Arcadia, Silvia, DA02 Cultists, Afflicted, TortureVictims + TES5_IsGhost = 0x20000000, // Ghost/non-interactable (Ghosts, Nocturnal) + TES5_Invulnerable = 0x80000000 + }; + + enum Template_Flags + { + TES5_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, + // voice type, death item; Sounds tab; Animation tab; Character Gen tabs + TES5_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, + // speed, bleedout, class + TES5_UseFactions = 0x0004, // both factions and assigned crime faction + TES5_UseSpellList = 0x0008, // both spells and perks + TES5_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and + // gift filter + TES5_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; + // rest of tab controlled by Def Pack List + TES5_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, + // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter + TES5_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item + // -- but not death item + TES5_UseScript = 0x0200, + TES5_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) + TES5_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, + // events, and data) + TES5_UseKeywords = 0x1000 + }; + +#pragma pack(push, 1) + struct SkillValues + { + std::uint8_t armorer; + std::uint8_t athletics; + std::uint8_t blade; + std::uint8_t block; + std::uint8_t blunt; + std::uint8_t handToHand; + std::uint8_t heavyArmor; + std::uint8_t alchemy; + std::uint8_t alteration; + std::uint8_t conjuration; + std::uint8_t destruction; + std::uint8_t illusion; + std::uint8_t mysticism; + std::uint8_t restoration; + std::uint8_t acrobatics; + std::uint8_t lightArmor; + std::uint8_t marksman; + std::uint8_t mercantile; + std::uint8_t security; + std::uint8_t sneak; + std::uint8_t speechcraft; + }; + + struct HairColour + { + std::uint8_t red; + std::uint8_t green; + std::uint8_t blue; + std::uint8_t custom; // alpha? + }; + + struct Data + { + SkillValues skills; + std::uint32_t health; + AttributeValues attribs; + }; + +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + bool mIsTES4; + bool mIsFONV; + + std::string mEditorId; + std::string mFullName; + std::string mModel; // skeleton model (can be a marker in FO3/FONV) + + FormId mRace; + FormId mClass; + FormId mHair; // not for TES5, see mHeadParts + FormId mEyes; + + std::vector mHeadParts; // FO3/FONV/TES5 + + float mHairLength; + HairColour mHairColour; // TES4/FO3/FONV + FormId mHairColourId; // TES5 + + FormId mDeathItem; + std::vector mSpell; + FormId mScriptId; + + AIData mAIData; + std::vector mAIPackages; // seems to be in priority order, 0 = highest priority + ActorBaseConfig mBaseConfig; // union + ActorFaction mFaction; + Data mData; + FormId mCombatStyle; + FormId mSoundBase; + FormId mSound; + std::uint8_t mSoundChance; + float mFootWeight; + + float mBoundRadius; + std::vector mKf; // filenames only, get directory path from mModel + + std::vector mInventory; + + FormId mBaseTemplate; // FO3/FONV/TES5 + FormId mWornArmor; // TES5 only? + + FormId mDefaultOutfit; // TES5 OTFT + FormId mSleepOutfit; // TES5 OTFT + FormId mDefaultPkg; + + std::vector mSymShapeModeCoefficients; // size 0 or 50 + std::vector mAsymShapeModeCoefficients; // size 0 or 30 + std::vector mSymTextureModeCoefficients; // size 0 or 50 + std::int16_t mFgRace; + + Npc(); + virtual ~Npc(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_NPC__H diff --git a/components/esm4/loadotft.cpp b/components/esm4/loadotft.cpp new file mode 100644 index 0000000000..dec2a05666 --- /dev/null +++ b/components/esm4/loadotft.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadotft.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Outfit::Outfit() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::Outfit::~Outfit() +{ +} + +void ESM4::Outfit::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_INAM: + { + std::size_t numObj = subHdr.dataSize / sizeof(FormId); + for (std::size_t i = 0; i < numObj; ++i) + { + FormId formId; + reader.getFormId(formId); + + mInventory.push_back(formId); + } + + break; + } + default: + //std::cout << "OTFT " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::OTFT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Outfit::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Outfit::blank() +//{ +//} diff --git a/components/esm4/loadotft.hpp b/components/esm4/loadotft.hpp new file mode 100644 index 0000000000..3b1db34d0e --- /dev/null +++ b/components/esm4/loadotft.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_OTFT_H +#define ESM4_OTFT_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Outfit + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::vector mInventory; + + Outfit(); + virtual ~Outfit(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_OTFT_H diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp new file mode 100644 index 0000000000..241147df00 --- /dev/null +++ b/components/esm4/loadpack.cpp @@ -0,0 +1,200 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadpack.hpp" + +#include +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::AIPackage::AIPackage() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + std::memset(&mData, 0, sizeof(PKDT)); + std::memset(&mSchedule, 0, sizeof(PSDT)); + std::memset(&mLocation, 0, sizeof(PLDT)); + mLocation.type = 0xff; // default to indicate no location data + std::memset(&mTarget, 0, sizeof(PTDT)); + mTarget.type = 0xff; // default to indicate no target data + + mConditions.clear(); +} + +ESM4::AIPackage::~AIPackage() +{ +} + +void ESM4::AIPackage::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_PKDT: + { + if (subHdr.dataSize != sizeof(PKDT) && subHdr.dataSize == 4) + { + //std::cout << "skip fallout" << mEditorId << std::endl; // FIXME + reader.get(mData.flags); + mData.type = 0; // FIXME + } + else if (subHdr.dataSize != sizeof(mData)) + reader.skipSubRecordData(); // FIXME: FO3 + else + reader.get(mData); + + break; + } + case ESM4::SUB_PSDT: //reader.get(mSchedule); break; + { + if (subHdr.dataSize != sizeof(mSchedule)) + reader.skipSubRecordData(); // FIXME: + else + reader.get(mSchedule); // TES4 + + break; + } + case ESM4::SUB_PLDT: + { + if (subHdr.dataSize != sizeof(mLocation)) + reader.skipSubRecordData(); // FIXME: + else + { + reader.get(mLocation); // TES4 + if (mLocation.type != 5) + reader.adjustFormId(mLocation.location); + } + + break; + } + case ESM4::SUB_PTDT: + { + if (subHdr.dataSize != sizeof(mTarget)) + reader.skipSubRecordData(); // FIXME: FO3 + else + { + reader.get(mTarget); // TES4 + if (mLocation.type != 2) + reader.adjustFormId(mTarget.target); + } + + break; + } + case ESM4::SUB_CTDA: + { + if (subHdr.dataSize != sizeof(CTDA)) + { + reader.skipSubRecordData(); // FIXME: FO3 + break; + } + + static CTDA condition; + reader.get(condition); + // FIXME: how to "unadjust" if not FormId? + //adjustFormId(condition.param1); + //adjustFormId(condition.param2); + mConditions.push_back(condition); + + break; + } + case ESM4::SUB_CTDT: // always 20 for TES4 + case ESM4::SUB_TNAM: // FO3 + case ESM4::SUB_INAM: // FO3 + case ESM4::SUB_CNAM: // FO3 + case ESM4::SUB_SCHR: // FO3 + case ESM4::SUB_POBA: // FO3 + case ESM4::SUB_POCA: // FO3 + case ESM4::SUB_POEA: // FO3 + case ESM4::SUB_SCTX: // FO3 + case ESM4::SUB_SCDA: // FO3 + case ESM4::SUB_SCRO: // FO3 + case ESM4::SUB_IDLA: // FO3 + case ESM4::SUB_IDLC: // FO3 + case ESM4::SUB_IDLF: // FO3 + case ESM4::SUB_IDLT: // FO3 + case ESM4::SUB_PKDD: // FO3 + case ESM4::SUB_PKD2: // FO3 + case ESM4::SUB_PKPT: // FO3 + case ESM4::SUB_PKED: // FO3 + case ESM4::SUB_PKE2: // FO3 + case ESM4::SUB_PKAM: // FO3 + case ESM4::SUB_PUID: // FO3 + case ESM4::SUB_PKW3: // FO3 + case ESM4::SUB_PTD2: // FO3 + case ESM4::SUB_PLD2: // FO3 + case ESM4::SUB_PKFD: // FO3 + case ESM4::SUB_SLSD: // FO3 + case ESM4::SUB_SCVR: // FO3 + case ESM4::SUB_SCRV: // FO3 + case ESM4::SUB_IDLB: // FO3 + case ESM4::SUB_ANAM: // TES5 + case ESM4::SUB_BNAM: // TES5 + case ESM4::SUB_FNAM: // TES5 + case ESM4::SUB_PNAM: // TES5 + case ESM4::SUB_QNAM: // TES5 + case ESM4::SUB_UNAM: // TES5 + case ESM4::SUB_XNAM: // TES5 + case ESM4::SUB_PDTO: // TES5 + case ESM4::SUB_PTDA: // TES5 + case ESM4::SUB_PFOR: // TES5 + case ESM4::SUB_PFO2: // TES5 + case ESM4::SUB_PRCB: // TES5 + case ESM4::SUB_PKCU: // TES5 + case ESM4::SUB_PKC2: // TES5 + case ESM4::SUB_CITC: // TES5 + case ESM4::SUB_CIS1: // TES5 + case ESM4::SUB_CIS2: // TES5 + case ESM4::SUB_VMAD: // TES5 + case ESM4::SUB_TPIC: // TES5 + { + //std::cout << "PACK " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::PACK::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::AIPackage::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::AIPackage::blank() +//{ +//} diff --git a/components/esm4/loadpack.hpp b/components/esm4/loadpack.hpp new file mode 100644 index 0000000000..93a9a8290c --- /dev/null +++ b/components/esm4/loadpack.hpp @@ -0,0 +1,110 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_PACK_H +#define ESM4_PACK_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct AIPackage + { +#pragma pack(push, 1) + struct PKDT // data + { + std::uint32_t flags; + std::int32_t type; + }; + + struct PSDT // schedule + { + std::uint8_t month; // Any = 0xff + std::uint8_t dayOfWeek; // Any = 0xff + std::uint8_t date; // Any = 0 + std::uint8_t time; // Any = 0xff + std::uint32_t duration; + }; + + struct PLDT // location + { + std::int32_t type; // 0 = near ref, 1 = in cell, 2 = current loc, 3 = editor loc, 4 = obj id, 5 = obj type + FormId location; // uint32_t if type = 5 + std::int32_t radius; + }; + + struct PTDT // target + { + std::int32_t type; // 0 = specific ref, 1 = obj id, 2 = obj type + FormId target; // uint32_t if type = 2 + std::int32_t distance; + }; + + // NOTE: param1/param2 can be FormId or number, but assume FormId so that adjustFormId + // can be called + struct CTDA + { + std::uint8_t condition; + std::uint8_t unknown1; // probably padding + std::uint8_t unknown2; // probably padding + std::uint8_t unknown3; // probably padding + float compValue; + std::int32_t fnIndex; + FormId param1; + FormId param2; + std::uint32_t unknown4; // probably padding + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + PKDT mData; + PSDT mSchedule; + PLDT mLocation; + PTDT mTarget; + std::vector mConditions; + + AIPackage(); + virtual ~AIPackage(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_PACK_H diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp new file mode 100644 index 0000000000..a0bfc9b119 --- /dev/null +++ b/components/esm4/loadpgrd.cpp @@ -0,0 +1,177 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadpgrd.hpp" + +#include +//#include // FIXME: for debugging only +//#include // FIXME: for debugging only + +//#include // FIXME for debugging only + +#include "formid.hpp" // FIXME: for mEditorId workaround +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Pathgrid::Pathgrid() : mFormId(0), mFlags(0), mData(0) +{ + mEditorId.clear(); + + mNodes.clear(); + mLinks.clear(); + mForeign.clear(); + mObjects.clear(); +} + +ESM4::Pathgrid::~Pathgrid() +{ +} + +void ESM4::Pathgrid::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_PGRP: + { + std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); + if (numNodes != std::size_t(mData)) // keep gcc quiet + throw std::runtime_error("ESM4::PGRD::load numNodes mismatch"); + + mNodes.resize(numNodes); + for (std::size_t i = 0; i < numNodes; ++i) + { + reader.get(mNodes.at(i)); + + if (int(mNodes.at(i).z) % 2 == 0) + mNodes.at(i).priority = 0; + else + mNodes.at(i).priority = 1; + } + + break; + } + case ESM4::SUB_PGRR: + { + static PGRR link; + + for (std::size_t i = 0; i < std::size_t(mData); ++i) // keep gcc quiet + { + for (std::size_t j = 0; j < mNodes[i].numLinks; ++j) + { + link.startNode = std::int16_t(i); + + reader.get(link.endNode); + if (link.endNode == -1) + continue; + + // ICMarketDistrictTheBestDefenseBasement doesn't have a PGRR sub-record + // CELL formId 00049E2A + // PGRD formId 000304B7 + //if (mFormId == 0x0001C2C8) + //std::cout << link.startNode << "," << link.endNode << std::endl; + mLinks.push_back(link); + } + } + + break; + } + case ESM4::SUB_PGRI: + { + std::size_t numForeign = subHdr.dataSize / sizeof(PGRI); + mForeign.resize(numForeign); + for (std::size_t i = 0; i < numForeign; ++i) + { + reader.get(mForeign.at(i)); + mForeign.at(i).localNode;// &= 0xffff; // some have junk high bits (maybe flags?) + } + + break; + } + case ESM4::SUB_PGRL: + { + static PGRL objLink; + reader.get(objLink.object); + // object linkedNode + std::size_t numNodes = (subHdr.dataSize - sizeof(int32_t)) / sizeof(int32_t); + + objLink.linkedNodes.resize(numNodes); + for (std::size_t i = 0; i < numNodes; ++i) + reader.get(objLink.linkedNodes.at(i)); + + mObjects.push_back(objLink); + + break; + } + case ESM4::SUB_PGAG: + { +#if 0 + boost::scoped_array mDataBuf(new unsigned char[subHdr.dataSize]); + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; + for (std::size_t i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) // looks like printable ascii char + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) // wrap around + ss << "\n"; + else if (i < subHdr.dataSize-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + //std::cout << "PGRD " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); +#endif + break; + } + default: + throw std::runtime_error("ESM4::PGRD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Pathgrid::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Pathgrid::blank() +//{ +//} diff --git a/components/esm4/loadpgrd.hpp b/components/esm4/loadpgrd.hpp new file mode 100644 index 0000000000..9f522af5ca --- /dev/null +++ b/components/esm4/loadpgrd.hpp @@ -0,0 +1,96 @@ +/* + Copyright (C) 2020 - 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_PGRD_H +#define ESM4_PGRD_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Pathgrid + { +#pragma pack(push, 1) + struct PGRP + { + float x; + float y; + float z; + std::uint8_t numLinks; + std::uint8_t priority; // probably padding, repurposing + std::uint16_t unknown; // probably padding + }; + + struct PGRR + { + std::int16_t startNode; + std::int16_t endNode; + }; + + struct PGRI + { + std::int32_t localNode; + float x; // foreign + float y; // foreign + float z; // foreign + }; +#pragma pack(pop) + + struct PGRL + { + FormId object; + std::vector linkedNodes; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; // FIXME: no such record for PGRD, but keep here to avoid extra work for now + + std::int16_t mData; // number of nodes + std::vector mNodes; + std::vector mLinks; + std::vector mForeign; + std::vector mObjects; + + Pathgrid(); + virtual ~Pathgrid(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_PGRD_H diff --git a/components/esm4/loadpgre.cpp b/components/esm4/loadpgre.cpp new file mode 100644 index 0000000000..9c3e745ab9 --- /dev/null +++ b/components/esm4/loadpgre.cpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadpgre.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::PlacedGrenade::PlacedGrenade() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::PlacedGrenade::~PlacedGrenade() +{ +} + +void ESM4::PlacedGrenade::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_NAME: + case ESM4::SUB_XEZN: + case ESM4::SUB_XRGD: + case ESM4::SUB_XRGB: + case ESM4::SUB_XPRD: + case ESM4::SUB_XPPA: + case ESM4::SUB_INAM: + case ESM4::SUB_TNAM: + case ESM4::SUB_XOWN: + case ESM4::SUB_XRNK: + case ESM4::SUB_XCNT: + case ESM4::SUB_XRDS: + case ESM4::SUB_XHLP: + case ESM4::SUB_XPWR: + case ESM4::SUB_XDCR: + case ESM4::SUB_XLKR: + case ESM4::SUB_XCLP: + case ESM4::SUB_XAPD: + case ESM4::SUB_XAPR: + case ESM4::SUB_XATO: + case ESM4::SUB_XESP: + case ESM4::SUB_XEMI: + case ESM4::SUB_XMBR: + case ESM4::SUB_XIBS: + case ESM4::SUB_XSCL: + case ESM4::SUB_DATA: + { + //std::cout << "PGRE " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "PGRE " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::PGRE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::PlacedGrenade::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::PlacedGrenade::blank() +//{ +//} diff --git a/components/esm4/loadpgre.hpp b/components/esm4/loadpgre.hpp new file mode 100644 index 0000000000..bb36acfc37 --- /dev/null +++ b/components/esm4/loadpgre.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_PGRE_H +#define ESM4_PGRE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct PlacedGrenade + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + PlacedGrenade(); + virtual ~PlacedGrenade(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_PGRE_H diff --git a/components/esm4/loadpwat.cpp b/components/esm4/loadpwat.cpp new file mode 100644 index 0000000000..a88adcf4c2 --- /dev/null +++ b/components/esm4/loadpwat.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadpwat.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::PlaceableWater::PlaceableWater() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::PlaceableWater::~PlaceableWater() +{ +} + +void ESM4::PlaceableWater::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_OBND: + case ESM4::SUB_MODL: + case ESM4::SUB_DNAM: + { + //std::cout << "PWAT " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "PWAT " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::PWAT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::PlaceableWater::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::PlaceableWater::blank() +//{ +//} diff --git a/components/esm4/loadpwat.hpp b/components/esm4/loadpwat.hpp new file mode 100644 index 0000000000..e1de91ab96 --- /dev/null +++ b/components/esm4/loadpwat.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_PWAT_H +#define ESM4_PWAT_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct PlaceableWater + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + PlaceableWater(); + virtual ~PlaceableWater(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_PWAT_H diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp new file mode 100644 index 0000000000..67e250ae76 --- /dev/null +++ b/components/esm4/loadqust.cpp @@ -0,0 +1,179 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadqust.hpp" + +#include +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Quest::Quest() : mFormId(0), mFlags(0), mQuestScript(0) +{ + mEditorId.clear(); + mQuestName.clear(); + mFileName.clear(); + + std::memset(&mScript.scriptHeader, 0, sizeof(ScriptHeader)); + mScript.scriptSource.clear(); + mScript.globReference = 0; +} + +ESM4::Quest::~Quest() +{ +} + +void ESM4::Quest::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mQuestName); break; + case ESM4::SUB_ICON: reader.getZString(mFileName); break; // TES4 (none in FO3/FONV) + case ESM4::SUB_DATA: + { + if (subHdr.dataSize == 2) // TES4 + { + reader.get(&mData, 2); + mData.questDelay = 0.f; // unused in TES4 but keep it clean + + //if ((mData.flags & Flag_StartGameEnabled) != 0) + //std::cout << "start quest " << mEditorId << std::endl; + } + else + reader.get(mData); // FO3 + + break; + } + case ESM4::SUB_SCRI: reader.get(mQuestScript); break; + case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + { + TargetCondition cond; + + if (subHdr.dataSize == 24) // TES4 + { + reader.get(&cond, 24); + cond.reference = 0; // unused in TES4 but keep it clean + } + else if (subHdr.dataSize == 28) + { + reader.get(cond); // FO3/FONV + if (cond.reference) + reader.adjustFormId(cond.reference); + } + else + { + // one record with size 20: EDID GenericSupMutBehemoth + reader.skipSubRecordData(); // FIXME + } + // FIXME: support TES5 + + mTargetConditions.push_back(cond); + + break; + } + case ESM4::SUB_SCHR: reader.get(mScript.scriptHeader); break; + case ESM4::SUB_SCDA: reader.skipSubRecordData(); break; // compiled script data + case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); break; + case ESM4::SUB_SCRO: reader.getFormId(mScript.globReference); break; + case ESM4::SUB_INDX: + case ESM4::SUB_QSDT: + case ESM4::SUB_CNAM: + case ESM4::SUB_QSTA: + case ESM4::SUB_NNAM: // FO3 + case ESM4::SUB_QOBJ: // FO3 + case ESM4::SUB_NAM0: // FO3 + case ESM4::SUB_ANAM: // TES5 + case ESM4::SUB_DNAM: // TES5 + case ESM4::SUB_ENAM: // TES5 + case ESM4::SUB_FNAM: // TES5 + case ESM4::SUB_NEXT: // TES5 + case ESM4::SUB_ALCA: // TES5 + case ESM4::SUB_ALCL: // TES5 + case ESM4::SUB_ALCO: // TES5 + case ESM4::SUB_ALDN: // TES5 + case ESM4::SUB_ALEA: // TES5 + case ESM4::SUB_ALED: // TES5 + case ESM4::SUB_ALEQ: // TES5 + case ESM4::SUB_ALFA: // TES5 + case ESM4::SUB_ALFC: // TES5 + case ESM4::SUB_ALFD: // TES5 + case ESM4::SUB_ALFE: // TES5 + case ESM4::SUB_ALFI: // TES5 + case ESM4::SUB_ALFL: // TES5 + case ESM4::SUB_ALFR: // TES5 + case ESM4::SUB_ALID: // TES5 + case ESM4::SUB_ALLS: // TES5 + case ESM4::SUB_ALNA: // TES5 + case ESM4::SUB_ALNT: // TES5 + case ESM4::SUB_ALPC: // TES5 + case ESM4::SUB_ALRT: // TES5 + case ESM4::SUB_ALSP: // TES5 + case ESM4::SUB_ALST: // TES5 + case ESM4::SUB_ALUA: // TES5 + case ESM4::SUB_CIS2: // TES5 + case ESM4::SUB_CNTO: // TES5 + case ESM4::SUB_COCT: // TES5 + case ESM4::SUB_ECOR: // TES5 + case ESM4::SUB_FLTR: // TES5 + case ESM4::SUB_KNAM: // TES5 + case ESM4::SUB_KSIZ: // TES5 + case ESM4::SUB_KWDA: // TES5 + case ESM4::SUB_QNAM: // TES5 + case ESM4::SUB_QTGL: // TES5 + case ESM4::SUB_SPOR: // TES5 + case ESM4::SUB_VMAD: // TES5 + case ESM4::SUB_VTCK: // TES5 + { + //std::cout << "QUST " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::QUST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //if (mEditorId == "DAConversations") + //std::cout << mEditorId << std::endl; +} + +//void ESM4::Quest::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Quest::blank() +//{ +//} diff --git a/components/esm4/loadqust.hpp b/components/esm4/loadqust.hpp new file mode 100644 index 0000000000..905a5cf0fa --- /dev/null +++ b/components/esm4/loadqust.hpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_QUST_H +#define ESM4_QUST_H + +#include + +#include "formid.hpp" +#include "script.hpp" // TargetCondition, ScriptDefinition + +namespace ESM4 +{ + class Reader; + class Writer; + +#pragma pack(push, 1) + struct QuestData + { + std::uint8_t flags; // Quest_Flags + std::uint8_t priority; + std::uint16_t padding; // FO3 + float questDelay; // FO3 + }; +#pragma pack(pop) + + struct Quest + { + // NOTE: these values are for TES4 + enum Quest_Flags + { + Flag_StartGameEnabled = 0x01, + Flag_AllowRepeatConvTopic = 0x04, + Flag_AllowRepeatStages = 0x08 + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mQuestName; + std::string mFileName; // texture file + FormId mQuestScript; + + QuestData mData; + + std::vector mTargetConditions; + + ScriptDefinition mScript; + + Quest(); + virtual ~Quest(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_QUST_H diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp new file mode 100644 index 0000000000..79f635bd48 --- /dev/null +++ b/components/esm4/loadrace.cpp @@ -0,0 +1,715 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadrace.hpp" + +#include +#include +#include // FIXME: for debugging only +#include // FIXME: for debugging only + +#include "formid.hpp" +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Race::Race() : mFormId(0), mFlags(0), mIsTES5(false) + , mHeightMale(1.f), mHeightFemale(1.f), mWeightMale(1.f), mWeightFemale(1.f) + , mRaceFlags(0), mFaceGenMainClamp(0.f), mFaceGenFaceClamp(0.f), mNumKeywords(0) + , mSkin(0) +{ + mEditorId.clear(); + mFullName.clear(); + mDesc.clear(); + mModelMale.clear(); + mModelFemale.clear(); + + std::memset(&mAttribMale, 0, sizeof(AttributeValues)); + std::memset(&mAttribFemale, 0, sizeof(AttributeValues)); + + mVNAM.resize(2); + mDefaultHair.resize(2); + + mBodyTemplate.bodyPart = 0; + mBodyTemplate.flags = 0; + mBodyTemplate.type = 0; +} + +ESM4::Race::~Race() +{ +} + +void ESM4::Race::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + bool isTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + bool isFO3 = false; + + bool isMale = false; + int curr_part = -1; // 0 = head, 1 = body, 2 = egt, 3 = hkx + std::uint32_t currentIndex = 0xffffffff; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + //std::cout << "RACE " << ESM::printName(subHdr.typeId) << std::endl; + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: + { + reader.getZString(mEditorId); + // TES4 + // Sheogorath 0x0005308E + // GoldenSaint 0x0001208F + // DarkSeducer 0x0001208E + // VampireRace 0x00000019 + // Dremora 0x00038010 + // Argonian 0x00023FE9 + // Nord 0x000224FD + // Breton 0x000224FC + // WoodElf 0x000223C8 + // Khajiit 0x000223C7 + // DarkElf 0x000191C1 + // Orc 0x000191C0 + // HighElf 0x00019204 + // Redguard 0x00000D43 + // Imperial 0x00000907 + break; + } + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: + { + if (subHdr.dataSize == 1) // FO3? + { + reader.skipSubRecordData(); + break; + } + + reader.getLocalizedString(mDesc); break; + } + case ESM4::SUB_SPLO: // bonus spell formid (TES5 may have SPCT and multiple SPLO) + { + FormId magic; + reader.getFormId(magic); + mBonusSpells.push_back(magic); +// std::cout << "RACE " << printName(subHdr.typeId) << " " << formIdToString(magic) << std::endl; + + break; + } + case ESM4::SUB_DATA: // ?? different length for TES5 + { +// DATA:size 128 +// 0f 0f ff 00 ff 00 ff 00 ff 00 ff 00 ff 00 00 00 +// 9a 99 99 3f 00 00 80 3f 00 00 80 3f 00 00 80 3f +// 48 89 10 00 00 00 40 41 00 00 00 00 00 00 48 43 +// 00 00 48 43 00 00 80 3f 9a 99 19 3f 00 00 00 40 +// 01 00 00 00 ff ff ff ff ff ff ff ff 00 00 00 00 +// ff ff ff ff 00 00 00 00 00 00 20 41 00 00 a0 40 +// 00 00 a0 40 00 00 80 42 ff ff ff ff 00 00 00 00 +// 00 00 00 00 9a 99 99 3e 00 00 a0 40 02 00 00 00 +#if 0 + unsigned char mDataBuf[256/*bufSize*/]; + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + ss << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; + for (unsigned int i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < 256/*bufSize*/-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + if (subHdr.dataSize == 36) // TES4/FO3/FONV + { + if (!isTES4 && !isFONV && !mIsTES5) + isFO3 = true; + + std::uint8_t skill; + std::uint8_t bonus; + for (unsigned int i = 0; i < 8; ++i) + { + reader.get(skill); + reader.get(bonus); + mSkillBonus[static_cast(skill)] = bonus; + } + reader.get(mHeightMale); + reader.get(mHeightFemale); + reader.get(mWeightMale); + reader.get(mWeightFemale); + reader.get(mRaceFlags); + } + else if (subHdr.dataSize >= 128 && subHdr.dataSize <= 164) // TES5 + { + mIsTES5 = true; + + std::uint8_t skill; + std::uint8_t bonus; + for (unsigned int i = 0; i < 7; ++i) + { + reader.get(skill); + reader.get(bonus); + mSkillBonus[static_cast(skill)] = bonus; + } + std::uint16_t unknown; + reader.get(unknown); + + reader.get(mHeightMale); + reader.get(mHeightFemale); + reader.get(mWeightMale); + reader.get(mWeightFemale); + reader.get(mRaceFlags); + + // FIXME + float dummy; + reader.get(dummy); // starting health + reader.get(dummy); // starting magicka + reader.get(dummy); // starting stamina + reader.get(dummy); // base carry weight + reader.get(dummy); // base mass + reader.get(dummy); // accleration rate + reader.get(dummy); // decleration rate + + uint32_t dummy2; + reader.get(dummy2); // size + reader.get(dummy2); // head biped object + reader.get(dummy2); // hair biped object + reader.get(dummy); // injured health % (0.f..1.f) + reader.get(dummy2); // shield biped object + reader.get(dummy); // health regen + reader.get(dummy); // magicka regen + reader.get(dummy); // stamina regen + reader.get(dummy); // unarmed damage + reader.get(dummy); // unarmed reach + reader.get(dummy2); // body biped object + reader.get(dummy); // aim angle tolerance + reader.get(dummy2); // unknown + reader.get(dummy); // angular accleration rate + reader.get(dummy); // angular tolerance + reader.get(dummy2); // flags + + if (subHdr.dataSize > 128) + { + reader.get(dummy2); // unknown 1 + reader.get(dummy2); // unknown 2 + reader.get(dummy2); // unknown 3 + reader.get(dummy2); // unknown 4 + reader.get(dummy2); // unknown 5 + reader.get(dummy2); // unknown 6 + reader.get(dummy2); // unknown 7 + reader.get(dummy2); // unknown 8 + reader.get(dummy2); // unknown 9 + } + } + else + { + reader.skipSubRecordData(); + std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + } +#endif + break; + } + case ESM4::SUB_DNAM: + { + reader.get(mDefaultHair[0]); // male + reader.get(mDefaultHair[1]); // female + + break; + } + case ESM4::SUB_CNAM: // Only in TES4? + // CNAM SNAM VNAM + // Sheogorath 0x0 0000 98 2b 10011000 00101011 + // Golden Saint 0x3 0011 26 46 00100110 01000110 + // Dark Seducer 0xC 1100 df 55 11011111 01010101 + // Vampire Race 0x0 0000 77 44 01110111 10001000 + // Dremora 0x7 0111 bf 32 10111111 00110010 + // Argonian 0x0 0000 dc 3c 11011100 00111100 + // Nord 0x5 0101 b6 03 10110110 00000011 + // Breton 0x5 0101 48 1d 01001000 00011101 00000000 00000907 (Imperial) + // Wood Elf 0xD 1101 2e 4a 00101110 01001010 00019204 00019204 (HighElf) + // khajiit 0x5 0101 54 5b 01010100 01011011 00023FE9 00023FE9 (Argonian) + // Dark Elf 0x0 0000 72 54 01110010 01010100 00019204 00019204 (HighElf) + // Orc 0xC 1100 74 09 01110100 00001001 000224FD 000224FD (Nord) + // High Elf 0xF 1111 e6 21 11100110 00100001 + // Redguard 0xD 1101 a9 61 10101001 01100001 + // Imperial 0xD 1101 8e 35 10001110 00110101 + { + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_PNAM: reader.get(mFaceGenMainClamp); break; // 0x40A00000 = 5.f + case ESM4::SUB_UNAM: reader.get(mFaceGenFaceClamp); break; // 0x40400000 = 3.f + case ESM4::SUB_ATTR: // Only in TES4? + { + if (subHdr.dataSize == 2) // FO3? + { + reader.skipSubRecordData(); + break; + } + reader.get(mAttribMale.strength); + reader.get(mAttribMale.intelligence); + reader.get(mAttribMale.willpower); + reader.get(mAttribMale.agility); + reader.get(mAttribMale.speed); + reader.get(mAttribMale.endurance); + reader.get(mAttribMale.personality); + reader.get(mAttribMale.luck); + reader.get(mAttribFemale.strength); + reader.get(mAttribFemale.intelligence); + reader.get(mAttribFemale.willpower); + reader.get(mAttribFemale.agility); + reader.get(mAttribFemale.speed); + reader.get(mAttribFemale.endurance); + reader.get(mAttribFemale.personality); + reader.get(mAttribFemale.luck); + + break; + } + // [0..9]-> ICON + // NAM0 -> INDX -> MODL --+ + // ^ -> MODB | + // | | + // +-------------+ + // + case ESM4::SUB_NAM0: // start marker head data /* 1 */ + { + curr_part = 0; // head part + + if (isFO3 || isFONV) + { + mHeadParts.resize(8); + mHeadPartsFemale.resize(8); + } + else if (isTES4) + mHeadParts.resize(9); // assumed based on Construction Set + else + { + mHeadPartIdsMale.resize(5); + mHeadPartIdsFemale.resize(5); + } + + currentIndex = 0xffffffff; + break; + } + case ESM4::SUB_INDX: + { + reader.get(currentIndex); + // FIXME: below check is rather useless + //if (headpart) + //{ + // if (currentIndex > 8) + // throw std::runtime_error("ESM4::RACE::load - too many head part " + currentIndex); + //} + //else // bodypart + //{ + // if (currentIndex > 4) + // throw std::runtime_error("ESM4::RACE::load - too many body part " + currentIndex); + //} + + break; + } + case ESM4::SUB_MODL: + { + if (curr_part == 0) // head part + { + if (isMale || isTES4) + reader.getZString(mHeadParts[currentIndex].mesh); + else + reader.getZString(mHeadPartsFemale[currentIndex].mesh); // TODO: check TES4 + + // TES5 keeps head part formid in mHeadPartIdsMale and mHeadPartIdsFemale + } + else if (curr_part == 1) // body part + { + if (isMale) + reader.getZString(mBodyPartsMale[currentIndex].mesh); + else + reader.getZString(mBodyPartsFemale[currentIndex].mesh); + + // TES5 seems to have no body parts at all, instead keep EGT models + } + else if (curr_part == 2) // egt + { + //std::cout << mEditorId << " egt " << currentIndex << std::endl; // FIXME + reader.skipSubRecordData(); // FIXME TES5 egt + } + else + { + //std::cout << mEditorId << " hkx " << currentIndex << std::endl; // FIXME + reader.skipSubRecordData(); // FIXME TES5 hkx + } + + break; + } + case ESM4::SUB_MODB: reader.skipSubRecordData(); break; // always 0x0000? + case ESM4::SUB_ICON: + { + if (curr_part == 0) // head part + { + if (isMale || isTES4) + reader.getZString(mHeadParts[currentIndex].texture); + else + reader.getZString(mHeadPartsFemale[currentIndex].texture); // TODO: check TES4 + } + else if (curr_part == 1) // body part + { + if (isMale) + reader.getZString(mBodyPartsMale[currentIndex].texture); + else + reader.getZString(mBodyPartsFemale[currentIndex].texture); + } + else + reader.skipSubRecordData(); // FIXME TES5 + + break; + } + // + case ESM4::SUB_NAM1: // start marker body data /* 4 */ + { + + if (isFO3 || isFONV) + { + curr_part = 1; // body part + + mBodyPartsMale.resize(4); + mBodyPartsFemale.resize(4); + } + else if (isTES4) + { + curr_part = 1; // body part + + mBodyPartsMale.resize(5); // 0 = upper body, 1 = legs, 2 = hands, 3 = feet, 4 = tail + mBodyPartsFemale.resize(5); // 0 = upper body, 1 = legs, 2 = hands, 3 = feet, 4 = tail + } + else // TES5 + curr_part = 2; // for TES5 NAM1 indicates the start of EGT model + + if (isTES4) + currentIndex = 4; // FIXME: argonian tail mesh without preceeding INDX + else + currentIndex = 0xffffffff; + + break; + } + case ESM4::SUB_MNAM: isMale = true; break; /* 2, 5, 7 */ + case ESM4::SUB_FNAM: isMale = false; break; /* 3, 6, 8 */ + // + case ESM4::SUB_HNAM: + { + std::size_t numHairChoices = subHdr.dataSize / sizeof(FormId); + mHairChoices.resize(numHairChoices); + for (unsigned int i = 0; i < numHairChoices; ++i) + reader.get(mHairChoices.at(i)); + + break; + } + case ESM4::SUB_ENAM: + { + std::size_t numEyeChoices = subHdr.dataSize / sizeof(FormId); + mEyeChoices.resize(numEyeChoices); + for (unsigned int i = 0; i < numEyeChoices; ++i) + reader.get(mEyeChoices.at(i)); + + break; + } + case ESM4::SUB_FGGS: + { + if (isMale || isTES4) + { + mSymShapeModeCoefficients.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymShapeModeCoefficients.at(i)); + } + else + { + mSymShapeModeCoeffFemale.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymShapeModeCoeffFemale.at(i)); + } + + break; + } + case ESM4::SUB_FGGA: + { + if (isMale || isTES4) + { + mAsymShapeModeCoefficients.resize(30); + for (std::size_t i = 0; i < 30; ++i) + reader.get(mAsymShapeModeCoefficients.at(i)); + } + else + { + mAsymShapeModeCoeffFemale.resize(30); + for (std::size_t i = 0; i < 30; ++i) + reader.get(mAsymShapeModeCoeffFemale.at(i)); + } + + break; + } + case ESM4::SUB_FGTS: + { + if (isMale || isTES4) + { + mSymTextureModeCoefficients.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymTextureModeCoefficients.at(i)); + } + else + { + mSymTextureModeCoeffFemale.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymTextureModeCoeffFemale.at(i)); + } + + break; + } + // + case ESM4::SUB_SNAM: //skipping...2 // only in TES4? + { + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XNAM: + { + FormId race; + std::int32_t adjustment; + reader.get(race); + reader.get(adjustment); + mDisposition[race] = adjustment; + + break; + } + case ESM4::SUB_VNAM: + { + if (subHdr.dataSize == 8) // TES4 + { + reader.get(mVNAM[0]); // For TES4 seems to be 2 race formids + reader.get(mVNAM[1]); + } + else if (subHdr.dataSize == 4) // TES5 + { + // equipment type flags meant to be uint32 ??? GLOB reference? shows up in + // SCRO in sript records and CTDA in INFO records + uint32_t dummy; + reader.get(dummy); + } + else + { + reader.skipSubRecordData(); + std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + } + + break; + } + // + case ESM4::SUB_ANAM: // TES5 + { + if (isMale) + reader.getZString(mModelMale); + else + reader.getZString(mModelFemale); + break; + } + case ESM4::SUB_KSIZ: reader.get(mNumKeywords); break; + case ESM4::SUB_KWDA: + { + std::uint32_t formid; + for (unsigned int i = 0; i < mNumKeywords; ++i) + reader.getFormId(formid); + break; + } + // + case ESM4::SUB_WNAM: // ARMO FormId + { + reader.getFormId(mSkin); + //std::cout << mEditorId << " skin " << formIdToString(mSkin) << std::endl; // FIXME + break; + } + case ESM4::SUB_BODT: // body template + { + reader.get(mBodyTemplate.bodyPart); + reader.get(mBodyTemplate.flags); + reader.get(mBodyTemplate.unknown1); // probably padding + reader.get(mBodyTemplate.unknown2); // probably padding + reader.get(mBodyTemplate.unknown3); // probably padding + reader.get(mBodyTemplate.type); + + break; + } + case ESM4::SUB_BOD2: // TES5 + { + reader.get(mBodyTemplate.bodyPart); + mBodyTemplate.flags = 0; + mBodyTemplate.unknown1 = 0; // probably padding + mBodyTemplate.unknown2 = 0; // probably padding + mBodyTemplate.unknown3 = 0; // probably padding + reader.get(mBodyTemplate.type); + + break; + } + case ESM4::SUB_HEAD: // TES5 + { + FormId formId; + reader.getFormId(formId); + + // FIXME: no order? head, mouth, eyes, brow, hair + if (isMale) + mHeadPartIdsMale[currentIndex] = formId; + else + mHeadPartIdsFemale[currentIndex] = formId; + + //std::cout << mEditorId << (isMale ? " male head " : " female head ") + //<< formIdToString(formId) << " " << currentIndex << std::endl; // FIXME + + break; + } + case ESM4::SUB_NAM3: // start of hkx model + { + curr_part = 3; // for TES5 NAM3 indicates the start of hkx model + + break; + } + // Not sure for what this is used - maybe to indicate which slots are in use? e.g.: + // + // ManakinRace HEAD + // ManakinRace Hair + // ManakinRace BODY + // ManakinRace Hands + // ManakinRace Forearms + // ManakinRace Amulet + // ManakinRace Ring + // ManakinRace Feet + // ManakinRace Calves + // ManakinRace SHIELD + // ManakinRace + // ManakinRace LongHair + // ManakinRace Circlet + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace DecapitateHead + // ManakinRace Decapitate + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace FX0 + case ESM4::SUB_NAME: // TES5 biped object names (x32) + { + std::string name; + reader.getZString(name); + //std::cout << mEditorId << " " << name << std::endl; + + break; + } + case ESM4::SUB_MTNM: // movement type + case ESM4::SUB_ATKD: // attack data + case ESM4::SUB_ATKE: // attach event + case ESM4::SUB_GNAM: // body part data + case ESM4::SUB_NAM4: // material type + case ESM4::SUB_NAM5: // unarmed impact? + case ESM4::SUB_LNAM: // close loot sound + case ESM4::SUB_QNAM: // equipment slot formid + case ESM4::SUB_HCLF: // default hair colour + case ESM4::SUB_UNES: // unarmed equipment slot formid + case ESM4::SUB_TINC: + case ESM4::SUB_TIND: + case ESM4::SUB_TINI: + case ESM4::SUB_TINL: + case ESM4::SUB_TINP: + case ESM4::SUB_TINT: + case ESM4::SUB_TINV: + case ESM4::SUB_TIRS: + case ESM4::SUB_PHWT: + case ESM4::SUB_AHCF: + case ESM4::SUB_AHCM: + case ESM4::SUB_MPAI: + case ESM4::SUB_MPAV: + case ESM4::SUB_DFTF: + case ESM4::SUB_DFTM: + case ESM4::SUB_FLMV: + case ESM4::SUB_FTSF: + case ESM4::SUB_FTSM: + case ESM4::SUB_MTYP: + case ESM4::SUB_NAM7: + case ESM4::SUB_NAM8: + case ESM4::SUB_PHTN: + case ESM4::SUB_RNAM: + case ESM4::SUB_RNMV: + case ESM4::SUB_RPRF: + case ESM4::SUB_RPRM: + case ESM4::SUB_SNMV: + case ESM4::SUB_SPCT: + case ESM4::SUB_SPED: + case ESM4::SUB_SWMV: + case ESM4::SUB_WKMV: + // + case ESM4::SUB_YNAM: // FO3 + case ESM4::SUB_NAM2: // FO3 + case ESM4::SUB_VTCK: // FO3 + case ESM4::SUB_MODT: // FO3 + case ESM4::SUB_MODD: // FO3 + case ESM4::SUB_ONAM: // FO3 + { + + //std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::RACE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Race::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Race::blank() +//{ +//} diff --git a/components/esm4/loadrace.hpp b/components/esm4/loadrace.hpp new file mode 100644 index 0000000000..0cede13b0b --- /dev/null +++ b/components/esm4/loadrace.hpp @@ -0,0 +1,174 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_RACE +#define ESM4_RACE + +#include +#include +#include + +#include "formid.hpp" +#include "actor.hpp" // AttributeValues, BodyTemplate + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Race + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = not playable, 0x02 = not male, 0x04 = not female, ?? = fixed + }; +#pragma pack(pop) + + enum SkillIndex + { + Skill_Armorer = 0x0C, + Skill_Athletics = 0x0D, + Skill_Blade = 0x0E, + Skill_Block = 0x0F, + Skill_Blunt = 0x10, + Skill_HandToHand = 0x11, + Skill_HeavyArmor = 0x12, + Skill_Alchemy = 0x13, + Skill_Alteration = 0x14, + Skill_Conjuration = 0x15, + Skill_Destruction = 0x16, + Skill_Illusion = 0x17, + Skill_Mysticism = 0x18, + Skill_Restoration = 0x19, + Skill_Acrobatics = 0x1A, + Skill_LightArmor = 0x1B, + Skill_Marksman = 0x1C, + Skill_Mercantile = 0x1D, + Skill_Security = 0x1E, + Skill_Sneak = 0x1F, + Skill_Speechcraft = 0x20, + Skill_None = 0xFF, + Skill_Unknown = 0x00 + }; + + enum HeadPartIndex // TES4 + { + Head = 0, + EarMale = 1, + EarFemale = 2, + Mouth = 3, + TeethLower = 4, + TeethUpper = 5, + Tongue = 6, + EyeLeft = 7, // no texture + EyeRight = 8, // no texture + NumHeadParts = 9 + }; + + enum BodyPartIndex // TES4 + { + UpperBody = 0, + LowerBody = 1, + Hands = 2, + Feet = 3, + Tail = 4, + NumBodyParts = 5 + }; + + struct BodyPart + { + std::string mesh; // can be empty for arms, hands, etc + std::string texture; // can be empty e.g. eye left, eye right + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + bool mIsTES5; + + std::string mEditorId; + std::string mFullName; + std::string mDesc; + std::string mModelMale; // TES5 skeleton (in TES4 skeleton is found in npc_) + std::string mModelFemale; // TES5 skeleton (in TES4 skeleton is found in npc_) + + AttributeValues mAttribMale; + AttributeValues mAttribFemale; + std::map mSkillBonus; + + // DATA + float mHeightMale; + float mHeightFemale; + float mWeightMale; + float mWeightFemale; + std::uint32_t mRaceFlags; // 0x0001 = playable? + + std::vector mHeadParts; // see HeadPartIndex + std::vector mHeadPartsFemale; // see HeadPartIndex + + std::vector mBodyPartsMale; // see BodyPartIndex + std::vector mBodyPartsFemale; // see BodyPartIndex + + std::vector mEyeChoices; // texture only + std::vector mHairChoices; // not for TES5 + + float mFaceGenMainClamp; + float mFaceGenFaceClamp; + std::vector mSymShapeModeCoefficients; // should be 50 + std::vector mSymShapeModeCoeffFemale; // should be 50 + std::vector mAsymShapeModeCoefficients; // should be 30 + std::vector mAsymShapeModeCoeffFemale; // should be 30 + std::vector mSymTextureModeCoefficients; // should be 50 + std::vector mSymTextureModeCoeffFemale; // should be 50 + + std::map mDisposition; // race adjustments + std::vector mBonusSpells; // race ability/power + std::vector mVNAM; // don't know what these are; 1 or 2 RACE FormIds + std::vector mDefaultHair; // male/female (HAIR FormId for TES4) + + std::uint32_t mNumKeywords; + + FormId mSkin; // TES5 + BodyTemplate mBodyTemplate; // TES5 + + // FIXME: there's no fixed order? + // head, mouth, eyes, brow, hair + std::vector mHeadPartIdsMale; // TES5 + std::vector mHeadPartIdsFemale; // TES5 + + Race(); + virtual ~Race(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_RACE diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp new file mode 100644 index 0000000000..4921fd16ca --- /dev/null +++ b/components/esm4/loadrefr.cpp @@ -0,0 +1,349 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadrefr.hpp" + +#include +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Reference::Reference() : mParent(0), mFormId(0), mFlags(0), + mBaseObj(0), mScale(1.f), mOwner(0), mGlobal(0), mFactionRank(-1), + mInitiallyDisabled(false), mIsMapMarker(false), mMapMarker(0), mCount(1), + mAudioLocation(0), mIsLocked(false), mLockLevel(0), mKey(0), mTargetRef(0) +{ + mEditorId.clear(); + mFullName.clear(); + + //mPlacement. + + mEsp.parent = 0; + mEsp.flags = 0; + + mRadio.rangeRadius = 0.f; + mRadio.broadcastRange = 0; + mRadio.staticPercentage = 0.f; + mRadio.posReference = 0; + + mDoor.destDoor = 0; + //mDoor.destPos. + mDoor.flags = 0; +} + +ESM4::Reference::~Reference() +{ +} + +void ESM4::Reference::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currCell(); // NOTE: only for persistent refs? + + // TODO: Let the engine apply this? Saved games? + //mInitiallyDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + FormId mid; + FormId sid; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_NAME: + { + reader.getFormId(mBaseObj); +#if 0 + if (mFlags & ESM4::Rec_Disabled) + std::cout << "REFR disable at start " << formIdToString(mFormId) << + " baseobj " << formIdToString(mBaseObj) << + " " << (mEditorId.empty() ? "" : mEditorId) << std::endl; // FIXME +#endif + //if (mBaseObj == 0x20) // e.g. FO3 mFormId == 0x0007E90F + //if (mBaseObj == 0x17) + //std::cout << mEditorId << std::endl; + break; + } + case ESM4::SUB_DATA: reader.get(mPlacement); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.getFormId(mGlobal); break; + case ESM4::SUB_XRNK: reader.get(mFactionRank); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + //std::cout << "REFR parent: " << formIdToString(mEsp.parent) << " ref " << formIdToString(mFormId) + //<< ", 0x" << std::hex << (mEsp.flags & 0xff) << std::endl;// FIXME + break; + } + case ESM4::SUB_XTEL: + { + reader.getFormId(mDoor.destDoor); + reader.get(mDoor.destPos); + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + reader.get(mDoor.flags); // not in Obvlivion + //std::cout << "REFR dest door: " << formIdToString(mDoor.destDoor) << std::endl;// FIXME + break; + } + case ESM4::SUB_XSED: + { + // 1 or 4 bytes + if (subHdr.dataSize == 1) + { + uint8_t data; + reader.get(data); + //std::cout << "REFR XSED " << std::hex << (int)data << std::endl; + break; + } + else if (subHdr.dataSize == 4) + { + uint32_t data; + reader.get(data); + //std::cout << "REFR XSED " << std::hex << (int)data << std::endl; + break; + } + + //std::cout << "REFR XSED dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XLOD: + { + // 12 bytes + if (subHdr.dataSize == 12) + { + float data, data2, data3; + reader.get(data); + reader.get(data2); + reader.get(data3); + //bool hasVisibleWhenDistantFlag = (mFlags & 0x00008000) != 0; // currently unused + // some are trees, e.g. 000E03B6, mBaseObj 00022F32, persistent, visible when distant + // some are doors, e.g. 000270F7, mBaseObj 000CD338, persistent, initially disabled + // (this particular one is an Oblivion Gate) + //std::cout << "REFR XLOD " << std::hex << (int)data << " " << (int)data2 << " " << (int)data3 << std::endl; + break; + } + //std::cout << "REFR XLOD dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XACT: + { + if (subHdr.dataSize == 4) + { + uint32_t data; + reader.get(data); + //std::cout << "REFR XACT " << std::hex << (int)data << std::endl; + break; + } + + //std::cout << "REFR XACT dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XRTM: // formId + { + // seems like another ref, e.g. 00064583 has base object 00000034 which is "XMarkerHeading" + // e.g. some are doors (prob. quest related) + // MS94OblivionGateRef XRTM : 00097C88 + // MQ11SkingradGate XRTM : 00064583 + // MQ11ChorrolGate XRTM : 00188770 + // MQ11LeyawiinGate XRTM : 0018AD7C + // MQ11AnvilGate XRTM : 0018D452 + // MQ11BravilGate XRTM : 0018AE1B + // e.g. some are XMarkerHeading + // XRTM : 000A4DD7 in OblivionRDCavesMiddleA05 (maybe spawn points?) + FormId marker; + reader.getFormId(marker); + //std::cout << "REFR " << mEditorId << " XRTM : " << formIdToString(marker) << std::endl;// FIXME + break; + } + case ESM4::SUB_TNAM: //reader.get(mMapMarker); break; + { + if (subHdr.dataSize != sizeof(mMapMarker)) + //reader.skipSubRecordData(); // FIXME: FO3 + reader.getFormId(mid); + else + reader.get(mMapMarker); // TES4 + + break; + } + case ESM4::SUB_XMRK: mIsMapMarker = true; break; // all have mBaseObj 0x00000010 "MapMarker" + case ESM4::SUB_FNAM: + { + //std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XTRG: // formId + { + reader.getFormId(mTargetRef); + //std::cout << "REFR XRTG : " << formIdToString(id) << std::endl;// FIXME + break; + } + case ESM4::SUB_CNAM: reader.getFormId(mAudioLocation); break; // FONV + case ESM4::SUB_XRDO: // FO3 + { + reader.get(mRadio.rangeRadius); + reader.get(mRadio.broadcastRange); + reader.get(mRadio.staticPercentage); + reader.getFormId(mRadio.posReference); + + break; + } + case ESM4::SUB_SCRO: // FO3 + { + reader.getFormId(sid); + //if (mFormId == 0x0016b74B) + //std::cout << "REFR SCRO : " << formIdToString(sid) << std::endl;// FIXME + break; + } + case ESM4::SUB_XLOC: + { + mIsLocked = true; + std::int8_t dummy; // FIXME: very poor code + + reader.get(mLockLevel); + reader.get(dummy); + reader.get(dummy); + reader.get(dummy); + reader.getFormId(mKey); + reader.get(dummy); // flag? + reader.get(dummy); + reader.get(dummy); + reader.get(dummy); + if (subHdr.dataSize == 16) + reader.skipSubRecordData(4); // Oblivion (sometimes), flag? + else if (subHdr.dataSize == 20) // Skyrim, FO3 + reader.skipSubRecordData(8); // flag? + + break; + } + // lighting + case ESM4::SUB_LNAM: // lighting template formId + case ESM4::SUB_XLIG: // struct, FOV, fade, etc + case ESM4::SUB_XEMI: // LIGH formId + case ESM4::SUB_XRDS: // Radius or Radiance + case ESM4::SUB_XRGB: + case ESM4::SUB_XRGD: // tangent data? + case ESM4::SUB_XALP: // alpha cutoff + // + case ESM4::SUB_XPCI: // formId + case ESM4::SUB_XLCM: + case ESM4::SUB_XCNT: + case ESM4::SUB_ONAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_XPRM: + case ESM4::SUB_INAM: + case ESM4::SUB_PDTO: + case ESM4::SUB_SCHR: + case ESM4::SUB_SCTX: + case ESM4::SUB_XAPD: + case ESM4::SUB_XAPR: + case ESM4::SUB_XCVL: + case ESM4::SUB_XCZA: + case ESM4::SUB_XCZC: + case ESM4::SUB_XEZN: + case ESM4::SUB_XFVC: + case ESM4::SUB_XHTW: + case ESM4::SUB_XIS2: + case ESM4::SUB_XLCN: + case ESM4::SUB_XLIB: + case ESM4::SUB_XLKR: + case ESM4::SUB_XLRM: + case ESM4::SUB_XLRT: + case ESM4::SUB_XLTW: + case ESM4::SUB_XMBO: + case ESM4::SUB_XMBP: + case ESM4::SUB_XMBR: + case ESM4::SUB_XNDP: + case ESM4::SUB_XOCP: + case ESM4::SUB_XPOD: + case ESM4::SUB_XPPA: + case ESM4::SUB_XPRD: + case ESM4::SUB_XPWR: + case ESM4::SUB_XRMR: + case ESM4::SUB_XSPC: + case ESM4::SUB_XTNM: + case ESM4::SUB_XTRI: + case ESM4::SUB_XWCN: + case ESM4::SUB_XWCU: + case ESM4::SUB_XATR: // Dawnguard only? + case ESM4::SUB_XHLT: // Unofficial Oblivion Patch + case ESM4::SUB_XCHG: // thievery.exp + case ESM4::SUB_XHLP: // FO3 + case ESM4::SUB_XAMT: // FO3 + case ESM4::SUB_XAMC: // FO3 + case ESM4::SUB_XRAD: // FO3 + case ESM4::SUB_XIBS: // FO3 + case ESM4::SUB_XORD: // FO3 + case ESM4::SUB_XCLP: // FO3 + case ESM4::SUB_SCDA: // FO3 + case ESM4::SUB_RCLR: // FO3 + case ESM4::SUB_BNAM: // FONV + case ESM4::SUB_MMRK: // FONV + case ESM4::SUB_MNAM: // FONV + case ESM4::SUB_NNAM: // FONV + case ESM4::SUB_XATO: // FONV + case ESM4::SUB_SCRV: // FONV + case ESM4::SUB_SCVR: // FONV + case ESM4::SUB_SLSD: // FONV + case ESM4::SUB_XSRF: // FONV + case ESM4::SUB_XSRD: // FONV + case ESM4::SUB_WMI1: // FONV + case ESM4::SUB_XLRL: // Unofficial Skyrim Patch + { + //if (mFormId == 0x0007e90f) // XPRM XPOD + //if (mBaseObj == 0x17) //XPRM XOCP occlusion plane data XMBO bound half extents + //std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::REFR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //if (mFormId == 0x0016B74B) // base is TACT vCasinoUltraLuxeRadio in cell ULCasino + //std::cout << "REFR SCRO " << formIdToString(sid) << std::endl; +} + +//void ESM4::Reference::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Reference::blank() +{ +} diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp new file mode 100644 index 0000000000..d9c96cbf9f --- /dev/null +++ b/components/esm4/loadrefr.hpp @@ -0,0 +1,119 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_REFR_H +#define ESM4_REFR_H + +#include + +#include "reference.hpp" // FormId, Placement, EnableParent + +namespace ESM4 +{ + class Reader; + class Writer; + + enum MapMarkerType + { + Map_None = 0x00, // ? + Map_Camp = 0x01, + Map_Cave = 0x02, + Map_City = 0x03, + Map_ElvenRuin = 0x04, + Map_FortRuin = 0x05, + Map_Mine = 0x06, + Map_Landmark = 0x07, + Map_Tavern = 0x08, + Map_Settlement = 0x09, + Map_DaedricShrine = 0x0A, + Map_OblivionGate = 0x0B, + Map_Unknown = 0x0C // ? (door icon) + }; + + struct TeleportDest + { + FormId destDoor; + Placement destPos; + std::uint32_t flags; // 0x01 no alarm (only in TES5) + }; + + struct RadioStationData + { + float rangeRadius; + // 0 radius, 1 everywhere, 2 worldspace and linked int, 3 linked int, 4 current cell only + std::uint32_t broadcastRange; + float staticPercentage; + FormId posReference; // only used if broadcastRange == 0 + }; + + struct Reference + { + FormId mParent; // cell FormId (currently persistent refs only), from the loading sequence + // NOTE: for exterior cells it will be the dummy cell FormId + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mBaseObj; + + Placement mPlacement; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + std::int32_t mFactionRank; + + bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) + bool mIsMapMarker; + std::uint16_t mMapMarker; + + EnableParent mEsp; + + std::uint32_t mCount; // only if > 1 (default 1) + + FormId mAudioLocation; + + RadioStationData mRadio; + + TeleportDest mDoor; + bool mIsLocked; + std::int8_t mLockLevel; + FormId mKey; + + FormId mTargetRef; + + Reference(); + virtual ~Reference(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_REFR_H diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp new file mode 100644 index 0000000000..6f871e8471 --- /dev/null +++ b/components/esm4/loadregn.cpp @@ -0,0 +1,164 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadregn.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include + +//#include // FIXME: debug only +//#include "formid.hpp" + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Region::Region() : mFormId(0), mFlags(0), mWorldId(0), mEdgeFalloff(0) +{ + mEditorId.clear(); + mShader.clear(); + mMapName.clear(); +} + +ESM4::Region::~Region() +{ +} + +void ESM4::Region::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_RCLR: reader.get(mColour); break; + case ESM4::SUB_WNAM: reader.getFormId(mWorldId); break; + case ESM4::SUB_ICON: reader.getZString(mShader); break; + case ESM4::SUB_RPLI: reader.get(mEdgeFalloff); break; + case ESM4::SUB_RPLD: + { + mRPLD.resize(subHdr.dataSize/sizeof(std::uint32_t)); + for (std::vector::iterator it = mRPLD.begin(); it != mRPLD.end(); ++it) + { + reader.get(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "RPLD: 0x" << std::hex << *it << std::endl; +#endif + } + + break; + } + case ESM4::SUB_RDAT: reader.get(mData); break; + case ESM4::SUB_RDMP: + { + assert(mData.type == RDAT_Map && "REGN unexpected data type"); + reader.getLocalizedString(mMapName); break; + } + // FO3 only 2: DemoMegatonSound and DC01 (both 0 RDMD) + // FONV none + case ESM4::SUB_RDMD: // music type; 0 default, 1 public, 2 dungeon + { +#if 0 + int dummy; + reader.get(dummy); + std::cout << "REGN " << mEditorId << " " << dummy << std::endl; +#else + reader.skipSubRecordData(); +#endif + break; + } + case ESM4::SUB_RDMO: // not seen in FO3/FONV? + { + //std::cout << "REGN " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_RDSD: // Possibly the same as RDSA + { + //assert(mData.type == RDAT_Map && "REGN unexpected data type"); + if (mData.type != RDAT_Sound) + throw std::runtime_error("ESM4::REGN::load - unexpected data type " + + ESM::printName(subHdr.typeId)); + + std::size_t numSounds = subHdr.dataSize / sizeof(RegionSound); + mSounds.resize(numSounds); + for (std::size_t i = 0; i < numSounds; ++i) + reader.get(mSounds.at(i)); + + break; + } + case ESM4::SUB_RDGS: // Only in Oblivion? (ToddTestRegion1) // formId + case ESM4::SUB_RDSA: + case ESM4::SUB_RDWT: // formId + case ESM4::SUB_RDOT: // formId + case ESM4::SUB_RDID: // FONV + case ESM4::SUB_RDSB: // FONV + case ESM4::SUB_RDSI: // FONV + case ESM4::SUB_NVMI: // TES5 + { + //RDAT skipping... following is a map + //RDMP skipping... map name + // + //RDAT skipping... following is weather + //RDWT skipping... weather data + // + //RDAT skipping... following is sound + //RDMD skipping... unknown, maybe music data + // + //RDSD skipping... unknown, maybe sound data + // + //RDAT skipping... following is grass + //RDGS skipping... unknown, maybe grass + + //std::cout << "REGN " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + default: + throw std::runtime_error("ESM4::REGN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Region::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Region::blank() +{ +} diff --git a/components/esm4/loadregn.hpp b/components/esm4/loadregn.hpp new file mode 100644 index 0000000000..36ed8eb9d5 --- /dev/null +++ b/components/esm4/loadregn.hpp @@ -0,0 +1,98 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_REGN_H +#define ESM4_REGN_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Region + { + enum RegionDataType + { + RDAT_None = 0x00, + RDAT_Objects = 0x02, + RDAT_Weather = 0x03, + RDAT_Map = 0x04, + RDAT_Landscape = 0x05, + RDAT_Grass = 0x06, + RDAT_Sound = 0x07, + RDAT_Imposter = 0x08 + }; + +#pragma pack(push, 1) + struct RegionData + { + std::uint32_t type; + std::uint8_t flag; + std::uint8_t priority; + std::uint16_t unknown; + }; + + struct RegionSound + { + FormId sound; + std::uint32_t flags; // 0 pleasant, 1 cloudy, 2 rainy, 3 snowy + std::uint32_t chance; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::uint32_t mColour; // RGBA + FormId mWorldId; // worldspace formid + + std::string mShader; //?? ICON + std::string mMapName; + + std::uint32_t mEdgeFalloff; + std::vector mRPLD; // unknown, point data? + + RegionData mData; + std::vector mSounds; + + Region(); + virtual ~Region(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_REGN_H diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp new file mode 100644 index 0000000000..cb7742eb7c --- /dev/null +++ b/components/esm4/loadroad.cpp @@ -0,0 +1,123 @@ +/* + Copyright (C) 2020 - 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadroad.hpp" + +#include +//#include // FIXME: for debugging only + +#include "formid.hpp" // FIXME: for workaround +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Road::Road() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + mNodes.clear(); + mLinks.clear(); +} + +ESM4::Road::~Road() +{ +} + +void ESM4::Road::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currWorld(); + + mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_PGRP: + { + std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); + + mNodes.resize(numNodes); + for (std::size_t i = 0; i < numNodes; ++i) + { + reader.get(mNodes.at(i)); + } + + break; + } + case ESM4::SUB_PGRR: + { + static PGRR link; + static RDRP linkPt; + + for (std::size_t i = 0; i < mNodes.size(); ++i) + { + for (std::size_t j = 0; j < mNodes[i].numLinks; ++j) + { + link.startNode = std::int16_t(i); + + reader.get(linkPt); + + // FIXME: instead of looping each time, maybe use a map? + bool found = false; + for (std::size_t k = 0; k < mNodes.size(); ++k) + { + if (linkPt.x != mNodes[k].x || linkPt.y != mNodes[k].y || linkPt.z != mNodes[k].z) + continue; + else + { + link.endNode = std::int16_t(k); + mLinks.push_back(link); + + found = true; + break; + } + } + + if (!found) + throw std::runtime_error("ESM4::ROAD::PGRR - Unknown link point " + + std::to_string(j) + "at node " + std::to_string(i) + "."); + } + } + + break; + } + default: + throw std::runtime_error("ESM4::ROAD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Road::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Road::blank() +//{ +//} diff --git a/components/esm4/loadroad.hpp b/components/esm4/loadroad.hpp new file mode 100644 index 0000000000..dad92e858c --- /dev/null +++ b/components/esm4/loadroad.hpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2020 - 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ROAD_H +#define ESM4_ROAD_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Road + { +#pragma pack(push, 1) + // FIXME: duplicated from PGRD + struct PGRP + { + float x; + float y; + float z; + std::uint8_t numLinks; + std::uint8_t unknown1; + std::uint16_t unknown2; + }; + + // FIXME: duplicated from PGRD + struct PGRR + { + std::int16_t startNode; + std::int16_t endNode; + }; + + struct RDRP + { + float x; + float y; + float z; + }; +#pragma pack(pop) + FormId mParent; // world FormId, from the loading sequence + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::vector mNodes; + std::vector mLinks; + + Road(); + virtual ~Road(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ROAD_H diff --git a/components/esm4/loadsbsp.cpp b/components/esm4/loadsbsp.cpp new file mode 100644 index 0000000000..c9af5b856e --- /dev/null +++ b/components/esm4/loadsbsp.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadsbsp.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SubSpace::SubSpace() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + mDimension.x = 0.f; + mDimension.y = 0.f; + mDimension.z = 0.f; +} + +ESM4::SubSpace::~SubSpace() +{ +} + +void ESM4::SubSpace::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_DNAM: + { + reader.get(mDimension.x); + reader.get(mDimension.y); + reader.get(mDimension.z); + break; + } + default: + throw std::runtime_error("ESM4::SBSP::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SubSpace::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SubSpace::blank() +//{ +//} diff --git a/components/esm4/loadsbsp.hpp b/components/esm4/loadsbsp.hpp new file mode 100644 index 0000000000..c0ca7c5ddf --- /dev/null +++ b/components/esm4/loadsbsp.hpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SBSP_H +#define ESM4_SBSP_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + + struct SubSpace + { + struct Dimension + { + float x; + float y; + float z; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + Dimension mDimension; + + SubSpace(); + virtual ~SubSpace(); + + virtual void load(Reader& reader); + //virtual void save(Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SBSP_H diff --git a/components/esm4/loadscol.cpp b/components/esm4/loadscol.cpp new file mode 100644 index 0000000000..1990af5f4b --- /dev/null +++ b/components/esm4/loadscol.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadscol.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::StaticCollection::StaticCollection() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::StaticCollection::~StaticCollection() +{ +} + +void ESM4::StaticCollection::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_OBND: + case ESM4::SUB_MODL: + case ESM4::SUB_MODT: + case ESM4::SUB_ONAM: + case ESM4::SUB_DATA: + { + //std::cout << "SCOL " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "SCOL " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::SCOL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::StaticCollection::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::StaticCollection::blank() +//{ +//} diff --git a/components/esm4/loadscol.hpp b/components/esm4/loadscol.hpp new file mode 100644 index 0000000000..c32cdd3ca9 --- /dev/null +++ b/components/esm4/loadscol.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_SCOL_H +#define ESM4_SCOL_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct StaticCollection + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + StaticCollection(); + virtual ~StaticCollection(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SCOL_H diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp new file mode 100644 index 0000000000..cf4bdeb58a --- /dev/null +++ b/components/esm4/loadscpt.cpp @@ -0,0 +1,173 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadscpt.hpp" + +#include +#include // FIXME: debugging only +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Script::Script() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::Script::~Script() +{ +} + +void ESM4::Script::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + static ScriptLocalVariableData localVar; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: + { + reader.getZString(mEditorId); + break; + } + case ESM4::SUB_SCHR: + { + // For debugging only +#if 0 + unsigned char mDataBuf[256/*bufSize*/]; + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + for (unsigned int i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < 256/*bufSize*/-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + reader.get(mScript.scriptHeader); +#endif + break; + } + case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); + //if (mEditorId == "CTrapLogs01SCRIPT") + //std::cout << mScript.scriptSource << std::endl; + break; + case ESM4::SUB_SCDA: // compiled script data + { + // For debugging only +#if 0 + if (subHdr.dataSize >= 4096) + { + std::cout << "Skipping " << mEditorId << std::endl; + reader.skipSubRecordData(); + break; + } + + std::cout << mEditorId << std::endl; + + unsigned char mDataBuf[4096/*bufSize*/]; + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + for (unsigned int i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < 4096/*bufSize*/-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + reader.skipSubRecordData(); +#endif + break; + } + case ESM4::SUB_SCRO: reader.getFormId(mScript.globReference); break; + case ESM4::SUB_SLSD: + { + localVar.clear(); + reader.get(localVar.index); + reader.get(localVar.unknown1); + reader.get(localVar.unknown2); + reader.get(localVar.unknown3); + reader.get(localVar.type); + reader.get(localVar.unknown4); + // WARN: assumes SCVR will follow immediately + + break; + } + case ESM4::SUB_SCVR: // assumed always pair with SLSD + { + reader.getZString(localVar.variableName); + + mScript.localVarData.push_back(localVar); + + break; + } + case ESM4::SUB_SCRV: + { + std::uint32_t index; + reader.get(index); + + mScript.localRefVarIndex.push_back(index); + + break; + } + default: + //std::cout << "SCPT " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + //break; + throw std::runtime_error("ESM4::SCPT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Script::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Script::blank() +//{ +//} diff --git a/components/esm4/loadscpt.hpp b/components/esm4/loadscpt.hpp new file mode 100644 index 0000000000..747f49444d --- /dev/null +++ b/components/esm4/loadscpt.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SCPT_H +#define ESM4_SCPT_H + +#include + +#include "formid.hpp" +#include "script.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Script + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + ScriptDefinition mScript; + + Script(); + virtual ~Script(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SCPT_H diff --git a/components/esm4/loadscrl.cpp b/components/esm4/loadscrl.cpp new file mode 100644 index 0000000000..c09147ce5e --- /dev/null +++ b/components/esm4/loadscrl.cpp @@ -0,0 +1,99 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadscrl.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Scroll::Scroll() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Scroll::~Scroll() +{ +} + +void ESM4::Scroll::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_DATA: + { + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + //case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_OBND: + case ESM4::SUB_CTDA: + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + case ESM4::SUB_ETYP: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MDOB: + case ESM4::SUB_MODT: + case ESM4::SUB_SPIT: + { + //std::cout << "SCRL " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SCRL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Scroll::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Scroll::blank() +//{ +//} diff --git a/components/esm4/loadscrl.hpp b/components/esm4/loadscrl.hpp new file mode 100644 index 0000000000..a88781a6f8 --- /dev/null +++ b/components/esm4/loadscrl.hpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SCRL_H +#define ESM4_SCRL_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Scroll + { + struct Data + { + std::uint32_t value; + float weight; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + + Data mData; + + Scroll(); + virtual ~Scroll(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SCRL_H diff --git a/components/esm4/loadsgst.cpp b/components/esm4/loadsgst.cpp new file mode 100644 index 0000000000..b46c6bad29 --- /dev/null +++ b/components/esm4/loadsgst.cpp @@ -0,0 +1,118 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadsgst.hpp" + +#include +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SigilStone::SigilStone() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.uses = 0; + mData.value = 0; + mData.weight = 0.f; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::SigilStone::~SigilStone() +{ +} + +void ESM4::SigilStone::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (mFullName.empty()) + { + if (!reader.getZString(mFullName)) + throw std::runtime_error ("SGST FULL data read error"); + } + else + { + // FIXME: should be part of a struct? + std::string scriptEffectName; + if (!reader.getZString(scriptEffectName)) + throw std::runtime_error ("SGST FULL data read error"); + mScriptEffect.push_back(scriptEffectName); + } + break; + } + case ESM4::SUB_DATA: + { + reader.get(mData.uses); + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + { + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SGST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SigilStone::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SigilStone::blank() +//{ +//} diff --git a/components/esm4/loadsgst.hpp b/components/esm4/loadsgst.hpp new file mode 100644 index 0000000000..b0bbf9d800 --- /dev/null +++ b/components/esm4/loadsgst.hpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SGST_H +#define ESM4_SGST_H + +#include +#include + +#include "effect.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct SigilStone + { + struct Data + { + std::uint8_t uses; + std::uint32_t value; // gold + float weight; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + std::vector mScriptEffect; // FIXME: prob. should be in a struct + FormId mScriptId; + ScriptEffect mEffect; + + Data mData; + + SigilStone(); + virtual ~SigilStone(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SGST_H diff --git a/components/esm4/loadslgm.cpp b/components/esm4/loadslgm.cpp new file mode 100644 index 0000000000..482c817802 --- /dev/null +++ b/components/esm4/loadslgm.cpp @@ -0,0 +1,91 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadslgm.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SoulGem::SoulGem() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mSoul(0), mSoulCapacity(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::SoulGem::~SoulGem() +{ +} + +void ESM4::SoulGem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_SOUL: reader.get(mSoul); break; + case ESM4::SUB_SLCP: reader.get(mSoulCapacity); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_NAM0: + case ESM4::SUB_OBND: + { + //std::cout << "SLGM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SLGM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SoulGem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SoulGem::blank() +//{ +//} diff --git a/components/esm4/loadslgm.hpp b/components/esm4/loadslgm.hpp new file mode 100644 index 0000000000..8724da13c1 --- /dev/null +++ b/components/esm4/loadslgm.hpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SLGM_H +#define ESM4_SLGM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct SoulGem + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + FormId mScriptId; + std::uint8_t mSoul; // 0 = None, 1 = Petty, 2 = Lesser, 3 = Common, 4 = Greater, 5 = Grand + std::uint8_t mSoulCapacity; // 0 = None, 1 = Petty, 2 = Lesser, 3 = Common, 4 = Greater, 5 = Grand + + Data mData; + + SoulGem(); + virtual ~SoulGem(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SLGM_H diff --git a/components/esm4/loadsndr.cpp b/components/esm4/loadsndr.cpp new file mode 100644 index 0000000000..25109e36b6 --- /dev/null +++ b/components/esm4/loadsndr.cpp @@ -0,0 +1,94 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadsndr.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SoundReference::SoundReference() : mFormId(0), mFlags(0), mSoundCategory(0), mSoundId(0), mOutputModel(0) +{ + mEditorId.clear(); + mSoundFile.clear(); +} + +ESM4::SoundReference::~SoundReference() +{ +} + +void ESM4::SoundReference::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_CTDA: + { + reader.get(&mTargetCondition, 20); + reader.get(mTargetCondition.runOn); + reader.get(mTargetCondition.reference); + if (mTargetCondition.reference) + reader.adjustFormId(mTargetCondition.reference); + reader.skipSubRecordData(4); // unknown + + break; + } + case ESM4::SUB_GNAM: reader.getFormId(mSoundCategory); break; + case ESM4::SUB_SNAM: reader.getFormId(mSoundId); break; + case ESM4::SUB_ONAM: reader.getFormId(mOutputModel); break; + case ESM4::SUB_ANAM: reader.getZString(mSoundFile); break; + case ESM4::SUB_LNAM: reader.get(mLoopInfo); break; + case ESM4::SUB_BNAM: reader.get(mData); break; + case ESM4::SUB_CNAM: // CRC32 hash + case ESM4::SUB_FNAM: // unknown + { + //std::cout << "SNDR " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SNDR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SoundReference::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SoundReference::blank() +//{ +//} diff --git a/components/esm4/loadsndr.hpp b/components/esm4/loadsndr.hpp new file mode 100644 index 0000000000..17f0491136 --- /dev/null +++ b/components/esm4/loadsndr.hpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SNDR_H +#define ESM4_SNDR_H + +#include +#include + +#include "formid.hpp" +#include "script.hpp" // TargetCondition + +namespace ESM4 +{ + class Reader; + class Writer; + +#pragma pack(push, 1) + struct LoopInfo + { + std::uint16_t flags; + std::uint8_t unknown; + std::uint8_t rumble; + }; + + struct SoundInfo + { + std::int8_t frequencyAdjustment; // %, signed + std::uint8_t frequencyVariance; // % + std::uint8_t priority; // default 128 + std::uint8_t dBVriance; + std::uint16_t staticAttenuation; // divide by 100 to get value in dB + }; +#pragma pack(pop) + + struct SoundReference + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + FormId mSoundCategory; // SNCT + FormId mSoundId; // another SNDR + FormId mOutputModel; // SOPM + + std::string mSoundFile; + LoopInfo mLoopInfo; + SoundInfo mData; + + TargetCondition mTargetCondition; + + SoundReference(); + virtual ~SoundReference(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SNDR_H diff --git a/components/esm4/loadsoun.cpp b/components/esm4/loadsoun.cpp new file mode 100644 index 0000000000..6441d8a799 --- /dev/null +++ b/components/esm4/loadsoun.cpp @@ -0,0 +1,95 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadsoun.hpp" + +#include +//#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Sound::Sound() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mSoundFile.clear(); +} + +ESM4::Sound::~Sound() +{ +} + +void ESM4::Sound::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FNAM: reader.getZString(mSoundFile); break; + case ESM4::SUB_SNDX: reader.get(mData); break; + case ESM4::SUB_SNDD: + { + if (subHdr.dataSize == 8) + reader.get(&mData, 8); + else + { + reader.get(mData); + reader.get(mExtra); + } + + break; + } + case ESM4::SUB_OBND: // TES5 only + case ESM4::SUB_SDSC: // TES5 only + case ESM4::SUB_ANAM: // FO3 + case ESM4::SUB_GNAM: // FO3 + case ESM4::SUB_HNAM: // FO3 + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "SOUN " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SOUN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Sound::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Sound::blank() +//{ +//} diff --git a/components/esm4/loadsoun.hpp b/components/esm4/loadsoun.hpp new file mode 100644 index 0000000000..845f38c380 --- /dev/null +++ b/components/esm4/loadsoun.hpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SOUN_H +#define ESM4_SOUN_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Sound + { + enum Flags + { + Flag_RandomFreqShift = 0x0001, + Flag_PlayAtRandom = 0x0002, + Flag_EnvIgnored = 0x0004, + Flag_RandomLocation = 0x0008, + Flag_Loop = 0x0010, + Flag_MenuSound = 0x0020, + Flag_2D = 0x0040, + Flag_360LFE = 0x0080 + }; + +#pragma pack(push, 1) + struct SNDX + { + std::uint8_t minAttenuation; // distance? + std::uint8_t maxAttenuation; // distance? + std::int8_t freqAdjustment; // %, signed + std::uint8_t unknown; // probably padding + std::uint16_t flags; + std::uint16_t unknown2; // probably padding + std::uint16_t staticAttenuation; // divide by 100 to get value in dB + std::uint8_t stopTime; // multiply by 1440/256 to get value in minutes + std::uint8_t startTime; // multiply by 1440/256 to get value in minutes + }; + + struct SoundData + { + std::int16_t attenuationPoint1; + std::int16_t attenuationPoint2; + std::int16_t attenuationPoint3; + std::int16_t attenuationPoint4; + std::int16_t attenuationPoint5; + std::int16_t reverbAttenuationControl; + std::int32_t priority; + std::int32_t x; + std::int32_t y; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::string mSoundFile; + SNDX mData; + SoundData mExtra; + + Sound(); + virtual ~Sound(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SOUN_H diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp new file mode 100644 index 0000000000..de487147fe --- /dev/null +++ b/components/esm4/loadstat.cpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadstat.hpp" + +#include +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Static::Static() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Static::~Static() +{ +} + +void ESM4::Static::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + { + // version is only availabe in TES5 (seems to be 27 or 28?) + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + //std::cout << "STAT MODT ver: " << std::hex << reader.hdr().record.version << std::endl; + + // for TES4 these are just a sequence of bytes + mMODT.resize(subHdr.dataSize/sizeof(std::uint8_t)); + for (std::vector::iterator it = mMODT.begin(); it != mMODT.end(); ++it) + { + reader.get(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "MODT: " << std::hex << *it << std::endl; +#endif + } + break; + } + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_DNAM: + case ESM4::SUB_MNAM: + case ESM4::SUB_BRUS: // FONV + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "STAT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::STAT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Static::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Static::blank() +//{ +//} diff --git a/components/esm4/loadstat.hpp b/components/esm4/loadstat.hpp new file mode 100644 index 0000000000..966721ef37 --- /dev/null +++ b/components/esm4/loadstat.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_STAT_H +#define ESM4_STAT_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Static + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + std::vector mMODT; // FIXME texture hash + + Static(); + virtual ~Static(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_STAT_H diff --git a/components/esm4/loadtact.cpp b/components/esm4/loadtact.cpp new file mode 100644 index 0000000000..c082048086 --- /dev/null +++ b/components/esm4/loadtact.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadtact.hpp" + +#include +#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::TalkingActivator::TalkingActivator() : mFormId(0), mFlags(0), mScriptId(0), mVoiceType(0), mLoopSound(0), + mRadioTemplate(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::TalkingActivator::~TalkingActivator() +{ +} + +void ESM4::TalkingActivator::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_VNAM: reader.getFormId(mVoiceType); break; + case ESM4::SUB_SNAM: reader.getFormId(mLoopSound); break; + case ESM4::SUB_INAM: reader.getFormId(mRadioTemplate); break; // FONV + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DEST: // FO3 destruction + case ESM4::SUB_DSTD: // FO3 destruction + case ESM4::SUB_DSTF: // FO3 destruction + case ESM4::SUB_FNAM: + case ESM4::SUB_PNAM: + case ESM4::SUB_MODT: // texture file hash? + case ESM4::SUB_OBND: + { + //std::cout << "TACT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TACT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::TalkingActivator::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::TalkingActivator::blank() +//{ +//} diff --git a/components/esm4/loadtact.hpp b/components/esm4/loadtact.hpp new file mode 100644 index 0000000000..a1d15524be --- /dev/null +++ b/components/esm4/loadtact.hpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TACT_H +#define ESM4_TACT_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + enum TalkingActivatorFlags + { + TACT_OnLocalMap = 0x00000200, + TACT_QuestItem = 0x00000400, + TACT_NoVoiceFilter = 0x00002000, + TACT_RandomAnimStart = 0x00010000, + TACT_RadioStation = 0x00020000, + TACT_NonProxy = 0x10000000, // only valid if Radio Station + TACT_ContBroadcast = 0x40000000 // only valid if Radio Station + }; + + struct TalkingActivator + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see above for details + + std::string mEditorId; + std::string mFullName; + + std::string mModel; + + FormId mScriptId; + FormId mVoiceType; // VTYP + FormId mLoopSound; // SOUN + FormId mRadioTemplate; // SOUN + + TalkingActivator(); + virtual ~TalkingActivator(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TACT_H diff --git a/components/esm4/loadterm.cpp b/components/esm4/loadterm.cpp new file mode 100644 index 0000000000..68a436d87c --- /dev/null +++ b/components/esm4/loadterm.cpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadterm.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Terminal::Terminal() : mFormId(0), mFlags(0), mScriptId(0), mPasswordNote(0), mSound(0) +{ + mEditorId.clear(); + mFullName.clear(); + mText.clear(); + + mModel.clear(); + mResultText.clear(); +} + +ESM4::Terminal::~Terminal() +{ +} + +void ESM4::Terminal::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_PNAM: reader.getFormId(mPasswordNote); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_RNAM: reader.getZString(mResultText); break; + case ESM4::SUB_DNAM: // difficulty + case ESM4::SUB_ANAM: // flags + case ESM4::SUB_CTDA: + case ESM4::SUB_INAM: + case ESM4::SUB_ITXT: + case ESM4::SUB_MODT: // texture hash? + case ESM4::SUB_SCDA: + case ESM4::SUB_SCHR: + case ESM4::SUB_SCRO: + case ESM4::SUB_SCRV: + case ESM4::SUB_SCTX: + case ESM4::SUB_SCVR: + case ESM4::SUB_SLSD: + case ESM4::SUB_TNAM: + case ESM4::SUB_OBND: + case ESM4::SUB_MODS: // FONV + { + //std::cout << "TERM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TERM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Terminal::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Terminal::blank() +//{ +//} diff --git a/components/esm4/loadterm.hpp b/components/esm4/loadterm.hpp new file mode 100644 index 0000000000..9fb0ee60a8 --- /dev/null +++ b/components/esm4/loadterm.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TERM_H +#define ESM4_TERM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Terminal + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mText; + + std::string mModel; + std::string mResultText; + + FormId mScriptId; + FormId mPasswordNote; + FormId mSound; + + Terminal(); + virtual ~Terminal(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TERM_H diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp new file mode 100644 index 0000000000..42c4acde4e --- /dev/null +++ b/components/esm4/loadtes4.cpp @@ -0,0 +1,113 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadtes4.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include + +#include // FIXME: debugging only + +#include "common.hpp" +#include "formid.hpp" +#include "reader.hpp" +//#include "writer.hpp" + +void ESM4::Header::load(ESM4::Reader& reader) +{ + mFlags = reader.hdr().record.flags; // 0x01 = Rec_ESM, 0x80 = Rec_Localized + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_HEDR: + { + if (!reader.getExact(mData.version) || !reader.getExact(mData.records) || !reader.getExact(mData.nextObjectId)) + throw std::runtime_error("TES4 HEDR data read error"); + + assert((size_t)subHdr.dataSize == sizeof(mData.version)+sizeof(mData.records)+sizeof(mData.nextObjectId) + && "TES4 HEDR data size mismatch"); + break; + } + case ESM4::SUB_CNAM: reader.getZString(mAuthor); break; + case ESM4::SUB_SNAM: reader.getZString(mDesc); break; + case ESM4::SUB_MAST: // multiple + { + ESM::MasterData m; + if (!reader.getZString(m.name)) + throw std::runtime_error("TES4 MAST data read error"); + + // NOTE: some mods do not have DATA following MAST so can't read DATA here + + mMaster.push_back (m); + break; + } + case ESM4::SUB_DATA: + { + // WARNING: assumes DATA always follows MAST + if (!reader.getExact(mMaster.back().size)) + throw std::runtime_error("TES4 DATA data read error"); + break; + } + case ESM4::SUB_ONAM: + { + mOverrides.resize(subHdr.dataSize/sizeof(FormId)); + for (unsigned int & mOverride : mOverrides) + { + if (!reader.getExact(mOverride)) + throw std::runtime_error("TES4 ONAM data read error"); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "ESM4::Header::ONAM overrides: " << formIdToString(mOverride) << std::endl; +#endif + } + break; + } + case ESM4::SUB_INTV: + case ESM4::SUB_INCC: + case ESM4::SUB_OFST: // Oblivion only? + case ESM4::SUB_DELE: // Oblivion only? + { + //std::cout << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::Header::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Header::save(ESM4::Writer& writer) +//{ +//} diff --git a/components/esm4/loadtes4.hpp b/components/esm4/loadtes4.hpp new file mode 100644 index 0000000000..6d84fe360b --- /dev/null +++ b/components/esm4/loadtes4.hpp @@ -0,0 +1,69 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TES4_H +#define ESM4_TES4_H + +#include + +#include "formid.hpp" +#include "../esm/common.hpp" // ESMVersion, MasterData + +namespace ESM4 +{ + class Reader; + class Writer; + +#pragma pack(push, 1) + struct Data + { + ESM::ESMVersion version; // File format version. + std::int32_t records; // Number of records + std::uint32_t nextObjectId; + }; +#pragma pack(pop) + + struct Header + { + std::uint32_t mFlags; // 0x01 esm, 0x80 localised strings + + Data mData; + std::string mAuthor; // Author's name + std::string mDesc; // File description + std::vector mMaster; + + std::vector mOverrides; // Skyrim only, cell children (ACHR, LAND, NAVM, PGRE, PHZD, REFR) + + // position in the vector = mod index of master files above + // value = adjusted mod index based on all the files loaded so far + //std::vector mModIndices; + + void load (Reader& reader); + //void save (Writer& writer); + }; +} + +#endif // ESM4_TES4_H diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp new file mode 100644 index 0000000000..7029222758 --- /dev/null +++ b/components/esm4/loadtree.cpp @@ -0,0 +1,85 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadtree.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Tree::Tree() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); + mLeafTexture.clear(); +} + +ESM4::Tree::~Tree() +{ +} + +void ESM4::Tree::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mLeafTexture); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_CNAM: + case ESM4::SUB_BNAM: + case ESM4::SUB_SNAM: + case ESM4::SUB_FULL: + case ESM4::SUB_OBND: + case ESM4::SUB_PFIG: + case ESM4::SUB_PFPC: + { + //std::cout << "TREE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TREE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Tree::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Tree::blank() +//{ +//} diff --git a/components/esm4/loadtree.hpp b/components/esm4/loadtree.hpp new file mode 100644 index 0000000000..2069f93d95 --- /dev/null +++ b/components/esm4/loadtree.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TREE_H +#define ESM4_TREE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Tree + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + std::string mLeafTexture; + + Tree(); + virtual ~Tree(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TREE_H diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp new file mode 100644 index 0000000000..ce281d4848 --- /dev/null +++ b/components/esm4/loadtxst.cpp @@ -0,0 +1,92 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadtxst.hpp" + +#include +#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::TextureSet::TextureSet() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mDiffuse.clear(); + mNormalMap.clear(); + mEnvMask.clear(); + mToneMap.clear(); + mDetailMap.clear(); + mEnvMap.clear(); + mUnknown.clear(); + mSpecular.clear(); +} + +ESM4::TextureSet::~TextureSet() +{ +} + +void ESM4::TextureSet::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_TX00: reader.getZString(mDiffuse); break; + case ESM4::SUB_TX01: reader.getZString(mNormalMap); break; + case ESM4::SUB_TX02: reader.getZString(mEnvMask); break; + case ESM4::SUB_TX03: reader.getZString(mToneMap); break; + case ESM4::SUB_TX04: reader.getZString(mDetailMap); break; + case ESM4::SUB_TX05: reader.getZString(mEnvMap); break; + case ESM4::SUB_TX06: reader.getZString(mUnknown); break; + case ESM4::SUB_TX07: reader.getZString(mSpecular); break; + case ESM4::SUB_DNAM: + case ESM4::SUB_DODT: + case ESM4::SUB_OBND: // object bounds + { + //std::cout << "TXST " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TXST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::TextureSet::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::TextureSet::blank() +//{ +//} diff --git a/components/esm4/loadtxst.hpp b/components/esm4/loadtxst.hpp new file mode 100644 index 0000000000..cc81923e1f --- /dev/null +++ b/components/esm4/loadtxst.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TXST_H +#define ESM4_TXST_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct TextureSet + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::string mDiffuse; // includes alpha info + std::string mNormalMap; // includes specular info (alpha channel) + std::string mEnvMask; + std::string mToneMap; + std::string mDetailMap; + std::string mEnvMap; + std::string mUnknown; + std::string mSpecular; + + TextureSet(); + virtual ~TextureSet(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TXST_H diff --git a/components/esm4/loadweap.cpp b/components/esm4/loadweap.cpp new file mode 100644 index 0000000000..b56e01171c --- /dev/null +++ b/components/esm4/loadweap.cpp @@ -0,0 +1,188 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadweap.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Weapon::Weapon() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0), + mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + mMiniIcon.clear(); +} + +ESM4::Weapon::~Weapon() +{ +} + +void ESM4::Weapon::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 10) // FO3 has 15 bytes even though VER_094 + { + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.damage); + } + else if (isFONV || subHdr.dataSize == 15) + { + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + reader.get(mData.damage); + reader.get(mData.clipSize); + } + else + { + reader.get(mData.type); + reader.get(mData.speed); + reader.get(mData.reach); + reader.get(mData.flags); + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + reader.get(mData.damage); + } + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_BAMT: + case ESM4::SUB_BIDS: + case ESM4::SUB_INAM: + case ESM4::SUB_CNAM: + case ESM4::SUB_CRDT: + case ESM4::SUB_DNAM: + case ESM4::SUB_EAMT: + case ESM4::SUB_EITM: + case ESM4::SUB_ETYP: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_NAM8: + case ESM4::SUB_NAM9: + case ESM4::SUB_OBND: + case ESM4::SUB_SNAM: + case ESM4::SUB_TNAM: + case ESM4::SUB_UNAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_VNAM: + case ESM4::SUB_WNAM: + case ESM4::SUB_XNAM: // Dawnguard only? + case ESM4::SUB_NNAM: + case ESM4::SUB_MODS: + case ESM4::SUB_NAM0: // FO3 + case ESM4::SUB_REPL: // FO3 + case ESM4::SUB_MOD2: // FO3 + case ESM4::SUB_MO2T: // FO3 + case ESM4::SUB_MO2S: // FO3 + case ESM4::SUB_NAM6: // FO3 + case ESM4::SUB_MOD4: // FO3 + case ESM4::SUB_MO4T: // FO3 + case ESM4::SUB_MO4S: // FO3 + case ESM4::SUB_BIPL: // FO3 + case ESM4::SUB_NAM7: // FO3 + case ESM4::SUB_MOD3: // FO3 + case ESM4::SUB_MO3T: // FO3 + case ESM4::SUB_MO3S: // FO3 + case ESM4::SUB_MODD: // FO3 + //case ESM4::SUB_MOSD: // FO3 + case ESM4::SUB_DEST: // FO3 + case ESM4::SUB_DSTD: // FO3 + case ESM4::SUB_DSTF: // FO3 + case ESM4::SUB_DMDL: // FO3 + case ESM4::SUB_DMDT: // FO3 + case ESM4::SUB_VATS: // FONV + case ESM4::SUB_VANM: // FONV + case ESM4::SUB_MWD1: // FONV + case ESM4::SUB_MWD2: // FONV + case ESM4::SUB_MWD3: // FONV + case ESM4::SUB_MWD4: // FONV + case ESM4::SUB_MWD5: // FONV + case ESM4::SUB_MWD6: // FONV + case ESM4::SUB_MWD7: // FONV + case ESM4::SUB_WMI1: // FONV + case ESM4::SUB_WMI2: // FONV + case ESM4::SUB_WMI3: // FONV + case ESM4::SUB_WMS1: // FONV + case ESM4::SUB_WMS2: // FONV + case ESM4::SUB_WNM1: // FONV + case ESM4::SUB_WNM2: // FONV + case ESM4::SUB_WNM3: // FONV + case ESM4::SUB_WNM4: // FONV + case ESM4::SUB_WNM5: // FONV + case ESM4::SUB_WNM6: // FONV + case ESM4::SUB_WNM7: // FONV + case ESM4::SUB_EFSD: // FONV DeadMoney + { + //std::cout << "WEAP " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::WEAP::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Weapon::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Weapon::blank() +//{ +//} diff --git a/components/esm4/loadweap.hpp b/components/esm4/loadweap.hpp new file mode 100644 index 0000000000..dae2da1e7e --- /dev/null +++ b/components/esm4/loadweap.hpp @@ -0,0 +1,96 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_WEAP_H +#define ESM4_WEAP_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Weapon + { + struct Data + { + // type + // 0 = Blade One Hand + // 1 = Blade Two Hand + // 2 = Blunt One Hand + // 3 = Blunt Two Hand + // 4 = Staff + // 5 = Bow + std::uint32_t type; + float speed; + float reach; + std::uint32_t flags; + std::uint32_t value; // gold + std::uint32_t health; + float weight; + std::uint16_t damage; + std::uint8_t clipSize; // FO3/FONV only + + Data() : type(0), speed(0.f), reach(0.f), flags(0), value(0), + health(0), weight(0.f), damage(0), clipSize(0) {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + std::string mIcon; + std::string mMiniIcon; + + FormId mPickUpSound; + FormId mDropSound; + + float mBoundRadius; + + FormId mScriptId; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Weapon(); + virtual ~Weapon(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_WEAP_H diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp new file mode 100644 index 0000000000..ec01556338 --- /dev/null +++ b/components/esm4/loadwrld.cpp @@ -0,0 +1,205 @@ +/* + Copyright (C) 2015-2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadwrld.hpp" + +#include +//#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::World::World() : mFormId(0), mFlags(0), mParent(0), mWorldFlags(0), mClimate(0), mWater(0), + mLandLevel(0.f), mWaterLevel(0.f), // -2700.f and -14000.f for TES5 + mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mSound(0), mMusic(0), mParentUseFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mMapFile.clear(); + + mMap.width = 0; + mMap.height = 0; + mMap.NWcellX = 0; + mMap.NWcellY = 0; + mMap.SEcellX = 0; + mMap.SEcellY = 0; + mMap.minHeight = 0.f; + mMap.maxHeight = 0.f; + mMap.initialPitch = 0.f; +} + +ESM4::World::~World() +{ +} + +void ESM4::World::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + // It should be possible to save the current world formId automatically while reading in + // the record header rather than doing it manually here but possibly less efficient (may + // need to check each record?). + // + // Alternatively it may be possible to figure it out by examining the group headers, but + // apparently the label field is not reliable so the parent world formid may have been + // corrupted by the use of ignore flag (TODO: should check to verify). + reader.setCurrWorld(mFormId); // save for CELL later + + std::uint32_t subSize = 0; // for XXXX sub record + + std::uint32_t esmVer = reader.esmVersion(); + //bool isTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100); + //bool isFONV = (esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134); + bool isTES5 = (esmVer == ESM::VER_094 || esmVer == ESM::VER_170); // WARN: FO3 is also VER_094 + bool usingDefaultLevels = true; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_WCTR: reader.get(mCenterCell); break; // Center cell, TES5 only + case ESM4::SUB_WNAM: reader.getFormId(mParent); break; + case ESM4::SUB_SNAM: reader.get(mSound); break; // sound, Oblivion only? + case ESM4::SUB_ICON: reader.getZString(mMapFile); break; // map filename, Oblivion only? + case ESM4::SUB_CNAM: reader.get(mClimate); break; + case ESM4::SUB_NAM2: reader.getFormId(mWater); break; + case ESM4::SUB_NAM0: + { + reader.get(mMinX); + reader.get(mMinY); + break; + } + case ESM4::SUB_NAM9: + { + reader.get(mMaxX); + reader.get(mMaxY); + break; + } + case ESM4::SUB_DATA: reader.get(mWorldFlags); break; + case ESM4::SUB_MNAM: + { + reader.get(mMap.width); + reader.get(mMap.height); + reader.get(mMap.NWcellX); + reader.get(mMap.NWcellY); + reader.get(mMap.SEcellX); + reader.get(mMap.SEcellY); + + if (subHdr.dataSize == 28) // Skyrim? + { + reader.get(mMap.minHeight); + reader.get(mMap.maxHeight); + reader.get(mMap.initialPitch); + } + + break; + } + case ESM4::SUB_DNAM: // defaults + { + reader.get(mLandLevel); // -2700.f for TES5 + reader.get(mWaterLevel); // -14000.f for TES5 + usingDefaultLevels = false; + + break; + } + // Only a few worlds in FO3 have music (I'm guessing 00090908 "explore" is the default?) + // 00090906 public WRLD: 00000A74 MegatonWorld + // 00090CE7 base WRLD: 0001A25D DCWorld18 (Arlington National Cemeteray) + // 00090CE7 base WRLD: 0001A266 DCWorld09 (The Mall) + // 00090CE7 base WRLD: 0001A267 DCWorld08 (Pennsylvania Avenue) + // 000BAD30 tranquilitylane WRLD: 000244A7 TranquilityLane + // 00090CE7 base WRLD: 000271C0 MonumentWorld (The Washington Monument) + // 00090907 dungeon WRLD: 0004C4D1 MamaDolcesWorld (Mama Dolce's Loading Yard) + // + // FONV has only 3 (note the different format, also can't find the files?): + // 00119D2E freeside\freeside_01.mp3 0010BEEA FreesideWorld (Freeside) + // 00119D2E freeside\freeside_01.mp3 0012D94D FreesideNorthWorld (Freeside) + // 00119D2E freeside\freeside_01.mp3 0012D94E FreesideFortWorld (Old Mormon Fort) + // NOTE: FONV DefaultObjectManager has 00090908 "explore" as the default music + case ESM4::SUB_ZNAM: reader.getFormId(mMusic); break; + case ESM4::SUB_PNAM: reader.get(mParentUseFlags); break; + case ESM4::SUB_RNAM: // multiple + case ESM4::SUB_MHDT: + case ESM4::SUB_LTMP: + case ESM4::SUB_XEZN: + case ESM4::SUB_XLCN: + case ESM4::SUB_NAM3: + case ESM4::SUB_NAM4: + case ESM4::SUB_MODL: + case ESM4::SUB_NAMA: + case ESM4::SUB_ONAM: + case ESM4::SUB_TNAM: + case ESM4::SUB_UNAM: + case ESM4::SUB_XWEM: + case ESM4::SUB_MODT: // from Dragonborn onwards? + case ESM4::SUB_INAM: // FO3 + case ESM4::SUB_NNAM: // FO3 + case ESM4::SUB_XNAM: // FO3 + case ESM4::SUB_IMPS: // FO3 Anchorage + case ESM4::SUB_IMPF: // FO3 Anchorage + { + //std::cout << "WRLD " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + case ESM4::SUB_OFST: + { + if (subSize) + { + reader.skipSubRecordData(subSize); // special post XXXX + reader.updateRecordRead(subSize); // WARNING: manually update + subSize = 0; + } + else + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + + break; + } + case ESM4::SUB_XXXX: + { + reader.get(subSize); + break; + } + default: + throw std::runtime_error("ESM4::WRLD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + + if (isTES5 && usingDefaultLevels) + { + mLandLevel = -2700.f; + mWaterLevel = -14000.f; + } + } +} + +//void ESM4::World::save(ESM4::Writer& writer) const +//{ +//} diff --git a/components/esm4/loadwrld.hpp b/components/esm4/loadwrld.hpp new file mode 100644 index 0000000000..0105dc12f4 --- /dev/null +++ b/components/esm4/loadwrld.hpp @@ -0,0 +1,137 @@ +/* + Copyright (C) 2015-2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_WRLD_H +#define ESM4_WRLD_H + +#include +#include +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct World + { + enum WorldFlags // TES4 TES5 + { // -------------------- ----------------- + WLD_Small = 0x01, // Small World Small World + WLD_NoFastTravel = 0x02, // Can't Fast Travel Can't Fast Travel + WLD_Oblivion = 0x04, // Oblivion worldspace + WLD_NoLODWater = 0x08, // No LOD Water + WLD_NoLandscpe = 0x10, // No LOD Water No Landscape + WLD_NoSky = 0x20, // No Sky + wLD_FixedDimension = 0x40, // Fixed Dimensions + WLD_NoGrass = 0x80 // No Grass + }; + + struct REFRcoord + { + FormId formId; + std::int16_t unknown1; + std::int16_t unknown2; + }; + + struct RNAMstruct + { + std::int16_t unknown1; + std::int16_t unknown2; + std::vector refrs; + }; + + //Map size struct 16 or 28 byte structure + struct Map + { + std::uint32_t width; // usable width of the map + std::uint32_t height; // usable height of the map + std::int16_t NWcellX; + std::int16_t NWcellY; + std::int16_t SEcellX; + std::int16_t SEcellY; + float minHeight; // Camera Data (default 50000), new as of Skyrim 1.8, purpose is not yet known. + float maxHeight; // Camera Data (default 80000) + float initialPitch; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mParent; // parent worldspace formid + std::uint8_t mWorldFlags; + FormId mClimate; + FormId mWater; + float mLandLevel; + float mWaterLevel; + + Map mMap; + + std::int32_t mMinX; + std::int32_t mMinY; + std::int32_t mMaxX; + std::int32_t mMaxY; + + // ------ TES4 only ----- + + std::int32_t mSound; // 0 = no record, 1 = Public, 2 = Dungeon + std::string mMapFile; + + // ------ TES5 only ----- + + Grid mCenterCell; + RNAMstruct mData; + + // ---------------------- + FormId mMusic; + + // 0x01 use Land data + // 0x02 use LOD data + // 0x04 use Map data + // 0x08 use Water data + // 0x10 use Climate data + // 0x20 use Image Space data (Climate for TES5) + // 0x40 use SkyCell (TES5) + // 0x80 needs water adjustment (this isn't for parent I think? FONV only set for wastelandnv) + std::uint16_t mParentUseFlags; // FO3/FONV + + // cache formId's of children (e.g. CELL, ROAD) + std::vector mCells; + std::vector mRoads; + + World(); + virtual ~World(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + }; +} + +#endif // ESM4_WRLD_H diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp new file mode 100644 index 0000000000..f4fe8def68 --- /dev/null +++ b/components/esm4/reader.cpp @@ -0,0 +1,639 @@ +/* + Copyright (C) 2015-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#include "reader.hpp" + +#ifdef NDEBUG // FIXME: debugging only +#undef NDEBUG +#endif + +#undef DEBUG_GROUPSTACK + +#include +#include +#include +#include // for debugging +#include // for debugging +#include // for debugging + +#if defined(_MSC_VER) + #pragma warning (push) + #pragma warning (disable : 4706) + #include + #pragma warning (pop) +#else + #include +#endif +#include +#include +#include +#include +#include + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + +ReaderContext::ReaderContext() : modIndex(0), recHeaderSize(sizeof(RecordHeader)), + filePos(0), recordRead(0), currWorld(0), currCell(0), cellGridValid(false) +{ + currCellGrid.cellId = 0; + currCellGrid.grid.x = 0; + currCellGrid.grid.y = 0; +} + +Reader::Reader(Files::IStreamPtr esmStream, const std::string& filename) + : mEncoder(nullptr), mFileSize(0), mStream(esmStream) +{ + // used by ESMReader only? + mCtx.filename = filename; + + mCtx.fileRead = 0; + mStream->seekg(0, mStream->end); + mFileSize = mStream->tellg(); + mStream->seekg(20); // go to the start but skip the "TES4" record header + + mSavedStream.reset(); + + // determine header size + std::uint32_t subRecName = 0; + mStream->read((char*)&subRecName, sizeof(subRecName)); + if (subRecName == 0x52444548) // "HEDR" + mCtx.recHeaderSize = sizeof(RecordHeader) - 4; // TES4 header size is 4 bytes smaller than TES5 header + else + mCtx.recHeaderSize = sizeof(RecordHeader); + + // restart from the beginning (i.e. "TES4" record header) + mStream->seekg(0, mStream->beg); +#if 0 + unsigned int esmVer = mHeader.mData.version.ui; + bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + //bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; + //bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + // TES4 header size is 4 bytes smaller than TES5 header + mCtx.recHeaderSize = isTes4 ? sizeof(ESM4::RecordHeader) - 4 : sizeof(ESM4::RecordHeader); +#endif + getRecordHeader(); + if (mCtx.recordHeader.record.typeId == REC_TES4) + { + mHeader.load(*this); + mCtx.fileRead += mCtx.recordHeader.record.dataSize; + + buildLStringIndex(); // for localised strings in Skyrim + } + else + fail("Unknown file format"); +} + +Reader::~Reader() +{ + close(); +} + +// Since the record data may have been compressed, it is not always possible to use seek() to +// go to a position of a sub record. +// +// The record header needs to be saved in the context or the header needs to be re-loaded after +// restoring the context. The latter option was chosen. +ReaderContext Reader::getContext() +{ + mCtx.filePos = mStream->tellg(); + mCtx.filePos -= mCtx.recHeaderSize; // update file position + return mCtx; +} + +// NOTE: Assumes that the caller has reopened the file if necessary +bool Reader::restoreContext(const ReaderContext& ctx) +{ + if (mSavedStream) // TODO: doesn't seem to ever happen + { + mStream = mSavedStream; + mSavedStream.reset(); + } + + mCtx.groupStack.clear(); // probably not necessary since it will be overwritten + mCtx = ctx; + mStream->seekg(ctx.filePos); // update file position + + return getRecordHeader(); +} + +void Reader::close() +{ + mStream.reset(); + //clearCtx(); + //mHeader.blank(); +} + +void Reader::openRaw(Files::IStreamPtr esmStream, const std::string& filename) +{ + close(); + + mStream = esmStream; + mCtx.filename = filename; + mCtx.fileRead = 0; + mStream->seekg(0, mStream->end); + mFileSize = mStream->tellg(); + mStream->seekg(0, mStream->beg); + +} + +void Reader::open(Files::IStreamPtr esmStream, const std::string &filename) +{ + openRaw(esmStream, filename); + + // should at least have the size of ESM3 record header (20 or 24 bytes for ESM4) + assert (mFileSize >= 16); + std::uint32_t modVer = 0; + if (getExact(modVer)) // get the first 4 bytes of the record header only + { + // FIXME: need to setup header/context + if (modVer == REC_TES4) + { + } + else + { + } + } + + throw std::runtime_error("Unknown file format"); // can't yet use fail() as mCtx is not setup +} + +void Reader::setRecHeaderSize(const std::size_t size) +{ + mCtx.recHeaderSize = size; +} + +// FIXME: only "English" strings supported for now +void Reader::buildLStringIndex() +{ + if ((mHeader.mFlags & Rec_ESM) == 0 || (mHeader.mFlags & Rec_Localized) == 0) + return; + + boost::filesystem::path p(mCtx.filename); + std::string filename = p.stem().filename().string(); + + buildLStringIndex("Strings/" + filename + "_English.STRINGS", Type_Strings); + buildLStringIndex("Strings/" + filename + "_English.ILSTRINGS", Type_ILStrings); + buildLStringIndex("Strings/" + filename + "_English.DLSTRINGS", Type_DLStrings); +} + +void Reader::buildLStringIndex(const std::string& stringFile, LocalizedStringType stringType) +{ + std::uint32_t numEntries; + std::uint32_t dataSize; + std::uint32_t stringId; + LStringOffset sp; + sp.type = stringType; + + // TODO: possibly check if the resource exists? + Files::IStreamPtr filestream = Files::IStreamPtr(Files::openConstrainedFileStream(stringFile.c_str())); + + filestream->seekg(0, std::ios::end); + std::size_t fileSize = filestream->tellg(); + filestream->seekg(0, std::ios::beg); + + switch (stringType) + { + case Type_Strings: mStrings = filestream; break; + case Type_ILStrings: mILStrings = filestream; break; + case Type_DLStrings: mDLStrings = filestream; break; + default: + throw std::runtime_error("ESM4::Reader::unknown localised string type"); + } + + filestream->read((char*)&numEntries, sizeof(numEntries)); + filestream->read((char*)&dataSize, sizeof(dataSize)); + std::size_t dataStart = fileSize - dataSize; + for (unsigned int i = 0; i < numEntries; ++i) + { + filestream->read((char*)&stringId, sizeof(stringId)); + filestream->read((char*)&sp.offset, sizeof(sp.offset)); + sp.offset += (std::uint32_t)dataStart; + mLStringIndex[stringId] = sp; + } + //assert (dataStart - filestream->tell() == 0 && "String file start of data section mismatch"); +} + +void Reader::getLocalizedString(std::string& str) +{ + if (!hasLocalizedStrings()) + return (void)getZString(str); + + std::uint32_t stringId; // FormId + get(stringId); + if (stringId) // TES5 FoxRace, BOOK + getLocalizedStringImpl(stringId, str); +} + +// FIXME: very messy and probably slow/inefficient +void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str) +{ + const std::map::const_iterator it = mLStringIndex.find(stringId); + + if (it != mLStringIndex.end()) + { + Files::IStreamPtr filestream; + + switch (it->second.type) + { + case Type_Strings: // no string size provided + { + filestream = mStrings; + filestream->seekg(it->second.offset); + + char ch; + std::vector data; + do { + filestream->read(&ch, sizeof(ch)); + data.push_back(ch); + } while (ch != 0); + + str = std::string(data.data()); + return; + } + case Type_ILStrings: filestream = mILStrings; break; + case Type_DLStrings: filestream = mDLStrings; break; + default: + throw std::runtime_error("ESM4::Reader::getLocalizedString unknown string type"); + } + + // get ILStrings or DLStrings (they provide string size) + filestream->seekg(it->second.offset); + std::uint32_t size = 0; + filestream->read((char*)&size, sizeof(size)); + getStringImpl(str, size, filestream, mEncoder, true); // expect null terminated string + } + else + throw std::runtime_error("ESM4::Reader::getLocalizedString localized string not found"); +} + +bool Reader::getRecordHeader() +{ + // FIXME: this seems very hacky but we may have skipped subrecords from within an inflated data block + if (/*mStream->eof() && */mSavedStream) + { + mStream = mSavedStream; + mSavedStream.reset(); + } + + mStream->read((char*)&mCtx.recordHeader, mCtx.recHeaderSize); + std::size_t bytesRead = (std::size_t)mStream->gcount(); + + // keep track of data left to read from the file + mCtx.fileRead += mCtx.recHeaderSize; + + mCtx.recordRead = 0; // for keeping track of sub records + + // After reading the record header we can cache a WRLD or CELL formId for convenient access later. + // FIXME: currently currWorld and currCell are set manually when loading the WRLD and CELL records + + // HACK: mCtx.groupStack.back() is updated before the record data are read/skipped + // N.B. the data must be fully read/skipped for this to work + if (mCtx.recordHeader.record.typeId != REC_GRUP && !mCtx.groupStack.empty()) + { + mCtx.groupStack.back().second += (std::uint32_t)mCtx.recHeaderSize + mCtx.recordHeader.record.dataSize; + + // keep track of data left to read from the file + mCtx.fileRead += mCtx.recordHeader.record.dataSize; + } + + return bytesRead == mCtx.recHeaderSize; +} + +void Reader::getRecordData(bool dump) +{ + std::uint32_t uncompressedSize = 0; + + if ((mCtx.recordHeader.record.flags & Rec_Compressed) != 0) + { + mStream->read(reinterpret_cast(&uncompressedSize), sizeof(std::uint32_t)); + + std::size_t recordSize = mCtx.recordHeader.record.dataSize - sizeof(std::uint32_t); + Bsa::MemoryInputStream compressedRecord(recordSize); + mStream->read(compressedRecord.getRawData(), recordSize); + std::istream *fileStream = (std::istream*)&compressedRecord; + mSavedStream = mStream; + + mCtx.recordHeader.record.dataSize = uncompressedSize - sizeof(uncompressedSize); + + std::shared_ptr memoryStreamPtr + = std::make_shared(uncompressedSize); + + boost::iostreams::filtering_streambuf inputStreamBuf; + inputStreamBuf.push(boost::iostreams::zlib_decompressor()); + inputStreamBuf.push(*fileStream); + + boost::iostreams::basic_array_sink sr(memoryStreamPtr->getRawData(), uncompressedSize); + boost::iostreams::copy(inputStreamBuf, sr); + + // For debugging only +//#if 0 +if (dump) +{ + std::ostringstream ss; + char* data = memoryStreamPtr->getRawData(); + for (unsigned int i = 0; i < uncompressedSize; ++i) + { + if (data[i] > 64 && data[i] < 91) + ss << (char)(data[i]) << " "; + else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(data[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < uncompressedSize-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +} +//#endif + mStream = std::shared_ptr(memoryStreamPtr, (std::istream*)memoryStreamPtr.get()); + } +} + +void Reader::skipRecordData() +{ + assert (mCtx.recordRead <= mCtx.recordHeader.record.dataSize && "Skipping after reading more than available"); + mStream->ignore(mCtx.recordHeader.record.dataSize - mCtx.recordRead); + mCtx.recordRead = mCtx.recordHeader.record.dataSize; // for getSubRecordHeader() +} + +bool Reader::getSubRecordHeader() +{ + bool result = false; + // NOTE: some SubRecords have 0 dataSize (e.g. SUB_RDSD in one of REC_REGN records in Oblivion.esm). + // Also SUB_XXXX has zero dataSize and the following 4 bytes represent the actual dataSize + // - hence it require manual updtes to mCtx.recordRead via updateRecordRead() + // See ESM4::NavMesh and ESM4::World. + if (mCtx.recordHeader.record.dataSize - mCtx.recordRead >= sizeof(mCtx.subRecordHeader)) + { + result = getExact(mCtx.subRecordHeader); + // HACK: below assumes sub-record data will be read or skipped in full; + // this hack aims to avoid updating mCtx.recordRead each time anything is read + mCtx.recordRead += (sizeof(mCtx.subRecordHeader) + mCtx.subRecordHeader.dataSize); + } + else if (mCtx.recordRead > mCtx.recordHeader.record.dataSize) + { + // try to correct any overshoot, seek to the end of the expected data + // this will only work if mCtx.subRecordHeader.dataSize was fully read or skipped + // (i.e. it will only correct mCtx.subRecordHeader.dataSize being incorrect) + // TODO: not tested + std::uint32_t overshoot = (std::uint32_t)mCtx.recordRead - mCtx.recordHeader.record.dataSize; + + std::size_t pos = mStream->tellg(); + mStream->seekg(pos - overshoot); + + return false; + } + + return result; +} + +void Reader::skipSubRecordData() +{ + mStream->ignore(mCtx.subRecordHeader.dataSize); +} + +void Reader::skipSubRecordData(std::uint32_t size) +{ + mStream->ignore(size); +} + +void Reader::enterGroup() +{ +#ifdef DEBUG_GROUPSTACK + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Starting record group " + << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << std::endl; +#endif + // empty group if the group size is same as the header size + if (mCtx.recordHeader.group.groupSize == (std::uint32_t)mCtx.recHeaderSize) + { +#ifdef DEBUG_GROUPSTACK + std::cout << padding << "Ignoring record group " // FIXME: debugging only + << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) + << " (empty)" << std::endl; +#endif + if (!mCtx.groupStack.empty()) // top group may be empty (e.g. HAIR in Skyrim) + { + // don't put on the stack, exitGroupCheck() may not get called before recursing into this method + mCtx.groupStack.back().second += mCtx.recordHeader.group.groupSize; + exitGroupCheck(); + } + + return; // don't push an empty group, just return + } + + // push group + mCtx.groupStack.push_back(std::make_pair(mCtx.recordHeader.group, (std::uint32_t)mCtx.recHeaderSize)); +} + +void Reader::exitGroupCheck() +{ + if (mCtx.groupStack.empty()) + return; + + // pop finished groups (note reading too much is allowed here) + std::uint32_t lastGroupSize = mCtx.groupStack.back().first.groupSize; + while (mCtx.groupStack.back().second >= lastGroupSize) + { +#ifdef DEBUG_GROUPSTACK + GroupTypeHeader grp = mCtx.groupStack.back().first; // FIXME: grp is for debugging only +#endif + // try to correct any overshoot + // TODO: not tested + std::uint32_t overshoot = mCtx.groupStack.back().second - lastGroupSize; + if (overshoot > 0) + { + std::size_t pos = mStream->tellg(); + mStream->seekg(pos - overshoot); + } + + mCtx.groupStack.pop_back(); +#ifdef DEBUG_GROUPSTACK + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Finished record group " << printLabel(grp.label, grp.type) << std::endl; +#endif + // if the previous group was the final one no need to do below + if (mCtx.groupStack.empty()) + return; + + mCtx.groupStack.back().second += lastGroupSize; + lastGroupSize = mCtx.groupStack.back().first.groupSize; + + assert (lastGroupSize >= mCtx.groupStack.back().second && "Read more records than available"); +//#if 0 + if (mCtx.groupStack.back().second > lastGroupSize) // FIXME: debugging only + std::cerr << printLabel(mCtx.groupStack.back().first.label, + mCtx.groupStack.back().first.type) + << " read more records than available" << std::endl; +//#endif + } +} + +// WARNING: this method should be used after first calling enterGroup() +// else the method may try to dereference an element that does not exist +const GroupTypeHeader& Reader::grp(std::size_t pos) const +{ + assert (pos <= mCtx.groupStack.size()-1 && "ESM4::Reader::grp - exceeded stack depth"); + + return (*(mCtx.groupStack.end()-pos-1)).first; +} + +void Reader::skipGroupData() +{ + assert (!mCtx.groupStack.empty() && "Skipping group with an empty stack"); + + // subtract what was already read/skipped + std::uint32_t skipSize = mCtx.groupStack.back().first.groupSize - mCtx.groupStack.back().second; + + mStream->ignore(skipSize); + + // keep track of data left to read from the file + mCtx.fileRead += skipSize; + + mCtx.groupStack.back().second = mCtx.groupStack.back().first.groupSize; +} + +void Reader::skipGroup() +{ +#ifdef DEBUG_GROUPSTACK + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Skipping record group " + << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << std::endl; +#endif + // subtract the size of header already read before skipping + std::uint32_t skipSize = mCtx.recordHeader.group.groupSize - (std::uint32_t)mCtx.recHeaderSize; + mStream->ignore(skipSize); + + // keep track of data left to read from the file + mCtx.fileRead += skipSize; + + // NOTE: mCtx.groupStack.back().second already has mCtx.recHeaderSize from enterGroup() + if (!mCtx.groupStack.empty()) + mCtx.groupStack.back().second += mCtx.recordHeader.group.groupSize; +} + +const CellGrid& Reader::currCellGrid() const +{ + // Maybe should throw an exception instead? + assert (mCtx.cellGridValid && "Attempt to use an invalid cell grid"); + + return mCtx.currCellGrid; +} + +// NOTE: the parameter 'files' must have the file names in the loaded order +void Reader::updateModIndices(const std::vector& files) +{ + if (files.size() >= 0xff) + throw std::runtime_error("ESM4::Reader::updateModIndices too many files"); // 0xff is reserved + + // NOTE: this map is rebuilt each time this method is called (i.e. each time a file is loaded) + // Perhaps there is an opportunity to optimize this by saving the result somewhere. + // But then, the number of files is at most around 250 so perhaps keeping it simple might be better. + + // build a lookup map + std::unordered_map fileIndex; + + for (size_t i = 0; i < files.size(); ++i) // ATTENTION: assumes current file is not included + fileIndex[boost::to_lower_copy(files[i])] = i; + + mCtx.parentFileIndices.resize(mHeader.mMaster.size()); + for (unsigned int i = 0; i < mHeader.mMaster.size(); ++i) + { + // locate the position of the dependency in already loaded files + std::unordered_map::const_iterator it + = fileIndex.find(boost::to_lower_copy(mHeader.mMaster[i].name)); + + if (it != fileIndex.end()) + mCtx.parentFileIndices[i] = (std::uint32_t)((it->second << 24) & 0xff000000); + else + throw std::runtime_error("ESM4::Reader::updateModIndices required dependency file not loaded"); +#if 0 + std::cout << "Master Mod: " << mCtx.header.mMaster[i].name << ", " // FIXME: debugging only + << formIdToString(mCtx.parentFileIndices[i]) << std::endl; +#endif + } + + if (!mCtx.parentFileIndices.empty() && mCtx.parentFileIndices[0] != 0) + throw std::runtime_error("ESM4::Reader::updateModIndices base modIndex is not zero"); +} + +// ModIndex adjusted formId according to master file dependencies +// (see http://www.uesp.net/wiki/Tes4Mod:FormID_Fixup) +// NOTE: need to update modindex to parentFileIndices.size() before saving +// +// FIXME: probably should add a parameter to check for mCtx.header::mOverrides +// (ACHR, LAND, NAVM, PGRE, PHZD, REFR), but not sure what exactly overrides mean +// i.e. use the modindx of its master? +// FIXME: Apparently ModIndex '00' in an ESP means the object is defined in one of its masters. +// This means we may need to search multiple times to get the correct id. +// (see https://www.uesp.net/wiki/Tes4Mod:Formid#ModIndex_Zero) +void Reader::adjustFormId(FormId& id) +{ + if (mCtx.parentFileIndices.empty()) + return; + + std::size_t index = (id >> 24) & 0xff; + + if (index < mCtx.parentFileIndices.size()) + id = mCtx.parentFileIndices[index] | (id & 0x00ffffff); + else + id = mCtx.modIndex | (id & 0x00ffffff); +} + +bool Reader::getFormId(FormId& id) +{ + if (!getExact(id)) + return false; + + adjustFormId(id); + return true; +} + +void Reader::adjustGRUPFormId() +{ + adjustFormId(mCtx.recordHeader.group.label.value); +} + +[[noreturn]] void Reader::fail(const std::string& msg) +{ + std::stringstream ss; + + ss << "ESM Error: " << msg; + ss << "\n File: " << mCtx.filename; + ss << "\n Record: " << ESM::printName(mCtx.recordHeader.record.typeId); + ss << "\n Subrecord: " << ESM::printName(mCtx.subRecordHeader.typeId); + if (mStream.get()) + ss << "\n Offset: 0x" << std::hex << mStream->tellg(); + + throw std::runtime_error(ss.str()); +} + +} diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp new file mode 100644 index 0000000000..b3f1070495 --- /dev/null +++ b/components/esm4/reader.hpp @@ -0,0 +1,298 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#ifndef ESM4_READER_H +#define ESM4_READER_H + +#include +#include +#include + +#include "common.hpp" +#include "loadtes4.hpp" +#include "../esm/reader.hpp" + +namespace ESM4 { + // bytes read from group, updated by + // getRecordHeader() in advance + // | + // v + typedef std::vector > GroupStack; + + struct ReaderContext { + std::string filename; // in case we need to reopen to restore the context + std::uint32_t modIndex; // the sequential position of this file in the load order: + // 0x00 reserved, 0xFF in-game (see notes below) + + // position in the vector = mod index of master files above + // value = adjusted mod index based on all the files loaded so far + std::vector parentFileIndices; + + std::size_t recHeaderSize; // normally should be already set correctly, but just in + // case the file was re-opened. default = TES5 size, + // can be reduced for TES4 by setRecHeaderSize() + + std::size_t filePos; // assume that the record header will be re-read once + // the context is restored + + // for keeping track of things + std::size_t fileRead; // number of bytes read, incl. the current record + + GroupStack groupStack; // keep track of bytes left to find when a group is done + RecordHeader recordHeader; // header of the current record or group being processed + SubRecordHeader subRecordHeader; // header of the current sub record being processed + std::uint32_t recordRead; // bytes read from the sub records, incl. the current one + + FormId currWorld; // formId of current world - for grouping CELL records + FormId currCell; // formId of current cell + // FIXME: try to get rid of these two members, seem like massive hacks + CellGrid currCellGrid; // TODO: should keep a map of cell formids + bool cellGridValid; + + ReaderContext(); + }; + + class Reader : public ESM::Reader + { + Header mHeader; // ESM4 header + + ReaderContext mCtx; + + ToUTF8::Utf8Encoder* mEncoder; + + std::size_t mFileSize; + + Files::IStreamPtr mStream; + Files::IStreamPtr mSavedStream; // mStream is saved here while using deflated memory stream + + Files::IStreamPtr mStrings; + Files::IStreamPtr mILStrings; + Files::IStreamPtr mDLStrings; + + enum LocalizedStringType + { + Type_Strings = 0, + Type_ILStrings = 1, + Type_DLStrings = 2 + }; + + struct LStringOffset + { + LocalizedStringType type; + std::uint32_t offset; + }; + + std::map mLStringIndex; + + void buildLStringIndex(const std::string& stringFile, LocalizedStringType stringType); + + inline bool hasLocalizedStrings() const { return (mHeader.mFlags & Rec_Localized) != 0; } + + void getLocalizedStringImpl(const FormId stringId, std::string& str); + + // Close the file, resets all information. + // After calling close() the structure may be reused to load a new file. + //void close(); + + // Raw opening. Opens the file and sets everything up but doesn't parse the header. + void openRaw(Files::IStreamPtr esmStream, const std::string& filename); + + // Load ES file from a new stream, parses the header. + // Closes the currently open file first, if any. + void open(Files::IStreamPtr esmStream, const std::string& filename); + + Reader() = default; + + public: + + Reader(Files::IStreamPtr esmStream, const std::string& filename); + ~Reader(); + + // FIXME: should be private but ESMTool uses it + void openRaw(const std::string& filename) { + openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); + } + + void open(const std::string& filename) { + open(Files::openConstrainedFileStream (filename.c_str ()), filename); + } + + void close() final; + + inline bool isEsm4() const final { return true; } + + inline void setEncoder(ToUTF8::Utf8Encoder* encoder) final { mEncoder = encoder; }; + + const std::vector& getGameFiles() const final { return mHeader.mMaster; } + + inline int getRecordCount() const final { return mHeader.mData.records; } + inline const std::string getAuthor() const final { return mHeader.mAuthor; } + inline int getFormat() const final { return 0; }; // prob. not relevant for ESM4 + inline const std::string getDesc() const final { return mHeader.mDesc; } + + inline std::string getFileName() const final { return mCtx.filename; }; // not used + + inline bool hasMoreRecs() const final { return (mFileSize - mCtx.fileRead) > 0; } + + // Methods added for updating loading progress bars + inline std::size_t getFileSize() const { return mFileSize; } + inline std::size_t getFileOffset() const { return mStream->tellg(); } + + // Methods added for saving/restoring context + ReaderContext getContext(); // WARN: must be called immediately after reading the record header + + bool restoreContext(const ReaderContext& ctx); // returns the result of re-reading the header + + template + inline void get(T& t) { mStream->read((char*)&t, sizeof(T)); } + + template + bool getExact(T& t) { + mStream->read((char*)&t, sizeof(T)); + return mStream->gcount() == sizeof(T); // FIXME: try/catch block needed? + } + + // for arrays + inline bool get(void* p, std::size_t size) { + mStream->read((char*)p, size); + return mStream->gcount() == (std::streamsize)size; // FIXME: try/catch block needed? + } + + // NOTE: must be called before calling getRecordHeader() + void setRecHeaderSize(const std::size_t size); + + inline unsigned int esmVersion() const { return mHeader.mData.version.ui; } + inline unsigned int numRecords() const { return mHeader.mData.records; } + + void buildLStringIndex(); + void getLocalizedString(std::string& str); + + // Read 24 bytes of header. The caller can then decide whether to process or skip the data. + bool getRecordHeader(); + + inline const RecordHeader& hdr() const { return mCtx.recordHeader; } + + const GroupTypeHeader& grp(std::size_t pos = 0) const; + + // The object setting up this reader needs to supply the file's load order index + // so that the formId's in this file can be adjusted with the file (i.e. mod) index. + void setModIndex(std::uint32_t index) final { mCtx.modIndex = (index << 24) & 0xff000000; } + void updateModIndices(const std::vector& files); + + // Maybe should throw an exception if called when not valid? + const CellGrid& currCellGrid() const; + + inline bool hasCellGrid() const { return mCtx.cellGridValid; } + + // This is set while loading a CELL record (XCLC sub record) and invalidated + // each time loading a CELL (see clearCellGrid()) + inline void setCurrCellGrid(const CellGrid& currCell) { + mCtx.cellGridValid = true; + mCtx.currCellGrid = currCell; + } + + // FIXME: This is called each time a new CELL record is read. Rather than calling this + // methos explicitly, mCellGridValid should be set automatically somehow. + // + // Cell 2c143 is loaded immedicatly after 1bdb1 and can mistakely appear to have grid 0, 1. + inline void clearCellGrid() { mCtx.cellGridValid = false; } + + // Should be set at the beginning of a CELL load + inline void setCurrCell(FormId formId) { mCtx.currCell = formId; } + + inline FormId currCell() const { return mCtx.currCell; } + + // Should be set at the beginning of a WRLD load + inline void setCurrWorld(FormId formId) { mCtx.currWorld = formId; } + + inline FormId currWorld() const { return mCtx.currWorld; } + + // Get the data part of a record + // Note: assumes the header was read correctly and nothing else was read + void getRecordData(bool dump = false); + + // Skip the data part of a record + // Note: assumes the header was read correctly (partial skip is allowed) + void skipRecordData(); + + // Skip the remaining part of the group + // Note: assumes the header was read correctly and group was pushed onto the stack + void skipGroupData(); + + // Skip the group without pushing onto the stack + // Note: assumes the header was read correctly and group was not pushed onto the stack + // (expected to be used during development only while some groups are not yet supported) + void skipGroup(); + + // Read 6 bytes of header. The caller can then decide whether to process or skip the data. + bool getSubRecordHeader(); + + // Manally update (i.e. increase) the bytes read after SUB_XXXX + inline void updateRecordRead(std::uint32_t subSize) { mCtx.recordRead += subSize; } + + inline const SubRecordHeader& subRecordHeader() const { return mCtx.subRecordHeader; } + + // Skip the data part of a subrecord + // Note: assumes the header was read correctly and nothing else was read + void skipSubRecordData(); + + // Special for a subrecord following a XXXX subrecord + void skipSubRecordData(std::uint32_t size); + + // Get a subrecord of a particular type and data type + template + bool getSubRecord(const ESM4::SubRecordTypes type, T& t) + { + ESM4::SubRecordHeader hdr; + if (!getExact(hdr) || (hdr.typeId != type) || (hdr.dataSize != sizeof(T))) + return false; + + return get(t); + } + + // ModIndex adjusted formId according to master file dependencies + void adjustFormId(FormId& id); + + bool getFormId(FormId& id); + + void adjustGRUPFormId(); + + // Note: uses the string size from the subrecord header rather than checking null termination + bool getZString(std::string& str) { + return getStringImpl(str, mCtx.subRecordHeader.dataSize, mStream, mEncoder, true); + } + bool getString(std::string& str) { + return getStringImpl(str, mCtx.subRecordHeader.dataSize, mStream, mEncoder); + } + + void enterGroup(); + void exitGroupCheck(); + + // for debugging only + size_t stackSize() const { return mCtx.groupStack.size(); } + + // Used for error handling + [[noreturn]] void fail(const std::string& msg); + }; +} + +#endif // ESM4_READER_H diff --git a/components/esm4/records.hpp b/components/esm4/records.hpp new file mode 100644 index 0000000000..d212091adb --- /dev/null +++ b/components/esm4/records.hpp @@ -0,0 +1,74 @@ +#ifndef ESM4_RECORDS_H +#define ESM4_RECORDS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // ESM4_RECORDS_H diff --git a/components/esm4/reference.hpp b/components/esm4/reference.hpp new file mode 100644 index 0000000000..5ac94b8519 --- /dev/null +++ b/components/esm4/reference.hpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_REFERENCE_H +#define ESM4_REFERENCE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ +#pragma pack(push, 1) + struct Vector3 + { + float x; + float y; + float z; + }; + + // REFR, ACHR, ACRE + struct Placement + { + Vector3 pos; + Vector3 rot; // angles are in radian, rz applied first and rx applied last + }; + + // REFR, ACHR, ACRE + struct EnableParent + { + FormId parent; + std::uint32_t flags; //0x0001 = Set Enable State Opposite Parent, 0x0002 = Pop In + }; +#pragma pack(pop) + + struct LODReference + { + FormId baseObj; + Placement placement; + float scale; + }; +} + +#endif // ESM4_REFERENCE_H diff --git a/components/esm4/script.hpp b/components/esm4/script.hpp new file mode 100644 index 0000000000..8ba8c0025c --- /dev/null +++ b/components/esm4/script.hpp @@ -0,0 +1,384 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_SCRIPT_H +#define ESM4_SCRIPT_H + +#include +#include +#include + +namespace ESM4 +{ + enum EmotionType + { + EMO_Neutral = 0, + EMO_Anger = 1, + EMO_Disgust = 2, + EMO_Fear = 3, + EMO_Sad = 4, + EMO_Happy = 5, + EMO_Surprise = 6, + EMO_Pained = 7 // FO3/FONV + }; + + enum ConditionTypeAndFlag + { + // flag + CTF_Combine = 0x01, + CTF_RunOnTarget = 0x02, + CTF_UseGlobal = 0x04, + // condition + CTF_EqualTo = 0x00, + CTF_NotEqualTo = 0x20, + CTF_GreaterThan = 0x40, + CTF_GrThOrEqTo = 0x60, + CTF_LessThan = 0x80, + CTF_LeThOrEqTo = 0xA0 + }; + + enum FunctionIndices + { + FUN_GetDistance = 1, + FUN_GetLocked = 5, + FUN_GetPos = 6, + FUN_GetAngle = 8, + FUN_GetStartingPos = 10, + FUN_GetStartingAngle = 11, + FUN_GetSecondsPassed = 12, + FUN_GetActorValue = 14, + FUN_GetCurrentTime = 18, + FUN_GetScale = 24, + FUN_IsMoving = 25, + FUN_IsTurning = 26, + FUN_GetLineOfSight = 27, + FUN_GetIsInSameCell = 32, + FUN_GetDisabled = 35, + FUN_GetMenuMode = 36, + FUN_GetDisease = 39, + FUN_GetVampire = 40, + FUN_GetClothingValue = 41, + FUN_SameFaction = 42, + FUN_SameRace = 43, + FUN_SameSex = 44, + FUN_GetDetected = 45, + FUN_GetDead = 46, + FUN_GetItemCount = 47, + FUN_GetGold = 48, + FUN_GetSleeping = 49, + FUN_GetTalkedToPC = 50, + FUN_GetScriptVariable = 53, + FUN_GetQuestRunning = 56, + FUN_GetStage = 58, + FUN_GetStageDone = 59, + FUN_GetFactionRankDifference = 60, + FUN_GetAlarmed = 61, + FUN_IsRaining = 62, + FUN_GetAttacked = 63, + FUN_GetIsCreature = 64, + FUN_GetLockLevel = 65, + FUN_GetShouldAttack = 66, + FUN_GetInCell = 67, + FUN_GetIsClass = 68, + FUN_GetIsRace = 69, + FUN_GetIsSex = 70, + FUN_GetInFaction = 71, + FUN_GetIsID = 72, + FUN_GetFactionRank = 73, + FUN_GetGlobalValue = 74, + FUN_IsSnowing = 75, + FUN_GetDisposition = 76, + FUN_GetRandomPercent = 77, + FUN_GetQuestVariable = 79, + FUN_GetLevel = 80, + FUN_GetArmorRating = 81, + FUN_GetDeadCount = 84, + FUN_GetIsAlerted = 91, + FUN_GetPlayerControlsDisabled = 98, + FUN_GetHeadingAngle = 99, + FUN_IsWeaponOut = 101, + FUN_IsTorchOut = 102, + FUN_IsShieldOut = 103, + FUN_IsFacingUp = 106, + FUN_GetKnockedState = 107, + FUN_GetWeaponAnimType = 108, + FUN_IsWeaponSkillType = 109, + FUN_GetCurrentAIPackage = 110, + FUN_IsWaiting = 111, + FUN_IsIdlePlaying = 112, + FUN_GetMinorCrimeCount = 116, + FUN_GetMajorCrimeCount = 117, + FUN_GetActorAggroRadiusViolated = 118, + FUN_GetCrime = 122, + FUN_IsGreetingPlayer = 123, + FUN_IsGuard = 125, + FUN_HasBeenEaten = 127, + FUN_GetFatiguePercentage = 128, + FUN_GetPCIsClass = 129, + FUN_GetPCIsRace = 130, + FUN_GetPCIsSex = 131, + FUN_GetPCInFaction = 132, + FUN_SameFactionAsPC = 133, + FUN_SameRaceAsPC = 134, + FUN_SameSexAsPC = 135, + FUN_GetIsReference = 136, + FUN_IsTalking = 141, + FUN_GetWalkSpeed = 142, + FUN_GetCurrentAIProcedure = 143, + FUN_GetTrespassWarningLevel = 144, + FUN_IsTrespassing = 145, + FUN_IsInMyOwnedCell = 146, + FUN_GetWindSpeed = 147, + FUN_GetCurrentWeatherPercent = 148, + FUN_GetIsCurrentWeather = 149, + FUN_IsContinuingPackagePCNear = 150, + FUN_CanHaveFlames = 153, + FUN_HasFlames = 154, + FUN_GetOpenState = 157, + FUN_GetSitting = 159, + FUN_GetFurnitureMarkerID = 160, + FUN_GetIsCurrentPackage = 161, + FUN_IsCurrentFurnitureRef = 162, + FUN_IsCurrentFurnitureObj = 163, + FUN_GetDayofWeek = 170, + FUN_GetTalkedToPCParam = 172, + FUN_IsPCSleeping = 175, + FUN_IsPCAMurderer = 176, + FUN_GetDetectionLevel = 180, + FUN_GetEquipped = 182, + FUN_IsSwimming = 185, + FUN_GetAmountSoldStolen = 190, + FUN_GetIgnoreCrime = 192, + FUN_GetPCExpelled = 193, + FUN_GetPCFactionMurder = 195, + FUN_GetPCEnemyofFaction = 197, + FUN_GetPCFactionAttack = 199, + FUN_GetDestroyed = 203, + FUN_HasMagicEffect = 214, + FUN_GetDefaultOpen = 215, + FUN_GetAnimAction = 219, + FUN_IsSpellTarget = 223, + FUN_GetVATSMode = 224, + FUN_GetPersuasionNumber = 225, + FUN_GetSandman = 226, + FUN_GetCannibal = 227, + FUN_GetIsClassDefault = 228, + FUN_GetClassDefaultMatch = 229, + FUN_GetInCellParam = 230, + FUN_GetVatsTargetHeight = 235, + FUN_GetIsGhost = 237, + FUN_GetUnconscious = 242, + FUN_GetRestrained = 244, + FUN_GetIsUsedItem = 246, + FUN_GetIsUsedItemType = 247, + FUN_GetIsPlayableRace = 254, + FUN_GetOffersServicesNow = 255, + FUN_GetUsedItemLevel = 258, + FUN_GetUsedItemActivate = 259, + FUN_GetBarterGold = 264, + FUN_IsTimePassing = 265, + FUN_IsPleasant = 266, + FUN_IsCloudy = 267, + FUN_GetArmorRatingUpperBody = 274, + FUN_GetBaseActorValue = 277, + FUN_IsOwner = 278, + FUN_IsCellOwner = 280, + FUN_IsHorseStolen = 282, + FUN_IsLeftUp = 285, + FUN_IsSneaking = 286, + FUN_IsRunning = 287, + FUN_GetFriendHit = 288, + FUN_IsInCombat = 289, + FUN_IsInInterior = 300, + FUN_IsWaterObject = 304, + FUN_IsActorUsingATorch = 306, + FUN_IsXBox = 309, + FUN_GetInWorldspace = 310, + FUN_GetPCMiscStat = 312, + FUN_IsActorEvil = 313, + FUN_IsActorAVictim = 314, + FUN_GetTotalPersuasionNumber = 315, + FUN_GetIdleDoneOnce = 318, + FUN_GetNoRumors = 320, + FUN_WhichServiceMenu = 323, + FUN_IsRidingHorse = 327, + FUN_IsInDangerousWater = 332, + FUN_GetIgnoreFriendlyHits = 338, + FUN_IsPlayersLastRiddenHorse = 339, + FUN_IsActor = 353, + FUN_IsEssential = 354, + FUN_IsPlayerMovingIntoNewSpace = 358, + FUN_GetTimeDead = 361, + FUN_GetPlayerHasLastRiddenHorse = 362, + FUN_IsChild = 365, + FUN_GetLastPlayerAction = 367, + FUN_IsPlayerActionActive = 368, + FUN_IsTalkingActivatorActor = 370, + FUN_IsInList = 372, + FUN_GetHasNote = 382, + FUN_GetHitLocation = 391, + FUN_IsPC1stPerson = 392, + FUN_GetCauseofDeath = 397, + FUN_IsLimbGone = 398, + FUN_IsWeaponInList = 399, + FUN_HasFriendDisposition = 403, + FUN_GetVATSValue = 408, + FUN_IsKiller = 409, + FUN_IsKillerObject = 410, + FUN_GetFactionCombatReaction = 411, + FUN_Exists = 415, + FUN_GetGroupMemberCount = 416, + FUN_GetGroupTargetCount = 417, + FUN_GetObjectiveCompleted = 420, + FUN_GetObjectiveDisplayed = 421, + FUN_GetIsVoiceType = 427, + FUN_GetPlantedExplosive = 428, + FUN_IsActorTalkingThroughActivator = 430, + FUN_GetHealthPercentage = 431, + FUN_GetIsObjectType = 433, + FUN_GetDialogueEmotion = 435, + FUN_GetDialogueEmotionValue = 436, + FUN_GetIsCreatureType = 438, + FUN_GetInZone = 446, + FUN_HasPerk = 449, + FUN_GetFactionRelation = 450, + FUN_IsLastIdlePlayed = 451, + FUN_GetPlayerTeammate = 454, + FUN_GetPlayerTeammateCount = 455, + FUN_GetActorCrimePlayerEnemy = 459, + FUN_GetActorFactionPlayerEnemy = 460, + FUN_IsPlayerTagSkill = 462, + FUN_IsPlayerGrabbedRef = 464, + FUN_GetDestructionStage = 471, + FUN_GetIsAlignment = 474, + FUN_GetThreatRatio = 478, + FUN_GetIsUsedItemEquipType = 480, + FUN_GetConcussed = 489, + FUN_GetMapMarkerVisible = 492, + FUN_GetPermanentActorValue = 495, + FUN_GetKillingBlowLimb = 496, + FUN_GetWeaponHealthPerc = 500, + FUN_GetRadiationLevel = 503, + FUN_GetLastHitCritical = 510, + FUN_IsCombatTarget = 515, + FUN_GetVATSRightAreaFree = 518, + FUN_GetVATSLeftAreaFree = 519, + FUN_GetVATSBackAreaFree = 520, + FUN_GetVATSFrontAreaFree = 521, + FUN_GetIsLockBroken = 522, + FUN_IsPS3 = 523, + FUN_IsWin32 = 524, + FUN_GetVATSRightTargetVisible = 525, + FUN_GetVATSLeftTargetVisible = 526, + FUN_GetVATSBackTargetVisible = 527, + FUN_GetVATSFrontTargetVisible = 528, + FUN_IsInCriticalStage = 531, + FUN_GetXPForNextLevel = 533, + FUN_GetQuestCompleted = 546, + FUN_IsGoreDisabled = 550, + FUN_GetSpellUsageNum = 555, + FUN_GetActorsInHigh = 557, + FUN_HasLoaded3D = 558, + FUN_GetReputation = 573, + FUN_GetReputationPct = 574, + FUN_GetReputationThreshold = 575, + FUN_IsHardcore = 586, + FUN_GetForceHitReaction = 601, + FUN_ChallengeLocked = 607, + FUN_GetCasinoWinningStage = 610, + FUN_PlayerInRegion = 612, + FUN_GetChallengeCompleted = 614, + FUN_IsAlwaysHardcore = 619 + }; + +#pragma pack(push, 1) + struct TargetResponseData + { + std::uint32_t emoType; // EmotionType + std::int32_t emoValue; + std::uint32_t unknown1; + std::uint32_t responseNo; // 1 byte + padding + // below FO3/FONV + FormId sound; // when 20 bytes usually 0 but there are exceptions (FO3 INFO FormId = 0x0002241f) + std::uint32_t flags; // 1 byte + padding (0x01 = use emotion anim) + }; + + struct TargetCondition + { + std::uint32_t condition; // ConditionTypeAndFlag + padding + float comparison; // WARN: can be GLOB FormId if flag set + std::uint32_t functionIndex; + std::uint32_t param1; // FIXME: if formid needs modindex adjustment or not? + std::uint32_t param2; + std::uint32_t runOn; // 0 subject, 1 target, 2 reference, 3 combat target, 4 linked reference + // below FO3/FONV/TES5 + FormId reference; + }; + + struct ScriptHeader + { + std::uint32_t unused; + std::uint32_t refCount; + std::uint32_t compiledSize; + std::uint32_t variableCount; + std::uint16_t type; // 0 object, 1 quest, 0x100 effect + std::uint16_t flag; // 0x01 enabled + }; +#pragma pack(pop) + + struct ScriptLocalVariableData + { + // SLSD + std::uint32_t index; + std::uint32_t unknown1; + std::uint32_t unknown2; + std::uint32_t unknown3; + std::uint32_t type; + std::uint32_t unknown4; + // SCVR + std::string variableName; + + void clear() { + index = 0; + type = 0; + variableName.clear(); + } + }; + + struct ScriptDefinition + { + ScriptHeader scriptHeader; + // SDCA compiled source + std::string scriptSource; + std::vector localVarData; + std::vector localRefVarIndex; + FormId globReference; + }; +} + +#endif // ESM4_SCRIPT_H diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index f7dc33fcbf..04edfda09d 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -84,6 +84,15 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) // is also ok.) assert(input[size] == 0); + std::string inputString(input, size); + std::string output; + toUtf8(inputString, output, size); + + return output; +} + +void Utf8Encoder::toUtf8(std::string& input, std::string& output, size_t size) +{ // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding // shares its first 128 values (0-127) with ASCII. There are no plans @@ -93,29 +102,36 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) // Compute output length, and check for pure ascii input at the same // time. bool ascii; - size_t outlen = getLength(input, ascii); + size_t outlen = getLength(input.c_str(), ascii); // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input, outlen); + { + std::swap(input, output); + return; + } // Make sure the output is large enough - resize(outlen); - char *out = &mOutput[0]; + if (output.size() <= outlen) + // Add some extra padding to reduce the chance of having to resize + // again later. + output.resize(3*outlen); + + // And make sure the string is zero terminated + output[outlen] = 0; + char *in = &input[0]; + char *out = &output[0]; // Translate - while (*input) - copyFromArray(*(input++), out); + while (*in) + copyFromArray(*(in++), out); // Make sure that we wrote the correct number of bytes - assert((out-&mOutput[0]) == (int)outlen); + assert((out-&output[0]) == (int)outlen); // And make extra sure the output is null terminated - assert(mOutput.size() > outlen); - assert(mOutput[outlen] == 0); - - // Return a string - return std::string(&mOutput[0], outlen); + assert(output.size() > outlen); + assert(output[outlen] == 0); } std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index d8c9f09d5d..23dc09c06e 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -34,6 +34,9 @@ namespace ToUTF8 return getUtf8(str.c_str(), str.size()); } + // Convert input to UTF8 to the given output string + void toUtf8(std::string& input, std::string& output, size_t size); + std::string getLegacyEnc(const char *input, size_t size); inline std::string getLegacyEnc(const std::string &str) { diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index ef0f432075..37068fd70d 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -53,13 +53,14 @@ namespace Translation if (!line.empty()) { - line = mEncoder->getUtf8(line); + std::string utf8Line; + mEncoder->toUtf8(line, utf8Line, line.size()); - size_t tab_pos = line.find('\t'); - if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) + size_t tab_pos = utf8Line.find('\t'); + if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < utf8Line.size() - 1) { - std::string key = line.substr(0, tab_pos); - std::string value = line.substr(tab_pos + 1); + std::string key = utf8Line.substr(0, tab_pos); + std::string value = utf8Line.substr(tab_pos + 1); if (!key.empty() && !value.empty()) container.insert(std::make_pair(key, value)); From 8113620dce5ac188347d688e7fb5894cbeb5c14b Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 30 Jan 2022 20:26:35 +0100 Subject: [PATCH 02/48] handle a few wearnings raised as errors --- components/esm4/loadpgrd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index a0bfc9b119..c9c3048605 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -115,7 +115,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) for (std::size_t i = 0; i < numForeign; ++i) { reader.get(mForeign.at(i)); - mForeign.at(i).localNode;// &= 0xffff; // some have junk high bits (maybe flags?) + // mForeign.at(i).localNode;// &= 0xffff; // some have junk high bits (maybe flags?) } break; From 8752f78fa45ebd1212cd2874ed0e105739369c23 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 11 Jan 2022 21:03:10 +0100 Subject: [PATCH 03/48] Remove weaponless, non-biped distinction --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 106 +------------------------- apps/openmw/mwmechanics/character.hpp | 1 - 3 files changed, 4 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2baee6552..710d0bff12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed Bug #4949: Incorrect particle lighting + Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0f8e2ebd97..820bfad07b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -707,9 +707,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (mPtr.getClass().isActor()) refreshHitRecoilAnims(idle); - std::string weap; - if (mPtr.getClass().hasInventoryStore(mPtr)) - weap = getWeaponType(mWeaponType)->mShortGroup; + std::string weap = getWeaponType(mWeaponType)->mShortGroup; refreshJumpAnims(weap, jump, idle, force); refreshMovementAnims(weap, movement, idle, force); @@ -1119,97 +1117,6 @@ void CharacterController::updateIdleStormState(bool inwater) } } -bool CharacterController::updateCreatureState() -{ - const MWWorld::Class &cls = mPtr.getClass(); - CreatureStats &stats = cls.getCreatureStats(mPtr); - - int weapType = ESM::Weapon::None; - if(stats.getDrawState() == DrawState_Weapon) - weapType = ESM::Weapon::HandToHand; - else if (stats.getDrawState() == DrawState_Spell) - weapType = ESM::Weapon::Spell; - - if (weapType != mWeaponType) - { - mWeaponType = weapType; - if (mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->disable(mCurrentWeapon); - } - - if(getAttackingOrSpell()) - { - if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) - { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - - std::string startKey = "start"; - std::string stopKey = "stop"; - if (weapType == ESM::Weapon::Spell) - { - const std::string spellid = stats.getSpells().getSelectedSpell(); - bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); - - if (!spellid.empty() && canCast) - { - MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); - cast.playSpellCastingEffects(spellid, false); - - if (!mAnimation->hasAnimation("spellcast")) - { - MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; - } - else - { - const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); - - switch(effectentry.mRange) - { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; - } - - startKey = mAttackType + " " + startKey; - stopKey = mAttackType + " " + stopKey; - mCurrentWeapon = "spellcast"; - } - } - else - mCurrentWeapon = ""; - } - - if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation - { - mCurrentWeapon = chooseRandomAttackAnimation(); - } - - if (!mCurrentWeapon.empty()) - { - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::BlendMask_All, true, - 1, startKey, stopKey, - 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; - - mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - - if (weapType == ESM::Weapon::HandToHand) - playSwishSound(0.0f); - } - } - - setAttackingOrSpell(false); - } - - bool animPlaying = mAnimation->getInfo(mCurrentWeapon); - if (!animPlaying) - mUpperBodyState = UpperCharState_Nothing; - return false; -} - bool CharacterController::updateCarriedLeftVisible(const int weaptype) const { // Shields/torches shouldn't be visible during any operation involving two hands @@ -2346,11 +2253,7 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. - if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) - forcestateupdate = updateWeaponState(idlestate) || forcestateupdate; - else - forcestateupdate = updateCreatureState() || forcestateupdate; + forcestateupdate = updateWeaponState(idlestate) || forcestateupdate; refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); updateIdleStormState(inwater); @@ -2879,10 +2782,7 @@ bool CharacterController::readyToStartAttack() const if (mHitState != CharState_None && mHitState != CharState_Block) return false; - if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) - return mUpperBodyState == UpperCharState_WeapEquiped; - else - return mUpperBodyState == UpperCharState_Nothing; + return mUpperBodyState == UpperCharState_WeapEquiped; } float CharacterController::getAttackStrength() const diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 1647980541..8c410bba25 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -206,7 +206,6 @@ class CharacterController : public MWRender::Animation::TextKeyListener void clearAnimQueue(bool clearPersistAnims = false); bool updateWeaponState(CharacterState& idle); - bool updateCreatureState(); void updateIdleStormState(bool inwater); std::string chooseRandomAttackAnimation() const; From 5ebcd37da109629041502cf630e76946b0b4c153 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 5 Feb 2022 19:07:44 +0100 Subject: [PATCH 04/48] Rename method and restore swish sounds --- apps/openmw/mwmechanics/character.cpp | 16 ++++++++++------ apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 820bfad07b..815f3b30cb 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1125,7 +1125,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const return mAnimation->updateCarriedLeftVisible(weaptype); } -bool CharacterController::updateWeaponState(CharacterState& idle) +bool CharacterController::updateState(CharacterState& idle) { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); @@ -1363,20 +1363,24 @@ bool CharacterController::updateWeaponState(CharacterState& idle) ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; if(getAttackingOrSpell()) { - MWWorld::Ptr player = getPlayer(); - bool resetIdle = ammunition; if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackStrength = 0; - // Randomize attacks for non-bipedal creatures with Weapon flag + // Randomize attacks for non-bipedal creatures if (mPtr.getClass().getType() == ESM::Creature::sRecordId && !mPtr.getClass().isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { mCurrentWeapon = chooseRandomAttackAnimation(); + if (!mPtr.getClass().hasInventoryStore(mPtr)) + { + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); + if (mWeaponType == ESM::Weapon::HandToHand) + playSwishSound(0.0f); + } } if(mWeaponType == ESM::Weapon::Spell) @@ -1384,7 +1388,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation setAttackingOrSpell(false); - if (mPtr == player) + if (mPtr == getPlayer()) { // For the player, set the spell we want to cast // This has to be done at the start of the casting animation, @@ -2253,7 +2257,7 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - forcestateupdate = updateWeaponState(idlestate) || forcestateupdate; + forcestateupdate = updateState(idlestate) || forcestateupdate; refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); updateIdleStormState(inwater); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 8c410bba25..1a0fd42fba 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -205,7 +205,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void clearAnimQueue(bool clearPersistAnims = false); - bool updateWeaponState(CharacterState& idle); + bool updateState(CharacterState& idle); void updateIdleStormState(bool inwater); std::string chooseRandomAttackAnimation() const; From 020e0b2ea5c5da26322119df8196c2a1ca5ab1c9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 5 Feb 2022 22:50:04 +0100 Subject: [PATCH 05/48] Don't allow non-bipedal actors to play hand-to-hand animations --- apps/openmw/mwmechanics/character.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 815f3b30cb..3ea09569ba 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -435,6 +435,8 @@ std::string CharacterController::getWeaponAnimation(int weaponType) const else if (isRealWeapon) weaponGroup = oneHandFallback; } + else if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + return "attack1"; return weaponGroup; } @@ -1293,7 +1295,7 @@ bool CharacterController::updateState(CharacterState& idle) } mWeaponType = weaptype; - mCurrentWeapon = getWeaponAnimation(mWeaponType); + mCurrentWeapon = weapgroup; if(!upSoundId.empty() && !isStillWeapon) { @@ -1376,11 +1378,7 @@ bool CharacterController::updateState(CharacterState& idle) { mCurrentWeapon = chooseRandomAttackAnimation(); if (!mPtr.getClass().hasInventoryStore(mPtr)) - { mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - if (mWeaponType == ESM::Weapon::HandToHand) - playSwishSound(0.0f); - } } if(mWeaponType == ESM::Weapon::Spell) @@ -1561,7 +1559,11 @@ bool CharacterController::updateState(CharacterState& idle) weapSpeed, startKey, stopKey, 0.0f, 0); if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) + { mUpperBodyState = UpperCharState_StartToMinAttack; + if (mWeaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + playSwishSound(0.0f); + } } } From 4657060d2caef27f3a7d67fb8995c969dc8a7440 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 6 Feb 2022 11:08:47 +0100 Subject: [PATCH 06/48] Extend swish and strength changes to all random attacks --- apps/openmw/mwmechanics/character.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 3ea09569ba..90a175af51 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1377,8 +1377,6 @@ bool CharacterController::updateState(CharacterState& idle) (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { mCurrentWeapon = chooseRandomAttackAnimation(); - if (!mPtr.getClass().hasInventoryStore(mPtr)) - mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); } if(mWeaponType == ESM::Weapon::Spell) @@ -1561,8 +1559,11 @@ bool CharacterController::updateState(CharacterState& idle) if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) { mUpperBodyState = UpperCharState_StartToMinAttack; - if (mWeaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + if (isRandomAttackAnimation(mCurrentWeapon)) + { + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); playSwishSound(0.0f); + } } } } From 5aef14eccdc944478f4efe30bb858eebe47bbefd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 20:28:27 +0100 Subject: [PATCH 07/48] Prevent division by 0 --- apps/openmw/mwdialogue/filter.cpp | 11 ++++++----- apps/openmw/mwgui/hud.cpp | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index a47334a2dd..7ae9f14a7a 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -257,9 +257,9 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); - - float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() / - player.getClass().getCreatureStats (player).getHealth().getModified(); + float ratio = player.getClass().getCreatureStats(player).getHealth().getModified(); + if(ratio > 0) + ratio = player.getClass().getCreatureStats(player).getHealth().getCurrent() / ratio; return select.selectCompare (static_cast(ratio*100)); } @@ -276,8 +276,9 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_HealthPercent: { - float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() / - mActor.getClass().getCreatureStats (mActor).getHealth().getModified(); + float ratio = mActor.getClass().getCreatureStats(mActor).getHealth().getModified(); + if(ratio > 0) + ratio = mActor.getClass().getCreatureStats(mActor).getHealth().getCurrent() / ratio; return select.selectCompare (static_cast(ratio*100)); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 4ed54c38d6..5af85b34ae 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -608,7 +608,8 @@ namespace MWGui mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) - mEnemyHealth->setProgressPosition(static_cast(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100)); + float health = stats.getHealth().getModified(); + mEnemyHealth->setProgressPosition(health == 0.f ? 0 : static_cast(stats.getHealth().getCurrent() / health * 100)); static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) From 4e52c96cf58e7b0c8b59db490e12c850bed2f7ca Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 20:31:27 +0100 Subject: [PATCH 08/48] Make Set/Mod[DynamicStat] work with negative values as in vanilla --- apps/openmw/mwgui/statswindow.cpp | 2 +- apps/openmw/mwscript/statsextensions.cpp | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 0830af0744..49ee7698ee 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -179,7 +179,7 @@ namespace MWGui void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { int current = static_cast(value.getCurrent()); - int modified = static_cast(value.getModified()); + int modified = static_cast(value.getModified(false)); // Fatigue can be negative if (id != "FBar") diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 78195b3693..f127b2bc4b 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -218,8 +218,8 @@ namespace MWScript MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); - stat.setModified (value, 0); - stat.setCurrent(value); + stat.setBase(value); + stat.setCurrent(value, true); ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } @@ -259,19 +259,18 @@ namespace MWScript } } - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); + MWMechanics::DynamicStat stat = stats.getDynamic(mIndex); - MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) - .getDynamic (mIndex)); + float current = stat.getCurrent(); + float base = diff + stat.getBase(); + if(mIndex != 2) + base = std::max(base, 0.f); + stat.setBase(base); + stat.setCurrent(diff + current, true); - stat.setModified (diff + stat.getModified(), 0); - stat.setCurrentModified (diff + stat.getCurrentModified()); - - stat.setCurrent (diff + current); - - ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); + stats.setDynamic (mIndex, stat); } }; From dc495a685ae50cebfa96637e8cdcf8370aa9c4b6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 20:32:59 +0100 Subject: [PATCH 09/48] Remove a member variable that doesn't get saved and remove fortify maximum health code --- apps/openmw/mwgui/race.cpp | 2 +- apps/openmw/mwgui/widgets.hpp | 4 - apps/openmw/mwmechanics/creaturestats.cpp | 15 +- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwmechanics/spelleffects.cpp | 3 +- apps/openmw/mwmechanics/stat.cpp | 168 +----------------- apps/openmw/mwmechanics/stat.hpp | 56 ++---- apps/openmw/mwworld/magiceffects.cpp | 6 +- 8 files changed, 36 insertions(+), 222 deletions(-) diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index d30eb65eb3..efa4cea981 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -399,7 +399,7 @@ namespace MWGui skillWidget = mSkillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default, std::string("Skill") + MyGUI::utility::toString(i)); skillWidget->setSkillNumber(skillId); - skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast(race->mData.mBonus[i].mBonus))); + skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast(race->mData.mBonus[i].mBonus), 0.f)); ToolTips::createSkillToolTip(skillWidget, skillId); diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 2f27cc029c..e430872487 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -181,8 +181,6 @@ namespace MWGui public: MWSpell(); - typedef MWMechanics::Stat SpellValue; - void setSpellId(const std::string &id); /** @@ -215,8 +213,6 @@ namespace MWGui public: MWEffectList(); - typedef MWMechanics::Stat EnchantmentValue; - enum EffectFlags { EF_NoTarget = 0x01, // potions have no target (target is always the player) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 9611f5b34a..a53caf1451 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -26,8 +26,6 @@ namespace MWMechanics mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) , mAttackingOrSpell(false) { - for (int i=0; i<4; ++i) - mAiSettings[i] = 0; } const AiSequence& CreatureStats::getAiSequence() const @@ -158,9 +156,8 @@ namespace MWMechanics float agility = getAttribute(ESM::Attribute::Agility).getModified(); float endurance = getAttribute(ESM::Attribute::Endurance).getModified(); DynamicStat fatigue = getFatigue(); - float diff = (strength+willpower+agility+endurance) - fatigue.getBase(); float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0; - fatigue.setModified(fatigue.getModified() + diff, 0); + fatigue.setBase(std::max(0.f, strength + willpower + agility + endurance)); fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio, false, true); setFatigue(fatigue); } @@ -196,8 +193,6 @@ namespace MWMechanics mDead = true; - mDynamic[index].setModifier(0); - mDynamic[index].setCurrentModifier(0); mDynamic[index].setCurrent(0); } } @@ -281,10 +276,7 @@ namespace MWMechanics { if (mDead) { - if (mDynamic[0].getModified() < 1) - mDynamic[0].setModified(1, 0); - - mDynamic[0].setCurrent(mDynamic[0].getModified()); + mDynamic[0].setCurrent(mDynamic[0].getBase()); mDead = false; mDeathAnimationFinished = false; } @@ -415,9 +407,8 @@ namespace MWMechanics double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; DynamicStat magicka = getMagicka(); - float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0; - magicka.setModified(magicka.getModified() + diff, 0); + magicka.setBase(magickaFactor * intelligence); magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); setMagicka(magicka); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0816445271..5c41e8142d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -255,7 +255,7 @@ namespace MWMechanics for (int i=0; i<3; ++i) { DynamicStat stat = creatureStats.getDynamic (i); - stat.setCurrent (stat.getModified()); + stat.setCurrent(stat.getModified()); creatureStats.setDynamic (i, stat); } @@ -1450,7 +1450,7 @@ namespace MWMechanics std::string script = target.getClass().getScript(target); if (!script.empty() && target.getRefData().getLocals().hasVar(script, "onpchitme") && attacker == player) { - int fight = std::max(0, target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified()); + int fight = target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified(); peaceful = (fight == 0); } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index d0584791a5..42c27c7931 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -64,8 +64,7 @@ namespace auto& creatureStats = target.getClass().getCreatureStats(target); auto stat = creatureStats.getDynamic(index); float current = stat.getCurrent(); - stat.setModified(stat.getModified() + magnitude, 0); - stat.setCurrentModified(stat.getCurrentModified() + magnitude); + stat.setBase(std::max(0.f, stat.getBase() + magnitude)); stat.setCurrent(current + magnitude); creatureStats.setDynamic(index, stat); } diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 263c867aea..9d62eb7873 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -5,174 +5,41 @@ namespace MWMechanics { template - Stat::Stat() : mBase (0), mModified (0), mCurrentModified (0) {} + Stat::Stat() : mBase (0), mModifier (0) {} template - Stat::Stat(T base) : mBase (base), mModified (base), mCurrentModified (base) {} - template - Stat::Stat(T base, T modified) : mBase (base), mModified (modified), mCurrentModified (modified) {} - - template - const T& Stat::getBase() const - { - return mBase; - } + Stat::Stat(T base, T modified) : mBase (base), mModifier (modified) {} template T Stat::getModified(bool capped) const { - if(!capped) - return mModified; - return std::max(static_cast(0), mModified); - } - - template - T Stat::getCurrentModified() const - { - return mCurrentModified; - } - - template - T Stat::getModifier() const - { - return mModified-mBase; - } - - template - T Stat::getCurrentModifier() const - { - return mCurrentModified - mModified; - } - - template - void Stat::set (const T& value) - { - T diff = value - mBase; - mBase = mModified = value; - mCurrentModified += diff; - } - - template - void Stat::setBase (const T& value) - { - T diff = value - mBase; - mBase = value; - mModified += diff; - mCurrentModified += diff; - } - - template - void Stat::setModified (T value, const T& min, const T& max) - { - T diff = value - mModified; - - if (mBase+diffmax) - { - value = max + (mModified - mBase); - diff = value - mModified; - } - - mModified = value; - mBase += diff; - mCurrentModified += diff; - } - - template - void Stat::setCurrentModified(T value) - { - mCurrentModified = value; - } - - template - void Stat::setModifier (const T& modifier) - { - mModified = mBase + modifier; - } - - template - void Stat::setCurrentModifier(const T& modifier) - { - mCurrentModified = mModified + modifier; + if(capped) + return std::max({}, mModifier + mBase); + return mModifier + mBase; } template void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; - state.mMod = mCurrentModified; + state.mMod = mModifier; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; - mModified = state.mBase; - mCurrentModified = state.mMod; + mModifier = state.mMod; } template - DynamicStat::DynamicStat() : mStatic (0), mCurrent (0) {} + DynamicStat::DynamicStat() : mStatic(0, 0), mCurrent(0) {} template - DynamicStat::DynamicStat(T base) : mStatic (base), mCurrent (base) {} + DynamicStat::DynamicStat(T base) : mStatic(base, 0), mCurrent(base) {} template DynamicStat::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {} template DynamicStat::DynamicStat(const Stat &stat, T current) : mStatic(stat), mCurrent (current) {} - - template - const T& DynamicStat::getBase() const - { - return mStatic.getBase(); - } - template - T DynamicStat::getModified() const - { - return mStatic.getModified(); - } - template - T DynamicStat::getCurrentModified() const - { - return mStatic.getCurrentModified(); - } - - template - const T& DynamicStat::getCurrent() const - { - return mCurrent; - } - - template - void DynamicStat::set (const T& value) - { - mStatic.set (value); - mCurrent = value; - } - template - void DynamicStat::setBase (const T& value) - { - mStatic.setBase (value); - - if (mCurrent>getModified()) - mCurrent = getModified(); - } - template - void DynamicStat::setModified (T value, const T& min, const T& max) - { - mStatic.setModified (value, min, max); - - if (mCurrent>getModified()) - mCurrent = getModified(); - } - template - void DynamicStat::setCurrentModified(T value) - { - mStatic.setCurrentModified(value); - } template void DynamicStat::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified) { @@ -197,23 +64,6 @@ namespace MWMechanics mCurrent = 0; } } - template - void DynamicStat::setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero) - { - T diff = modifier - mStatic.getModifier(); - mStatic.setModifier (modifier); - setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); - } - - template - void DynamicStat::setCurrentModifier(const T& modifier, bool allowCurrentDecreaseBelowZero) - { - T diff = modifier - mStatic.getCurrentModifier(); - mStatic.setCurrentModifier(modifier); - - // The (modifier > 0) check here allows increase over modified only if the modifier is positive (a fortify effect is active). - setCurrent (getCurrent() + diff, allowCurrentDecreaseBelowZero, (modifier > 0)); - } template void DynamicStat::writeState (ESM::StatState& state) const diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index c80c5b1b70..e08d0af71e 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -16,38 +16,22 @@ namespace MWMechanics class Stat { T mBase; - T mModified; - T mCurrentModified; + T mModifier; public: typedef T Type; Stat(); - Stat(T base); Stat(T base, T modified); - const T& getBase() const; + const T& getBase() const { return mBase; }; T getModified(bool capped = true) const; - T getCurrentModified() const; - T getModifier() const; - T getCurrentModifier() const; + T getModifier() const { return mModifier; }; - /// Set base and modified to \a value. - void set (const T& value); + void setBase(const T& value) { mBase = value; }; - /// Set base and adjust modified accordingly. - void setBase (const T& value); - - /// Set modified value and adjust base accordingly. - void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); - - /// Set "current modified," used for drain and fortify. Unlike the regular modifier - /// this just adds and subtracts from the current value without changing the maximum. - void setCurrentModified(T value); - - void setModifier (const T& modifier); - void setCurrentModifier (const T& modifier); + void setModifier(const T& modifier) { mModifier = modifier; }; void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); @@ -57,7 +41,7 @@ namespace MWMechanics inline bool operator== (const Stat& left, const Stat& right) { return left.getBase()==right.getBase() && - left.getModified()==right.getModified(); + left.getModifier()==right.getModifier(); } template @@ -80,27 +64,17 @@ namespace MWMechanics DynamicStat(T base, T modified, T current); DynamicStat(const Stat &stat, T current); - const T& getBase() const; - T getModified() const; - T getCurrentModified() const; - const T& getCurrent() const; - - /// Set base, modified and current to \a value. - void set (const T& value); + const T& getBase() const { return mStatic.getBase(); }; + T getModified(bool capped = true) const { return mStatic.getModified(capped); }; + const T& getCurrent() const { return mCurrent; }; - /// Set base and adjust modified accordingly. - void setBase (const T& value); - - /// Set modified value and adjust base accordingly. - void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); - - /// Set "current modified," used for drain and fortify. Unlike the regular modifier - /// this just adds and subtracts from the current value without changing the maximum. - void setCurrentModified(T value); + /// Set base and adjust current accordingly. + void setBase(const T& value) { mStatic.setBase(value); }; void setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false); - void setModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero=false); - void setCurrentModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero = false); + + T getModifier() const { return mStatic.getModifier(); } + void setModifier(T value) { mStatic.setModifier(value); } void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); @@ -110,7 +84,7 @@ namespace MWMechanics inline bool operator== (const DynamicStat& left, const DynamicStat& right) { return left.getBase()==right.getBase() && - left.getModified()==right.getModified() && + left.getModifier()==right.getModifier() && left.getCurrent()==right.getCurrent(); } diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 9c257d9093..1138a55238 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -198,7 +198,11 @@ namespace MWWorld for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) creatureStats.mAttributes[i].mMod = 0.f; for(std::size_t i = 0; i < 3; ++i) - creatureStats.mDynamic[i].mMod = 0.f; + { + auto& dynamic = creatureStats.mDynamic[i]; + dynamic.mCurrent -= dynamic.mMod - dynamic.mBase; + dynamic.mMod = 0.f; + } for(std::size_t i = 0; i < 4; ++i) creatureStats.mAiSettings[i].mMod = 0.f; if(npcStats) From ede9d27437da965dbffcfa6c5931516a67bbc54e Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 10 Feb 2022 19:43:27 +0000 Subject: [PATCH 10/48] Element-wise multiplication and division of Lua vectors --- .../lua/test_utilpackage.cpp | 12 ++++++ components/lua/utilpackage.cpp | 41 ++++++++++-------- files/lua_api/openmw/util.lua | 43 +++++++++++++++++++ 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index ba19cca383..5e27b36916 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -39,6 +39,10 @@ namespace EXPECT_TRUE(get(lua, "v2 == util.vector2(3/5, 4/5)")); lua.safe_script("_, len = util.vector2(0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); + lua.safe_script("ediv0 = util.vector2(1, 0):ediv(util.vector2(0, 0))"); + EXPECT_TRUE(get(lua, "ediv0.x == math.huge and ediv0.y ~= ediv0.y")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2):emul(util.vector2(3, 4)) == util.vector2(3, 8)")); + EXPECT_TRUE(get(lua, "util.vector2(4, 6):ediv(util.vector2(2, 3)) == util.vector2(2, 2)")); } TEST(LuaUtilPackageTest, Vector3) @@ -68,6 +72,10 @@ namespace EXPECT_TRUE(get(lua, "v2 == util.vector3(3/5, 4/5, 0)")); lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); + lua.safe_script("ediv0 = util.vector3(1, 1, 1):ediv(util.vector3(0, 0, 0))"); + EXPECT_TRUE(get(lua, "ediv0.z == math.huge")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3):emul(util.vector3(3, 4, 5)) == util.vector3(3, 8, 15)")); + EXPECT_TRUE(get(lua, "util.vector3(4, 6, 8):ediv(util.vector3(2, 3, 4)) == util.vector3(2, 2, 2)")); } TEST(LuaUtilPackageTest, Vector4) @@ -95,6 +103,10 @@ namespace EXPECT_TRUE(get(lua, "v2 == util.vector4(3/5, 0, 0, 4/5)")); lua.safe_script("_, len = util.vector4(0, 0, 0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); + lua.safe_script("ediv0 = util.vector4(1, 1, 1, -1):ediv(util.vector4(0, 0, 0, 0))"); + EXPECT_TRUE(get(lua, "ediv0.w == -math.huge")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4):emul(util.vector4(3, 4, 5, 6)) == util.vector4(3, 8, 15, 24)")); + EXPECT_TRUE(get(lua, "util.vector4(4, 6, 8, 9):ediv(util.vector4(2, 3, 4, 3)) == util.vector4(2, 2, 2, 3)")); } TEST(LuaUtilPackageTest, Color) diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index c51b00a7d5..d31a8259be 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -55,6 +55,29 @@ namespace LuaUtil else return std::make_tuple(v * (1.f / len), len); }; + vectorType["emul"] = [](const T& a, const T& b) + { + T result; + for (int i = 0; i < T::num_components; ++i) + result[i] = a[i] * b[i]; + return result; + }; + vectorType["ediv"] = [](const T& a, const T& b) + { + T result; + for (int i = 0; i < T::num_components; ++i) + result[i] = a[i] / b[i]; + return result; + }; + vectorType[sol::meta_function::to_string] = [](const T& v) + { + std::stringstream ss; + ss << "(" << v[0]; + for (int i = 1; i < T::num_components; ++i) + ss << ", " << v[i]; + ss << ")"; + return ss.str(); + }; } } @@ -67,12 +90,6 @@ namespace LuaUtil sol::usertype vec2Type = lua.new_usertype("Vec2"); vec2Type["x"] = sol::readonly_property([](const Vec2& v) -> float { return v.x(); } ); vec2Type["y"] = sol::readonly_property([](const Vec2& v) -> float { return v.y(); } ); - vec2Type[sol::meta_function::to_string] = [](const Vec2& v) - { - std::stringstream ss; - ss << "(" << v.x() << ", " << v.y() << ")"; - return ss.str(); - }; addVectorMethods(vec2Type); vec2Type["rotate"] = &Misc::rotateVec2f; @@ -82,12 +99,6 @@ namespace LuaUtil vec3Type["x"] = sol::readonly_property([](const Vec3& v) -> float { return v.x(); } ); vec3Type["y"] = sol::readonly_property([](const Vec3& v) -> float { return v.y(); } ); vec3Type["z"] = sol::readonly_property([](const Vec3& v) -> float { return v.z(); } ); - vec3Type[sol::meta_function::to_string] = [](const Vec3& v) - { - std::stringstream ss; - ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; - return ss.str(); - }; addVectorMethods(vec3Type); vec3Type[sol::meta_function::involution] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; vec3Type["cross"] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; @@ -100,12 +111,6 @@ namespace LuaUtil vec4Type["y"] = sol::readonly_property([](const Vec4& v) -> float { return v.y(); }); vec4Type["z"] = sol::readonly_property([](const Vec4& v) -> float { return v.z(); }); vec4Type["w"] = sol::readonly_property([](const Vec4& v) -> float { return v.w(); }); - vec4Type[sol::meta_function::to_string] = [](const Vec4& v) - { - std::stringstream ss; - ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ", " << v.w() << ")"; - return ss.str(); - }; addVectorMethods(vec4Type); // Lua bindings for Color diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index dca1683d45..4ea0ee4b0d 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -87,6 +87,20 @@ -- @param #Vector2 v -- @return #number +--- +-- Element-wise multiplication +-- @function [parent=#Vector2] emul +-- @param self +-- @param #Vector2 v +-- @return #Vector2 + +--- +-- Element-wise division +-- @function [parent=#Vector2] ediv +-- @param self +-- @param #Vector2 v +-- @return #Vector2 + --- -- Immutable 3D vector @@ -152,6 +166,20 @@ -- @param #Vector3 v -- @return #Vector3 +--- +-- Element-wise multiplication +-- @function [parent=#Vector3] emul +-- @param self +-- @param #Vector3 v +-- @return #Vector3 + +--- +-- Element-wise division +-- @function [parent=#Vector3] ediv +-- @param self +-- @param #Vector3 v +-- @return #Vector3 + --- -- Immutable 4D vector. @@ -210,6 +238,21 @@ -- @param #Vector4 v -- @return #number +--- +-- Element-wise multiplication +-- @function [parent=#Vector4] emul +-- @param self +-- @param #Vector4 v +-- @return #Vector4 + +--- +-- Element-wise division +-- @function [parent=#Vector4] ediv +-- @param self +-- @param #Vector4 v +-- @return #Vector4 + + --- -- Color in RGBA format. All of the component values are in the range [0, 1]. -- @type Color From 6b203892fc55cf5bb1c0eac15cffd3b6009c4eb8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 20:46:20 +0100 Subject: [PATCH 11/48] Fix mod not increasing fortified values --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwscript/statsextensions.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5c41e8142d..d2ebef45e7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -255,7 +255,7 @@ namespace MWMechanics for (int i=0; i<3; ++i) { DynamicStat stat = creatureStats.getDynamic (i); - stat.setCurrent(stat.getModified()); + stat.setCurrent (stat.getModified()); creatureStats.setDynamic (i, stat); } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index f127b2bc4b..f2de338fb3 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -219,7 +219,7 @@ namespace MWScript .getDynamic (mIndex)); stat.setBase(value); - stat.setCurrent(value, true); + stat.setCurrent(stat.getModified(false), true, true); ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } @@ -268,7 +268,7 @@ namespace MWScript if(mIndex != 2) base = std::max(base, 0.f); stat.setBase(base); - stat.setCurrent(diff + current, true); + stat.setCurrent(diff + current, true, true); stats.setDynamic (mIndex, stat); } From 054d8babc48cfe30e344ca65df85c411a0569569 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 22:10:46 +0100 Subject: [PATCH 12/48] Add getRatio method --- apps/openmw/mwdialogue/filter.cpp | 12 ++---------- apps/openmw/mwgui/hud.cpp | 3 +-- apps/openmw/mwmechanics/aicombataction.cpp | 3 +-- apps/openmw/mwmechanics/stat.cpp | 13 +++++++++++++ apps/openmw/mwmechanics/stat.hpp | 1 + apps/openmw/mwscript/statsextensions.cpp | 12 ++---------- 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 7ae9f14a7a..e90ba79481 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -257,11 +257,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); - float ratio = player.getClass().getCreatureStats(player).getHealth().getModified(); - if(ratio > 0) - ratio = player.getClass().getCreatureStats(player).getHealth().getCurrent() / ratio; - - return select.selectCompare (static_cast(ratio*100)); + return select.selectCompare(static_cast(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100)); } case SelectWrapper::Function_PcDynamicStat: @@ -276,11 +272,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_HealthPercent: { - float ratio = mActor.getClass().getCreatureStats(mActor).getHealth().getModified(); - if(ratio > 0) - ratio = mActor.getClass().getCreatureStats(mActor).getHealth().getCurrent() / ratio; - - return select.selectCompare (static_cast(ratio*100)); + return select.selectCompare(static_cast(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100)); } default: diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 5af85b34ae..e591163731 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -608,8 +608,7 @@ namespace MWGui mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) - float health = stats.getHealth().getModified(); - mEnemyHealth->setProgressPosition(health == 0.f ? 0 : static_cast(stats.getHealth().getCurrent() / health * 100)); + mEnemyHealth->setProgressPosition(static_cast(stats.getHealth().getRatio() * 100)); static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 3be68f8394..43b43eb9f9 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -476,8 +476,7 @@ namespace MWMechanics static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat(); static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat(); - float healthPercentage = (stats.getHealth().getModified() == 0.0f) - ? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified(); + float healthPercentage = stats.getHealth().getRatio(false); float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult; static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger(); diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 9d62eb7873..eacfca98ae 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -65,6 +65,19 @@ namespace MWMechanics } } + template + T DynamicStat::getRatio(bool nanIsZero) const + { + T modified = getModified(); + if(modified == T{}) + { + if(nanIsZero) + return modified; + return {1}; + } + return getCurrent() / modified; + } + template void DynamicStat::writeState (ESM::StatState& state) const { diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index e08d0af71e..1e9bb100d0 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -67,6 +67,7 @@ namespace MWMechanics const T& getBase() const { return mStatic.getBase(); }; T getModified(bool capped = true) const { return mStatic.getModified(capped); }; const T& getCurrent() const { return mCurrent; }; + T getRatio(bool nanIsZero = true) const; /// Set base and adjust current accordingly. void setBase(const T& value) { mStatic.setBase(value); }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index f2de338fb3..a62d40065d 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -324,17 +324,9 @@ namespace MWScript void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - - Interpreter::Type_Float value = 0; - - Interpreter::Type_Float max = stats.getDynamic(mIndex).getModified(); - - if (max>0) - value = stats.getDynamic(mIndex).getCurrent() / max; - - runtime.push (value); + runtime.push(stats.getDynamic(mIndex).getRatio()); } }; From 6cd12823e88bd9e6cc503146bd0227557b4aac48 Mon Sep 17 00:00:00 2001 From: Niek Wilting Date: Fri, 11 Feb 2022 19:26:13 +0100 Subject: [PATCH 13/48] Fix always-use-best-attack rounding --- apps/openmw/mwmechanics/character.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0f8e2ebd97..a1d686baaa 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -55,9 +55,9 @@ namespace std::string getBestAttack (const ESM::Weapon* weapon) { - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + int slash = weapon->mData.mSlash[0] + weapon->mData.mSlash[1]; + int chop = weapon->mData.mChop[0] + weapon->mData.mChop[1]; + int thrust = weapon->mData.mThrust[0] + weapon->mData.mThrust[1]; if (slash == chop && slash == thrust) return "slash"; else if (thrust >= chop && thrust >= slash) From d097c16206077933234aac566957ef513f1459ae Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 20:40:38 +0100 Subject: [PATCH 14/48] Use unique_ptr to manage nif record lifetime --- components/nif/niffile.cpp | 20 +++++++------------- components/nif/niffile.hpp | 6 ++---- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 1a1bbd7217..e976a9d96a 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -17,17 +17,11 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name) parse(stream); } -NIFFile::~NIFFile() -{ - for (Record* record : records) - delete record; -} - -template static Record* construct() { return new NodeType; } +template static std::unique_ptr construct() { return std::make_unique(); } struct RecordFactoryEntry { - using create_t = Record* (*)(); + using create_t = std::unique_ptr (*)(); create_t mCreate; RecordType mType; @@ -290,7 +284,7 @@ void NIFFile::parse(Files::IStreamPtr stream) const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10,0,0,0) && ver < NIFStream::generateVersion(10,2,0,0); for (std::size_t i = 0; i < recNum; i++) { - Record *r = nullptr; + std::unique_ptr r; std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString(); if(rec.empty()) @@ -315,7 +309,7 @@ void NIFFile::parse(Files::IStreamPtr stream) if (entry != factories.end()) { - r = entry->second.mCreate (); + r = entry->second.mCreate(); r->recType = entry->second.mType; } else @@ -328,8 +322,8 @@ void NIFFile::parse(Files::IStreamPtr stream) assert(r->recType != RC_MISSING); r->recName = rec; r->recIndex = i; - records[i] = r; r->read(&nif); + records[i] = std::move(r); } const std::size_t rootNum = nif.getUInt(); @@ -341,7 +335,7 @@ void NIFFile::parse(Files::IStreamPtr stream) int idx = nif.getInt(); if (idx >= 0 && static_cast(idx) < records.size()) { - roots[i] = records[idx]; + roots[i] = records[idx].get(); } else { @@ -351,7 +345,7 @@ void NIFFile::parse(Files::IStreamPtr stream) } // Once parsing is done, do post-processing. - for (Record* record : records) + for (const auto& record : records) record->post(this); } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 6884f51d58..d9f46795c1 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -55,7 +55,7 @@ class NIFFile final : public File std::string hash; /// Record list - std::vector records; + std::vector> records; /// Root list. This is a select portion of the pointers from records std::vector roots; @@ -107,13 +107,11 @@ public: /// Open a NIF stream. The name is used for error messages. NIFFile(Files::IStreamPtr stream, const std::string &name); - ~NIFFile(); /// Get a given record Record *getRecord(size_t index) const override { - Record *res = records.at(index); - return res; + return records.at(index).get(); } /// Number of records size_t numRecords() const override { return records.size(); } From 082810f9244b6666ee5c2f5e3e27ef314328ed83 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 21:17:11 +0100 Subject: [PATCH 15/48] Store record type as a part of construct function type --- components/nif/niffile.cpp | 272 ++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 138 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index e976a9d96a..f3355c4e77 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -17,144 +17,143 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name) parse(stream); } -template static std::unique_ptr construct() { return std::make_unique(); } - -struct RecordFactoryEntry { - - using create_t = std::unique_ptr (*)(); - - create_t mCreate; - RecordType mType; +template +static std::unique_ptr construct() +{ + auto result = std::make_unique(); + result->recType = recordType; + return result; +} -}; +using CreateRecord = std::unique_ptr (*)(); ///These are all the record types we know how to read. -static std::map makeFactory() +static std::map makeFactory() { - std::map factory; - factory["NiNode"] = {&construct , RC_NiNode }; - factory["NiSwitchNode"] = {&construct , RC_NiSwitchNode }; - factory["NiLODNode"] = {&construct , RC_NiLODNode }; - factory["AvoidNode"] = {&construct , RC_AvoidNode }; - factory["NiCollisionSwitch"] = {&construct , RC_NiCollisionSwitch }; - factory["NiBSParticleNode"] = {&construct , RC_NiBSParticleNode }; - factory["NiBSAnimationNode"] = {&construct , RC_NiBSAnimationNode }; - factory["NiBillboardNode"] = {&construct , RC_NiBillboardNode }; - factory["NiTriShape"] = {&construct , RC_NiTriShape }; - factory["NiTriStrips"] = {&construct , RC_NiTriStrips }; - factory["NiLines"] = {&construct , RC_NiLines }; - factory["NiParticles"] = {&construct , RC_NiParticles }; - factory["NiRotatingParticles"] = {&construct , RC_NiParticles }; - factory["NiAutoNormalParticles"] = {&construct , RC_NiParticles }; - factory["NiCamera"] = {&construct , RC_NiCamera }; - factory["RootCollisionNode"] = {&construct , RC_RootCollisionNode }; - factory["NiTexturingProperty"] = {&construct , RC_NiTexturingProperty }; - factory["NiFogProperty"] = {&construct , RC_NiFogProperty }; - factory["NiMaterialProperty"] = {&construct , RC_NiMaterialProperty }; - factory["NiZBufferProperty"] = {&construct , RC_NiZBufferProperty }; - factory["NiAlphaProperty"] = {&construct , RC_NiAlphaProperty }; - factory["NiVertexColorProperty"] = {&construct , RC_NiVertexColorProperty }; - factory["NiShadeProperty"] = {&construct , RC_NiShadeProperty }; - factory["NiDitherProperty"] = {&construct , RC_NiDitherProperty }; - factory["NiWireframeProperty"] = {&construct , RC_NiWireframeProperty }; - factory["NiSpecularProperty"] = {&construct , RC_NiSpecularProperty }; - factory["NiStencilProperty"] = {&construct , RC_NiStencilProperty }; - factory["NiVisController"] = {&construct , RC_NiVisController }; - factory["NiGeomMorpherController"] = {&construct , RC_NiGeomMorpherController }; - factory["NiKeyframeController"] = {&construct , RC_NiKeyframeController }; - factory["NiAlphaController"] = {&construct , RC_NiAlphaController }; - factory["NiRollController"] = {&construct , RC_NiRollController }; - factory["NiUVController"] = {&construct , RC_NiUVController }; - factory["NiPathController"] = {&construct , RC_NiPathController }; - factory["NiMaterialColorController"] = {&construct , RC_NiMaterialColorController }; - factory["NiBSPArrayController"] = {&construct , RC_NiBSPArrayController }; - factory["NiParticleSystemController"] = {&construct , RC_NiParticleSystemController }; - factory["NiFlipController"] = {&construct , RC_NiFlipController }; - factory["NiAmbientLight"] = {&construct , RC_NiLight }; - factory["NiDirectionalLight"] = {&construct , RC_NiLight }; - factory["NiPointLight"] = {&construct , RC_NiLight }; - factory["NiSpotLight"] = {&construct , RC_NiLight }; - factory["NiTextureEffect"] = {&construct , RC_NiTextureEffect }; - factory["NiExtraData"] = {&construct , RC_NiExtraData }; - factory["NiVertWeightsExtraData"] = {&construct , RC_NiVertWeightsExtraData }; - factory["NiTextKeyExtraData"] = {&construct , RC_NiTextKeyExtraData }; - factory["NiStringExtraData"] = {&construct , RC_NiStringExtraData }; - factory["NiGravity"] = {&construct , RC_NiGravity }; - factory["NiPlanarCollider"] = {&construct , RC_NiPlanarCollider }; - factory["NiSphericalCollider"] = {&construct , RC_NiSphericalCollider }; - factory["NiParticleGrowFade"] = {&construct , RC_NiParticleGrowFade }; - factory["NiParticleColorModifier"] = {&construct , RC_NiParticleColorModifier }; - factory["NiParticleRotation"] = {&construct , RC_NiParticleRotation }; - factory["NiFloatData"] = {&construct , RC_NiFloatData }; - factory["NiTriShapeData"] = {&construct , RC_NiTriShapeData }; - factory["NiTriStripsData"] = {&construct , RC_NiTriStripsData }; - factory["NiLinesData"] = {&construct , RC_NiLinesData }; - factory["NiVisData"] = {&construct , RC_NiVisData }; - factory["NiColorData"] = {&construct , RC_NiColorData }; - factory["NiPixelData"] = {&construct , RC_NiPixelData }; - factory["NiMorphData"] = {&construct , RC_NiMorphData }; - factory["NiKeyframeData"] = {&construct , RC_NiKeyframeData }; - factory["NiSkinData"] = {&construct , RC_NiSkinData }; - factory["NiUVData"] = {&construct , RC_NiUVData }; - factory["NiPosData"] = {&construct , RC_NiPosData }; - factory["NiParticlesData"] = {&construct , RC_NiParticlesData }; - factory["NiRotatingParticlesData"] = {&construct , RC_NiParticlesData }; - factory["NiAutoNormalParticlesData"] = {&construct , RC_NiParticlesData }; - factory["NiSequenceStreamHelper"] = {&construct , RC_NiSequenceStreamHelper }; - factory["NiSourceTexture"] = {&construct , RC_NiSourceTexture }; - factory["NiSkinInstance"] = {&construct , RC_NiSkinInstance }; - factory["NiLookAtController"] = {&construct , RC_NiLookAtController }; - factory["NiPalette"] = {&construct , RC_NiPalette }; - factory["NiIntegerExtraData"] = {&construct , RC_NiIntegerExtraData }; - factory["NiIntegersExtraData"] = {&construct , RC_NiIntegersExtraData }; - factory["NiBinaryExtraData"] = {&construct , RC_NiBinaryExtraData }; - factory["NiBooleanExtraData"] = {&construct , RC_NiBooleanExtraData }; - factory["NiVectorExtraData"] = {&construct , RC_NiVectorExtraData }; - factory["NiColorExtraData"] = {&construct , RC_NiColorExtraData }; - factory["NiFloatExtraData"] = {&construct , RC_NiFloatExtraData }; - factory["NiFloatsExtraData"] = {&construct , RC_NiFloatsExtraData }; - factory["NiStringPalette"] = {&construct , RC_NiStringPalette }; - factory["NiBoolData"] = {&construct , RC_NiBoolData }; - factory["NiSkinPartition"] = {&construct , RC_NiSkinPartition }; - factory["BSXFlags"] = {&construct , RC_BSXFlags }; - factory["BSBound"] = {&construct , RC_BSBound }; - factory["NiTransformData"] = {&construct , RC_NiKeyframeData }; - factory["BSFadeNode"] = {&construct , RC_NiNode }; - factory["bhkBlendController"] = {&construct , RC_bhkBlendController }; - factory["NiFloatInterpolator"] = {&construct , RC_NiFloatInterpolator }; - factory["NiBoolInterpolator"] = {&construct , RC_NiBoolInterpolator }; - factory["NiPoint3Interpolator"] = {&construct , RC_NiPoint3Interpolator }; - factory["NiTransformController"] = {&construct , RC_NiKeyframeController }; - factory["NiTransformInterpolator"] = {&construct , RC_NiTransformInterpolator }; - factory["NiColorInterpolator"] = {&construct , RC_NiColorInterpolator }; - factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; - factory["BSLODTriShape"] = {&construct , RC_BSLODTriShape }; - factory["BSShaderProperty"] = {&construct , RC_BSShaderProperty }; - factory["BSShaderPPLightingProperty"] = {&construct , RC_BSShaderPPLightingProperty }; - factory["BSShaderNoLightingProperty"] = {&construct , RC_BSShaderNoLightingProperty }; - factory["BSFurnitureMarker"] = {&construct , RC_BSFurnitureMarker }; - factory["NiCollisionObject"] = {&construct , RC_NiCollisionObject }; - factory["bhkCollisionObject"] = {&construct , RC_bhkCollisionObject }; - factory["BSDismemberSkinInstance"] = {&construct , RC_BSDismemberSkinInstance }; - factory["NiControllerManager"] = {&construct , RC_NiControllerManager }; - factory["bhkMoppBvTreeShape"] = {&construct , RC_bhkMoppBvTreeShape }; - factory["bhkNiTriStripsShape"] = {&construct , RC_bhkNiTriStripsShape }; - factory["bhkPackedNiTriStripsShape"] = {&construct , RC_bhkPackedNiTriStripsShape }; - factory["hkPackedNiTriStripsData"] = {&construct , RC_hkPackedNiTriStripsData }; - factory["bhkConvexVerticesShape"] = {&construct , RC_bhkConvexVerticesShape }; - factory["bhkBoxShape"] = {&construct , RC_bhkBoxShape }; - factory["bhkListShape"] = {&construct , RC_bhkListShape }; - factory["bhkRigidBody"] = {&construct , RC_bhkRigidBody }; - factory["bhkRigidBodyT"] = {&construct , RC_bhkRigidBodyT }; - factory["BSLightingShaderProperty"] = {&construct , RC_BSLightingShaderProperty }; - factory["NiSortAdjustNode"] = {&construct , RC_NiNode }; - factory["NiClusterAccumulator"] = {&construct , RC_NiClusterAccumulator }; - factory["NiAlphaAccumulator"] = {&construct , RC_NiAlphaAccumulator }; - return factory; + return { + {"NiNode" , &construct }, + {"NiSwitchNode" , &construct }, + {"NiLODNode" , &construct }, + {"AvoidNode" , &construct }, + {"NiCollisionSwitch" , &construct }, + {"NiBSParticleNode" , &construct }, + {"NiBSAnimationNode" , &construct }, + {"NiBillboardNode" , &construct }, + {"NiTriShape" , &construct }, + {"NiTriStrips" , &construct }, + {"NiLines" , &construct }, + {"NiParticles" , &construct }, + {"NiRotatingParticles" , &construct }, + {"NiAutoNormalParticles" , &construct }, + {"NiCamera" , &construct }, + {"RootCollisionNode" , &construct }, + {"NiTexturingProperty" , &construct }, + {"NiFogProperty" , &construct }, + {"NiMaterialProperty" , &construct }, + {"NiZBufferProperty" , &construct }, + {"NiAlphaProperty" , &construct }, + {"NiVertexColorProperty" , &construct }, + {"NiShadeProperty" , &construct }, + {"NiDitherProperty" , &construct }, + {"NiWireframeProperty" , &construct }, + {"NiSpecularProperty" , &construct }, + {"NiStencilProperty" , &construct }, + {"NiVisController" , &construct }, + {"NiGeomMorpherController" , &construct }, + {"NiKeyframeController" , &construct }, + {"NiAlphaController" , &construct }, + {"NiRollController" , &construct }, + {"NiUVController" , &construct }, + {"NiPathController" , &construct }, + {"NiMaterialColorController" , &construct }, + {"NiBSPArrayController" , &construct }, + {"NiParticleSystemController" , &construct }, + {"NiFlipController" , &construct }, + {"NiAmbientLight" , &construct }, + {"NiDirectionalLight" , &construct }, + {"NiPointLight" , &construct }, + {"NiSpotLight" , &construct }, + {"NiTextureEffect" , &construct }, + {"NiExtraData" , &construct }, + {"NiVertWeightsExtraData" , &construct }, + {"NiTextKeyExtraData" , &construct }, + {"NiStringExtraData" , &construct }, + {"NiGravity" , &construct }, + {"NiPlanarCollider" , &construct }, + {"NiSphericalCollider" , &construct }, + {"NiParticleGrowFade" , &construct }, + {"NiParticleColorModifier" , &construct }, + {"NiParticleRotation" , &construct }, + {"NiFloatData" , &construct }, + {"NiTriShapeData" , &construct }, + {"NiTriStripsData" , &construct }, + {"NiLinesData" , &construct }, + {"NiVisData" , &construct }, + {"NiColorData" , &construct }, + {"NiPixelData" , &construct }, + {"NiMorphData" , &construct }, + {"NiKeyframeData" , &construct }, + {"NiSkinData" , &construct }, + {"NiUVData" , &construct }, + {"NiPosData" , &construct }, + {"NiParticlesData" , &construct }, + {"NiRotatingParticlesData" , &construct }, + {"NiAutoNormalParticlesData" , &construct }, + {"NiSequenceStreamHelper" , &construct }, + {"NiSourceTexture" , &construct }, + {"NiSkinInstance" , &construct }, + {"NiLookAtController" , &construct }, + {"NiPalette" , &construct }, + {"NiIntegerExtraData" , &construct }, + {"NiIntegersExtraData" , &construct }, + {"NiBinaryExtraData" , &construct }, + {"NiBooleanExtraData" , &construct }, + {"NiVectorExtraData" , &construct }, + {"NiColorExtraData" , &construct }, + {"NiFloatExtraData" , &construct }, + {"NiFloatsExtraData" , &construct }, + {"NiStringPalette" , &construct }, + {"NiBoolData" , &construct }, + {"NiSkinPartition" , &construct }, + {"BSXFlags" , &construct }, + {"BSBound" , &construct }, + {"NiTransformData" , &construct }, + {"BSFadeNode" , &construct }, + {"bhkBlendController" , &construct }, + {"NiFloatInterpolator" , &construct }, + {"NiBoolInterpolator" , &construct }, + {"NiPoint3Interpolator" , &construct }, + {"NiTransformController" , &construct }, + {"NiTransformInterpolator" , &construct }, + {"NiColorInterpolator" , &construct }, + {"BSShaderTextureSet" , &construct }, + {"BSLODTriShape" , &construct }, + {"BSShaderProperty" , &construct }, + {"BSShaderPPLightingProperty" , &construct }, + {"BSShaderNoLightingProperty" , &construct }, + {"BSFurnitureMarker" , &construct }, + {"NiCollisionObject" , &construct }, + {"bhkCollisionObject" , &construct }, + {"BSDismemberSkinInstance" , &construct }, + {"NiControllerManager" , &construct }, + {"bhkMoppBvTreeShape" , &construct }, + {"bhkNiTriStripsShape" , &construct }, + {"bhkPackedNiTriStripsShape" , &construct }, + {"hkPackedNiTriStripsData" , &construct }, + {"bhkConvexVerticesShape" , &construct }, + {"bhkBoxShape" , &construct }, + {"bhkListShape" , &construct }, + {"bhkRigidBody" , &construct }, + {"bhkRigidBodyT" , &construct }, + {"BSLightingShaderProperty" , &construct }, + {"NiSortAdjustNode" , &construct }, + {"NiClusterAccumulator" , &construct }, + {"NiAlphaAccumulator" , &construct }, + }; } ///Make the factory map used for parsing the file -static const std::map factories = makeFactory(); +static const std::map factories = makeFactory(); std::string NIFFile::printVersion(unsigned int version) { @@ -305,16 +304,13 @@ void NIFFile::parse(Files::IStreamPtr stream) } } - std::map::const_iterator entry = factories.find(rec); + const auto entry = factories.find(rec); - if (entry != factories.end()) - { - r = entry->second.mCreate(); - r->recType = entry->second.mType; - } - else + if (entry == factories.end()) fail("Unknown record type " + rec); + r = entry->second(); + if (!supported) Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" << filename << ")"; From fbd95516f4a7ffadc2fb1baee5f56c3fcea8229f Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 21:28:24 +0100 Subject: [PATCH 16/48] Repalce raw for-loops by corresponding algorithms --- components/nif/niffile.cpp | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index f3355c4e77..bf604f5adf 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace Nif { @@ -181,18 +182,11 @@ void NIFFile::parse(Files::IStreamPtr stream) "NetImmerse File Format", "Gamebryo File Format" }; - bool supported = false; - for (const std::string& verString : verStrings) - { - supported = (head.compare(0, verString.size(), verString) == 0); - if (supported) - break; - } - if (!supported) + const bool supportedHeader = std::any_of(verStrings.begin(), verStrings.end(), + [&] (const std::string& verString) { return head.compare(0, verString.size(), verString) == 0; }); + if (!supportedHeader) fail("Invalid NIF header: " + head); - supported = false; - // Get BCD version ver = nif.getUInt(); // 4.0.0.0 is an older, practically identical version of the format. @@ -202,13 +196,8 @@ void NIFFile::parse(Files::IStreamPtr stream) NIFStream::generateVersion(4,0,0,0), VER_MW }; - for (uint32_t supportedVer : supportedVers) - { - supported = (ver == supportedVer); - if (supported) - break; - } - if (!supported) + const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), ver) != supportedVers.end(); + if (!supportedVersion) { if (sLoadUnsupportedFiles) warn("Unsupported NIF version: " + printVersion(ver) + ". Proceed with caution!"); @@ -311,7 +300,7 @@ void NIFFile::parse(Files::IStreamPtr stream) r = entry->second(); - if (!supported) + if (!supportedVersion) Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" << filename << ")"; assert(r != nullptr); From 283b68025cf4cd532c86f08861b78af9d9ba9267 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 21:36:08 +0100 Subject: [PATCH 17/48] Avoid possible race condition on NIFFile::sLoadUnsupportedFiles Its value is written from the main thread but other threads read it. --- components/nif/niffile.cpp | 2 +- components/nif/niffile.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index bf604f5adf..8b17680cf3 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -344,7 +344,7 @@ bool NIFFile::getUseSkinning() const return mUseSkinning; } -bool NIFFile::sLoadUnsupportedFiles = false; +std::atomic_bool NIFFile::sLoadUnsupportedFiles = false; void NIFFile::setLoadUnsupportedFiles(bool load) { diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index d9f46795c1..def2b8870d 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -65,7 +66,7 @@ class NIFFile final : public File bool mUseSkinning = false; - static bool sLoadUnsupportedFiles; + static std::atomic_bool sLoadUnsupportedFiles; /// Parse the file void parse(Files::IStreamPtr stream); From e1fe5010134550088a937b81d8eafbad720eb0da Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 21:52:00 +0100 Subject: [PATCH 18/48] Use proper type for Record::recType --- components/nif/record.hpp | 2 +- components/nifosg/nifloader.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 937ce1d1e9..f18a639aae 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -149,7 +149,7 @@ enum RecordType struct Record { // Record type and type name - int recType{RC_MISSING}; + RecordType recType{RC_MISSING}; std::string recName; unsigned int recIndex{~0u}; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 206bbf2092..a543b7a67f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2122,6 +2122,8 @@ namespace NifOsg specStrength = shaderprop->mSpecStrength; break; } + default: + break; } } From 2a87cf1720b80197a78fac069aefc1f3ec694506 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 22:08:13 +0100 Subject: [PATCH 19/48] Replace unordered_map by switch statement Add handling for missing Nif::BSLightingShaderType::ShaderType_SkinTint. Use string_view instead of string to avoid lifetime issues for returning value. osg::Object::setUserValue will anyway copy string. --- components/nifosg/nifloader.cpp | 106 +++++++++++++++----------------- 1 file changed, 49 insertions(+), 57 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a543b7a67f..73bd2068fb 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1,6 +1,7 @@ #include "nifloader.hpp" #include +#include #include #include @@ -1743,64 +1744,55 @@ namespace NifOsg } } - const std::string& getBSShaderPrefix(unsigned int type) const + std::string_view getBSShaderPrefix(unsigned int type) const { - static const std::unordered_map mapping = - { - {Nif::BSShaderType::ShaderType_TallGrass, std::string()}, - {Nif::BSShaderType::ShaderType_Default, "nv_default"}, - {Nif::BSShaderType::ShaderType_Sky, std::string()}, - {Nif::BSShaderType::ShaderType_Skin, std::string()}, - {Nif::BSShaderType::ShaderType_Water, std::string()}, - {Nif::BSShaderType::ShaderType_Lighting30, std::string()}, - {Nif::BSShaderType::ShaderType_Tile, std::string()}, - {Nif::BSShaderType::ShaderType_NoLighting, "nv_nolighting"}, - }; - auto prefix = mapping.find(static_cast(type)); - if (prefix == mapping.end()) - Log(Debug::Warning) << "Unknown BSShaderType " << type << " in " << mFilename; - else if (prefix->second.empty()) - Log(Debug::Warning) << "Unhandled BSShaderType " << type << " in " << mFilename; - else - return prefix->second; - - return mapping.at(Nif::BSShaderType::ShaderType_Default); + switch (static_cast(type)) + { + case Nif::BSShaderType::ShaderType_Default: return "nv_default"; + case Nif::BSShaderType::ShaderType_NoLighting: return "nv_nolighting"; + case Nif::BSShaderType::ShaderType_TallGrass: + case Nif::BSShaderType::ShaderType_Sky: + case Nif::BSShaderType::ShaderType_Skin: + case Nif::BSShaderType::ShaderType_Water: + case Nif::BSShaderType::ShaderType_Lighting30: + case Nif::BSShaderType::ShaderType_Tile: + Log(Debug::Warning) << "Unhandled BSShaderType " << type << " in " << mFilename; + return std::string_view(); + } + Log(Debug::Warning) << "Unknown BSShaderType " << type << " in " << mFilename; + return std::string_view(); } - const std::string& getBSLightingShaderPrefix(unsigned int type) const + std::string_view getBSLightingShaderPrefix(unsigned int type) const { - static const std::unordered_map mapping = - { - {Nif::BSLightingShaderType::ShaderType_Default, "nv_default"}, - {Nif::BSLightingShaderType::ShaderType_EnvMap, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Glow, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Parallax, std::string()}, - {Nif::BSLightingShaderType::ShaderType_FaceTint, std::string()}, - {Nif::BSLightingShaderType::ShaderType_HairTint, std::string()}, - {Nif::BSLightingShaderType::ShaderType_ParallaxOcc, std::string()}, - {Nif::BSLightingShaderType::ShaderType_MultitexLand, std::string()}, - {Nif::BSLightingShaderType::ShaderType_LODLand, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Snow, std::string()}, - {Nif::BSLightingShaderType::ShaderType_MultiLayerParallax, std::string()}, - {Nif::BSLightingShaderType::ShaderType_TreeAnim, std::string()}, - {Nif::BSLightingShaderType::ShaderType_LODObjects, std::string()}, - {Nif::BSLightingShaderType::ShaderType_SparkleSnow, std::string()}, - {Nif::BSLightingShaderType::ShaderType_LODObjectsHD, std::string()}, - {Nif::BSLightingShaderType::ShaderType_EyeEnvmap, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Cloud, std::string()}, - {Nif::BSLightingShaderType::ShaderType_LODNoise, std::string()}, - {Nif::BSLightingShaderType::ShaderType_MultitexLandLODBlend, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Dismemberment, std::string()} - }; - auto prefix = mapping.find(static_cast(type)); - if (prefix == mapping.end()) - Log(Debug::Warning) << "Unknown BSLightingShaderType " << type << " in " << mFilename; - else if (prefix->second.empty()) - Log(Debug::Warning) << "Unhandled BSLightingShaderType " << type << " in " << mFilename; - else - return prefix->second; - - return mapping.at(Nif::BSLightingShaderType::ShaderType_Default); + switch (static_cast(type)) + { + case Nif::BSLightingShaderType::ShaderType_Default: return "nv_default"; + case Nif::BSLightingShaderType::ShaderType_EnvMap: + case Nif::BSLightingShaderType::ShaderType_Glow: + case Nif::BSLightingShaderType::ShaderType_Parallax: + case Nif::BSLightingShaderType::ShaderType_FaceTint: + case Nif::BSLightingShaderType::ShaderType_SkinTint: + case Nif::BSLightingShaderType::ShaderType_HairTint: + case Nif::BSLightingShaderType::ShaderType_ParallaxOcc: + case Nif::BSLightingShaderType::ShaderType_MultitexLand: + case Nif::BSLightingShaderType::ShaderType_LODLand: + case Nif::BSLightingShaderType::ShaderType_Snow: + case Nif::BSLightingShaderType::ShaderType_MultiLayerParallax: + case Nif::BSLightingShaderType::ShaderType_TreeAnim: + case Nif::BSLightingShaderType::ShaderType_LODObjects: + case Nif::BSLightingShaderType::ShaderType_SparkleSnow: + case Nif::BSLightingShaderType::ShaderType_LODObjectsHD: + case Nif::BSLightingShaderType::ShaderType_EyeEnvmap: + case Nif::BSLightingShaderType::ShaderType_Cloud: + case Nif::BSLightingShaderType::ShaderType_LODNoise: + case Nif::BSLightingShaderType::ShaderType_MultitexLandLODBlend: + case Nif::BSLightingShaderType::ShaderType_Dismemberment: + Log(Debug::Warning) << "Unhandled BSLightingShaderType " << type << " in " << mFilename; + return std::string_view(); + } + Log(Debug::Warning) << "Unknown BSLightingShaderType " << type << " in " << mFilename; + return std::string_view(); } void handleProperty(const Nif::Property *property, @@ -1895,7 +1887,7 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", getBSShaderPrefix(texprop->type)); + node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->textureSet.empty()) @@ -1910,7 +1902,7 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", getBSShaderPrefix(texprop->type)); + node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->filename.empty()) @@ -1952,7 +1944,7 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", getBSLightingShaderPrefix(texprop->type)); + node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->mTextureSet.empty()) From fdfde836fe58e127521ee5306e4e9c4f6f0c3259 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 12 Feb 2022 01:03:12 +0100 Subject: [PATCH 20/48] Expect recent saves to store the modified value --- apps/openmw/mwmechanics/stat.cpp | 4 ++-- apps/openmw/mwworld/magiceffects.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index eacfca98ae..585808645a 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -21,13 +21,13 @@ namespace MWMechanics void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; - state.mMod = mModifier; + state.mMod = mModifier + mBase; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; - mModifier = state.mMod; + mModifier = state.mMod - mBase; } diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 1138a55238..f52a61af52 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -201,10 +201,10 @@ namespace MWWorld { auto& dynamic = creatureStats.mDynamic[i]; dynamic.mCurrent -= dynamic.mMod - dynamic.mBase; - dynamic.mMod = 0.f; + dynamic.mMod = dynamic.mBase; } for(std::size_t i = 0; i < 4; ++i) - creatureStats.mAiSettings[i].mMod = 0.f; + creatureStats.mAiSettings[i].mMod = creatureStats.mAiSettings[i].mBase; if(npcStats) { for(std::size_t i = 0; i < ESM::Skill::Length; ++i) From 68768517666a90c4a02e8a25b2a2b04ec1128b5d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 12 Feb 2022 11:59:39 +0100 Subject: [PATCH 21/48] Allow `require` to return not a table in Lua --- components/lua/luastate.cpp | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index bda39d270d..13e1421116 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -162,26 +162,27 @@ namespace LuaUtil std::string envName = namePrefix + "[" + path + "]:"; env["print"] = mLua["printGen"](envName); + auto maybeRunLoader = [&hiddenData](const sol::object& package) -> sol::object + { + if (package.is()) + return call(package.as(), hiddenData); + else + return package; + }; sol::table loaded(mLua, sol::create); for (const auto& [key, value] : mCommonPackages) - loaded[key] = value; + loaded[key] = maybeRunLoader(value); for (const auto& [key, value] : packages) - loaded[key] = value; - env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) + loaded[key] = maybeRunLoader(value); + env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) mutable { - sol::table packages = loaded; - sol::object package = packages[packageName]; - if (package == sol::nil) - { - sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS)); - sol::set_environment(env, packageLoader); - package = call(packageLoader, packageName); - if (!package.is()) - throw std::runtime_error("Lua package must return a table."); - packages[packageName] = package; - } - else if (package.is()) - package = packages[packageName] = call(package.as(), hiddenData); + sol::object package = loaded[packageName]; + if (package != sol::nil) + return package; + sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS)); + sol::set_environment(env, packageLoader); + package = call(packageLoader, packageName); + loaded[packageName] = package; return package; }; From c382910c1fe6ef0d5bb301708e93a700b66a78ef Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 12 Feb 2022 13:25:27 +0100 Subject: [PATCH 22/48] Restart all quests with the same name when a quest is restarted --- CHANGELOG.md | 1 + apps/openmw/mwdialogue/journalimp.cpp | 13 +++++++++- apps/openmw/mwdialogue/quest.cpp | 37 +++++++++++---------------- apps/openmw/mwdialogue/quest.hpp | 5 ++-- apps/openmw/mwdialogue/topic.cpp | 5 ++-- apps/openmw/mwdialogue/topic.hpp | 2 +- 6 files changed, 35 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b26d758847..2cc4fee33b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -273,6 +273,7 @@ Bug #6142: Groundcover plugins change cells flags Bug #6276: Deleted groundcover instances are not deleted in game Bug #6294: Game crashes with empty pathgrid + Bug #6606: Quests with multiple IDs cannot always be restarted Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 9f4c8c3689..41b30a95ce 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -93,7 +95,16 @@ namespace MWDialogue StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor); Quest& quest = getQuest (id); - quest.addEntry (entry); // we are doing slicing on purpose here + if(quest.addEntry(entry)) // we are doing slicing on purpose here + { + // Restart all "other" quests with the same name as well + std::string name = quest.getName(); + for(auto& it : mQuests) + { + if(it.second.isFinished() && Misc::StringUtils::ciEqual(it.second.getName(), name)) + it.second.setFinished(false); + } + } // there is no need to show empty entries in journal if (!entry.getText().empty()) diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 16e229ca7f..ce05676965 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,5 +1,7 @@ #include "quest.hpp" +#include + #include #include "../mwworld/esmstore.hpp" @@ -50,42 +52,33 @@ namespace MWDialogue return mFinished; } - void Quest::addEntry (const JournalEntry& entry) + void Quest::setFinished(bool finished) { - int index = -1; + mFinished = finished; + } + bool Quest::addEntry (const JournalEntry& entry) + { const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (entry.mTopic); - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) - if (iter->mId == entry.mInfoId) - { - index = iter->mData.mJournalIndex; - break; - } + auto info = std::find_if(dialogue->mInfo.begin(), dialogue->mInfo.end(), [&](const auto& info) { return info.mId == entry.mInfoId; }); - if (index==-1) + if (info == dialogue->mInfo.end() || info->mData.mJournalIndex == -1) throw std::runtime_error ("unknown journal entry for topic " + mTopic); - for (auto &info : dialogue->mInfo) - { - if (info.mData.mJournalIndex == index - && (info.mQuestStatus == ESM::DialInfo::QS_Finished || info.mQuestStatus == ESM::DialInfo::QS_Restart)) - { - mFinished = (info.mQuestStatus == ESM::DialInfo::QS_Finished); - break; - } - } + if (info->mQuestStatus == ESM::DialInfo::QS_Finished || info->mQuestStatus == ESM::DialInfo::QS_Restart) + mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished; - if (index > mIndex) - mIndex = index; + if (info->mData.mJournalIndex > mIndex) + mIndex = info->mData.mJournalIndex; for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) if (iter->mInfoId==entry.mInfoId) - return; + return info->mQuestStatus == ESM::DialInfo::QS_Restart; mEntries.push_back (entry); // we want slicing here + return info->mQuestStatus == ESM::DialInfo::QS_Restart; } void Quest::write (ESM::QuestState& state) const diff --git a/apps/openmw/mwdialogue/quest.hpp b/apps/openmw/mwdialogue/quest.hpp index 712f94fae4..53b4d02f65 100644 --- a/apps/openmw/mwdialogue/quest.hpp +++ b/apps/openmw/mwdialogue/quest.hpp @@ -33,9 +33,10 @@ namespace MWDialogue ///< Calling this function with a non-existent index will throw an exception. bool isFinished() const; + void setFinished(bool finished); - void addEntry (const JournalEntry& entry) override; - ///< Add entry and adjust index accordingly. + bool addEntry (const JournalEntry& entry) override; + ///< Add entry and adjust index accordingly. Returns true if the quest should be restarted. /// /// \note Redundant entries are ignored, but the index is still adjusted. diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index eb7fbdc1de..9d56028184 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -18,7 +18,7 @@ namespace MWDialogue Topic::~Topic() {} - void Topic::addEntry (const JournalEntry& entry) + bool Topic::addEntry (const JournalEntry& entry) { if (entry.mTopic!=mTopic) throw std::runtime_error ("topic does not match: " + mTopic); @@ -27,10 +27,11 @@ namespace MWDialogue for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it) { if (it->mInfoId == entry.mInfoId) - return; + return false; } mEntries.push_back (entry); // we want slicing here + return false; } void Topic::insertEntry (const ESM::JournalEntry& entry) diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index 72486ef8af..12df484aa7 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -35,7 +35,7 @@ namespace MWDialogue virtual ~Topic(); - virtual void addEntry (const JournalEntry& entry); + virtual bool addEntry (const JournalEntry& entry); ///< Add entry /// /// \note Redundant entries are ignored. From c9c7fb7e49aa65b65005c6e9280b3e8cadf52b25 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 12 Feb 2022 11:52:52 +0100 Subject: [PATCH 23/48] Remove redundant functions from Utf8Encoder interface --- components/esm3/esmreader.cpp | 2 +- components/to_utf8/to_utf8.cpp | 30 ++++++++++++++++++------------ components/to_utf8/to_utf8.hpp | 15 ++++----------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index a10eba2378..667132c60f 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -320,7 +320,7 @@ std::string ESMReader::getString(int size) // Convert to UTF8 and return if (mEncoder) - return mEncoder->getUtf8(ptr, size); + return mEncoder->getUtf8(std::string_view(ptr, size)); return std::string (ptr, size); } diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index f7dc33fcbf..3c4421c605 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -77,12 +77,15 @@ Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): } } -std::string Utf8Encoder::getUtf8(const char* input, size_t size) +std::string Utf8Encoder::getUtf8(std::string_view input) { + if (input.empty()) + return input; + // Double check that the input string stops at some point (it might // contain zero terminators before this, inside its own data, which // is also ok.) - assert(input[size] == 0); + assert(input[input.size()] == 0); // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding @@ -93,19 +96,19 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) // Compute output length, and check for pure ascii input at the same // time. bool ascii; - size_t outlen = getLength(input, ascii); + size_t outlen = getLength(input.data(), ascii); // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input, outlen); + return std::string(input.data(), outlen); // Make sure the output is large enough resize(outlen); char *out = &mOutput[0]; // Translate - while (*input) - copyFromArray(*(input++), out); + for (const char* ptr = input.data(); *ptr;) + copyFromArray(*(ptr++), out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); @@ -118,12 +121,15 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) return std::string(&mOutput[0], outlen); } -std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) +std::string Utf8Encoder::getLegacyEnc(std::string_view input) { + if (input.empty()) + return input; + // Double check that the input string stops at some point (it might // contain zero terminators before this, inside its own data, which // is also ok.) - assert(input[size] == 0); + assert(input[input.size()] == 0); // TODO: The rest of this function is designed for single-character // input encodings only. It also assumes that the input the input @@ -134,19 +140,19 @@ std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) // Compute output length, and check for pure ascii input at the same // time. bool ascii; - size_t outlen = getLength2(input, ascii); + size_t outlen = getLength2(input.data(), ascii); // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input, outlen); + return std::string(input.data(), outlen); // Make sure the output is large enough resize(outlen); char *out = &mOutput[0]; // Translate - while(*input) - copyFromArray2(input, out); + for (const char* ptr = input.data(); *ptr;) + copyFromArray2(ptr, out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index d8c9f09d5d..07960ae3f9 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ToUTF8 { @@ -28,17 +29,9 @@ namespace ToUTF8 Utf8Encoder(FromType sourceEncoding); // Convert to UTF8 from the previously given code page. - std::string getUtf8(const char *input, size_t size); - inline std::string getUtf8(const std::string &str) - { - return getUtf8(str.c_str(), str.size()); - } - - std::string getLegacyEnc(const char *input, size_t size); - inline std::string getLegacyEnc(const std::string &str) - { - return getLegacyEnc(str.c_str(), str.size()); - } + std::string getUtf8(std::string_view input); + + std::string getLegacyEnc(std::string_view input); private: void resize(size_t size); From 4502569660d0f1a8bc0aebb89fdf3cd2c7a80a38 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 12 Feb 2022 18:31:52 +0300 Subject: [PATCH 24/48] Clarify stretch menu background documentation (#5880) --- docs/source/reference/modding/settings/GUI.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index 00f99e47bc..91d2e67d6c 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -70,7 +70,7 @@ stretch menu background Stretch or shrink the main menu screen, loading splash screens, introductory movie, and cut scenes to fill the specified video resolution, distorting their aspect ratio. The Bethesda provided assets have a 4:3 aspect ratio, but other assets are permitted to have other aspect ratios. -If this setting is false, the assets will be centered in their correct aspect ratio, +If this setting is false, the assets will be centered in the mentioned 4:3 aspect ratio, with black bars filling the remainder of the screen. This setting can be configured in the Interface section of Advanced tab of the launcher. From c75e938c46a660253a6dd76c3545f82ce40f8eb9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 12 Feb 2022 12:00:35 +0100 Subject: [PATCH 25/48] Return string_view from Utf8Encoder functions To avoid redundant std::string constructions. --- components/esm3/esmreader.cpp | 2 +- components/esm3/esmwriter.cpp | 4 ++-- components/fontloader/fontloader.cpp | 21 ++++++++++++++------- components/to_utf8/to_utf8.cpp | 14 ++++++-------- components/to_utf8/to_utf8.hpp | 12 ++++++++---- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 667132c60f..165263d6e4 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -320,7 +320,7 @@ std::string ESMReader::getString(int size) // Convert to UTF8 and return if (mEncoder) - return mEncoder->getUtf8(std::string_view(ptr, size)); + return std::string(mEncoder->getUtf8(std::string_view(ptr, size))); return std::string (ptr, size); } diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index f65340f703..d0137c5131 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -193,9 +193,9 @@ namespace ESM else { // Convert to UTF8 and return - std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; + const std::string_view string = mEncoder != nullptr ? mEncoder->getLegacyEnc(data) : data; - write(string.c_str(), string.size()); + write(string.data(), string.size()); } } diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 76f554bec7..36174d0b7e 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -1,6 +1,8 @@ #include "fontloader.hpp" #include +#include +#include #include @@ -26,7 +28,7 @@ namespace { - unsigned long utf8ToUnicode(const std::string& utf8) + unsigned long utf8ToUnicode(std::string_view utf8) { size_t i = 0; unsigned long unicode; @@ -116,16 +118,21 @@ namespace } } - // getUtf8, aka the worst function ever written. - // This includes various hacks for dealing with Morrowind's .fnt files that are *mostly* + // getUnicode includes various hacks for dealing with Morrowind's .fnt files that are *mostly* // in the expected win12XX encoding, but also have randomly swapped characters sometimes. // Looks like the Morrowind developers found standard encodings too boring and threw in some twists for fun. - std::string getUtf8 (unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) + unsigned long getUnicode(unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) { if (encoding == ToUTF8::WINDOWS_1250) // Hack for polish font - return encoder.getUtf8(std::string(1, mapUtf8Char(c))); + { + const std::array str {static_cast(mapUtf8Char(c)), '\0'}; + return utf8ToUnicode(encoder.getUtf8(std::string_view(str.data(), 1))); + } else - return encoder.getUtf8(std::string(1, c)); + { + const std::array str {static_cast(c), '\0'}; + return utf8ToUnicode(encoder.getUtf8(std::string_view(str.data(), 1))); + } } [[noreturn]] void fail (Files::IStreamPtr file, const std::string& fileName, const std::string& message) @@ -355,7 +362,7 @@ namespace Gui float h = data[i].bottom_left.y*height - y1; ToUTF8::Utf8Encoder encoder(mEncoding); - unsigned long unicodeVal = utf8ToUnicode(getUtf8(i, encoder, mEncoding)); + unsigned long unicodeVal = getUnicode(i, encoder, mEncoding); MyGUI::xml::ElementPtr code = codes->createChild("Code"); code->addAttribute("index", unicodeVal); diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 3c4421c605..7fd0e3cd88 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -77,7 +77,7 @@ Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): } } -std::string Utf8Encoder::getUtf8(std::string_view input) +std::string_view Utf8Encoder::getUtf8(std::string_view input) { if (input.empty()) return input; @@ -100,7 +100,7 @@ std::string Utf8Encoder::getUtf8(std::string_view input) // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input.data(), outlen); + return std::string_view(input.data(), outlen); // Make sure the output is large enough resize(outlen); @@ -117,11 +117,10 @@ std::string Utf8Encoder::getUtf8(std::string_view input) assert(mOutput.size() > outlen); assert(mOutput[outlen] == 0); - // Return a string - return std::string(&mOutput[0], outlen); + return std::string_view(mOutput.data(), outlen); } -std::string Utf8Encoder::getLegacyEnc(std::string_view input) +std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) { if (input.empty()) return input; @@ -144,7 +143,7 @@ std::string Utf8Encoder::getLegacyEnc(std::string_view input) // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input.data(), outlen); + return std::string_view(input.data(), outlen); // Make sure the output is large enough resize(outlen); @@ -161,8 +160,7 @@ std::string Utf8Encoder::getLegacyEnc(std::string_view input) assert(mOutput.size() > outlen); assert(mOutput[outlen] == 0); - // Return a string - return std::string(&mOutput[0], outlen); + return std::string_view(mOutput.data(), outlen); } // Make sure the output vector is large enough for 'size' bytes, diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 07960ae3f9..0e9db01e1d 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -28,10 +28,14 @@ namespace ToUTF8 public: Utf8Encoder(FromType sourceEncoding); - // Convert to UTF8 from the previously given code page. - std::string getUtf8(std::string_view input); - - std::string getLegacyEnc(std::string_view input); + /// Convert to UTF8 from the previously given code page. + /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not + /// ASCII-only string. Otherwise returns a view to the input. + std::string_view getUtf8(std::string_view input); + + /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not + /// ASCII-only string. Otherwise returns a view to the input. + std::string_view getLegacyEnc(std::string_view input); private: void resize(size_t size); From 367bdcf0cc5678951b35953d4b8d2722020ab362 Mon Sep 17 00:00:00 2001 From: Matt <3397065-ZehMatt@users.noreply.gitlab.com> Date: Sat, 12 Feb 2022 23:50:41 +0000 Subject: [PATCH 26/48] #6091: Optimize isInCombat --- apps/openmw/mwlua/localscripts.cpp | 13 +- apps/openmw/mwmechanics/actors.cpp | 21 +- apps/openmw/mwmechanics/aisequence.cpp | 258 ++++++++++-------- apps/openmw/mwmechanics/aisequence.hpp | 56 +++- .../mwmechanics/mechanicsmanagerimp.cpp | 8 +- apps/openmw/mwmechanics/spelleffects.cpp | 4 +- 6 files changed, 209 insertions(+), 151 deletions(-) diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 552ce77541..c95eae43a7 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -113,15 +113,12 @@ namespace MWLua { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - std::list>& list = ai.getUnderlyingList(); - for (auto it = list.begin(); it != list.end();) + + ai.erasePackagesIf([&](auto& entry) { - bool keep = LuaUtil::call(callback, *it).get(); - if (keep) - ++it; - else - it = list.erase(it); - } + bool keep = LuaUtil::call(callback, entry).template get(); + return !keep; + }); }; selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2f447e12d6..b6275070e0 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -73,23 +73,20 @@ bool isCommanded(const MWWorld::Ptr& actor) // Check for command effects having ended and remove package if necessary void adjustCommandedActor (const MWWorld::Ptr& actor) { - MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + if (!isCommanded(actor)) + return; - bool hasCommandPackage = false; + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - auto it = stats.getAiSequence().begin(); - for (; it != stats.getAiSequence().end(); ++it) + stats.getAiSequence().erasePackageIf([](auto& entry) { - if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow && - static_cast(it->get())->isCommanded()) + if (entry->getTypeId() == MWMechanics::AiPackageTypeId::Follow && + static_cast(entry.get())->isCommanded()) { - hasCommandPackage = true; - break; + return true; } - } - - if (!isCommanded(actor) && hasCommandPackage) - stats.getAiSequence().erase(it); + return false; + }); } void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 86bc714964..a1f2b5c3e3 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -1,6 +1,7 @@ #include "aisequence.hpp" #include +#include #include #include @@ -29,6 +30,9 @@ void AiSequence::copy (const AiSequence& sequence) // We need to keep an AiWander storage, if present - it has a state machine. // Not sure about another temporary storages sequence.mAiState.copy(mAiState); + + mNumCombatPackages = sequence.mNumCombatPackages; + mNumPursuitPackages = sequence.mNumPursuitPackages; } AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {} @@ -58,6 +62,28 @@ AiSequence::~AiSequence() clear(); } +void AiSequence::onPackageAdded(const AiPackage& package) +{ + if (package.getTypeId() == AiPackageTypeId::Combat) + mNumCombatPackages++; + else if (package.getTypeId() == AiPackageTypeId::Pursue) + mNumPursuitPackages++; + + assert(mNumCombatPackages >= 0); + assert(mNumPursuitPackages >= 0); +} + +void AiSequence::onPackageRemoved(const AiPackage& package) +{ + if (package.getTypeId() == AiPackageTypeId::Combat) + mNumCombatPackages--; + else if (package.getTypeId() == AiPackageTypeId::Pursue) + mNumPursuitPackages--; + + assert(mNumCombatPackages >= 0); + assert(mNumPursuitPackages >= 0); +} + AiPackageTypeId AiSequence::getTypeId() const { if (mPackages.empty()) @@ -87,32 +113,30 @@ bool AiSequence::getCombatTargets(std::vector &targetActors) const return !targetActors.empty(); } -void AiSequence::erase(std::list>::const_iterator package) +AiPackages::iterator AiSequence::erase(AiPackages::iterator package) { // Not sure if manually terminated packages should trigger mDone, probably not? - for(auto it = mPackages.begin(); it != mPackages.end(); ++it) - { - if (package == it) - { - mPackages.erase(it); - return; - } - } - throw std::runtime_error("can't find package to erase"); + auto& ptr = *package; + onPackageRemoved(*ptr); + + return mPackages.erase(package); } bool AiSequence::isInCombat() const { - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) - { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) - return true; - } - return false; + return mNumCombatPackages > 0; +} + +bool AiSequence::isInPursuit() const +{ + return mNumPursuitPackages > 0; } bool AiSequence::isEngagedWithActor() const { + if (!isInCombat()) + return false; + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) @@ -127,16 +151,18 @@ bool AiSequence::isEngagedWithActor() const bool AiSequence::hasPackage(AiPackageTypeId typeId) const { - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + auto it = std::find_if(mPackages.begin(), mPackages.end(), [typeId](const auto& package) { - if ((*it)->getTypeId() == typeId) - return true; - } - return false; + return package->getTypeId() == typeId; + }); + return it != mPackages.end(); } bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const { + if (!isInCombat()) + return false; + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) @@ -148,27 +174,31 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const return false; } -// TODO: use std::list::remove_if for all these methods when we switch to C++20 -void AiSequence::stopCombat() +void AiSequence::removePackagesById(AiPackageTypeId id) { - for(auto it = mPackages.begin(); it != mPackages.end(); ) + for (auto it = mPackages.begin(); it != mPackages.end(); ) { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) + if ((*it)->getTypeId() == id) { - it = mPackages.erase(it); + it = erase(it); } else ++it; } } +void AiSequence::stopCombat() +{ + removePackagesById(AiPackageTypeId::Combat); +} + void AiSequence::stopCombat(const std::vector& targets) { for(auto it = mPackages.begin(); it != mPackages.end(); ) { if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end()) { - it = mPackages.erase(it); + it = erase(it); } else ++it; @@ -177,15 +207,7 @@ void AiSequence::stopCombat(const std::vector& targets) void AiSequence::stopPursuit() { - for(auto it = mPackages.begin(); it != mPackages.end(); ) - { - if ((*it)->getTypeId() == AiPackageTypeId::Pursue) - { - it = mPackages.erase(it); - } - else - ++it; - } + removePackagesById(AiPackageTypeId::Pursue); } bool AiSequence::isPackageDone() const @@ -204,112 +226,117 @@ namespace void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange) { - if(actor != getPlayer()) + if (actor == getPlayer()) { - if (mPackages.empty()) - { - mLastAiPackage = AiPackageTypeId::None; - return; - } + // Players don't use this. + return; + } - auto packageIt = mPackages.begin(); - MWMechanics::AiPackage* package = packageIt->get(); - if (!package->alwaysActive() && outOfRange) - return; + if (mPackages.empty()) + { + mLastAiPackage = AiPackageTypeId::None; + return; + } - auto packageTypeId = package->getTypeId(); - // workaround ai packages not being handled as in the vanilla engine - if (isActualAiPackage(packageTypeId)) - mLastAiPackage = packageTypeId; - // if active package is combat one, choose nearest target - if (packageTypeId == AiPackageTypeId::Combat) - { - auto itActualCombat = mPackages.end(); + auto packageIt = mPackages.begin(); + MWMechanics::AiPackage* package = packageIt->get(); + if (!package->alwaysActive() && outOfRange) + return; + + auto packageTypeId = package->getTypeId(); + // workaround ai packages not being handled as in the vanilla engine + if (isActualAiPackage(packageTypeId)) + mLastAiPackage = packageTypeId; + // if active package is combat one, choose nearest target + if (packageTypeId == AiPackageTypeId::Combat) + { + auto itActualCombat = mPackages.end(); - float nearestDist = std::numeric_limits::max(); - osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); + float nearestDist = std::numeric_limits::max(); + osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); - float bestRating = 0.f; + float bestRating = 0.f; - for (auto it = mPackages.begin(); it != mPackages.end();) - { - if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; + for (auto it = mPackages.begin(); it != mPackages.end();) + { + if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; - MWWorld::Ptr target = (*it)->getTarget(); + MWWorld::Ptr target = (*it)->getTarget(); - // target disappeared (e.g. summoned creatures) - if (target.isEmpty()) - { - it = mPackages.erase(it); - } - else - { - float rating = MWMechanics::getBestActionRating(actor, target); + // target disappeared (e.g. summoned creatures) + if (target.isEmpty()) + { + it = erase(it); + } + else + { + float rating = MWMechanics::getBestActionRating(actor, target); - const ESM::Position &targetPos = target.getRefData().getPosition(); + const ESM::Position &targetPos = target.getRefData().getPosition(); - float distTo = (targetPos.asVec3() - vActorPos).length2(); + float distTo = (targetPos.asVec3() - vActorPos).length2(); - // Small threshold for changing target - if (it == mPackages.begin()) - distTo = std::max(0.f, distTo - 2500.f); + // Small threshold for changing target + if (it == mPackages.begin()) + distTo = std::max(0.f, distTo - 2500.f); - // if a target has higher priority than current target or has same priority but closer - if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating)) - { - nearestDist = distTo; - itActualCombat = it; - bestRating = rating; - } - ++it; + // if a target has higher priority than current target or has same priority but closer + if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating)) + { + nearestDist = distTo; + itActualCombat = it; + bestRating = rating; } + ++it; } + } - assert(!mPackages.empty()); + assert(!mPackages.empty()); - if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) - { - assert(itActualCombat != mPackages.end()); - // move combat package with nearest target to the front - mPackages.splice(mPackages.begin(), mPackages, itActualCombat); - } - - packageIt = mPackages.begin(); - package = packageIt->get(); - packageTypeId = package->getTypeId(); + if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + { + assert(itActualCombat != mPackages.end()); + // move combat package with nearest target to the front + std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat)); } - try + packageIt = mPackages.begin(); + package = packageIt->get(); + packageTypeId = package->getTypeId(); + } + + try + { + if (package->execute(actor, characterController, mAiState, duration)) { - if (package->execute(actor, characterController, mAiState, duration)) - { - // Put repeating noncombat AI packages on the end of the stack so they can be used again - if (isActualAiPackage(packageTypeId) && package->getRepeat()) - { - package->reset(); - mPackages.push_back(package->clone()); - } - // To account for the rare case where AiPackage::execute() queued another AI package - // (e.g. AiPursue executing a dialogue script that uses startCombat) - mPackages.erase(packageIt); - if (isActualAiPackage(packageTypeId)) - mDone = true; - } - else + // Put repeating noncombat AI packages on the end of the stack so they can be used again + if (isActualAiPackage(packageTypeId) && package->getRepeat()) { - mDone = false; + package->reset(); + mPackages.push_back(package->clone()); } + // To account for the rare case where AiPackage::execute() queued another AI package + // (e.g. AiPursue executing a dialogue script that uses startCombat) + erase(packageIt); + if (isActualAiPackage(packageTypeId)) + mDone = true; } - catch (std::exception& e) + else { - Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); + mDone = false; } } + catch (std::exception& e) + { + Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); + } } void AiSequence::clear() { mPackages.clear(); + mNumCombatPackages = 0; + mNumPursuitPackages = 0; } void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther) @@ -353,7 +380,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo { if((*it)->canCancel()) { - it = mPackages.erase(it); + it = erase(it); } else ++it; @@ -373,11 +400,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo if((*it)->getPriority() <= package.getPriority()) { + onPackageAdded(package); mPackages.insert(it, package.clone()); return; } } + onPackageAdded(package); mPackages.push_back(package.clone()); // Make sure that temporary storage is empty @@ -435,6 +464,8 @@ void AiSequence::fill(const ESM::AIPackageList &list) ESM::AITarget data = esmPackage.mTarget; package = std::make_unique(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } + + onPackageAdded(*package); mPackages.push_back(std::move(package)); } } @@ -504,6 +535,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) if (!package.get()) continue; + onPackageAdded(*package); mPackages.push_back(std::move(package)); } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 6cbfcf045d..0d23207b63 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -1,8 +1,9 @@ #ifndef GAME_MWMECHANICS_AISEQUENCE_H #define GAME_MWMECHANICS_AISEQUENCE_H -#include #include +#include +#include #include "aistate.hpp" #include "aipackagetypeid.hpp" @@ -22,8 +23,6 @@ namespace ESM } } - - namespace MWMechanics { class AiPackage; @@ -33,15 +32,20 @@ namespace MWMechanics struct AiTemporaryBase; typedef DerivedClassStorage AiState; + using AiPackages = std::vector>; + /// \brief Sequence of AI-packages for a single actor /** The top-most AI package is run each frame. When completed, it is removed from the stack. **/ class AiSequence { ///AiPackages to run though - std::list> mPackages; + AiPackages mPackages; ///Finished with top AIPackage, set for one frame - bool mDone; + bool mDone{}; + + int mNumCombatPackages{}; + int mNumPursuitPackages{}; ///Copy AiSequence void copy (const AiSequence& sequence); @@ -50,6 +54,11 @@ namespace MWMechanics AiPackageTypeId mLastAiPackage; AiState mAiState; + void onPackageAdded(const AiPackage& package); + void onPackageRemoved(const AiPackage& package); + + AiPackages::iterator erase(AiPackages::iterator package); + public: ///Default constructor AiSequence(); @@ -63,12 +72,31 @@ namespace MWMechanics virtual ~AiSequence(); /// Iterator may be invalidated by any function calls other than begin() or end(). - std::list>::const_iterator begin() const { return mPackages.begin(); } - std::list>::const_iterator end() const { return mPackages.end(); } - - void erase(std::list>::const_iterator package); - - std::list>& getUnderlyingList() { return mPackages; } + AiPackages::const_iterator begin() const { return mPackages.begin(); } + AiPackages::const_iterator end() const { return mPackages.end(); } + + /// Removes all packages controlled by the predicate. + template + void erasePackagesIf(const F&& pred) + { + mPackages.erase(std::remove_if(mPackages.begin(), mPackages.end(), [&](auto& entry) + { + const bool doRemove = pred(entry); + if (doRemove) + onPackageRemoved(*entry); + return doRemove; + }), mPackages.end()); + } + + /// Removes a single package controlled by the predicate. + template + void erasePackageIf(const F&& pred) + { + auto it = std::find_if(mPackages.begin(), mPackages.end(), pred); + if (it == mPackages.end()) + return; + erase(it); + } /// Returns currently executing AiPackage type /** \see enum class AiPackageTypeId **/ @@ -89,6 +117,12 @@ namespace MWMechanics /// Is there any combat package? bool isInCombat () const; + /// Is there any pursuit package. + bool isInPursuit() const; + + /// Removes all packages using the specified id. + void removePackagesById(AiPackageTypeId id); + /// Are we in combat with any other actor, who's also engaging us? bool isEngagedWithActor () const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0816445271..dc349a1e6b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1318,7 +1318,7 @@ namespace MWMechanics // once the bounty has been paid. actor.getClass().getNpcStats(actor).setCrimeId(id); - if (!actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit()) { actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); } @@ -1396,7 +1396,7 @@ namespace MWMechanics { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. - if (!victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit()) startCombat(victim, player); // Set the crime ID, which we will use to calm down participants @@ -1442,7 +1442,7 @@ namespace MWMechanics { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. - if (!target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!target.getClass().getCreatureStats(target).getAiSequence().isInPursuit()) { // If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player, // he will attack the player only if we will force him (e.g. via StartCombat console command) @@ -1467,7 +1467,7 @@ namespace MWMechanics const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) && !isAggressive(target, attacker) && !seq.isEngagedWithActor() - && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue); + && !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit(); } void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index d0584791a5..3ae6de23da 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -980,12 +980,10 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) { auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); - auto it = std::find_if(seq.begin(), seq.end(), [&](const auto& package) + seq.erasePackageIf([&](const auto& package) { return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast(package.get())->isCommanded(); }); - if(it != seq.end()) - seq.erase(it); } break; case ESM::MagicEffect::ExtraSpell: From 2d4d28fb8e9765cf2a5724a4ed60f26d45c0d095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Sun, 13 Feb 2022 15:30:21 +0000 Subject: [PATCH 27/48] Documentation how to get an Animated creature in OpenMW --- .../reference/modding/custom-models/index.rst | 2 +- ...pipeline-blender-collada-animated-creature | 221 ++++++++++++++++++ 2 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature diff --git a/docs/source/reference/modding/custom-models/index.rst b/docs/source/reference/modding/custom-models/index.rst index 8b6c5d87f7..b2d92032c5 100644 --- a/docs/source/reference/modding/custom-models/index.rst +++ b/docs/source/reference/modding/custom-models/index.rst @@ -18,7 +18,7 @@ Below is a quick overview of supported formats, followed by separate articles wi :caption: Table of Contents :maxdepth: 1 - pipeline-blender-collada pipeline-blender-collada-static-models + pipeline-blender-collada-animated-creature pipeline-blender-osgnative pipeline-blender-nif diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature b/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature new file mode 100644 index 0000000000..b759bf9a02 --- /dev/null +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature @@ -0,0 +1,221 @@ +############################# +Animated Creature via COLLADA +############################# + +This tutorial shows how to get an animated creature from Blender to OpenMW using +the COLLADA format. It does not cover using Blender itself, as there are many +better resources for that. The focus is solely on the animation pipeline and its +specific requirements. Most of them are related to how the model, rig, and +animations are set up in Blender. + +.. note:: + This tutorial builds upon the :doc:`pipeline-blender-collada-static-models` tutorial. All fundamentals of exporting static objects apply to animated ones as well. + +Requirements +************ + +To use the Blender to OpenMW pipeline via COLLADA, you will need the following. + +* `OpenMW 0.48 `_ or later +* `Blender 2.83 `_ or later. Latest confirmed, working version is Blender 3.0 +* `OpenMW COLLADA Exporter `_ +* An animated model you would like to export. In our case the flamboyant Land Racer! + +The Land Racer +************** + +The creature, and its revelant files, are available from the `Example Suite repository `_. +This should be useful for further study of how to set things up in case this +tutorial is not clear on any particular thing. + +* ``data/meshes/land_racer.dae`` – exported model +* ``data/meshes/land_racer.txt`` – animation textkeys +* ``data/textures/land_racer.dds`` – diffuse texture +* ``data/textures/land_racer_n.dds`` - normal map +* ``source_assets/land_racer.blend`` – source file configured as this tutorial specifies + +Model +***** + +The model needs to be a child of the rig and have an Armature modifier asigned. +Bone weights are limited to a maximum of 4 bones per vertex. The model needs to +have default location, rotation, and scale. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-in-blender.jpg + :align: center + + +Collision Shape +*************** + +Collision is set up with an empty named ``Collision`` or ``collision`` with a +single child mesh. OpenMW will use the bounding box of this mesh for physics +collision shape so a simple, cuboid mesh is enough. If no collision shape is +defined, the bounding box of the animated model will be used instead. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-collision-shape.jpg + :align: center + + +Armature / Rig +************** + +.. note:: + Only a single rig object should be included in the exported file. Exporting multiple rigs is not reliably supported and can lead to errors. + +Root +---- + +The rig needs to be structured in a specific way. There should be a single top +bone in the rig’s hierarchy, the root bone named ``Bip01``. The name is +required so OpenMW recognizes and uses it for root movement. For this same +reason, the bone should be by default aligned with the world. The root bone +needs to have its ``Deform`` flag **enabled**. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-root-bone.jpg + :align: center + + +Deform Bones +------------ + +Below the root bone, the bones are divided into two branches. One branch +contains the deform bones which get included in the final exported file. These +are otherwise not animated directly but inherit motion from other bones via +constraints. They have their ``Deform`` flag **enabled**. For creatures, the +deform bones can be named as you desire and don’t need to follow the naming +convention used for NPC and player models. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-rig-hierarchy.jpg + :align: center + +Control Bones +------------- + +The other branch holds control and helper bones that enable comfortable +animation in Blender, but are neither required nor included in the exported +file. They have their ``Deform`` flag **disabled**. How these bones are +structured is a big separate topic on its own that this tutorial does not cover, +but you can study the provided source file. + + +Animations +********** + +A creature in OpenMW is expected to have a set of animations to display its +various actions. These animations are recognized and used by their name. + +.. list-table:: + :widths: 25 25 50 + :header-rows: 1 + + * - Animation name + - Possible variations + - Purpose + * - attack1 + - attack2, attack3 + - The creature performs an attack + * - death1 + - death2 - death5 + - The creature dies while upright + * - hit1 + - hit2 - hit5 + - The creature is hit in combat + * - idle + - idle2 - idle9 + - Flavour animations when the creature does nothing in particular + * - knockout + - / + - When creature's fatigue goes below 0 and it staggers to the ground + * - deathknockout + - / + - The creature dies while knocked out + * - knockdown + - / + - When the creatures receives a heavy hit in combat or lands from a considerable height + * - deathknockdown + - / + - The creature dies while knocked down + * - runforward + - / + - Moving forward fast + * - walkforward + - / + - Moving forward at regular speed + +Animating in Blender is done at 30 fps. Specific to how OpenMW works, each +exported animation needs to take a unique range on the timeline. To achieve +this, actions are placed as strips in the NLA editor with an obligatory one +frame gap between them. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-nla-strips.jpg + :align: center + +NLA strips affect the exported result based on their scale, name, frame range, +repetition, or any other factor affecting the end animation result. It's +*What you see is what you get* principle. + +Root movement is required for animations such as ``walkforward`` and +``runforward`` and is likely to work for other animations if needed. +Root movement works only when the root bone is named ``Bip01``. + +Textkeys +******** + +The exported COLLADA file requires a corresponding textkeys file for OpenMW to +properly read the animations. Textkeys is a ``.txt`` file containing animation +definitions and events. At a minimum it needs to include at least animation +``start`` and ``stop`` values in a format as shown in this example. + +.. code:: + + idle: start 0.033333 + idle: stop 2.033333 + walkforward: start 2.066667 + walkforward: stop 3.666667 + runforward: start 3.7 + runforward: stop 4.433333 + attack1: start 4.466667 + attack1: stop 5.433333 + ... + +The textkeys file is placed in the same folder as the model and matches the model's name. + +* ``meshes/land_racer.dae`` +* ``meshes/land_racer.txt`` + +While it's possible to write it by hand, OpenMW's Collada Exporter offers a +convenient option to export a textkeys file based on Blender's timeline markers +(not to be confused with pose markers which are contained per action). What you +need to do is create properly named timeline markers for each animation and +enable the ``Export Textkeys`` option in the exporter. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-textkey-markers.jpg + :align: center + +In the example of ``walkforward`` the timeline markers should be named +``walkforward: start`` and ``walkforward: stop``. + + +Exporter Settings +***************** + +For animated models, use the following exporter settings. Before export, select +all objects you wish to include in the exported file and have the ``Selected +Objects`` option enabled. Without this, the exporter could fail. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-exporter-settings.jpg + :align: center + + +Getting the Model In-game +************************* + +Once the Land Racer is exported, both of its ``.dae`` and ``.txt`` files need to +be placed in the correct folder where OpenMW will read it. Afterwards in +OpenMW-CS, it should be visible in the Assets->Meshes table and can be assigned +to the ``Model/Animation`` field of a creature. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-in-openmwcs.jpg + :align: center + From 7884a0102632694b524a19ebe21d3e1438742907 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 13 Feb 2022 16:38:49 +0100 Subject: [PATCH 28/48] Add tests for Utf8Encoder --- apps/openmw_test_suite/CMakeLists.txt | 6 +- .../toutf8/data}/french-utf8.txt | 0 .../toutf8/data}/french-win1252.txt | 0 .../toutf8/data}/russian-utf8.txt | 0 .../toutf8/data}/russian-win1251.txt | 0 apps/openmw_test_suite/toutf8/toutf8.cpp | 139 ++++++++++++++++++ components/to_utf8/tests/.gitignore | 1 - .../to_utf8/tests/output/to_utf8_test.out | 4 - components/to_utf8/tests/test.sh | 18 --- components/to_utf8/tests/to_utf8_test.cpp | 59 -------- components/to_utf8/to_utf8.cpp | 139 +++++++++--------- components/to_utf8/to_utf8.hpp | 10 +- 12 files changed, 219 insertions(+), 157 deletions(-) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/french-utf8.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/french-win1252.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/russian-utf8.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/russian-win1251.txt (100%) create mode 100644 apps/openmw_test_suite/toutf8/toutf8.cpp delete mode 100644 components/to_utf8/tests/.gitignore delete mode 100644 components/to_utf8/tests/output/to_utf8_test.out delete mode 100755 components/to_utf8/tests/test.sh delete mode 100644 components/to_utf8/tests/to_utf8_test.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 2ee34186d8..16d820c4f9 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -70,6 +70,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) esmloader/esmdata.cpp files/hash.cpp + + toutf8/toutf8.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) @@ -93,6 +95,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) EXPECTED_MD5 bf3691034a38611534c74c3b89a7d2c3 ) - target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data") + target_compile_definitions(openmw_test_suite + PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data" + OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") endif() diff --git a/components/to_utf8/tests/test_data/french-utf8.txt b/apps/openmw_test_suite/toutf8/data/french-utf8.txt similarity index 100% rename from components/to_utf8/tests/test_data/french-utf8.txt rename to apps/openmw_test_suite/toutf8/data/french-utf8.txt diff --git a/components/to_utf8/tests/test_data/french-win1252.txt b/apps/openmw_test_suite/toutf8/data/french-win1252.txt similarity index 100% rename from components/to_utf8/tests/test_data/french-win1252.txt rename to apps/openmw_test_suite/toutf8/data/french-win1252.txt diff --git a/components/to_utf8/tests/test_data/russian-utf8.txt b/apps/openmw_test_suite/toutf8/data/russian-utf8.txt similarity index 100% rename from components/to_utf8/tests/test_data/russian-utf8.txt rename to apps/openmw_test_suite/toutf8/data/russian-utf8.txt diff --git a/components/to_utf8/tests/test_data/russian-win1251.txt b/apps/openmw_test_suite/toutf8/data/russian-win1251.txt similarity index 100% rename from components/to_utf8/tests/test_data/russian-win1251.txt rename to apps/openmw_test_suite/toutf8/data/russian-win1251.txt diff --git a/apps/openmw_test_suite/toutf8/toutf8.cpp b/apps/openmw_test_suite/toutf8/toutf8.cpp new file mode 100644 index 0000000000..bad4f34fd5 --- /dev/null +++ b/apps/openmw_test_suite/toutf8/toutf8.cpp @@ -0,0 +1,139 @@ +#include + +#include + +#include + +#ifndef OPENMW_TEST_SUITE_SOURCE_DIR +#define OPENMW_TEST_SUITE_SOURCE_DIR "" +#endif + +namespace +{ + using namespace testing; + using namespace ToUTF8; + + struct Params + { + FromType mLegacyEncoding; + std::string mLegacyEncodingFileName; + std::string mUtf8FileName; + }; + + std::string readContent(const std::string& fileName) + { + std::ifstream file; + file.exceptions(std::ios::failbit | std::ios::badbit); + file.open(std::string(OPENMW_TEST_SUITE_SOURCE_DIR) + "/toutf8/data/" + fileName); + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); + } + + struct Utf8EncoderTest : TestWithParam {}; + + TEST(Utf8EncoderTest, getUtf8ShouldReturnEmptyAsIs) + { + Utf8Encoder encoder(FromType::CP437); + EXPECT_EQ(encoder.getUtf8(std::string_view()), std::string_view()); + } + + TEST(Utf8EncoderTest, getUtf8ShouldReturnAsciiOnlyAsIs) + { + std::string input; + for (int c = 1; c <= std::numeric_limits::max(); ++c) + input.push_back(c); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getUtf8(input); + EXPECT_EQ(result.data(), input.data()); + EXPECT_EQ(result.size(), input.size()); + } + + TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilZero) + { + const std::string input("a\0b"); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getUtf8(input); + EXPECT_EQ(result, "a"); + } + + TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForAscii) + { + const std::string input("abc"); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2)); + EXPECT_EQ(result, "ab"); + } + + TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForNonAscii) + { + const std::string input("a\x92" "b"); + Utf8Encoder encoder(FromType::WINDOWS_1252); + const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2)); + EXPECT_EQ(result, "a\xE2\x80\x99"); + } + + TEST_P(Utf8EncoderTest, getUtf8ShouldConvertFromLegacyEncodingToUtf8) + { + const std::string input(readContent(GetParam().mLegacyEncodingFileName)); + const std::string expected(readContent(GetParam().mUtf8FileName)); + Utf8Encoder encoder(GetParam().mLegacyEncoding); + const std::string_view result = encoder.getUtf8(input); + EXPECT_EQ(result, expected); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldReturnEmptyAsIs) + { + Utf8Encoder encoder(FromType::CP437); + EXPECT_EQ(encoder.getLegacyEnc(std::string_view()), std::string_view()); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldReturnAsciiOnlyAsIs) + { + std::string input; + for (int c = 1; c <= std::numeric_limits::max(); ++c) + input.push_back(c); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getLegacyEnc(input); + EXPECT_EQ(result.data(), input.data()); + EXPECT_EQ(result.size(), input.size()); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilZero) + { + const std::string input("a\0b"); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getLegacyEnc(input); + EXPECT_EQ(result, "a"); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilEndOfInputForAscii) + { + const std::string input("abc"); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 2)); + EXPECT_EQ(result, "ab"); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldStripIncompleteCharacters) + { + const std::string input("a\xc3\xa2\xe2\x80\x99"); + Utf8Encoder encoder(FromType::WINDOWS_1252); + const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 5)); + EXPECT_EQ(result, "a\xe2"); + } + + TEST_P(Utf8EncoderTest, getLegacyEncShouldConvertFromUtf8ToLegacyEncoding) + { + const std::string input(readContent(GetParam().mUtf8FileName)); + const std::string expected(readContent(GetParam().mLegacyEncodingFileName)); + Utf8Encoder encoder(GetParam().mLegacyEncoding); + const std::string_view result = encoder.getLegacyEnc(input); + EXPECT_EQ(result, expected); + } + + INSTANTIATE_TEST_SUITE_P(Files, Utf8EncoderTest, Values( + Params {ToUTF8::WINDOWS_1251, "russian-win1251.txt", "russian-utf8.txt"}, + Params {ToUTF8::WINDOWS_1252, "french-win1252.txt", "french-utf8.txt"} + )); +} diff --git a/components/to_utf8/tests/.gitignore b/components/to_utf8/tests/.gitignore deleted file mode 100644 index 8144904045..0000000000 --- a/components/to_utf8/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_test diff --git a/components/to_utf8/tests/output/to_utf8_test.out b/components/to_utf8/tests/output/to_utf8_test.out deleted file mode 100644 index dcb32359ab..0000000000 --- a/components/to_utf8/tests/output/to_utf8_test.out +++ /dev/null @@ -1,4 +0,0 @@ -original: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? -converted: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? -original: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. -converted: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. diff --git a/components/to_utf8/tests/test.sh b/components/to_utf8/tests/test.sh deleted file mode 100755 index 2d07708adc..0000000000 --- a/components/to_utf8/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/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 diff --git a/components/to_utf8/tests/to_utf8_test.cpp b/components/to_utf8/tests/to_utf8_test.cpp deleted file mode 100644 index 3fcddd1581..0000000000 --- a/components/to_utf8/tests/to_utf8_test.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include -#include -#include - -#include "../to_utf8.hpp" - -std::string getFirstLine(const std::string &filename); -void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, - const std::string &utf8File); - -/// Test character encoding conversion to and from UTF-8 -void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, - const std::string &utf8File) -{ - // get some test data - std::string legacyEncLine = getFirstLine(legacyEncFile); - std::string utf8Line = getFirstLine(utf8File); - - // create an encoder for specified character encoding - ToUTF8::Utf8Encoder encoder (encoding); - - // convert text to UTF-8 - std::string convertedUtf8Line = encoder.getUtf8(legacyEncLine); - - std::cout << "original: " << utf8Line << std::endl; - std::cout << "converted: " << convertedUtf8Line << std::endl; - - // check correctness - assert(convertedUtf8Line == utf8Line); - - // convert UTF-8 text to legacy encoding - std::string convertedLegacyEncLine = encoder.getLegacyEnc(utf8Line); - // check correctness - assert(convertedLegacyEncLine == legacyEncLine); -} - -std::string getFirstLine(const std::string &filename) -{ - std::string line; - std::ifstream text (filename.c_str()); - - if (!text.is_open()) - { - throw std::runtime_error("Unable to open file " + filename); - } - - std::getline(text, line); - text.close(); - - return line; -} - -int main() -{ - testEncoder(ToUTF8::WINDOWS_1251, "test_data/russian-win1251.txt", "test_data/russian-utf8.txt"); - testEncoder(ToUTF8::WINDOWS_1252, "test_data/french-win1252.txt", "test_data/french-utf8.txt"); - return 0; -} diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 7fd0e3cd88..1f0a81ad10 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -44,6 +45,14 @@ using namespace ToUTF8; +namespace +{ + std::string_view::iterator skipAscii(std::string_view input) + { + return std::find_if(input.begin(), input.end(), [] (unsigned char v) { return v == 0 || v >= 128; }); + } +} + Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): mOutput(50*1024) { @@ -82,11 +91,6 @@ std::string_view Utf8Encoder::getUtf8(std::string_view input) if (input.empty()) return input; - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - assert(input[input.size()] == 0); - // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding // shares its first 128 values (0-127) with ASCII. There are no plans @@ -95,8 +99,7 @@ std::string_view Utf8Encoder::getUtf8(std::string_view input) // Compute output length, and check for pure ascii input at the same // time. - bool ascii; - size_t outlen = getLength(input.data(), ascii); + const auto [outlen, ascii] = getLength(input); // If we're pure ascii, then don't bother converting anything. if(ascii) @@ -107,8 +110,8 @@ std::string_view Utf8Encoder::getUtf8(std::string_view input) char *out = &mOutput[0]; // Translate - for (const char* ptr = input.data(); *ptr;) - copyFromArray(*(ptr++), out); + for (auto it = input.begin(); it != input.end() && *it != 0; ++it) + copyFromArray(*it, out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); @@ -125,11 +128,6 @@ std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) if (input.empty()) return input; - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - assert(input[input.size()] == 0); - // TODO: The rest of this function is designed for single-character // input encodings only. It also assumes that the input the input // encoding shares its first 128 values (0-127) with ASCII. These @@ -138,8 +136,7 @@ std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) // Compute output length, and check for pure ascii input at the same // time. - bool ascii; - size_t outlen = getLength2(input.data(), ascii); + const auto [outlen, ascii] = getLengthLegacyEnc(input); // If we're pure ascii, then don't bother converting anything. if(ascii) @@ -150,8 +147,8 @@ std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) char *out = &mOutput[0]; // Translate - for (const char* ptr = input.data(); *ptr;) - copyFromArray2(ptr, out); + for (auto it = input.begin(); it != input.end() && *it != 0;) + copyFromArrayLegacyEnc(it, input.end(), out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); @@ -186,34 +183,30 @@ void Utf8Encoder::resize(size_t size) is the case, then the ascii parameter is set to true, and the caller can optimize for this case. */ -size_t Utf8Encoder::getLength(const char* input, bool &ascii) const +std::pair Utf8Encoder::getLength(std::string_view input) const { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost // always the entire string.) - while (inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. - if (inp) + if (it == input.end() || *it == 0) + return {it - input.begin(), true}; + + std::size_t len = it - input.begin(); + + do { - ascii = false; - while (inp) - { - // Find the translated length of this character in the - // lookup table. - len += translationArray[inp*6]; - inp = *(++ptr); - } + // Find the translated length of this character in the + // lookup table. + len += translationArray[static_cast(*it) * 6]; + ++it; } - return len; + while (it != input.end() && *it != 0); + + return {len, false}; } // Translate one character 'ch' using the translation array 'arr', and @@ -233,51 +226,52 @@ void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const out += len; } -size_t Utf8Encoder::getLength2(const char* input, bool &ascii) const +std::pair Utf8Encoder::getLengthLegacyEnc(std::string_view input) const { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost // always the entire string.) - while (inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. - if (inp) + if (it == input.end() || *it == 0) + return {it - input.begin(), true}; + + std::size_t len = it - input.begin(); + std::size_t symbolLen = 0; + + do { - ascii = false; - while(inp) + symbolLen += 1; + // Find the translated length of this character in the + // lookup table. + switch (static_cast(*it)) { - len += 1; - // Find the translated length of this character in the - // lookup table. - switch(inp) - { - case 0xe2: len -= 2; break; - case 0xc2: - case 0xcb: - case 0xc4: - case 0xc6: - case 0xc3: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xc5: len -= 1; break; - } - - inp = *(++ptr); + case 0xe2: symbolLen -= 2; break; + case 0xc2: + case 0xcb: + case 0xc4: + case 0xc6: + case 0xc3: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xc5: symbolLen -= 1; break; + default: + len += symbolLen; + symbolLen = 0; + break; } + + ++it; } - return len; + while (it != input.end() && *it != 0); + + return {len, false}; } -void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const +void Utf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const { unsigned char ch = *(chp++); // Optimize for ASCII values @@ -308,10 +302,17 @@ void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const return; } + if (chp == end) + return; + unsigned char ch2 = *(chp++); unsigned char ch3 = '\0'; if (len == 3) + { + if (chp == end) + return; ch3 = *(chp++); + } for (int i = 128; i < 256; i++) { diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 0e9db01e1d..794c9148e5 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -38,11 +38,11 @@ namespace ToUTF8 std::string_view getLegacyEnc(std::string_view input); private: - void resize(size_t size); - size_t getLength(const char* input, bool &ascii) const; - void copyFromArray(unsigned char chp, char* &out) const; - size_t getLength2(const char* input, bool &ascii) const; - void copyFromArray2(const char*& chp, char* &out) const; + inline void resize(std::size_t size); + inline std::pair getLength(std::string_view input) const; + inline void copyFromArray(unsigned char chp, char* &out) const; + inline std::pair getLengthLegacyEnc(std::string_view input) const; + inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; std::vector mOutput; const signed char* translationArray; From b43eb29465014eec2a7a8e195925a025392fc346 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 13 Feb 2022 20:13:14 +0100 Subject: [PATCH 29/48] Log duration of writing save game file --- apps/openmw/mwstate/statemanagerimp.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 3b459ec2ce..634dcdab53 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -188,6 +188,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot try { + const auto start = std::chrono::steady_clock::now(); + if (!character) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); @@ -302,6 +304,11 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot Settings::Manager::setString ("character", "Saves", slot->mPath.parent_path().filename().string()); + + const auto finish = std::chrono::steady_clock::now(); + + Log(Debug::Info) << '\'' << description << "' is saved in " + << std::chrono::duration_cast>(finish - start).count() << "ms"; } catch (const std::exception& e) { From a4d7b7251122d1b31194ffb69053ef5343c71c7c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 13 Feb 2022 20:45:05 +0100 Subject: [PATCH 30/48] Write png image of the global map for save asynchronously Write global map to the save file last to give more time for async job to finish. --- apps/openmw/mwbase/windowmanager.hpp | 2 + apps/openmw/mwgui/mapwindow.cpp | 8 +++- apps/openmw/mwgui/mapwindow.hpp | 4 +- apps/openmw/mwgui/windowmanagerimp.cpp | 5 +++ apps/openmw/mwgui/windowmanagerimp.hpp | 2 + apps/openmw/mwrender/globalmap.cpp | 59 +++++++++++++++++++------ apps/openmw/mwrender/globalmap.hpp | 5 +++ apps/openmw/mwstate/statemanagerimp.cpp | 8 ++-- 8 files changed, 74 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 3044e13e9b..eeb2d6a56b 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -358,6 +358,8 @@ namespace MWBase virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0; virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0; + + virtual void asyncPrepareSaveMap() = 0; }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 2aa1b51950..b5f281d333 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -759,7 +759,7 @@ namespace MWGui , mGlobal(Settings::Manager::getBool("global", "Map")) , mEventBoxGlobal(nullptr) , mEventBoxLocal(nullptr) - , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue)) + , mGlobalMapRender(std::make_unique(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() { static bool registered = false; @@ -1028,7 +1028,6 @@ namespace MWGui MapWindow::~MapWindow() { - delete mGlobalMapRender; } void MapWindow::setCellName(const std::string& cellName) @@ -1357,6 +1356,11 @@ namespace MWGui marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } + void MapWindow::asyncPrepareSaveMap() + { + mGlobalMapRender->asyncWritePng(); + } + // ------------------------------------------------------------------- EditNoteDialog::EditNoteDialog() diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 9f6ea1339d..c50a92dac3 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -260,6 +260,8 @@ namespace MWGui void write (ESM::ESMWriter& writer, Loading::Listener& progress); void readRecord (ESM::ESMReader& reader, uint32_t type); + void asyncPrepareSaveMap(); + private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); @@ -304,7 +306,7 @@ namespace MWGui MyGUI::Button* mEventBoxLocal; float mGlobalMapZoom = 1.0f; - MWRender::GlobalMap* mGlobalMapRender; + std::unique_ptr mGlobalMapRender; struct MapMarkerType { diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index c2f7785da3..04e48fb280 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2263,4 +2263,9 @@ namespace MWGui for(auto* window : mWindows) window->onDeleteCustomData(ptr); } + + void WindowManager::asyncPrepareSaveMap() + { + mMap->asyncPrepareSaveMap(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index ef19329cdf..11d10ab45e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -392,6 +392,8 @@ namespace MWGui void onDeleteCustomData(const MWWorld::Ptr& ptr) override; void forceLootMode(const MWWorld::Ptr& ptr) override; + void asyncPrepareSaveMap() override; + private: unsigned int mOldUpdateMask; unsigned int mOldCullMask; diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 5da79ec037..3d0a066451 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -93,6 +93,26 @@ namespace MWRender::GlobalMap* mParent; }; + std::vector writePng(const osg::Image& overlayImage) + { + std::ostringstream ostream; + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!readerwriter) + { + Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found"; + return std::vector(); + } + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(overlayImage, ostream); + if (!result.success()) + { + Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status(); + return std::vector(); + } + + std::string data = ostream.str(); + return std::vector(data.begin(), data.end()); + } } namespace MWRender @@ -221,6 +241,20 @@ namespace MWRender osg::ref_ptr mOverlayTexture; }; + struct GlobalMap::WritePng final : public SceneUtil::WorkItem + { + osg::ref_ptr mOverlayImage; + std::vector mImageData; + + explicit WritePng(osg::ref_ptr overlayImage) + : mOverlayImage(std::move(overlayImage)) {} + + void doWork() override + { + mImageData = writePng(*mOverlayImage); + } + }; + GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue) : mRoot(root) , mWorkQueue(workQueue) @@ -400,23 +434,15 @@ namespace MWRender map.mBounds.mMinY = mMinY; map.mBounds.mMaxY = mMaxY; - std::ostringstream ostream; - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); - if (!readerwriter) - { - Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found"; - return; - } - - osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream); - if (!result.success()) + if (mWritePng != nullptr) { - Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status(); + mWritePng->waitTillDone(); + map.mImageData = std::move(mWritePng->mImageData); + mWritePng = nullptr; return; } - std::string data = ostream.str(); - map.mImageData = std::vector(data.begin(), data.end()); + map.mImageData = writePng(*mOverlayImage); } struct Box @@ -606,4 +632,11 @@ namespace MWRender cam->removeChildren(0, cam->getNumChildren()); mRoot->removeChild(cam); } + + void GlobalMap::asyncWritePng() + { + // Use deep copy to avoid any sychronization + mWritePng = new WritePng(new osg::Image(*mOverlayImage, osg::CopyOp::DEEP_COPY_ALL)); + mWorkQueue->addWorkItem(mWritePng, /*front=*/true); + } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index fd8a8d1016..28531f14df 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -72,7 +72,11 @@ namespace MWRender void ensureLoaded(); + void asyncWritePng(); + private: + struct WritePng; + /** * Request rendering a 2d quad onto mOverlayTexture. * x, y, width and height are the destination coordinates (top-left coordinate origin) @@ -121,6 +125,7 @@ namespace MWRender osg::ref_ptr mWorkQueue; osg::ref_ptr mWorkItem; + osg::ref_ptr mWritePng; int mWidth; int mHeight; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 634dcdab53..c8cd53a331 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -190,6 +190,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot { const auto start = std::chrono::steady_clock::now(); + MWBase::Environment::get().getWindowManager()->asyncPrepareSaveMap(); + if (!character) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); @@ -257,9 +259,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot +MWBase::Environment::get().getWorld()->countSavedGameRecords() +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() - +MWBase::Environment::get().getWindowManager()->countSavedGameRecords() +MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords() - +MWBase::Environment::get().getInputManager()->countSavedGameRecords(); + +MWBase::Environment::get().getInputManager()->countSavedGameRecords() + +MWBase::Environment::get().getWindowManager()->countSavedGameRecords(); writer.setRecordCount (recordCount); writer.save (stream); @@ -282,9 +284,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::Environment::get().getLuaManager()->write(writer, listener); MWBase::Environment::get().getWorld()->write (writer, listener); MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); - MWBase::Environment::get().getWindowManager()->write(writer, listener); MWBase::Environment::get().getMechanicsManager()->write(writer, listener); MWBase::Environment::get().getInputManager()->write(writer, listener); + MWBase::Environment::get().getWindowManager()->write(writer, listener); // Ensure we have written the number of records that was estimated if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record From d787317df91de048008e7c5ad767bec7a0355f25 Mon Sep 17 00:00:00 2001 From: David Nagy Date: Mon, 14 Feb 2022 11:14:41 +0000 Subject: [PATCH 31/48] Update overview.rst (#6598) --- AUTHORS.md | 1 + .../reference/lua-scripting/overview.rst | 35 ++++++++----------- files/builtin_scripts/openmw_aux/time.lua | 2 +- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index ecfc03dc1e..3edba5f25c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -249,6 +249,7 @@ Documentation Joakim Berg (lysol90) Ryan Tucker (Ravenwing) sir_herrbatka + David Nagy (zuzaman) Packagers --------- diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index f270def2fa..e6024a27a1 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -546,10 +546,10 @@ Timers ====== Timers are in the :ref:`openmw.async ` package. -They can be set either in game seconds or in game hours. +They can be set either in simulation time or in game time. -- `Game seconds`: the number of seconds in the game world (i.e. seconds when the game is not paused), passed from starting a new game. -- `Game hours`: current time of the game world in hours. The number of seconds in a game hour is not guaranteed to be fixed. +- `Simulation time`: the number of seconds in the game world (i.e. seconds when the game is not paused), passed from starting a new game. +- `Game time`: current time of the game world in seconds. Note that game time generally goes faster than the simulation time. When the game is paused, all timers are paused as well. @@ -578,7 +578,7 @@ An example: end) local function teleportWithDelay(delay, actor, cellName, pos) - async:newTimerInSeconds(delay, teleportWithDelayCallback, { + async:newSimulationTimer(delay, teleportWithDelayCallback, { actor = actor, destCellName = cellName, destPos = pos, @@ -603,7 +603,7 @@ An example: engineHandlers = { onKeyPress = function(key) if key.symbol == 'x' then - async:newUnsavableTimerInSeconds( + async:newUnsavableSimulationTimer( 10, function() ui.showMessage('You have pressed "X" 10 seconds ago') @@ -613,28 +613,21 @@ An example: } } -Also in `openmw_aux`_ are the helper functions ``runEveryNSeconds`` and ``runEveryNHours``, they are implemented on top of unsavable timers: +Also in `openmw_aux`_ is the helper function ``runRepeatedly``, it is implemented on top of unsavable timers: .. code-block:: Lua - local async = require('openmw.async') local core = require('openmw.core') + local time = require('openmw_aux.time') - -- call `doSomething()` at the end of every game day. - -- `timeBeforeMidnight` is a delay before the first call. `24` is an interval. + -- call `doSomething()` at the end of every game day. + -- the second argument (`time.day`) is the interval. -- the periodical evaluation can be stopped at any moment by calling `stopFn()` - local timeBeforeMidnight = 24 - math.fmod(core.getGameTimeInHours(), 24) - local stopFn = aux_util.runEveryNHours(24, doSomething, timeBeforeMidnight) - - return { - engineHandlers = { - onLoad = function() - -- the timer is unsavable, so we need to restart it in `onLoad`. - timeBeforeMidnight = 24 - math.fmod(core.getGameTimeInHours(), 24) - stopFn = aux_util.runEveryNHours(24, doSomething, timeBeforeMidnight) - end, - } - } + local timeBeforeMidnight = time.day - core.getGameTime() % time.day + local stopFn = time.runRepeatedly(doSomething, time.day, { + initialDelay = timeBeforeMidnight, + type = time.GameTime, + }) Queries diff --git a/files/builtin_scripts/openmw_aux/time.lua b/files/builtin_scripts/openmw_aux/time.lua index a6aac0fa1a..1c543c53b8 100644 --- a/files/builtin_scripts/openmw_aux/time.lua +++ b/files/builtin_scripts/openmw_aux/time.lua @@ -66,7 +66,7 @@ end -- function() print('Test2') end, 5 * time.minute, -- { initialDelay = 30 * time.second }) -- @usage --- local timeBeforeMidnight = time.day - time.gameTime() % time.day +-- local timeBeforeMidnight = time.day - core.getGameTime() % time.day -- time.runRepeatedly(doSomething, time.day, { -- initialDelay = timeBeforeMidnight, -- type = time.GameTime, From 649c2f828659f559a821209a9f30c4f85cab2dcf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 14 Feb 2022 18:38:37 +0100 Subject: [PATCH 32/48] Fix stats not working right for saves started before version 17 --- apps/openmw/mwmechanics/stat.cpp | 4 ++-- apps/openmw/mwworld/cellstore.cpp | 5 +++++ apps/openmw/mwworld/magiceffects.cpp | 13 +++++++++++-- apps/openmw/mwworld/magiceffects.hpp | 2 ++ apps/openmw/mwworld/player.cpp | 2 ++ components/esm3/savedgame.cpp | 2 +- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 585808645a..eacfca98ae 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -21,13 +21,13 @@ namespace MWMechanics void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; - state.mMod = mModifier + mBase; + state.mMod = mModifier; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; - mModifier = state.mMod - mBase; + mModifier = state.mMod; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b0c2ea3a23..75136eb915 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -185,6 +185,11 @@ namespace else if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); } + else if(state.mVersion < 20) + { + if constexpr (std::is_same_v || std::is_same_v) + MWWorld::convertStats(state.mCreatureStats); + } if (state.mRef.mRefNum.hasContentFile()) { diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index f52a61af52..44c4061832 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -201,14 +201,23 @@ namespace MWWorld { auto& dynamic = creatureStats.mDynamic[i]; dynamic.mCurrent -= dynamic.mMod - dynamic.mBase; - dynamic.mMod = dynamic.mBase; + dynamic.mMod = 0.f; } for(std::size_t i = 0; i < 4; ++i) - creatureStats.mAiSettings[i].mMod = creatureStats.mAiSettings[i].mBase; + creatureStats.mAiSettings[i].mMod = 0.f; if(npcStats) { for(std::size_t i = 0; i < ESM::Skill::Length; ++i) npcStats->mSkills[i].mMod = 0.f; } } + + // Versions 17-19 wrote different modifiers to the savegame depending on whether the save had upgraded from a pre-17 version or not + void convertStats(ESM::CreatureStats& creatureStats) + { + for(std::size_t i = 0; i < 3; ++i) + creatureStats.mDynamic[i].mMod = 0.f; + for(std::size_t i = 0; i < 4; ++i) + creatureStats.mAiSettings[i].mMod = 0.f; + } } diff --git a/apps/openmw/mwworld/magiceffects.hpp b/apps/openmw/mwworld/magiceffects.hpp index 31d5ed2038..fdcc578e53 100644 --- a/apps/openmw/mwworld/magiceffects.hpp +++ b/apps/openmw/mwworld/magiceffects.hpp @@ -12,6 +12,8 @@ namespace MWWorld { void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr); + + void convertStats(ESM::CreatureStats& creatureStats); } #endif diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 7062366697..45e87a7942 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -383,6 +383,8 @@ namespace MWWorld } if (reader.getFormat() < 17) convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats); + else if(reader.getFormat() < 20) + convertStats(player.mObject.mCreatureStats); if (!player.mObject.mEnabled) { diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 6cecf26489..18498abe2c 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 19; +int ESM::SavedGame::sCurrentFormat = 20; void ESM::SavedGame::load (ESMReader &esm) { From 071ab3f6508890080ae38a7b13a141f9292276a2 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 14 Feb 2022 19:56:50 +0100 Subject: [PATCH 33/48] Fix out of bounds access for std::string_view --- components/fontloader/fontloader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 36174d0b7e..d0125398f1 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -30,6 +30,8 @@ namespace { unsigned long utf8ToUnicode(std::string_view utf8) { + if (utf8.empty()) + return 0; size_t i = 0; unsigned long unicode; size_t numbytes; From fbbf8710673057801b7063d4b7d6d36baf1c4e11 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 14 Feb 2022 22:36:07 +0100 Subject: [PATCH 34/48] Avoid extra copy for Utf8Encoder::getUtf8 result --- apps/mwiniimporter/importer.cpp | 36 ++++++++++++++------------ components/translation/translation.cpp | 12 ++++----- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 35d9701803..f7523b11cf 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -663,49 +663,51 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::p std::string line; while (std::getline(file, line)) { - line = encoder.getUtf8(line); + std::string_view utf8 = encoder.getUtf8(line); // unify Unix-style and Windows file ending - if (!(line.empty()) && (line[line.length()-1]) == '\r') { - line = line.substr(0, line.length()-1); + if (!(utf8.empty()) && (utf8[utf8.length()-1]) == '\r') { + utf8 = utf8.substr(0, utf8.length()-1); } - if(line.empty()) { + if(utf8.empty()) { continue; } - if(line[0] == '[') { - int pos = static_cast(line.find(']')); + if(utf8[0] == '[') { + int pos = static_cast(utf8.find(']')); if(pos < 2) { - std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl; + std::cout << "Warning: ini file wrongly formatted (" << utf8 << "). Line ignored." << std::endl; continue; } - section = line.substr(1, line.find(']')-1); + section = utf8.substr(1, utf8.find(']')-1); continue; } - int comment_pos = static_cast(line.find(';')); + int comment_pos = static_cast(utf8.find(';')); if(comment_pos > 0) { - line = line.substr(0,comment_pos); + utf8 = utf8.substr(0,comment_pos); } - int pos = static_cast(line.find('=')); + int pos = static_cast(utf8.find('=')); if(pos < 1) { continue; } - std::string key(section + ":" + line.substr(0,pos)); - std::string value(line.substr(pos+1)); + std::string key(section + ":" + std::string(utf8.substr(0, pos))); + const std::string_view value(utf8.substr(pos+1)); if(value.empty()) { std::cout << "Warning: ignored empty value for key '" << key << "'." << std::endl; continue; } - if(map.find(key) == map.end()) { - map.insert( std::make_pair (key, std::vector() ) ); - } - map[key].push_back(value); + auto it = map.find(key); + + if (it == map.end()) + it = map.emplace_hint(it, std::move(key), std::vector()); + + it->second.push_back(std::string(value)); } return map; diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index ef0f432075..f2cdd803e7 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -53,16 +53,16 @@ namespace Translation if (!line.empty()) { - line = mEncoder->getUtf8(line); + const std::string_view utf8 = mEncoder->getUtf8(line); - size_t tab_pos = line.find('\t'); - if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) + size_t tab_pos = utf8.find('\t'); + if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < utf8.size() - 1) { - std::string key = line.substr(0, tab_pos); - std::string value = line.substr(tab_pos + 1); + const std::string_view key = utf8.substr(0, tab_pos); + const std::string_view value = utf8.substr(tab_pos + 1); if (!key.empty() && !value.empty()) - container.insert(std::make_pair(key, value)); + container.emplace(key, value); } } } From c044bef6a77edeea44d35ac908841cbe49610b75 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 14 Feb 2022 22:26:01 +0100 Subject: [PATCH 35/48] Add StatelessUtf8Encoder to support caller provided buffer for output --- apps/opencs/editor.cpp | 2 +- .../contentselector/model/contentmodel.cpp | 3 +- components/to_utf8/to_utf8.cpp | 131 ++++++++++-------- components/to_utf8/to_utf8.hpp | 41 ++++-- 4 files changed, 105 insertions(+), 72 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 1d5934fe5d..07154f6d55 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -370,7 +370,7 @@ int CS::Editor::run() else { ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder = ToUTF8::calculateEncoding(mEncodingName); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName)); fileReader.setEncoder(&encoder); fileReader.open(mFileToLoad.string()); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 57dfe0f87e..f7cedc83a4 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -445,8 +445,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) try { ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder = - ToUTF8::calculateEncoding(mEncoding.toStdString()); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); fileReader.setEncoder(&encoder); fileReader.open(std::string(dir.absoluteFilePath(path2).toUtf8().constData())); diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 1f0a81ad10..a193e6375d 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -51,42 +51,52 @@ namespace { return std::find_if(input.begin(), input.end(), [] (unsigned char v) { return v == 0 || v >= 128; }); } -} -Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): - mOutput(50*1024) -{ - switch (sourceEncoding) + std::basic_string_view getTranslationArray(FromType sourceEncoding) { - case ToUTF8::WINDOWS_1252: + switch (sourceEncoding) { - translationArray = ToUTF8::windows_1252; - break; - } - case ToUTF8::WINDOWS_1250: - { - translationArray = ToUTF8::windows_1250; - break; - } - case ToUTF8::WINDOWS_1251: - { - translationArray = ToUTF8::windows_1251; - break; - } - case ToUTF8::CP437: - { - translationArray = ToUTF8::cp437; - break; + case ToUTF8::WINDOWS_1252: + return ToUTF8::windows_1252; + case ToUTF8::WINDOWS_1250: + return ToUTF8::windows_1250; + case ToUTF8::WINDOWS_1251: + return ToUTF8::windows_1251; + case ToUTF8::CP437: + return ToUTF8::cp437; } + throw std::logic_error("Invalid source encoding: " + std::to_string(sourceEncoding)); + } - default: + // Make sure the output vector is large enough for 'size' bytes, + // including a terminating zero after it. + void resize(std::size_t size, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) + { + if (buffer.size() >= size) + return; + + switch (bufferAllocationPolicy) { - assert(0); + case BufferAllocationPolicy::FitToRequiredSize: + buffer.resize(size); + break; + case BufferAllocationPolicy::UseGrowFactor: + // Add some extra padding to reduce the chance of having to resize + // again later. + buffer.resize(3 * size); + // And make sure the string is zero terminated + buffer[size] = 0; + break; } } } -std::string_view Utf8Encoder::getUtf8(std::string_view input) +StatelessUtf8Encoder::StatelessUtf8Encoder(FromType sourceEncoding) + : mTranslationArray(getTranslationArray(sourceEncoding)) +{ +} + +std::string_view StatelessUtf8Encoder::getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { if (input.empty()) return input; @@ -106,24 +116,24 @@ std::string_view Utf8Encoder::getUtf8(std::string_view input) return std::string_view(input.data(), outlen); // Make sure the output is large enough - resize(outlen); - char *out = &mOutput[0]; + resize(outlen, bufferAllocationPolicy, buffer); + char *out = buffer.data(); // Translate for (auto it = input.begin(); it != input.end() && *it != 0; ++it) copyFromArray(*it, out); // Make sure that we wrote the correct number of bytes - assert((out-&mOutput[0]) == (int)outlen); + assert((out - buffer.data()) == (int)outlen); // And make extra sure the output is null terminated - assert(mOutput.size() > outlen); - assert(mOutput[outlen] == 0); + assert(buffer.size() >= outlen); + assert(buffer[outlen] == 0); - return std::string_view(mOutput.data(), outlen); + return std::string_view(buffer.data(), outlen); } -std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) +std::string_view StatelessUtf8Encoder::getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { if (input.empty()) return input; @@ -143,34 +153,21 @@ std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) return std::string_view(input.data(), outlen); // Make sure the output is large enough - resize(outlen); - char *out = &mOutput[0]; + resize(outlen, bufferAllocationPolicy, buffer); + char *out = buffer.data(); // Translate for (auto it = input.begin(); it != input.end() && *it != 0;) copyFromArrayLegacyEnc(it, input.end(), out); // Make sure that we wrote the correct number of bytes - assert((out-&mOutput[0]) == (int)outlen); + assert((out - buffer.data()) == static_cast(outlen)); // And make extra sure the output is null terminated - assert(mOutput.size() > outlen); - assert(mOutput[outlen] == 0); + assert(buffer.size() >= outlen); + assert(buffer[outlen] == 0); - return std::string_view(mOutput.data(), outlen); -} - -// Make sure the output vector is large enough for 'size' bytes, -// including a terminating zero after it. -void Utf8Encoder::resize(size_t size) -{ - if (mOutput.size() <= size) - // Add some extra padding to reduce the chance of having to resize - // again later. - mOutput.resize(3*size); - - // And make sure the string is zero terminated - mOutput[size] = 0; + return std::string_view(buffer.data(), outlen); } /** Get the total length length needed to decode the given string with @@ -183,7 +180,7 @@ void Utf8Encoder::resize(size_t size) is the case, then the ascii parameter is set to true, and the caller can optimize for this case. */ -std::pair Utf8Encoder::getLength(std::string_view input) const +std::pair StatelessUtf8Encoder::getLength(std::string_view input) const { // Do away with the ascii part of the string first (this is almost // always the entire string.) @@ -201,7 +198,7 @@ std::pair Utf8Encoder::getLength(std::string_view input) cons { // Find the translated length of this character in the // lookup table. - len += translationArray[static_cast(*it) * 6]; + len += mTranslationArray[static_cast(*it) * 6]; ++it; } while (it != input.end() && *it != 0); @@ -211,7 +208,7 @@ std::pair Utf8Encoder::getLength(std::string_view input) cons // Translate one character 'ch' using the translation array 'arr', and // advance the output pointer accordingly. -void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const +void StatelessUtf8Encoder::copyFromArray(unsigned char ch, char* &out) const { // Optimize for ASCII values if (ch < 128) @@ -220,13 +217,13 @@ void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const return; } - const signed char *in = translationArray + ch*6; + const signed char *in = &mTranslationArray[ch * 6]; int len = *(in++); memcpy(out, in, len); out += len; } -std::pair Utf8Encoder::getLengthLegacyEnc(std::string_view input) const +std::pair StatelessUtf8Encoder::getLengthLegacyEnc(std::string_view input) const { // Do away with the ascii part of the string first (this is almost // always the entire string.) @@ -271,7 +268,7 @@ std::pair Utf8Encoder::getLengthLegacyEnc(std::string_view in return {len, false}; } -void Utf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const +void StatelessUtf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const { unsigned char ch = *(chp++); // Optimize for ASCII values @@ -316,7 +313,7 @@ void Utf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::s for (int i = 128; i < 256; i++) { - unsigned char b1 = translationArray[i*6 + 1], b2 = translationArray[i*6 + 2], b3 = translationArray[i*6 + 3]; + unsigned char b1 = mTranslationArray[i*6 + 1], b2 = mTranslationArray[i*6 + 2], b3 = mTranslationArray[i*6 + 3]; if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) { *(out++) = (char)i; @@ -329,6 +326,22 @@ void Utf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::s *(out++) = ch; // Could not find glyph, just put whatever } +Utf8Encoder::Utf8Encoder(FromType sourceEncoding) + : mBuffer(50 * 1024, '\0') + , mImpl(sourceEncoding) +{ +} + +std::string_view Utf8Encoder::getUtf8(std::string_view input) +{ + return mImpl.getUtf8(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); +} + +std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) +{ + return mImpl.getLegacyEnc(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); +} + ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) { if (encodingName == "win1250") diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 794c9148e5..037e3ea3bf 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -18,34 +18,55 @@ namespace ToUTF8 CP437 // Used for fonts (*.fnt) if data files encoding is 1252. Otherwise, uses the same encoding as the data files. }; + enum class BufferAllocationPolicy + { + FitToRequiredSize, + UseGrowFactor, + }; + FromType calculateEncoding(const std::string& encodingName); std::string encodingUsingMessage(const std::string& encodingName); - // class + class StatelessUtf8Encoder + { + public: + explicit StatelessUtf8Encoder(FromType sourceEncoding); + + /// Convert to UTF8 from the previously given code page. + /// Returns a view to passed buffer that will be resized to fit output if it's too small. + std::string_view getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; + + /// Convert from UTF-8 to sourceEncoding. + /// Returns a view to passed buffer that will be resized to fit output if it's too small. + std::string_view getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; + + private: + inline std::pair getLength(std::string_view input) const; + inline void copyFromArray(unsigned char chp, char* &out) const; + inline std::pair getLengthLegacyEnc(std::string_view input) const; + inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; + + const std::basic_string_view mTranslationArray; + }; class Utf8Encoder { public: - Utf8Encoder(FromType sourceEncoding); + explicit Utf8Encoder(FromType sourceEncoding); /// Convert to UTF8 from the previously given code page. /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not /// ASCII-only string. Otherwise returns a view to the input. std::string_view getUtf8(std::string_view input); + /// Convert from UTF-8 to sourceEncoding. /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not /// ASCII-only string. Otherwise returns a view to the input. std::string_view getLegacyEnc(std::string_view input); private: - inline void resize(std::size_t size); - inline std::pair getLength(std::string_view input) const; - inline void copyFromArray(unsigned char chp, char* &out) const; - inline std::pair getLengthLegacyEnc(std::string_view input) const; - inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; - - std::vector mOutput; - const signed char* translationArray; + std::string mBuffer; + StatelessUtf8Encoder mImpl; }; } From 3305b400dcbeff24c39247c3317f1a88be1d296b Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 28 Jan 2022 18:40:17 +0100 Subject: [PATCH 36/48] Use ESM::NAME instead of const char* and std::string as argument type --- apps/esmtool/esmtool.cpp | 6 +-- .../esm/test_fixed_string.cpp | 31 ++++++++------ components/esm/esmcommon.hpp | 42 +++++++++++++++++++ components/esm3/activespells.cpp | 4 +- components/esm3/aipackage.cpp | 2 +- components/esm3/cellref.cpp | 8 ++-- components/esm3/cellref.hpp | 5 ++- components/esm3/esmreader.cpp | 16 +++---- components/esm3/esmreader.hpp | 22 +++++----- components/esm3/esmwriter.cpp | 31 +++++--------- components/esm3/esmwriter.hpp | 32 +++++++------- components/esm3/loadlevlist.hpp | 16 ++++--- components/esm3/weatherstate.cpp | 22 +++++----- 13 files changed, 137 insertions(+), 100 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 4fe0b248af..a7946c77a6 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -484,9 +484,9 @@ int clone(Arguments& info) if (i <= 0) break; - const ESM::NAME& typeName = record->getType(); + const ESM::NAME typeName = record->getType(); - esm.startRecord(typeName.toString(), record->getFlags()); + esm.startRecord(typeName, record->getFlags()); record->save(esm); if (typeName.toInt() == ESM::REC_CELL) { @@ -498,7 +498,7 @@ int clone(Arguments& info) } } - esm.endRecord(typeName.toString()); + esm.endRecord(typeName); saved++; int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount)*100); diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index 26fb1590d5..1189f667e5 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -1,12 +1,12 @@ #include #include "components/esm/esmcommon.hpp" +#include "components/esm/defs.hpp" TEST(EsmFixedString, operator__eq_ne) { { SCOPED_TRACE("asdc == asdc"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); char s[4] = {'a', 's', 'd', 'c'}; std::string ss(s, 4); @@ -16,8 +16,7 @@ TEST(EsmFixedString, operator__eq_ne) } { SCOPED_TRACE("asdc == asdcx"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); char s[5] = {'a', 's', 'd', 'c', 'x'}; std::string ss(s, 5); @@ -27,8 +26,7 @@ TEST(EsmFixedString, operator__eq_ne) } { SCOPED_TRACE("asdc == asdc[NULL]"); - ESM::NAME name; - name.assign("asdc"); + const ESM::NAME name("asdc"); char s[5] = {'a', 's', 'd', 'c', '\0'}; std::string ss(s, 5); @@ -41,8 +39,7 @@ TEST(EsmFixedString, operator__eq_ne_const) { { SCOPED_TRACE("asdc == asdc (const)"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); const char s[4] = { 'a', 's', 'd', 'c' }; std::string ss(s, 4); @@ -52,8 +49,7 @@ TEST(EsmFixedString, operator__eq_ne_const) } { SCOPED_TRACE("asdc == asdcx (const)"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); const char s[5] = { 'a', 's', 'd', 'c', 'x' }; std::string ss(s, 5); @@ -63,8 +59,7 @@ TEST(EsmFixedString, operator__eq_ne_const) } { SCOPED_TRACE("asdc == asdc[NULL] (const)"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); const char s[5] = { 'a', 's', 'd', 'c', '\0' }; std::string ss(s, 5); @@ -148,3 +143,15 @@ TEST(EsmFixedString, assignment_operator_is_supported_for_uint32) value = static_cast(0xFEDCBA98u); EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); } + +TEST(EsmFixedString, construction_from_uint32_is_supported) +{ + constexpr ESM::NAME value(0xFEDCBA98u); + EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); +} + +TEST(EsmFixedString, construction_from_RecNameInts_is_supported) +{ + constexpr ESM::NAME value(ESM::RecNameInts::REC_ACTI); + EXPECT_EQ(value, static_cast(ESM::RecNameInts::REC_ACTI)) << value.toInt(); +} diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 921eeed744..7ca059020f 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace ESM { @@ -30,6 +32,41 @@ struct FixedString char mData[capacity]; + FixedString() = default; + + template + constexpr FixedString(const char (&value)[size]) noexcept + : mData() + { + if constexpr (capacity == sizeof(std::uint32_t)) + { + static_assert(capacity == size || capacity + 1 == size); + if constexpr (capacity + 1 == size) + assert(value[capacity] == '\0'); + for (std::size_t i = 0; i < capacity; ++i) + mData[i] = value[i]; + } + else + { + const std::size_t length = std::min(capacity, size); + for (std::size_t i = 0; i < length; ++i) + mData[i] = value[i]; + mData[std::min(capacity - 1, length)] = '\0'; + } + } + + constexpr explicit FixedString(std::uint32_t value) noexcept + : mData() + { + static_assert(capacity == sizeof(std::uint32_t)); + for (std::size_t i = 0; i < capacity; ++i) + mData[i] = static_cast((value >> (i * std::numeric_limits::digits)) & std::numeric_limits::max()); + } + + template + constexpr explicit FixedString(T value) noexcept + : FixedString(static_cast(value)) {} + std::string_view toStringView() const noexcept { return std::string_view(mData, strnlen(mData, capacity)); @@ -116,6 +153,11 @@ inline bool operator==(const FixedString<4>& lhs, std::uint32_t rhs) noexcept return lhs.toInt() == rhs; } +inline bool operator==(const FixedString<4>& lhs, const FixedString<4>& rhs) noexcept +{ + return lhs.toInt() == rhs.toInt(); +} + template inline bool operator!=(const FixedString& lhs, const Rhs& rhs) noexcept { diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 22f862b6e4..61be6a7838 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -5,7 +5,7 @@ namespace { - void save(ESM::ESMWriter& esm, const std::vector& spells, const std::string& tag) + void save(ESM::ESMWriter& esm, const std::vector& spells, ESM::NAME tag) { for (const auto& params : spells) { @@ -38,7 +38,7 @@ namespace } } - void load(ESM::ESMReader& esm, std::vector& spells, const char* tag) + void load(ESM::ESMReader& esm, std::vector& spells, ESM::NAME tag) { int format = esm.getFormat(); diff --git a/components/esm3/aipackage.cpp b/components/esm3/aipackage.cpp index fa20d271c0..5a95e58ca8 100644 --- a/components/esm3/aipackage.cpp +++ b/components/esm3/aipackage.cpp @@ -65,7 +65,7 @@ namespace ESM case AI_Escort: case AI_Follow: { - const char *name = (it->mType == AI_Escort) ? "AI_E" : "AI_F"; + const ESM::NAME name = (it->mType == AI_Escort) ? ESM::NAME("AI_E") : ESM::NAME("AI_F"); esm.writeHNT(name, it->mTarget, sizeof(it->mTarget)); esm.writeHNOCString("CNDT", it->mCellName); break; diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 002a885d92..8487b3c0f0 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -5,15 +5,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) +void ESM::RefNum::load(ESMReader& esm, bool wide, ESM::NAME tag) { if (wide) - esm.getHNT (*this, tag.c_str(), 8); + esm.getHNT(*this, tag, 8); else - esm.getHNT (mIndex, tag.c_str()); + esm.getHNT(mIndex, tag); } -void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const +void ESM::RefNum::save(ESMWriter &esm, bool wide, ESM::NAME tag) const { if (wide) esm.writeHNT (tag, *this, 8); diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 55e4b700fc..cff635f455 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -5,6 +5,7 @@ #include #include "components/esm/defs.hpp" +#include "components/esm/esmcommon.hpp" namespace ESM { @@ -18,9 +19,9 @@ namespace ESM unsigned int mIndex; int mContentFile; - void load (ESMReader& esm, bool wide = false, const std::string& tag = "FRMR"); + void load(ESMReader& esm, bool wide = false, ESM::NAME tag = "FRMR"); - void save (ESMWriter &esm, bool wide = false, const std::string& tag = "FRMR") const; + void save(ESMWriter &esm, bool wide = false, ESM::NAME tag = "FRMR") const; inline bool hasContentFile() const { return mContentFile >= 0; } diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 165263d6e4..47974e45a8 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -113,14 +113,14 @@ void ESMReader::open(const std::string &file) open (Files::openConstrainedFileStream (file.c_str ()), file); } -std::string ESMReader::getHNOString(const char* name) +std::string ESMReader::getHNOString(NAME name) { if (isNextSub(name)) return getHString(); return ""; } -std::string ESMReader::getHNString(const char* name) +std::string ESMReader::getHNString(NAME name) { getSubNameIs(name); return getHString(); @@ -156,21 +156,21 @@ void ESMReader::getHExact(void*p, int size) } // Read the given number of bytes from a named subrecord -void ESMReader::getHNExact(void*p, int size, const char* name) +void ESMReader::getHNExact(void*p, int size, NAME name) { getSubNameIs(name); getHExact(p, size); } // Get the next subrecord name and check if it matches the parameter -void ESMReader::getSubNameIs(const char* name) +void ESMReader::getSubNameIs(NAME name) { getSubName(); if (mCtx.subName != name) - fail("Expected subrecord " + std::string(name) + " but got " + mCtx.subName.toString()); + fail("Expected subrecord " + name.toString() + " but got " + mCtx.subName.toString()); } -bool ESMReader::isNextSub(const char* name) +bool ESMReader::isNextSub(NAME name) { if (!hasMoreSubs()) return false; @@ -185,7 +185,7 @@ bool ESMReader::isNextSub(const char* name) return !mCtx.subCached; } -bool ESMReader::peekNextSub(const char *name) +bool ESMReader::peekNextSub(NAME name) { if (!hasMoreSubs()) return false; @@ -226,7 +226,7 @@ void ESMReader::skipHSubSize(int size) reportSubSizeMismatch(mCtx.leftSub, size); } -void ESMReader::skipHSubUntil(const char *name) +void ESMReader::skipHSubUntil(NAME name) { while (hasMoreSubs() && !isNextSub(name)) { diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 3b384dc603..e370946ee9 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -98,7 +98,7 @@ public: // Read data of a given type, stored in a subrecord of a given name template - void getHNT(X &x, const char* name) + void getHNT(X &x, NAME name) { getSubNameIs(name); getHT(x); @@ -106,7 +106,7 @@ public: // Optional version of getHNT template - void getHNOT(X &x, const char* name) + void getHNOT(X &x, NAME name) { if(isNextSub(name)) getHT(x); @@ -115,7 +115,7 @@ public: // Version with extra size checking, to make sure the compiler // doesn't mess up our struct padding. template - void getHNT(X &x, const char* name, int size) + void getHNT(X &x, NAME name, int size) { assert(sizeof(X) == size); getSubNameIs(name); @@ -123,7 +123,7 @@ public: } template - void getHNOT(X &x, const char* name, int size) + void getHNOT(X &x, NAME name, int size) { assert(sizeof(X) == size); if(isNextSub(name)) @@ -150,10 +150,10 @@ public: } // Read a string by the given name if it is the next record. - std::string getHNOString(const char* name); + std::string getHNOString(NAME name); // Read a string with the given sub-record name - std::string getHNString(const char* name); + std::string getHNString(NAME name); // Read a string, including the sub-record header (but not the name) std::string getHString(); @@ -162,7 +162,7 @@ public: void getHExact(void*p, int size); // Read the given number of bytes from a named subrecord - void getHNExact(void*p, int size, const char* name); + void getHNExact(void*p, int size, NAME name); /************************************************************************* * @@ -171,16 +171,16 @@ public: *************************************************************************/ // Get the next subrecord name and check if it matches the parameter - void getSubNameIs(const char* name); + void getSubNameIs(NAME name); /** Checks if the next sub record name matches the parameter. If it does, it is read into 'subName' just as if getSubName() was called. If not, the read name will still be available for future calls to getSubName(), isNextSub() and getSubNameIs(). */ - bool isNextSub(const char* name); + bool isNextSub(NAME name); - bool peekNextSub(const char* name); + bool peekNextSub(NAME name); // Store the current subrecord name for the next call of getSubName() void cacheSubName() {mCtx.subCached = true; }; @@ -197,7 +197,7 @@ public: void skipHSubSize(int size); // Skip all subrecords until the given subrecord or no more subrecords remaining - void skipHSubUntil(const char* name); + void skipHSubUntil(NAME name); /* Sub-record header. This updates leftRec beyond the current sub-record as well. leftSub contains size of current sub-record. diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index d0137c5131..77ba5fc6aa 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -86,7 +86,7 @@ namespace ESM throw std::runtime_error ("Unclosed record remaining"); } - void ESMWriter::startRecord(const std::string& name, uint32_t flags) + void ESMWriter::startRecord(ESM::NAME name, uint32_t flags) { mRecordCount++; @@ -105,15 +105,10 @@ namespace ESM void ESMWriter::startRecord (uint32_t name, uint32_t flags) { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic - type += reinterpret_cast (&name)[i]; - - startRecord (type, flags); + startRecord(ESM::NAME(name), flags); } - void ESMWriter::startSubRecord(const std::string& name) + void ESMWriter::startSubRecord(ESM::NAME name) { // Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. assert (mRecords.size() <= 1); @@ -129,7 +124,7 @@ namespace ESM assert(mRecords.back().size == 0); } - void ESMWriter::endRecord(const std::string& name) + void ESMWriter::endRecord(ESM::NAME name) { RecordData rec = mRecords.back(); assert(rec.name == name); @@ -147,22 +142,17 @@ namespace ESM void ESMWriter::endRecord (uint32_t name) { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic - type += reinterpret_cast (&name)[i]; - - endRecord (type); + endRecord(ESM::NAME(name)); } - void ESMWriter::writeHNString(const std::string& name, const std::string& data) + void ESMWriter::writeHNString(ESM::NAME name, const std::string& data) { startSubRecord(name); writeHString(data); endRecord(name); } - void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) + void ESMWriter::writeHNString(ESM::NAME name, const std::string& data, size_t size) { assert(data.size() <= size); startSubRecord(name); @@ -177,7 +167,7 @@ namespace ESM endRecord(name); } - void ESMWriter::writeFixedSizeString(const std::string &data, int size) + void ESMWriter::writeFixedSizeString(const std::string& data, int size) { std::string string; if (!data.empty()) @@ -206,10 +196,9 @@ namespace ESM write("\0", 1); } - void ESMWriter::writeName(const std::string& name) + void ESMWriter::writeName(ESM::NAME name) { - assert((name.size() == 4 && name[3] != '\0')); - write(name.c_str(), name.size()); + write(name.mData, ESM::NAME::sCapacity); } void ESMWriter::write(const char* data, size_t size) diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 3073daf15e..8ee507f39d 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -19,7 +19,7 @@ class ESMWriter { struct RecordData { - std::string name; + ESM::NAME name; std::streampos position; uint32_t size; }; @@ -56,27 +56,27 @@ class ESMWriter void close(); ///< \note Does not close the stream. - void writeHNString(const std::string& name, const std::string& data); - void writeHNString(const std::string& name, const std::string& data, size_t size); - void writeHNCString(const std::string& name, const std::string& data) + void writeHNString(ESM::NAME name, const std::string& data); + void writeHNString(ESM::NAME name, const std::string& data, size_t size); + void writeHNCString(ESM::NAME name, const std::string& data) { startSubRecord(name); writeHCString(data); endRecord(name); } - void writeHNOString(const std::string& name, const std::string& data) + void writeHNOString(ESM::NAME name, const std::string& data) { if (!data.empty()) writeHNString(name, data); } - void writeHNOCString(const std::string& name, const std::string& data) + void writeHNOCString(ESM::NAME name, const std::string& data) { if (!data.empty()) writeHNCString(name, data); } template - void writeHNT(const std::string& name, const T& data) + void writeHNT(ESM::NAME name, const T& data) { startSubRecord(name); writeT(data); @@ -84,7 +84,7 @@ class ESMWriter } template - void writeHNT(const std::string& name, const T (&data)[size]) + void writeHNT(ESM::NAME name, const T (&data)[size]) { startSubRecord(name); writeT(data); @@ -94,15 +94,15 @@ class ESMWriter // Prevent using writeHNT with strings. This already happened by accident and results in // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. - void writeHNT(const std::string& name, const std::string& data) = delete; + void writeHNT(ESM::NAME name, const std::string& data) = delete; - void writeT(const std::string& data) = delete; + void writeT(ESM::NAME data) = delete; template - void writeHNT(const std::string& name, const T (&data)[size], int) = delete; + void writeHNT(ESM::NAME name, const T (&data)[size], int) = delete; template - void writeHNT(const std::string& name, const T& data, int size) + void writeHNT(ESM::NAME name, const T& data, int size) { startSubRecord(name); writeT(data, size); @@ -129,16 +129,16 @@ class ESMWriter write((char*)&data, size); } - void startRecord(const std::string& name, uint32_t flags = 0); + void startRecord(ESM::NAME name, uint32_t flags = 0); void startRecord(uint32_t name, uint32_t flags = 0); /// @note Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. - void startSubRecord(const std::string& name); - void endRecord(const std::string& name); + void startSubRecord(ESM::NAME name); + void endRecord(ESM::NAME name); void endRecord(uint32_t name); void writeFixedSizeString(const std::string& data, int size); void writeHString(const std::string& data); void writeHCString(const std::string& data); - void writeName(const std::string& data); + void writeName(ESM::NAME data); void write(const char* data, size_t size); private: diff --git a/components/esm3/loadlevlist.hpp b/components/esm3/loadlevlist.hpp index 0ebd7a64cb..aea85b20e0 100644 --- a/components/esm3/loadlevlist.hpp +++ b/components/esm3/loadlevlist.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace ESM { @@ -27,7 +29,7 @@ struct LevelledListBase // Record name used to read references. Must be set before load() is // called. - const char *mRecName; + ESM::NAME mRecName; struct LevelItem { @@ -37,6 +39,8 @@ struct LevelledListBase std::vector mList; + explicit LevelledListBase(ESM::NAME recName) : mRecName(recName) {} + void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; @@ -58,10 +62,7 @@ struct CreatureLevList: LevelledListBase // player. }; - CreatureLevList() - { - mRecName = "CNAM"; - } + CreatureLevList() : LevelledListBase("CNAM") {} }; struct ItemLevList: LevelledListBase @@ -84,10 +85,7 @@ struct ItemLevList: LevelledListBase // player. }; - ItemLevList() - { - mRecName = "INAM"; - } + ItemLevList() : LevelledListBase("INAM") {} }; } diff --git a/components/esm3/weatherstate.cpp b/components/esm3/weatherstate.cpp index cd1a82b0b7..c791aac145 100644 --- a/components/esm3/weatherstate.cpp +++ b/components/esm3/weatherstate.cpp @@ -5,17 +5,17 @@ namespace { - const char* currentRegionRecord = "CREG"; - const char* timePassedRecord = "TMPS"; - const char* fastForwardRecord = "FAST"; - const char* weatherUpdateTimeRecord = "WUPD"; - const char* transitionFactorRecord = "TRFC"; - const char* currentWeatherRecord = "CWTH"; - const char* nextWeatherRecord = "NWTH"; - const char* queuedWeatherRecord = "QWTH"; - const char* regionNameRecord = "RGNN"; - const char* regionWeatherRecord = "RGNW"; - const char* regionChanceRecord = "RGNC"; + constexpr ESM::NAME currentRegionRecord = "CREG"; + constexpr ESM::NAME timePassedRecord = "TMPS"; + constexpr ESM::NAME fastForwardRecord = "FAST"; + constexpr ESM::NAME weatherUpdateTimeRecord = "WUPD"; + constexpr ESM::NAME transitionFactorRecord = "TRFC"; + constexpr ESM::NAME currentWeatherRecord = "CWTH"; + constexpr ESM::NAME nextWeatherRecord = "NWTH"; + constexpr ESM::NAME queuedWeatherRecord = "QWTH"; + constexpr ESM::NAME regionNameRecord = "RGNN"; + constexpr ESM::NAME regionWeatherRecord = "RGNW"; + constexpr ESM::NAME regionChanceRecord = "RGNC"; } namespace ESM From f9da792386eb4692dcc3a87e0665ecb2d2da7cc6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 15 Feb 2022 17:25:07 +0100 Subject: [PATCH 37/48] Force a scale update when changing view modes --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 6 +++--- apps/openmw/mwworld/worldimp.hpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1d9e4fae2d..0b55484278 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -293,7 +293,7 @@ namespace MWBase virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec) = 0; ///< @return an updated Ptr - virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; + virtual void scaleObject (const MWWorld::Ptr& ptr, float scale, bool force = false) = 0; virtual void rotateObject(const MWWorld::Ptr& ptr, const osg::Vec3f& rot, RotationFlags flags = RotationFlag_inverseOrder) = 0; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 47aeaf53af..3fdfaed72d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -296,7 +296,7 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) return; mViewMode = viewMode; - MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change + MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change mAmmunition.reset(); rebuild(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 61abb7fcc2..a5ab0968ee 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1281,9 +1281,9 @@ namespace MWWorld return moveObject(ptr, newpos); } - void World::scaleObject (const Ptr& ptr, float scale) + void World::scaleObject (const Ptr& ptr, float scale, bool force) { - if (scale == ptr.getCellRef().getScale()) + if (!force && scale == ptr.getCellRef().getScale()) return; if (mPhysics->getActor(ptr)) mNavigator->removeAgent(getPathfindingHalfExtents(ptr)); @@ -2482,7 +2482,7 @@ namespace MWWorld player.getClass().getInventoryStore(player).setInvListener(anim, player); player.getClass().getInventoryStore(player).setContListener(anim); - scaleObject(player, player.getCellRef().getScale()); // apply race height + scaleObject(player, player.getCellRef().getScale(), true); // apply race height rotateObject(player, osg::Vec3f(), MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust); MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr()); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 84d97ce37c..61154afb59 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -377,7 +377,7 @@ namespace MWWorld MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec) override; ///< @return an updated Ptr - void scaleObject (const Ptr& ptr, float scale) override; + void scaleObject (const Ptr& ptr, float scale, bool force = false) override; /// World rotates object, uses radians /// @note Rotations via this method use a different rotation order than the initial rotations in the CS. This From 875d9dcead1490406e42276fd7e52f53b342fff9 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 15 Feb 2022 22:54:23 +0100 Subject: [PATCH 38/48] Fix buffer resizing by StatelessUtf8Encoder --- apps/openmw_test_suite/toutf8/toutf8.cpp | 20 ++++++++++++++++++++ components/to_utf8/to_utf8.cpp | 8 +++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/toutf8/toutf8.cpp b/apps/openmw_test_suite/toutf8/toutf8.cpp index bad4f34fd5..d1cf6b2851 100644 --- a/apps/openmw_test_suite/toutf8/toutf8.cpp +++ b/apps/openmw_test_suite/toutf8/toutf8.cpp @@ -136,4 +136,24 @@ namespace Params {ToUTF8::WINDOWS_1251, "russian-win1251.txt", "russian-utf8.txt"}, Params {ToUTF8::WINDOWS_1252, "french-win1252.txt", "french-utf8.txt"} )); + + TEST(StatelessUtf8EncoderTest, shouldCleanupBuffer) + { + std::string buffer; + StatelessUtf8Encoder encoder(FromType::WINDOWS_1252); + encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::UseGrowFactor, buffer); + const std::string shortString("short\x92"); + ASSERT_GT(buffer.size(), shortString.size()); + const std::string_view shortUtf8 = encoder.getUtf8(shortString, BufferAllocationPolicy::UseGrowFactor, buffer); + ASSERT_GE(buffer.size(), shortUtf8.size()); + EXPECT_EQ(buffer[shortUtf8.size()], '\0') << buffer; + } + + TEST(StatelessUtf8EncoderTest, withFitToRequiredSizeShouldResizeBuffer) + { + std::string buffer; + StatelessUtf8Encoder encoder(FromType::WINDOWS_1252); + const std::string_view utf8 = encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::FitToRequiredSize, buffer); + EXPECT_EQ(buffer.size(), utf8.size()); + } } diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index a193e6375d..f4b52b644b 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -72,7 +72,13 @@ namespace // including a terminating zero after it. void resize(std::size_t size, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) { - if (buffer.size() >= size) + if (buffer.size() > size) + { + buffer[size] = 0; + return; + } + + if (buffer.size() == size) return; switch (bufferAllocationPolicy) From 2e38f0b641c44ef8ff3139d2b93fd288c487871e Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 16 Feb 2022 02:58:21 +0100 Subject: [PATCH 39/48] Fix btAABB initialization --- apps/navmeshtool/worldspacedata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 81bbd857f5..50e529c6e5 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -178,7 +178,7 @@ namespace NavMeshTool static_cast(cellPosition.y() * ESM::Land::REAL_SIZE), minHeight ); - aabb.m_min = btVector3( + aabb.m_max = btVector3( static_cast((cellPosition.x() + 1) * ESM::Land::REAL_SIZE), static_cast((cellPosition.y() + 1) * ESM::Land::REAL_SIZE), maxHeight From 4a06351c3b0745b7f38955e0ba56f612eaa22462 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 16 Feb 2022 10:48:49 +0100 Subject: [PATCH 40/48] update to_utf8 and translation to make use of new stateless utf8 --- components/to_utf8/to_utf8.cpp | 280 +++++++++++++------------ components/to_utf8/to_utf8.hpp | 63 +++--- components/translation/translation.cpp | 11 +- 3 files changed, 185 insertions(+), 169 deletions(-) diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 04edfda09d..a193e6375d 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -44,55 +45,62 @@ using namespace ToUTF8; -Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): - mOutput(50*1024) +namespace { - switch (sourceEncoding) + std::string_view::iterator skipAscii(std::string_view input) { - case ToUTF8::WINDOWS_1252: - { - translationArray = ToUTF8::windows_1252; - break; - } - case ToUTF8::WINDOWS_1250: - { - translationArray = ToUTF8::windows_1250; - break; - } - case ToUTF8::WINDOWS_1251: - { - translationArray = ToUTF8::windows_1251; - break; - } - case ToUTF8::CP437: + return std::find_if(input.begin(), input.end(), [] (unsigned char v) { return v == 0 || v >= 128; }); + } + + std::basic_string_view getTranslationArray(FromType sourceEncoding) + { + switch (sourceEncoding) { - translationArray = ToUTF8::cp437; - break; + case ToUTF8::WINDOWS_1252: + return ToUTF8::windows_1252; + case ToUTF8::WINDOWS_1250: + return ToUTF8::windows_1250; + case ToUTF8::WINDOWS_1251: + return ToUTF8::windows_1251; + case ToUTF8::CP437: + return ToUTF8::cp437; } + throw std::logic_error("Invalid source encoding: " + std::to_string(sourceEncoding)); + } + + // Make sure the output vector is large enough for 'size' bytes, + // including a terminating zero after it. + void resize(std::size_t size, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) + { + if (buffer.size() >= size) + return; - default: + switch (bufferAllocationPolicy) { - assert(0); + case BufferAllocationPolicy::FitToRequiredSize: + buffer.resize(size); + break; + case BufferAllocationPolicy::UseGrowFactor: + // Add some extra padding to reduce the chance of having to resize + // again later. + buffer.resize(3 * size); + // And make sure the string is zero terminated + buffer[size] = 0; + break; } } } -std::string Utf8Encoder::getUtf8(const char* input, size_t size) +StatelessUtf8Encoder::StatelessUtf8Encoder(FromType sourceEncoding) + : mTranslationArray(getTranslationArray(sourceEncoding)) { - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - assert(input[size] == 0); - - std::string inputString(input, size); - std::string output; - toUtf8(inputString, output, size); - - return output; } -void Utf8Encoder::toUtf8(std::string& input, std::string& output, size_t size) +std::string_view StatelessUtf8Encoder::getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { + if (input.empty()) + return input; + // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding // shares its first 128 values (0-127) with ASCII. There are no plans @@ -101,45 +109,34 @@ void Utf8Encoder::toUtf8(std::string& input, std::string& output, size_t size) // Compute output length, and check for pure ascii input at the same // time. - bool ascii; - size_t outlen = getLength(input.c_str(), ascii); + const auto [outlen, ascii] = getLength(input); // If we're pure ascii, then don't bother converting anything. if(ascii) - { - std::swap(input, output); - return; - } + return std::string_view(input.data(), outlen); // Make sure the output is large enough - if (output.size() <= outlen) - // Add some extra padding to reduce the chance of having to resize - // again later. - output.resize(3*outlen); - - // And make sure the string is zero terminated - output[outlen] = 0; - char *in = &input[0]; - char *out = &output[0]; + resize(outlen, bufferAllocationPolicy, buffer); + char *out = buffer.data(); // Translate - while (*in) - copyFromArray(*(in++), out); + for (auto it = input.begin(); it != input.end() && *it != 0; ++it) + copyFromArray(*it, out); // Make sure that we wrote the correct number of bytes - assert((out-&output[0]) == (int)outlen); + assert((out - buffer.data()) == (int)outlen); // And make extra sure the output is null terminated - assert(output.size() > outlen); - assert(output[outlen] == 0); + assert(buffer.size() >= outlen); + assert(buffer[outlen] == 0); + + return std::string_view(buffer.data(), outlen); } -std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) +std::string_view StatelessUtf8Encoder::getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - assert(input[size] == 0); + if (input.empty()) + return input; // TODO: The rest of this function is designed for single-character // input encodings only. It also assumes that the input the input @@ -149,43 +146,28 @@ std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) // Compute output length, and check for pure ascii input at the same // time. - bool ascii; - size_t outlen = getLength2(input, ascii); + const auto [outlen, ascii] = getLengthLegacyEnc(input); // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input, outlen); + return std::string_view(input.data(), outlen); // Make sure the output is large enough - resize(outlen); - char *out = &mOutput[0]; + resize(outlen, bufferAllocationPolicy, buffer); + char *out = buffer.data(); // Translate - while(*input) - copyFromArray2(input, out); + for (auto it = input.begin(); it != input.end() && *it != 0;) + copyFromArrayLegacyEnc(it, input.end(), out); // Make sure that we wrote the correct number of bytes - assert((out-&mOutput[0]) == (int)outlen); + assert((out - buffer.data()) == static_cast(outlen)); // And make extra sure the output is null terminated - assert(mOutput.size() > outlen); - assert(mOutput[outlen] == 0); - - // Return a string - return std::string(&mOutput[0], outlen); -} - -// Make sure the output vector is large enough for 'size' bytes, -// including a terminating zero after it. -void Utf8Encoder::resize(size_t size) -{ - if (mOutput.size() <= size) - // Add some extra padding to reduce the chance of having to resize - // again later. - mOutput.resize(3*size); + assert(buffer.size() >= outlen); + assert(buffer[outlen] == 0); - // And make sure the string is zero terminated - mOutput[size] = 0; + return std::string_view(buffer.data(), outlen); } /** Get the total length length needed to decode the given string with @@ -198,39 +180,35 @@ void Utf8Encoder::resize(size_t size) is the case, then the ascii parameter is set to true, and the caller can optimize for this case. */ -size_t Utf8Encoder::getLength(const char* input, bool &ascii) const +std::pair StatelessUtf8Encoder::getLength(std::string_view input) const { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost // always the entire string.) - while (inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. - if (inp) + if (it == input.end() || *it == 0) + return {it - input.begin(), true}; + + std::size_t len = it - input.begin(); + + do { - ascii = false; - while (inp) - { - // Find the translated length of this character in the - // lookup table. - len += translationArray[inp*6]; - inp = *(++ptr); - } + // Find the translated length of this character in the + // lookup table. + len += mTranslationArray[static_cast(*it) * 6]; + ++it; } - return len; + while (it != input.end() && *it != 0); + + return {len, false}; } // Translate one character 'ch' using the translation array 'arr', and // advance the output pointer accordingly. -void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const +void StatelessUtf8Encoder::copyFromArray(unsigned char ch, char* &out) const { // Optimize for ASCII values if (ch < 128) @@ -239,57 +217,58 @@ void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const return; } - const signed char *in = translationArray + ch*6; + const signed char *in = &mTranslationArray[ch * 6]; int len = *(in++); memcpy(out, in, len); out += len; } -size_t Utf8Encoder::getLength2(const char* input, bool &ascii) const +std::pair StatelessUtf8Encoder::getLengthLegacyEnc(std::string_view input) const { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost // always the entire string.) - while (inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. - if (inp) + if (it == input.end() || *it == 0) + return {it - input.begin(), true}; + + std::size_t len = it - input.begin(); + std::size_t symbolLen = 0; + + do { - ascii = false; - while(inp) + symbolLen += 1; + // Find the translated length of this character in the + // lookup table. + switch (static_cast(*it)) { - len += 1; - // Find the translated length of this character in the - // lookup table. - switch(inp) - { - case 0xe2: len -= 2; break; - case 0xc2: - case 0xcb: - case 0xc4: - case 0xc6: - case 0xc3: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xc5: len -= 1; break; - } - - inp = *(++ptr); + case 0xe2: symbolLen -= 2; break; + case 0xc2: + case 0xcb: + case 0xc4: + case 0xc6: + case 0xc3: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xc5: symbolLen -= 1; break; + default: + len += symbolLen; + symbolLen = 0; + break; } + + ++it; } - return len; + while (it != input.end() && *it != 0); + + return {len, false}; } -void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const +void StatelessUtf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const { unsigned char ch = *(chp++); // Optimize for ASCII values @@ -320,14 +299,21 @@ void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const return; } + if (chp == end) + return; + unsigned char ch2 = *(chp++); unsigned char ch3 = '\0'; if (len == 3) + { + if (chp == end) + return; ch3 = *(chp++); + } for (int i = 128; i < 256; i++) { - unsigned char b1 = translationArray[i*6 + 1], b2 = translationArray[i*6 + 2], b3 = translationArray[i*6 + 3]; + unsigned char b1 = mTranslationArray[i*6 + 1], b2 = mTranslationArray[i*6 + 2], b3 = mTranslationArray[i*6 + 3]; if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) { *(out++) = (char)i; @@ -340,6 +326,22 @@ void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const *(out++) = ch; // Could not find glyph, just put whatever } +Utf8Encoder::Utf8Encoder(FromType sourceEncoding) + : mBuffer(50 * 1024, '\0') + , mImpl(sourceEncoding) +{ +} + +std::string_view Utf8Encoder::getUtf8(std::string_view input) +{ + return mImpl.getUtf8(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); +} + +std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) +{ + return mImpl.getLegacyEnc(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); +} + ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) { if (encodingName == "win1250") diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 23dc09c06e..037e3ea3bf 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ToUTF8 { @@ -17,41 +18,55 @@ namespace ToUTF8 CP437 // Used for fonts (*.fnt) if data files encoding is 1252. Otherwise, uses the same encoding as the data files. }; + enum class BufferAllocationPolicy + { + FitToRequiredSize, + UseGrowFactor, + }; + FromType calculateEncoding(const std::string& encodingName); std::string encodingUsingMessage(const std::string& encodingName); - // class + class StatelessUtf8Encoder + { + public: + explicit StatelessUtf8Encoder(FromType sourceEncoding); + + /// Convert to UTF8 from the previously given code page. + /// Returns a view to passed buffer that will be resized to fit output if it's too small. + std::string_view getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; + + /// Convert from UTF-8 to sourceEncoding. + /// Returns a view to passed buffer that will be resized to fit output if it's too small. + std::string_view getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; + + private: + inline std::pair getLength(std::string_view input) const; + inline void copyFromArray(unsigned char chp, char* &out) const; + inline std::pair getLengthLegacyEnc(std::string_view input) const; + inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; + + const std::basic_string_view mTranslationArray; + }; class Utf8Encoder { public: - Utf8Encoder(FromType sourceEncoding); - - // Convert to UTF8 from the previously given code page. - std::string getUtf8(const char *input, size_t size); - inline std::string getUtf8(const std::string &str) - { - return getUtf8(str.c_str(), str.size()); - } + explicit Utf8Encoder(FromType sourceEncoding); - // Convert input to UTF8 to the given output string - void toUtf8(std::string& input, std::string& output, size_t size); + /// Convert to UTF8 from the previously given code page. + /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not + /// ASCII-only string. Otherwise returns a view to the input. + std::string_view getUtf8(std::string_view input); - std::string getLegacyEnc(const char *input, size_t size); - inline std::string getLegacyEnc(const std::string &str) - { - return getLegacyEnc(str.c_str(), str.size()); - } + /// Convert from UTF-8 to sourceEncoding. + /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not + /// ASCII-only string. Otherwise returns a view to the input. + std::string_view getLegacyEnc(std::string_view input); private: - void resize(size_t size); - size_t getLength(const char* input, bool &ascii) const; - void copyFromArray(unsigned char chp, char* &out) const; - size_t getLength2(const char* input, bool &ascii) const; - void copyFromArray2(const char*& chp, char* &out) const; - - std::vector mOutput; - const signed char* translationArray; + std::string mBuffer; + StatelessUtf8Encoder mImpl; }; } diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index 37068fd70d..ef0f432075 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -53,14 +53,13 @@ namespace Translation if (!line.empty()) { - std::string utf8Line; - mEncoder->toUtf8(line, utf8Line, line.size()); + line = mEncoder->getUtf8(line); - size_t tab_pos = utf8Line.find('\t'); - if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < utf8Line.size() - 1) + size_t tab_pos = line.find('\t'); + if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) { - std::string key = utf8Line.substr(0, tab_pos); - std::string value = utf8Line.substr(tab_pos + 1); + std::string key = line.substr(0, tab_pos); + std::string value = line.substr(tab_pos + 1); if (!key.empty() && !value.empty()) container.insert(std::make_pair(key, value)); From 139ae9325ada954222cc691144926b519ae7abb1 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 14 Feb 2022 23:28:44 +0100 Subject: [PATCH 41/48] Fix compile errors by using StatelessUtf8Encoder --- components/esm/reader.cpp | 38 +++++--------------------------------- components/esm/reader.hpp | 4 ++-- components/esm4/reader.hpp | 4 ++-- 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/components/esm/reader.cpp b/components/esm/reader.cpp index 1f22b95b47..7fcc2cce3a 100644 --- a/components/esm/reader.cpp +++ b/components/esm/reader.cpp @@ -38,45 +38,17 @@ namespace ESM } bool Reader::getStringImpl(std::string& str, std::size_t size, - Files::IStreamPtr filestream, ToUTF8::Utf8Encoder* encoder, bool hasNull) + Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull) { std::size_t newSize = size; if (encoder) { - if (!hasNull) - newSize += 1; // Utf8Encoder::getLength() expects a null terminator - - std::string tmp; - tmp.resize(newSize); - filestream->read(&tmp[0], size); - if ((std::size_t)filestream->gcount() == size) + std::string input(size, '\0'); + filestream->read(input.data(), size); + if (filestream->gcount() == static_cast(size)) { - if (hasNull) - { - assert (tmp[newSize - 1] == '\0' - && "ESM4::Reader::getString string is not terminated with a null"); - } - else - { - // NOTE: script text of some mods have terminating null; - // unfortunately we just have to deal with it - //if (tmp[newSize - 2] != '\0') - tmp[newSize - 1] = '\0'; // for Utf8Encoder::getLength() - } - - encoder->toUtf8(tmp, str, newSize - 1); - // NOTE: the encoder converts “keep” as “keep†which increases the length, - // which results in below resize() truncating the string - if (str.size() == newSize) // ascii - str.resize(newSize - 1); // don't want the null terminator - else - { - // this is a horrible hack but fortunately there's only a few like this - std::string tmp2(str.data()); - str.swap(tmp2); - } - + encoder->getUtf8(input, ToUTF8::BufferAllocationPolicy::FitToRequiredSize, str); return true; } } diff --git a/components/esm/reader.hpp b/components/esm/reader.hpp index a5e3dd516b..5356d9bd01 100644 --- a/components/esm/reader.hpp +++ b/components/esm/reader.hpp @@ -31,7 +31,7 @@ namespace ESM virtual inline bool hasMoreRecs() const = 0; - virtual inline void setEncoder(ToUTF8::Utf8Encoder* encoder) = 0; + virtual inline void setEncoder(ToUTF8::StatelessUtf8Encoder* encoder) = 0; // used to check for dependencies e.g. CS::Editor::run() virtual inline const std::vector& getGameFiles() const = 0; @@ -53,7 +53,7 @@ namespace ESM protected: bool getStringImpl(std::string& str, std::size_t size, - Files::IStreamPtr filestream, ToUTF8::Utf8Encoder* encoder, bool hasNull = false); + Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false); }; } diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index b3f1070495..c1bf808dd5 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -77,7 +77,7 @@ namespace ESM4 { ReaderContext mCtx; - ToUTF8::Utf8Encoder* mEncoder; + ToUTF8::StatelessUtf8Encoder* mEncoder; std::size_t mFileSize; @@ -140,7 +140,7 @@ namespace ESM4 { inline bool isEsm4() const final { return true; } - inline void setEncoder(ToUTF8::Utf8Encoder* encoder) final { mEncoder = encoder; }; + inline void setEncoder(ToUTF8::StatelessUtf8Encoder* encoder) final { mEncoder = encoder; }; const std::vector& getGameFiles() const final { return mHeader.mMaster; } From b96c41df075d90ea3aba940ebe6bd297925d39ac Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 16 Feb 2022 17:08:39 +0100 Subject: [PATCH 42/48] Initialize string_view with explicit size Otherwise size is detected by null terminating character. --- components/to_utf8/to_utf8.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index f4b52b644b..7e7d3101fc 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -57,13 +57,13 @@ namespace switch (sourceEncoding) { case ToUTF8::WINDOWS_1252: - return ToUTF8::windows_1252; + return {ToUTF8::windows_1252, std::size(ToUTF8::windows_1252)}; case ToUTF8::WINDOWS_1250: - return ToUTF8::windows_1250; + return {ToUTF8::windows_1250, std::size(ToUTF8::windows_1250)}; case ToUTF8::WINDOWS_1251: - return ToUTF8::windows_1251; + return {ToUTF8::windows_1251, std::size(ToUTF8::windows_1251)}; case ToUTF8::CP437: - return ToUTF8::cp437; + return {ToUTF8::cp437, std::size(ToUTF8::cp437)}; } throw std::logic_error("Invalid source encoding: " + std::to_string(sourceEncoding)); } From 83be3826ff398e98f9926db77e67dc61cdbb6336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:19:10 +0200 Subject: [PATCH 43/48] Fix #6618: Crash due to iterator invalidation --- apps/openmw/mwmechanics/aisequence.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index a1f2b5c3e3..0bece95088 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -309,12 +309,18 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if (package->execute(actor, characterController, mAiState, duration)) { - // Put repeating noncombat AI packages on the end of the stack so they can be used again + const auto packageIdx = std::distance(mPackages.begin(), packageIt); + + // Put repeating non-combat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && package->getRepeat()) { package->reset(); mPackages.push_back(package->clone()); } + + // Iterator may have been invalidated by push back ensure its correct. + packageIt = mPackages.begin() + packageIdx; + // To account for the rare case where AiPackage::execute() queued another AI package // (e.g. AiPursue executing a dialogue script that uses startCombat) erase(packageIt); From e60e0b55ebd9688adc6902b88d2950b4fc573e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:19:55 +0200 Subject: [PATCH 44/48] Fix potential another crash --- apps/openmw/mwmechanics/aisequence.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 0bece95088..8506e1daf0 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -291,7 +291,8 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac } } - assert(!mPackages.empty()); + if (mPackages.empty()) + return; if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) { From 0ce29a61314f088202274e631b0ec6720a57a781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 16 Feb 2022 20:21:10 +0200 Subject: [PATCH 45/48] Simplify logic in AiSequence::execute --- apps/openmw/mwmechanics/aisequence.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 8506e1daf0..0cb1e97603 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -238,8 +238,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac return; } - auto packageIt = mPackages.begin(); - MWMechanics::AiPackage* package = packageIt->get(); + auto* package = mPackages.front().get(); if (!package->alwaysActive() && outOfRange) return; @@ -301,8 +300,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat)); } - packageIt = mPackages.begin(); - package = packageIt->get(); + package = mPackages.front().get(); packageTypeId = package->getTypeId(); } @@ -310,8 +308,6 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if (package->execute(actor, characterController, mAiState, duration)) { - const auto packageIdx = std::distance(mPackages.begin(), packageIt); - // Put repeating non-combat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && package->getRepeat()) { @@ -319,12 +315,9 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac mPackages.push_back(package->clone()); } - // Iterator may have been invalidated by push back ensure its correct. - packageIt = mPackages.begin() + packageIdx; - // To account for the rare case where AiPackage::execute() queued another AI package // (e.g. AiPursue executing a dialogue script that uses startCombat) - erase(packageIt); + erase(mPackages.begin()); if (isActualAiPackage(packageTypeId)) mDone = true; } From 7bd4971e0c17ab8567aa4b4296c1106ba67ce611 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 16 Feb 2022 21:58:22 +0100 Subject: [PATCH 46/48] Disallow non-bipedal hand-to-hand refreshes --- apps/openmw/mwmechanics/character.cpp | 6 ++++-- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5f1c3c2d92..25a5ac6aa0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -709,7 +709,9 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (mPtr.getClass().isActor()) refreshHitRecoilAnims(idle); - std::string weap = getWeaponType(mWeaponType)->mShortGroup; + std::string weap; + if (mWeaponType != ESM::Weapon::HandToHand || mPtr.getClass().isBipedal(mPtr)) + weap = getWeaponType(mWeaponType)->mShortGroup; refreshJumpAnims(weap, jump, idle, force); refreshMovementAnims(weap, movement, idle, force); @@ -1127,7 +1129,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const return mAnimation->updateCarriedLeftVisible(weaptype); } -bool CharacterController::updateState(CharacterState& idle) +bool CharacterController::updateState(CharacterState idle) { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 1a0fd42fba..e7f445054e 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -205,7 +205,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void clearAnimQueue(bool clearPersistAnims = false); - bool updateState(CharacterState& idle); + bool updateState(CharacterState idle); void updateIdleStormState(bool inwater); std::string chooseRandomAttackAnimation() const; From de7f9f643994bc7bf1801c1678969895ae3f4866 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 16 Feb 2022 17:24:56 +0100 Subject: [PATCH 47/48] Replace raw for loop by algorithm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To fix compilation error with -D_GLIBCXX_DEBUG: /home/elsid/dev/openmw/apps/openmw/mwdialogue/dialoguemanagerimp.cpp: In member function ‘virtual void MWDialogue::DialogueManager::write(ESM::ESMWriter&, Loading::Listener&) const’: /home/elsid/dev/openmw/apps/openmw/mwdialogue/dialoguemanagerimp.cpp:679:78: error: no matching function for call to ‘__gnu_debug::_Safe_iterator >, std::__debug::set >, std::bidirectional_iterator_tag>::_Safe_iterator(std::__debug::set, Misc::StringUtils::CiComp>::const_iterator)’ 679 | for (std::set::const_iterator iter (mKnownTopics.begin()); | ^ --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 4612e2bdfd..feb3c22035 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -676,11 +676,8 @@ namespace MWDialogue { ESM::DialogueState state; - for (std::set::const_iterator iter (mKnownTopics.begin()); - iter!=mKnownTopics.end(); ++iter) - { - state.mKnownTopics.push_back (*iter); - } + state.mKnownTopics.reserve(mKnownTopics.size()); + std::copy(mKnownTopics.begin(), mKnownTopics.end(), std::back_inserter(state.mKnownTopics)); state.mChangedFactionReaction = mChangedFactionReaction; From d96e2037e39f335c0c40a89fa9dd05a6f34da92b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 17 Feb 2022 01:27:25 +0100 Subject: [PATCH 48/48] Build tests by gcc with enabled stdlibc++ assertions in debug mode This can catch some problems in the code like out of bounds access for string_view element via operator[] when it refers a buffer larger than the view. --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aaf7f233f0..4c5223c212 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -116,11 +116,12 @@ Ubuntu_GCC_tests: Ubuntu_GCC_tests_Debug: extends: Ubuntu_GCC cache: - key: Ubuntu_GCC_tests_Debug.v1 + key: Ubuntu_GCC_tests_Debug.v2 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O0 -D_GLIBCXX_ASSERTIONS artifacts: paths: [] expire_in: 1 minute