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