1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 *
19 */
20
21 #include "WEDImporter.h"
22
23 #include "GameData.h"
24 #include "Interface.h"
25 #include "PluginMgr.h"
26 #include "TileSetMgr.h"
27
28 #include <cmath>
29 #include <iterator>
30
31 #include "System/swab.h"
32
33 using namespace GemRB;
34
35 //the net sizeof(wed_polygon) is 0x12 but not all compilers know that
36 #define WED_POLYGON_SIZE 0x12
37
WEDImporter(void)38 WEDImporter::WEDImporter(void)
39 {
40 }
41
~WEDImporter(void)42 WEDImporter::~WEDImporter(void)
43 {
44 delete str;
45 }
46
Open(DataStream * stream)47 bool WEDImporter::Open(DataStream* stream)
48 {
49 if (stream == NULL) {
50 return false;
51 }
52 delete str;
53 str = stream;
54 char Signature[8];
55 str->Read( Signature, 8 );
56 if (strncmp( Signature, "WED V1.3", 8 ) != 0) {
57 Log(ERROR, "WEDImporter", "This file is not a valid WED File! Actual signature: %s", Signature);
58 return false;
59 }
60 str->ReadDword( &OverlaysCount );
61 str->ReadDword( &DoorsCount );
62 str->ReadDword( &OverlaysOffset );
63 str->ReadDword( &SecHeaderOffset );
64 str->ReadDword( &DoorsOffset );
65 str->ReadDword( &DoorTilesOffset );
66 // currently unused fields from the original
67 // WORD nVisiblityRange;
68 // WORD nChanceOfRain; - likely unused, since it's present in the ARE file
69 // WORD nChanceOfFog; - most likely unused, since it's present in the ARE file
70 // WORD nChanceOfSnow; - most likely unused, since it's present in the ARE file
71 // DWORD dwFlags;
72
73 str->Seek( OverlaysOffset, GEM_STREAM_START );
74 for (unsigned int i = 0; i < OverlaysCount; i++) {
75 Overlay o;
76 str->ReadWord( &o.Width );
77 str->ReadWord( &o.Height );
78 str->ReadResRef( o.TilesetResRef );
79 str->ReadWord( &o.UniqueTileCount );
80 str->ReadWord( &o.MovementType );
81 str->ReadDword( &o.TilemapOffset );
82 str->ReadDword( &o.TILOffset );
83 overlays.push_back( o );
84 }
85 //Reading the Secondary Header
86 str->Seek( SecHeaderOffset, GEM_STREAM_START );
87 str->ReadDword( &WallPolygonsCount );
88 str->ReadDword( &PolygonsOffset );
89 str->ReadDword( &VerticesOffset );
90 str->ReadDword( &WallGroupsOffset );
91 str->ReadDword( &PLTOffset );
92 ExtendedNight = false;
93
94 ReadWallPolygons();
95 return true;
96 }
97
AddOverlay(TileMap * tm,const Overlay * overlays,bool rain) const98 int WEDImporter::AddOverlay(TileMap *tm, const Overlay *overlays, bool rain) const
99 {
100 ieResRef res;
101 int usedoverlays = 0;
102
103 memcpy(res, overlays->TilesetResRef, sizeof(ieResRef));
104 size_t len = strlen(res);
105 // in BG1 extended night WEDs alway reference the day TIS instead of the matching night TIS
106 if (ExtendedNight && len == 6) {
107 strcat(res, "N");
108 if (!gamedata->Exists(res, IE_TIS_CLASS_ID)) {
109 res[len] = '\0';
110 } else {
111 len++;
112 }
113 }
114 if (rain && len < 8) {
115 strcat(res, "R");
116 //no rain tileset available, rolling back
117 if (!gamedata->Exists(res, IE_TIS_CLASS_ID)) {
118 res[len] = '\0';
119 }
120 }
121 DataStream* tisfile = gamedata->GetResource(res, IE_TIS_CLASS_ID);
122 if (!tisfile) {
123 return -1;
124 }
125 PluginHolder<TileSetMgr> tis(IE_TIS_CLASS_ID);
126 tis->Open( tisfile );
127 TileOverlay *over = new TileOverlay( overlays->Width, overlays->Height );
128 for (int y = 0; y < overlays->Height; y++) {
129 for (int x = 0; x < overlays->Width; x++) {
130 str->Seek( overlays->TilemapOffset +
131 ( y * overlays->Width + x) * 10,
132 GEM_STREAM_START );
133 ieWord startindex, count, secondary;
134 ieByte overlaymask, animspeed;
135 str->ReadWord( &startindex );
136 str->ReadWord( &count );
137 str->ReadWord( &secondary );
138 str->Read( &overlaymask, 1 ); // bFlags in the original
139 str->Read( &animspeed, 1 );
140 // WORD wFlags in the original (currently unused)
141 if (animspeed == 0) {
142 animspeed = ANI_DEFAULT_FRAMERATE;
143 }
144 str->Seek( overlays->TILOffset + ( startindex * 2 ),
145 GEM_STREAM_START );
146 ieWord* indices = ( ieWord* ) calloc( count, sizeof(ieWord) );
147 str->Read( indices, count * sizeof(ieWord) );
148 if( DataStream::BigEndian()) {
149 swabs(indices, count * sizeof(ieWord));
150 }
151 Tile* tile;
152 if (secondary == 0xffff) {
153 tile = tis->GetTile( indices, count );
154 } else {
155 tile = tis->GetTile( indices, 1, &secondary );
156 tile->anim[1]->fps = animspeed;
157 }
158 tile->anim[0]->fps = animspeed;
159 tile->om = overlaymask;
160 usedoverlays |= overlaymask;
161 over->AddTile( tile );
162 free( indices );
163 }
164 }
165
166 if (rain) {
167 tm->AddRainOverlay( over );
168 } else {
169 tm->AddOverlay( over );
170 }
171 return usedoverlays;
172 }
173
174 //this will replace the tileset of an existing tilemap, or create a new one
GetTileMap(TileMap * tm) const175 TileMap* WEDImporter::GetTileMap(TileMap *tm) const
176 {
177 int usedoverlays;
178 bool freenew = false;
179
180 if (!overlays.size()) {
181 return NULL;
182 }
183
184 if (!tm) {
185 tm = new TileMap();
186 freenew = true;
187 }
188
189 usedoverlays = AddOverlay(tm, &overlays.at(0), false);
190 if (usedoverlays == -1) {
191 if (freenew) {
192 delete tm;
193 }
194 return NULL;
195 }
196 // rain_overlays[0] is never used
197 tm->AddRainOverlay( NULL );
198
199 //reading additional overlays
200 int mask=2;
201 for(ieDword i=1;i<OverlaysCount;i++) {
202 //skipping unused overlays
203 if (!(mask&usedoverlays)) {
204 tm->AddOverlay( NULL );
205 tm->AddRainOverlay( NULL );
206 } else {
207 // FIXME: should fix AddOverlay not to load an overlay twice if there's no rain version!!
208 AddOverlay(tm, &overlays.at(i), false);
209 AddOverlay(tm, &overlays.at(i), true);
210 }
211 mask<<=1;
212 }
213 return tm;
214 }
215
GetDoorPolygonCount(ieWord count,ieDword offset)216 void WEDImporter::GetDoorPolygonCount(ieWord count, ieDword offset)
217 {
218 ieDword basecount = offset-PolygonsOffset;
219 if (basecount%WED_POLYGON_SIZE) {
220 basecount+=WED_POLYGON_SIZE;
221 Log(WARNING, "WEDImporter", "Found broken door polygon header!");
222 }
223 ieDword polycount = basecount/WED_POLYGON_SIZE+count-WallPolygonsCount;
224 if (polycount>DoorPolygonsCount) {
225 DoorPolygonsCount=polycount;
226 }
227 }
228
ClosedDoorPolygons() const229 WallPolygonGroup WEDImporter::ClosedDoorPolygons() const
230 {
231 size_t index = (ClosedPolyOffset-PolygonsOffset)/WED_POLYGON_SIZE;
232 size_t count = ClosedPolyCount;
233 return MakeGroupFromTableEntries(index, count);
234 }
235
OpenDoorPolygons() const236 WallPolygonGroup WEDImporter::OpenDoorPolygons() const
237 {
238 size_t index = (OpenPolyOffset-PolygonsOffset)/WED_POLYGON_SIZE;
239 size_t count = OpenPolyCount;
240 return MakeGroupFromTableEntries(index, count);
241 }
242
GetDoorIndices(char * ResRef,int * count,bool & BaseClosed)243 ieWord* WEDImporter::GetDoorIndices(char* ResRef, int* count, bool& BaseClosed)
244 {
245 ieWord DoorClosed, DoorTileStart, DoorTileCount, * DoorTiles;
246 ieResRef Name;
247 unsigned int i;
248
249 for (i = 0; i < DoorsCount; i++) {
250 str->Seek( DoorsOffset + ( i * 0x1A ), GEM_STREAM_START );
251 str->ReadResRef( Name );
252 if (strnicmp( Name, ResRef, 8 ) == 0)
253 break;
254 }
255 //The door has no representation in the WED file
256 if (i == DoorsCount) {
257 *count = 0;
258 Log(ERROR, "WEDImporter", "Found door without WED entry!");
259 return NULL;
260 }
261
262 str->ReadWord( &DoorClosed );
263 str->ReadWord( &DoorTileStart );
264 str->ReadWord( &DoorTileCount );
265 str->ReadWord( &OpenPolyCount );
266 str->ReadWord( &ClosedPolyCount );
267 str->ReadDword( &OpenPolyOffset );
268 str->ReadDword( &ClosedPolyOffset );
269
270 //Reading Door Tile Cells
271 str->Seek( DoorTilesOffset + ( DoorTileStart * 2 ), GEM_STREAM_START );
272 DoorTiles = ( ieWord* ) calloc( DoorTileCount, sizeof( ieWord) );
273 str->Read( DoorTiles, DoorTileCount * sizeof( ieWord ) );
274 if( DataStream::BigEndian()) {
275 swabs(DoorTiles, DoorTileCount * sizeof(ieWord));
276 }
277 *count = DoorTileCount;
278 BaseClosed = DoorClosed != 0;
279 return DoorTiles;
280 }
281
ReadWallPolygons()282 void WEDImporter::ReadWallPolygons()
283 {
284 for (ieDword i = 0; i < DoorsCount; i++) {
285 constexpr uint8_t doorSize = 0x1A;
286 constexpr uint8_t polyOffset = 14;
287 str->Seek( DoorsOffset + polyOffset + ( i * doorSize ), GEM_STREAM_START );
288
289 str->ReadWord( &OpenPolyCount );
290 str->ReadWord( &ClosedPolyCount );
291 str->ReadDword( &OpenPolyOffset );
292 str->ReadDword( &ClosedPolyOffset );
293
294 GetDoorPolygonCount(OpenPolyCount, OpenPolyOffset);
295 GetDoorPolygonCount(ClosedPolyCount, ClosedPolyOffset);
296 }
297
298 ieDword polygonCount = WallPolygonsCount+DoorPolygonsCount;
299
300 struct wed_polygon {
301 ieDword FirstVertex;
302 ieDword CountVertex;
303 ieWord Flags; // two bytes in the original: type and height with height currently unused
304 ieWord MinX, MaxX, MinY, MaxY;
305 };
306
307 polygonTable.resize(polygonCount);
308 wed_polygon *PolygonHeaders = new wed_polygon[polygonCount];
309
310 str->Seek (PolygonsOffset, GEM_STREAM_START);
311
312 for (ieDword i=0; i < polygonCount; i++) {
313 str->ReadDword ( &PolygonHeaders[i].FirstVertex);
314 str->ReadDword ( &PolygonHeaders[i].CountVertex);
315 str->ReadWord ( &PolygonHeaders[i].Flags);
316 str->ReadWord ( &PolygonHeaders[i].MinX);
317 str->ReadWord ( &PolygonHeaders[i].MaxX);
318 str->ReadWord ( &PolygonHeaders[i].MinY);
319 str->ReadWord ( &PolygonHeaders[i].MaxY);
320 }
321
322 for (ieDword i=0; i < polygonCount; i++) {
323 str->Seek (PolygonHeaders[i].FirstVertex*4+VerticesOffset, GEM_STREAM_START);
324 //compose polygon
325 ieDword count = PolygonHeaders[i].CountVertex;
326 if (count<3) {
327 //danger, danger
328 continue;
329 }
330 ieDword flags = PolygonHeaders[i].Flags&~(WF_BASELINE|WF_HOVER);
331 Point base0, base1;
332 if (PolygonHeaders[i].Flags&WF_HOVER) {
333 count-=2;
334 ieWord x,y;
335 str->ReadWord (&x);
336 str->ReadWord (&y);
337 base0 = Point(x,y);
338 str->ReadWord (&x);
339 str->ReadWord (&y);
340 base1 = Point(x,y);
341 flags |= WF_BASELINE;
342 }
343 Point *points = new Point[count];
344 str->Read (points, count * sizeof (Point) );
345 if( DataStream::BigEndian()) {
346 swabs(points, count * sizeof (Point));
347 }
348
349 if (!(flags&WF_BASELINE) ) {
350 if (PolygonHeaders[i].Flags&WF_BASELINE) {
351 base0 = points[0];
352 base1 = points[1];
353 flags |= WF_BASELINE;
354 }
355 }
356 Region rgn;
357 rgn.x = PolygonHeaders[i].MinX;
358 rgn.y = PolygonHeaders[i].MinY;
359 rgn.w = PolygonHeaders[i].MaxX - PolygonHeaders[i].MinX;
360 rgn.h = PolygonHeaders[i].MaxY - PolygonHeaders[i].MinY;
361
362 if (!rgn.Dimensions().IsEmpty()) { // PST AR0600 is known to have a polygon with 0 height
363 polygonTable[i] = std::make_shared<Wall_Polygon>(points, count, &rgn);
364 if (flags&WF_BASELINE) {
365 polygonTable[i]->SetBaseline(base0, base1);
366 }
367 if (core->HasFeature(GF_PST_STATE_FLAGS)) {
368 flags |= WF_COVERANIMS;
369 }
370 polygonTable[i]->SetPolygonFlag(flags);
371 }
372
373 delete [] points;
374 }
375 delete [] PolygonHeaders;
376 }
377
MakeGroupFromTableEntries(size_t idx,size_t cnt) const378 WallPolygonGroup WEDImporter::MakeGroupFromTableEntries(size_t idx, size_t cnt) const
379 {
380 auto begin = polygonTable.begin() + idx;
381 auto end = begin + cnt;
382 WallPolygonGroup grp;
383 std::copy_if(begin, end, std::back_inserter(grp), [](const std::shared_ptr<Wall_Polygon>& wp) {
384 return wp != nullptr;
385 });
386 return grp;
387 }
388
GetWallGroups() const389 std::vector<WallPolygonGroup> WEDImporter::GetWallGroups() const
390 {
391 str->Seek (PLTOffset, GEM_STREAM_START);
392 size_t PLTSize = (VerticesOffset - PLTOffset) / 2;
393 std::vector<ieWord> PLT(PLTSize);
394
395 for (ieWord& idx : PLT) {
396 str->ReadWord (&idx);
397 }
398
399 size_t groupSize = ceilf(overlays[0].Width/10.0f) * ceilf(overlays[0].Height/7.5f);
400 std::vector<WallPolygonGroup> polygonGroups;
401 polygonGroups.reserve(groupSize);
402
403 str->Seek (WallGroupsOffset, GEM_STREAM_START);
404 for (size_t i = 0; i < groupSize; ++i) {
405 ieWord index, count;
406 str->ReadWord (&index);
407 str->ReadWord (&count);
408
409 polygonGroups.emplace_back(WallPolygonGroup());
410 WallPolygonGroup& group = polygonGroups.back();
411
412 for (ieWord i = index; i < index + count; ++i) {
413 ieWord polyIndex = PLT[i];
414 auto wp = polygonTable[polyIndex];
415 if (wp) {
416 group.push_back(wp);
417 }
418 }
419 }
420
421 return polygonGroups;
422 }
423
424 #include "plugindef.h"
425
426 GEMRB_PLUGIN(0x7486BE7, "WED File Importer")
427 PLUGIN_CLASS(IE_WED_CLASS_ID, WEDImporter)
428 END_PLUGIN()
429