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