1 #include "converter.hpp"
2 
3 #include <stdexcept>
4 #include <algorithm>
5 
6 #include <osgDB/WriteFile>
7 
8 #include <components/esm/creaturestate.hpp>
9 #include <components/esm/containerstate.hpp>
10 
11 #include <components/misc/constants.hpp>
12 
13 #include "convertcrec.hpp"
14 #include "convertcntc.hpp"
15 #include "convertscri.hpp"
16 
17 namespace
18 {
19 
convertImage(char * data,int size,int width,int height,GLenum pf,const std::string & out)20     void convertImage(char* data, int size, int width, int height, GLenum pf, const std::string& out)
21     {
22         osg::ref_ptr<osg::Image> image (new osg::Image);
23         image->allocateImage(width, height, 1, pf, GL_UNSIGNED_BYTE);
24         memcpy(image->data(), data, size);
25         image->flipVertical();
26 
27         osgDB::writeImageFile(*image, out);
28     }
29 
30 
convertCellRef(const ESSImport::CellRef & cellref,ESM::ObjectState & objstate)31     void convertCellRef(const ESSImport::CellRef& cellref, ESM::ObjectState& objstate)
32     {
33         objstate.mEnabled = cellref.mEnabled;
34         objstate.mPosition = cellref.mPos;
35         objstate.mRef.mRefNum = cellref.mRefNum;
36         if (cellref.mDeleted)
37             objstate.mCount = 0;
38         convertSCRI(cellref.mSCRI, objstate.mLocals);
39         objstate.mHasLocals = !objstate.mLocals.mVariables.empty();
40 
41         if (cellref.mHasANIS)
42             convertANIS(cellref.mANIS, objstate.mAnimationState);
43     }
44 
isIndexedRefId(const std::string & indexedRefId)45     bool isIndexedRefId(const std::string& indexedRefId)
46     {
47         if (indexedRefId.size() <= 8)
48             return false;
49 
50         if (indexedRefId.find_first_not_of("0123456789") == std::string::npos)
51             return false; // entirely numeric refid, this is a reference to
52                           // a dynamically created record e.g. player-enchanted weapon
53 
54         std::string index = indexedRefId.substr(indexedRefId.size()-8);
55         return index.find_first_not_of("0123456789ABCDEF") == std::string::npos;
56     }
57 
splitIndexedRefId(const std::string & indexedRefId,int & refIndex,std::string & refId)58     void splitIndexedRefId(const std::string& indexedRefId, int& refIndex, std::string& refId)
59     {
60         std::stringstream stream;
61         stream << std::hex << indexedRefId.substr(indexedRefId.size()-8,8);
62         stream >> refIndex;
63 
64         refId = indexedRefId.substr(0,indexedRefId.size()-8);
65     }
66 
convertActorId(const std::string & indexedRefId,ESSImport::Context & context)67     int convertActorId(const std::string& indexedRefId, ESSImport::Context& context)
68     {
69         if (isIndexedRefId(indexedRefId))
70         {
71             int refIndex;
72             std::string refId;
73             splitIndexedRefId(indexedRefId, refIndex, refId);
74 
75             auto it = context.mActorIdMap.find(std::make_pair(refIndex, refId));
76             if (it == context.mActorIdMap.end())
77                 return -1;
78             return it->second;
79         }
80         else if (indexedRefId == "PlayerSaveGame")
81         {
82             return context.mPlayer.mObject.mCreatureStats.mActorId;
83         }
84 
85         return -1;
86     }
87 }
88 
89 namespace ESSImport
90 {
91 
92 
93     struct MAPH
94     {
95         unsigned int size;
96         unsigned int value;
97     };
98 
read(ESM::ESMReader & esm)99     void ConvertFMAP::read(ESM::ESMReader &esm)
100     {
101         MAPH maph;
102         esm.getHNT(maph, "MAPH");
103         std::vector<char> data;
104         esm.getSubNameIs("MAPD");
105         esm.getSubHeader();
106         data.resize(esm.getSubSize());
107         esm.getExact(&data[0], data.size());
108 
109         mGlobalMapImage = new osg::Image;
110         mGlobalMapImage->allocateImage(maph.size, maph.size, 1, GL_RGB, GL_UNSIGNED_BYTE);
111         memcpy(mGlobalMapImage->data(), &data[0], data.size());
112 
113         // to match openmw size
114         // FIXME: filtering?
115         mGlobalMapImage->scaleImage(maph.size*2, maph.size*2, 1, GL_UNSIGNED_BYTE);
116     }
117 
write(ESM::ESMWriter & esm)118     void ConvertFMAP::write(ESM::ESMWriter &esm)
119     {
120         int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly
121                                                        // with the 512x512 map the game has by default
122         int cellSize = mGlobalMapImage->s()/numcells;
123 
124         // Note the upper left corner of the (0,0) cell should be at (width/2, height/2)
125 
126         mContext->mGlobalMapState.mBounds.mMinX = -numcells/2;
127         mContext->mGlobalMapState.mBounds.mMaxX = (numcells-1)/2;
128         mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2;
129         mContext->mGlobalMapState.mBounds.mMaxY = numcells/2;
130 
131         osg::ref_ptr<osg::Image> image2 (new osg::Image);
132         int width = cellSize*numcells;
133         int height = cellSize*numcells;
134         std::vector<unsigned char> data;
135         data.resize(width*height*4, 0);
136 
137         image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
138         memcpy(image2->data(), &data[0], data.size());
139 
140         for (const auto & exploredCell : mContext->mExploredCells)
141         {
142             if (exploredCell.first > mContext->mGlobalMapState.mBounds.mMaxX
143                     || exploredCell.first < mContext->mGlobalMapState.mBounds.mMinX
144                     || exploredCell.second > mContext->mGlobalMapState.mBounds.mMaxY
145                     || exploredCell.second < mContext->mGlobalMapState.mBounds.mMinY)
146             {
147                 // out of bounds, I think this could happen, since the original engine had a fixed-size map
148                 continue;
149             }
150 
151             int imageLeftSrc = mGlobalMapImage->s()/2;
152             int imageTopSrc = mGlobalMapImage->t()/2;
153             imageLeftSrc += exploredCell.first * cellSize;
154             imageTopSrc -= exploredCell.second * cellSize;
155             int imageLeftDst = width/2;
156             int imageTopDst = height/2;
157             imageLeftDst += exploredCell.first * cellSize;
158             imageTopDst -= exploredCell.second * cellSize;
159             for (int x=0; x<cellSize; ++x)
160                 for (int y=0; y<cellSize; ++y)
161                 {
162                     unsigned int col = *(unsigned int*)mGlobalMapImage->data(imageLeftSrc+x, imageTopSrc+y, 0);
163                     *(unsigned int*)image2->data(imageLeftDst+x, imageTopDst+y, 0) = col;
164                 }
165         }
166 
167         std::stringstream ostream;
168         osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png");
169         if (!readerwriter)
170         {
171             std::cerr << "Error: can't write global map image, no png readerwriter found" << std::endl;
172             return;
173         }
174 
175         image2->flipVertical();
176 
177         osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream);
178         if (!result.success())
179         {
180             std::cerr << "Error: can't write global map image: " << result.message() << " code " << result.status() << std::endl;
181             return;
182         }
183 
184         std::string outData = ostream.str();
185         mContext->mGlobalMapState.mImageData = std::vector<char>(outData.begin(), outData.end());
186 
187         esm.startRecord(ESM::REC_GMAP);
188         mContext->mGlobalMapState.save(esm);
189         esm.endRecord(ESM::REC_GMAP);
190     }
191 
read(ESM::ESMReader & esm)192     void ConvertCell::read(ESM::ESMReader &esm)
193     {
194         ESM::Cell cell;
195         bool isDeleted = false;
196 
197         cell.load(esm, isDeleted, false);
198 
199         // I wonder what 0x40 does?
200         if (cell.isExterior() && cell.mData.mFlags & 0x20)
201         {
202             mContext->mGlobalMapState.mMarkers.insert(std::make_pair(cell.mData.mX, cell.mData.mY));
203         }
204 
205         // note if the player is in a nameless exterior cell, we will assign the cellId later based on player position
206         if (cell.mName == mContext->mPlayerCellName)
207         {
208             mContext->mPlayer.mCellId = cell.getCellId();
209         }
210 
211         Cell newcell;
212         newcell.mCell = cell;
213 
214         // fog of war
215         // seems to be a 1-bit pixel format, 16*16 pixels
216         // TODO: add bleeding of FOW into neighbouring cells (openmw handles this by writing to the textures,
217         // MW handles it when rendering only)
218         unsigned char nam8[32];
219         // exterior has 1 NAM8, interior can have multiple ones, and have an extra 4 byte flag at the start
220         // (probably offset of that specific fog texture?)
221         while (esm.isNextSub("NAM8"))
222         {
223             if (cell.isExterior()) // TODO: NAM8 occasionally exists for cells that haven't been explored.
224                                    // are there any flags marking explored cells?
225                 mContext->mExploredCells.insert(std::make_pair(cell.mData.mX, cell.mData.mY));
226 
227             esm.getSubHeader();
228 
229             if (esm.getSubSize() == 36)
230             {
231                 // flag on interiors
232                 esm.skip(4);
233             }
234 
235             esm.getExact(nam8, 32);
236 
237             newcell.mFogOfWar.reserve(16*16);
238             for (int x=0; x<16; ++x)
239             {
240                 for (int y=0; y<16; ++y)
241                 {
242                     size_t pos = x*16+y;
243                     size_t bytepos = pos/8;
244                     assert(bytepos<32);
245                     int bit = pos%8;
246                     newcell.mFogOfWar.push_back(((nam8[bytepos] >> bit) & (0x1)) ? 0xffffffff : 0x000000ff);
247                 }
248             }
249 
250             if (cell.isExterior())
251             {
252                 std::ostringstream filename;
253                 filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga";
254 
255                 convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, GL_RGBA, filename.str());
256             }
257         }
258 
259         // moved reference, not handled yet
260         // NOTE: MVRF can also occur in within normal references (importcellref.cpp)?
261         // this does not match the ESM file implementation,
262         // verify if that can happen with ESM files too
263         while (esm.isNextSub("MVRF"))
264         {
265             esm.skipHSub(); // skip MVRF
266             esm.getSubName();
267             esm.skipHSub(); // skip CNDT
268         }
269 
270         std::vector<CellRef> cellrefs;
271         while (esm.hasMoreSubs() && esm.isNextSub("FRMR"))
272         {
273             CellRef ref;
274             ref.load (esm);
275             cellrefs.push_back(ref);
276         }
277 
278         while (esm.isNextSub("MPCD"))
279         {
280             float notepos[3];
281             esm.getHT(notepos, 3*sizeof(float));
282 
283             // Markers seem to be arranged in a 32*32 grid, notepos has grid-indices.
284             // This seems to be the reason markers can't be placed everywhere in interior cells,
285             // i.e. when the grid is exceeded.
286             // Converting the interior markers correctly could be rather tricky, but is probably similar logic
287             // as used for the FoW texture placement, which we need to figure out anyway
288             notepos[1] += 31.f;
289             notepos[0] += 0.5;
290             notepos[1] += 0.5;
291             notepos[0] = Constants::CellSizeInUnits * notepos[0] / 32.f;
292             notepos[1] = Constants::CellSizeInUnits * notepos[1] / 32.f;
293             if (cell.isExterior())
294             {
295                 notepos[0] += Constants::CellSizeInUnits * cell.mData.mX;
296                 notepos[1] += Constants::CellSizeInUnits * cell.mData.mY;
297             }
298             // TODO: what encoding is this in?
299             std::string note = esm.getHNString("MPNT");
300             ESM::CustomMarker marker;
301             marker.mWorldX = notepos[0];
302             marker.mWorldY = notepos[1];
303             marker.mNote = note;
304             marker.mCell = cell.getCellId();
305             mMarkers.push_back(marker);
306         }
307 
308         newcell.mRefs = cellrefs;
309 
310 
311         if (cell.isExterior())
312             mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell;
313         else
314             mIntCells[cell.mName] = newcell;
315     }
316 
writeCell(const Cell & cell,ESM::ESMWriter & esm)317     void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm)
318     {
319         ESM::Cell esmcell = cell.mCell;
320         esm.startRecord(ESM::REC_CSTA);
321         ESM::CellState csta;
322         csta.mHasFogOfWar = 0;
323         csta.mId = esmcell.getCellId();
324         csta.mId.save(esm);
325         // TODO csta.mLastRespawn;
326         // shouldn't be needed if we respawn on global schedule like in original MW
327         csta.mWaterLevel = esmcell.mWater;
328         csta.save(esm);
329 
330         for (const auto & cellref : cell.mRefs)
331         {
332             ESM::CellRef out (cellref);
333 
334             // TODO: use mContext->mCreatures/mNpcs
335 
336             if (!isIndexedRefId(cellref.mIndexedRefId))
337             {
338                 // non-indexed RefNum, i.e. no CREC/NPCC/CNTC record associated with it
339                 // this could be any type of object really (even creatures/npcs too)
340                 out.mRefID = cellref.mIndexedRefId;
341                 std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
342 
343                 ESM::ObjectState objstate;
344                 objstate.blank();
345                 objstate.mRef = out;
346                 objstate.mRef.mRefID = idLower;
347                 objstate.mHasCustomState = false;
348                 convertCellRef(cellref, objstate);
349                 esm.writeHNT ("OBJE", 0);
350                 objstate.save(esm);
351                 continue;
352             }
353             else
354             {
355                 int refIndex;
356                 splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID);
357 
358                 std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
359 
360                 std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find(
361                             std::make_pair(refIndex, out.mRefID));
362                 if (npccIt != mContext->mNpcChanges.end())
363                 {
364                     ESM::NpcState objstate;
365                     objstate.blank();
366                     objstate.mRef = out;
367                     objstate.mRef.mRefID = idLower;
368                     // TODO: need more micromanagement here so we don't overwrite values
369                     // from the ESM with default values
370                     if (cellref.mHasACDT)
371                         convertACDT(cellref.mACDT, objstate.mCreatureStats);
372                     if (cellref.mHasACSC)
373                         convertACSC(cellref.mACSC, objstate.mCreatureStats);
374                     convertNpcData(cellref, objstate.mNpcStats);
375                     convertNPCC(npccIt->second, objstate);
376                     convertCellRef(cellref, objstate);
377 
378                     objstate.mCreatureStats.mActorId = mContext->generateActorId();
379                     mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
380 
381                     esm.writeHNT ("OBJE", ESM::REC_NPC_);
382                     objstate.save(esm);
383                     continue;
384                 }
385 
386                 std::map<std::pair<int, std::string>, CNTC>::const_iterator cntcIt = mContext->mContainerChanges.find(
387                             std::make_pair(refIndex, out.mRefID));
388                 if (cntcIt != mContext->mContainerChanges.end())
389                 {
390                     ESM::ContainerState objstate;
391                     objstate.blank();
392                     objstate.mRef = out;
393                     objstate.mRef.mRefID = idLower;
394                     convertCNTC(cntcIt->second, objstate);
395                     convertCellRef(cellref, objstate);
396                     esm.writeHNT ("OBJE", ESM::REC_CONT);
397                     objstate.save(esm);
398                     continue;
399                 }
400 
401                 std::map<std::pair<int, std::string>, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find(
402                             std::make_pair(refIndex, out.mRefID));
403                 if (crecIt != mContext->mCreatureChanges.end())
404                 {
405                     ESM::CreatureState objstate;
406                     objstate.blank();
407                     objstate.mRef = out;
408                     objstate.mRef.mRefID = idLower;
409                     // TODO: need more micromanagement here so we don't overwrite values
410                     // from the ESM with default values
411                     if (cellref.mHasACDT)
412                         convertACDT(cellref.mACDT, objstate.mCreatureStats);
413                     if (cellref.mHasACSC)
414                         convertACSC(cellref.mACSC, objstate.mCreatureStats);
415                     convertCREC(crecIt->second, objstate);
416                     convertCellRef(cellref, objstate);
417 
418                     objstate.mCreatureStats.mActorId = mContext->generateActorId();
419                     mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
420 
421                     esm.writeHNT ("OBJE", ESM::REC_CREA);
422                     objstate.save(esm);
423                     continue;
424                 }
425 
426                 std::stringstream error;
427                 error << "Can't find type for " << cellref.mIndexedRefId << std::endl;
428                 throw std::runtime_error(error.str());
429             }
430         }
431 
432         esm.endRecord(ESM::REC_CSTA);
433     }
434 
write(ESM::ESMWriter & esm)435     void ConvertCell::write(ESM::ESMWriter &esm)
436     {
437         for (const auto & cell : mIntCells)
438             writeCell(cell.second, esm);
439 
440         for (const auto & cell : mExtCells)
441             writeCell(cell.second, esm);
442 
443         for (const auto & marker : mMarkers)
444         {
445             esm.startRecord(ESM::REC_MARK);
446             marker.save(esm);
447             esm.endRecord(ESM::REC_MARK);
448         }
449     }
450 
read(ESM::ESMReader & esm)451     void ConvertPROJ::read(ESM::ESMReader& esm)
452     {
453         mProj.load(esm);
454     }
455 
write(ESM::ESMWriter & esm)456     void ConvertPROJ::write(ESM::ESMWriter& esm)
457     {
458         for (const PROJ::PNAM& pnam : mProj.mProjectiles)
459         {
460             if (!pnam.isMagic())
461             {
462                 ESM::ProjectileState out;
463                 convertBaseState(out, pnam);
464 
465                 out.mBowId = pnam.mBowId.toString();
466                 out.mVelocity = pnam.mVelocity;
467                 out.mAttackStrength = pnam.mAttackStrength;
468 
469                 esm.startRecord(ESM::REC_PROJ);
470                 out.save(esm);
471                 esm.endRecord(ESM::REC_PROJ);
472             }
473             else
474             {
475                 ESM::MagicBoltState out;
476                 convertBaseState(out, pnam);
477 
478                 auto it = std::find_if(mContext->mActiveSpells.begin(), mContext->mActiveSpells.end(),
479                                        [&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; });
480 
481                 if (it == mContext->mActiveSpells.end())
482                 {
483                     std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (invalid spell link)" << std::endl;
484                     continue;
485                 }
486 
487                 out.mSpellId = it->mSPDT.mId.toString();
488                 out.mSpeed = pnam.mSpeed * 0.001f; // not sure where this factor comes from
489 
490                 esm.startRecord(ESM::REC_MPRJ);
491                 out.save(esm);
492                 esm.endRecord(ESM::REC_MPRJ);
493             }
494         }
495     }
496 
convertBaseState(ESM::BaseProjectileState & base,const PROJ::PNAM & pnam)497     void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam)
498     {
499         base.mId = pnam.mArrowId.toString();
500         base.mPosition = pnam.mPosition;
501 
502         osg::Quat orient;
503         orient.makeRotate(osg::Vec3f(0,1,0), pnam.mVelocity);
504         base.mOrientation = orient;
505 
506         base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext);
507     }
508 
read(ESM::ESMReader & esm)509     void ConvertSPLM::read(ESM::ESMReader& esm)
510     {
511         mSPLM.load(esm);
512         mContext->mActiveSpells = mSPLM.mActiveSpells;
513     }
514 
write(ESM::ESMWriter & esm)515     void ConvertSPLM::write(ESM::ESMWriter& esm)
516     {
517         std::cerr << "Warning: Skipped active spell conversion (not implemented)" << std::endl;
518     }
519 
520 }
521