1 #include "loadcell.hpp"
2 
3 #include <string>
4 #include <list>
5 
6 #include <boost/concept_check.hpp>
7 
8 #include <components/misc/stringops.hpp>
9 
10 #include "esmreader.hpp"
11 #include "esmwriter.hpp"
12 #include "defs.hpp"
13 #include "cellid.hpp"
14 
15 namespace
16 {
17     ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum
adjustRefNum(ESM::RefNum & refNum,ESM::ESMReader & reader)18     void adjustRefNum (ESM::RefNum& refNum, ESM::ESMReader& reader)
19     {
20         unsigned int local = (refNum.mIndex & 0xff000000) >> 24;
21 
22         // If we have an index value that does not make sense, assume that it was an addition
23         // by the present plugin (but a faulty one)
24         if (local && local <= reader.getParentFileIndices().size())
25         {
26             // If the most significant 8 bits are used, then this reference already exists.
27             // In this case, do not spawn a new reference, but overwrite the old one.
28             refNum.mIndex &= 0x00ffffff; // delete old plugin ID
29             refNum.mContentFile = reader.getParentFileIndices()[local-1];
30         }
31         else
32         {
33             // This is an addition by the present plugin. Set the corresponding plugin index.
34             refNum.mContentFile = reader.getIndex();
35         }
36     }
37 }
38 
39 namespace ESM
40 {
41     unsigned int Cell::sRecordId = REC_CELL;
42 
43     // Some overloaded compare operators.
operator ==(const MovedCellRef & ref,const RefNum & refNum)44     bool operator== (const MovedCellRef& ref, const RefNum& refNum)
45     {
46         return ref.mRefNum == refNum;
47     }
48 
operator ==(const CellRef & ref,const RefNum & refNum)49     bool operator== (const CellRef& ref, const RefNum& refNum)
50     {
51         return ref.mRefNum == refNum;
52     }
53 
load(ESMReader & esm,bool & isDeleted,bool saveContext)54     void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext)
55     {
56         loadNameAndData(esm, isDeleted);
57         loadCell(esm, saveContext);
58     }
59 
loadNameAndData(ESMReader & esm,bool & isDeleted)60     void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted)
61     {
62         isDeleted = false;
63 
64         blank();
65 
66         bool hasData = false;
67         bool isLoaded = false;
68         while (!isLoaded && esm.hasMoreSubs())
69         {
70             esm.getSubName();
71             switch (esm.retSubName().intval)
72             {
73                 case ESM::SREC_NAME:
74                     mName = esm.getHString();
75                     break;
76                 case ESM::FourCC<'D','A','T','A'>::value:
77                     esm.getHT(mData, 12);
78                     hasData = true;
79                     break;
80                 case ESM::SREC_DELE:
81                     esm.skipHSub();
82                     isDeleted = true;
83                     break;
84                 default:
85                     esm.cacheSubName();
86                     isLoaded = true;
87                     break;
88             }
89         }
90 
91         if (!hasData)
92             esm.fail("Missing DATA subrecord");
93 
94         mCellId.mPaged = !(mData.mFlags & Interior);
95 
96         if (mCellId.mPaged)
97         {
98             mCellId.mWorldspace = ESM::CellId::sDefaultWorldspace;
99             mCellId.mIndex.mX = mData.mX;
100             mCellId.mIndex.mY = mData.mY;
101         }
102         else
103         {
104             mCellId.mWorldspace = Misc::StringUtils::lowerCase (mName);
105             mCellId.mIndex.mX = 0;
106             mCellId.mIndex.mY = 0;
107         }
108     }
109 
loadCell(ESMReader & esm,bool saveContext)110     void Cell::loadCell(ESMReader &esm, bool saveContext)
111     {
112         bool isLoaded = false;
113         mHasAmbi = false;
114         while (!isLoaded && esm.hasMoreSubs())
115         {
116             esm.getSubName();
117             switch (esm.retSubName().intval)
118             {
119                 case ESM::FourCC<'I','N','T','V'>::value:
120                     int waterl;
121                     esm.getHT(waterl);
122                     mWater = static_cast<float>(waterl);
123                     mWaterInt = true;
124                     break;
125                 case ESM::FourCC<'W','H','G','T'>::value:
126                     esm.getHT(mWater);
127                     mWaterInt = false;
128                     break;
129                 case ESM::FourCC<'A','M','B','I'>::value:
130                     esm.getHT(mAmbi);
131                     mHasAmbi = true;
132                     break;
133                 case ESM::FourCC<'R','G','N','N'>::value:
134                     mRegion = esm.getHString();
135                     break;
136                 case ESM::FourCC<'N','A','M','5'>::value:
137                     esm.getHT(mMapColor);
138                     break;
139                 case ESM::FourCC<'N','A','M','0'>::value:
140                     esm.getHT(mRefNumCounter);
141                     break;
142                 default:
143                     esm.cacheSubName();
144                     isLoaded = true;
145                     break;
146             }
147         }
148 
149         if (saveContext)
150         {
151             mContextList.push_back(esm.getContext());
152             esm.skipRecord();
153         }
154     }
155 
postLoad(ESMReader & esm)156     void Cell::postLoad(ESMReader &esm)
157     {
158         // Save position of the cell references and move on
159         mContextList.push_back(esm.getContext());
160         esm.skipRecord();
161     }
162 
save(ESMWriter & esm,bool isDeleted) const163     void Cell::save(ESMWriter &esm, bool isDeleted) const
164     {
165         esm.writeHNCString("NAME", mName);
166         esm.writeHNT("DATA", mData, 12);
167 
168         if (isDeleted)
169         {
170             esm.writeHNCString("DELE", "");
171             return;
172         }
173 
174         if (mData.mFlags & Interior)
175         {
176             if (mWaterInt) {
177                 int water =
178                     (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
179                 esm.writeHNT("INTV", water);
180             } else {
181                 esm.writeHNT("WHGT", mWater);
182             }
183 
184             if (mData.mFlags & QuasiEx)
185                 esm.writeHNOCString("RGNN", mRegion);
186             else
187             {
188                 // Try to avoid saving ambient lighting information when it's unnecessary.
189                 // This is to fix black lighting in resaved cell records that lack this information.
190                 if (mHasAmbi)
191                     esm.writeHNT("AMBI", mAmbi, 16);
192             }
193         }
194         else
195         {
196             esm.writeHNOCString("RGNN", mRegion);
197             if (mMapColor != 0)
198                 esm.writeHNT("NAM5", mMapColor);
199         }
200 
201         if (mRefNumCounter != 0)
202             esm.writeHNT("NAM0", mRefNumCounter);
203     }
204 
restore(ESMReader & esm,int iCtx) const205     void Cell::restore(ESMReader &esm, int iCtx) const
206     {
207         esm.restoreContext(mContextList.at (iCtx));
208     }
209 
getDescription() const210     std::string Cell::getDescription() const
211     {
212         if (mData.mFlags & Interior)
213             return mName;
214 
215         std::string cellGrid = "(" + std::to_string(mData.mX) + ", " + std::to_string(mData.mY) + ")";
216         if (!mName.empty())
217             return mName + ' ' + cellGrid;
218         // FIXME: should use sDefaultCellname GMST instead, but it's not available in this scope
219         std::string region = !mRegion.empty() ? mRegion : "Wilderness";
220 
221         return region + ' ' + cellGrid;
222     }
223 
getNextRef(ESMReader & esm,CellRef & ref,bool & isDeleted,bool ignoreMoves,MovedCellRef * mref)224     bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool &isDeleted, bool ignoreMoves, MovedCellRef *mref)
225     {
226         isDeleted = false;
227 
228         // TODO: Try and document reference numbering, I don't think this has been done anywhere else.
229         if (!esm.hasMoreSubs())
230             return false;
231 
232         // NOTE: We should not need this check. It is a safety check until we have checked
233         // more plugins, and how they treat these moved references.
234         if (esm.isNextSub("MVRF"))
235         {
236             if (ignoreMoves)
237             {
238                 esm.getHT (mref->mRefNum.mIndex);
239                 esm.getHNOT (mref->mTarget, "CNDT");
240                 adjustRefNum (mref->mRefNum, esm);
241             }
242             else
243             {
244                 // skip rest of cell record (moved references), they are handled elsewhere
245                 esm.skipRecord(); // skip MVRF, CNDT
246                 return false;
247             }
248         }
249 
250         if (esm.peekNextSub("FRMR"))
251         {
252             ref.load (esm, isDeleted);
253 
254             // Identify references belonging to a parent file and adapt the ID accordingly.
255             adjustRefNum (ref.mRefNum, esm);
256             return true;
257         }
258         return false;
259     }
260 
getNextMVRF(ESMReader & esm,MovedCellRef & mref)261     bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
262     {
263         esm.getHT(mref.mRefNum.mIndex);
264         esm.getHNOT(mref.mTarget, "CNDT");
265 
266         adjustRefNum (mref.mRefNum, esm);
267 
268         return true;
269     }
270 
blank()271     void Cell::blank()
272     {
273         mName.clear();
274         mRegion.clear();
275         mWater = 0;
276         mWaterInt = false;
277         mMapColor = 0;
278         mRefNumCounter = 0;
279 
280         mData.mFlags = 0;
281         mData.mX = 0;
282         mData.mY = 0;
283 
284         mHasAmbi = true;
285         mAmbi.mAmbient = 0;
286         mAmbi.mSunlight = 0;
287         mAmbi.mFog = 0;
288         mAmbi.mFogDensity = 0;
289     }
290 
getCellId() const291     const CellId& Cell::getCellId() const
292     {
293         return mCellId;
294     }
295 }
296