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 "AREImporter.h"
22 
23 #include "strrefs.h"
24 #include "ie_cursors.h"
25 
26 #include "ActorMgr.h"
27 #include "Ambient.h"
28 #include "AnimationFactory.h"
29 #include "DataFileMgr.h"
30 #include "DisplayMessage.h"
31 #include "EffectMgr.h"
32 #include "Game.h"
33 #include "GameData.h"
34 #include "ImageMgr.h"
35 #include "Interface.h"
36 #include "Palette.h"
37 #include "PluginMgr.h"
38 #include "ProjectileServer.h"
39 #include "RNG.h"
40 #include "TileMapMgr.h"
41 #include "GameScript/GameScript.h"
42 #include "Scriptable/Container.h"
43 #include "Scriptable/Door.h"
44 #include "Scriptable/InfoPoint.h"
45 #include "System/FileStream.h"
46 #include "System/SlicedStream.h"
47 
48 #include <stdlib.h>
49 #ifdef ANDROID
50 // android lacks mblen
wctomb(char * s,wchar_t wc)51 int wctomb(char *s, wchar_t wc) { return wcrtomb(s, wc, NULL); }
mbtowc(wchar_t * pwc,const char * s,size_t n)52 int mbtowc(wchar_t *pwc, const char *s, size_t n) { return mbrtowc(pwc, s, n, NULL); }
53 #endif
54 
55 using namespace GemRB;
56 
57 #define DEF_OPEN   0
58 #define DEF_CLOSE  1
59 #define DEF_HOPEN  2
60 #define DEF_HCLOSE 3
61 
62 #define DEF_COUNT 4
63 
64 //something non signed, non ascii
65 #define UNINITIALIZED_BYTE  0x11
66 
67 static ieResRef Sounds[DEF_COUNT] = {
68 	{UNINITIALIZED_BYTE},
69 };
70 
71 struct ResRefToStrRef {
72 	ieResRef areaName;
73 	ieStrRef text;
74 	bool trackFlag;
75 	int difficulty;
76 };
77 
78 Holder<DataFileMgr> INInote;
79 ResRefToStrRef *tracks = NULL;
80 int trackcount = 0;
81 
ReleaseMemory()82 static void ReleaseMemory()
83 {
84 	INInote.release();
85 
86 	delete [] tracks;
87 	tracks = NULL;
88 }
89 
ReadAutonoteINI()90 static void ReadAutonoteINI()
91 {
92 	INInote = PluginHolder<DataFileMgr>(IE_INI_CLASS_ID);
93 	char tINInote[_MAX_PATH];
94 	PathJoin( tINInote, core->GamePath, "autonote.ini", NULL );
95 	FileStream* fs = FileStream::OpenFile( tINInote );
96 	INInote->Open(fs);
97 }
98 
GetTrackString(const ieResRef areaName)99 static int GetTrackString(const ieResRef areaName)
100 {
101 	int i;
102 	bool trackflag = displaymsg->HasStringReference(STR_TRACKING);
103 
104 	if (!tracks) {
105 		AutoTable tm("tracking", true);
106 		if (!tm.ok())
107 			return -1;
108 		trackcount = tm->GetRowCount();
109 		tracks = new ResRefToStrRef[trackcount];
110 		for (i=0;i<trackcount;i++) {
111 			const char *poi = tm->QueryField(i,0);
112 			if (poi[0]=='O' && poi[1]=='_') {
113 				tracks[i].trackFlag=false;
114 				poi+=2;
115 			} else {
116 				tracks[i].trackFlag=trackflag;
117 			}
118 			tracks[i].text = ieStrRef(atoi(poi));
119 			tracks[i].difficulty=atoi(tm->QueryField(i,1));
120 			strnlwrcpy(tracks[i].areaName, tm->GetRowName(i), 8 );
121 		}
122 	}
123 
124 	for (i=0;i<trackcount;i++) {
125 		if (!strnicmp(tracks[i].areaName, areaName, 8)) {
126 			return i;
127 		}
128 	}
129 	return -1;
130 }
131 
AREImporter(void)132 AREImporter::AREImporter(void)
133 {
134 	EntrancesOffset = ContainersOffset = InfoPointsOffset = SpawnOffset = 0;
135 	EntrancesCount = ContainersCount = InfoPointsCount = SpawnCount = 0;
136 	ItemsOffset = VariablesOffset = AmbiOffset = TileOffset = TrapOffset = 0;
137 	ItemsCount = VariablesCount = AmbiCount = TileCount = TrapCount = 0;
138 	ActorCount = VerticesCount = NoteCount = 0;
139 	ActorOffset = VerticesOffset = NoteOffset = EffectOffset = 0;
140 	AreaDifficulty = AreaFlags = AreaType = 0;
141 	SongHeader = RestHeader = bigheader = 0;
142 	WRain = WSnow = WFog = WLightning = WUnknown = 0;
143 	EmbeddedCreOffset = AnimOffset = AnimCount = DoorsOffset = DoorsCount = 0;
144 	ExploredBitmapSize = ExploredBitmapOffset = 0;
145 	LastSave = 0;
146 
147 	str = NULL;
148 	if (Sounds[0][0] == UNINITIALIZED_BYTE) {
149 		memset( Sounds, 0, sizeof( Sounds ) );
150 		AutoTable at("defsound");
151 		if (at.ok()) {
152 			for (int i = 0; i < DEF_COUNT; i++) {
153 				strncpy( Sounds[i], at->QueryField( i, 0 ), 8 );
154 				if(Sounds[i][0]=='*') {
155 					Sounds[i][0]=0;
156 				}
157 			}
158 		}
159 	}
160 }
161 
~AREImporter(void)162 AREImporter::~AREImporter(void)
163 {
164 	delete str;
165 	Sounds[0][0]=UNINITIALIZED_BYTE;
166 }
167 
Open(DataStream * stream)168 bool AREImporter::Open(DataStream* stream)
169 {
170 	if (stream == NULL) {
171 		return false;
172 	}
173 	delete str;
174 	str = stream;
175 	char Signature[8];
176 	str->Read( Signature, 8 );
177 
178 	if (strncmp( Signature, "AREAV1.0", 8 ) != 0) {
179 		if (strncmp( Signature, "AREAV9.1", 8 ) != 0) {
180 			return false;
181 		} else {
182 			bigheader = 16;
183 		}
184 	} else {
185 		bigheader = 0;
186 	}
187 	//TEST VERSION: SKIPPING VALUES
188 	str->ReadResRef( WEDResRef );
189 	str->ReadDword( &LastSave );
190 	str->ReadDword( &AreaFlags );
191 	//skipping bg1 area connection fields
192 	str->Seek( 0x48, GEM_STREAM_START );
193 	str->ReadWord( &AreaType );
194 	str->ReadWord( &WRain );
195 	str->ReadWord( &WSnow );
196 	str->ReadWord( &WFog );
197 	str->ReadWord( &WLightning );
198 	// unused wind speed, TODO: EEs use it for transparency
199 	// a single byte was re-purposed to control the alpha on the stencil water for more or less transparency.
200 	// If you set it to 0, then the water should be appropriately 50% transparent.
201 	// If you set it to any other number, it will be that transparent.
202 	// It's 1 byte, so setting it to 128 you'll have the same as the default of 0
203 	str->ReadWord( &WUnknown );
204 
205 	AreaDifficulty = 0;
206 	if (bigheader) {
207 		// are9.1 difficulty bits for level2/level3
208 		// ar4000 for example has a bunch of actors for all area difficulty levels, so these here are likely just the allowed levels
209 		AreaDifficulty = 1;
210 		ieByte tmp = 0;
211 		int avgPartyLevel = core->GetGame()->GetTotalPartyLevel(false) / core->GetGame()->GetPartySize(false);
212 		str->Read(&tmp, 1); // 0x54
213 		if (tmp && avgPartyLevel >= tmp) {
214 			AreaDifficulty = 2;
215 		}
216 		tmp = 0;
217 		str->Read(&tmp, 1); // 0x55
218 		if (tmp && avgPartyLevel >= tmp) {
219 			AreaDifficulty = 4;
220 		}
221 		// 0x56 held the average party level at load time (usually 1, since it had no access yet),
222 		// but we resolve everything here and store AreaDifficulty instead
223 	}
224 	//bigheader gap is here
225 	str->Seek( 0x54 + bigheader, GEM_STREAM_START );
226 	str->ReadDword( &ActorOffset );
227 	str->ReadWord( &ActorCount );
228 	str->ReadWord( &InfoPointsCount );
229 	str->ReadDword( &InfoPointsOffset );
230 	str->ReadDword( &SpawnOffset );
231 	str->ReadDword( &SpawnCount );
232 	str->ReadDword( &EntrancesOffset );
233 	str->ReadDword( &EntrancesCount );
234 	str->ReadDword( &ContainersOffset );
235 	str->ReadWord( &ContainersCount );
236 	str->ReadWord( &ItemsCount );
237 	str->ReadDword( &ItemsOffset );
238 	str->ReadDword( &VerticesOffset );
239 	str->ReadWord( &VerticesCount );
240 	str->ReadWord( &AmbiCount );
241 	str->ReadDword( &AmbiOffset );
242 	str->ReadDword( &VariablesOffset );
243 	str->ReadDword( &VariablesCount );
244 	ieDword tmp;
245 	str->ReadDword( &tmp );
246 	str->ReadResRef( Script );
247 	str->ReadDword( &ExploredBitmapSize );
248 	str->ReadDword( &ExploredBitmapOffset );
249 	str->ReadDword( &DoorsCount );
250 	str->ReadDword( &DoorsOffset );
251 	str->ReadDword( &AnimCount );
252 	str->ReadDword( &AnimOffset );
253 	str->ReadDword( &TileCount );
254 	str->ReadDword( &TileOffset );
255 	str->ReadDword( &SongHeader );
256 	str->ReadDword( &RestHeader );
257 	if (core->HasFeature(GF_AUTOMAP_INI) ) {
258 		str->ReadDword( &tmp ); //skipping unknown in PST
259 	}
260 	str->ReadDword( &NoteOffset );
261 	str->ReadDword( &NoteCount );
262 	str->ReadDword( &TrapOffset );
263 	str->ReadDword( &TrapCount );
264 	str->ReadResRef( Dream1 );
265 	str->ReadResRef( Dream2 );
266 	return true;
267 }
268 
269 //alter a map to the night/day version in case of an extended night map (bg2 specific)
270 //return true, if change happened, in which case a movie is played by the Game object
ChangeMap(Map * map,bool day_or_night)271 bool AREImporter::ChangeMap(Map *map, bool day_or_night)
272 {
273 	ieResRef TmpResRef;
274 
275 	//get the right tilemap name
276 	if (day_or_night) {
277 		memcpy( TmpResRef, map->WEDResRef, 9);
278 	} else {
279 		snprintf( TmpResRef, 9, "%.7sN", map->WEDResRef);
280 	}
281 	PluginHolder<TileMapMgr> tmm(IE_WED_CLASS_ID);
282 	DataStream* wedfile = gamedata->GetResource( TmpResRef, IE_WED_CLASS_ID );
283 	tmm->Open( wedfile );
284 	tmm->SetExtendedNight( !day_or_night );
285 
286 	//alter the tilemap object, not all parts of that object are coming from the wed/tis
287 	//this is why we have to be careful
288 	//TODO: consider refactoring TileMap so invariable data coming from the .ARE file
289 	//are not handled by it, then TileMap could be simply swapped
290 	TileMap* tm = map->GetTileMap();
291 
292 	if (tm) {
293 		tm->ClearOverlays();
294 	}
295 	tm = tmm->GetTileMap(tm);
296 	if (!tm) {
297 		Log(ERROR, "AREImporter", "No tile map available.");
298 		return false;
299 	}
300 
301 	// Small map for MapControl
302 	ResourceHolder<ImageMgr> sm = GetResourceHolder<ImageMgr>(TmpResRef);
303 
304 	// night small map is *optional*!
305 	if (!sm) {
306 		//fall back to day minimap
307 		sm = GetResourceHolder<ImageMgr>(map->WEDResRef);
308 	}
309 
310 	//the map state was altered, no need to hold this off for any later
311 	map->DayNight = day_or_night;
312 
313 	//get the lightmap name
314 	if (day_or_night) {
315 		snprintf( TmpResRef, 9, "%.6sLM", map->WEDResRef);
316 	} else {
317 		snprintf( TmpResRef, 9, "%.6sLN", map->WEDResRef);
318 	}
319 
320 	ResourceHolder<ImageMgr> lm = GetResourceHolder<ImageMgr>(TmpResRef);
321 	if (!lm) {
322 		Log(ERROR, "AREImporter", "No lightmap available.");
323 		return false;
324 	}
325 
326 	//alter the lightmap and the minimap (the tileset was already swapped)
327 	map->ChangeTileMap(lm->GetImage(), sm?sm->GetSprite2D():NULL);
328 
329 	// update the tiles and tilecount (eg. door0304 in Edwin's Docks (ar0300) entrance
330 	for (size_t i = 0; i < tm->GetDoorCount(); i++) {
331 		Door* door = tm->GetDoor(i);
332 		bool baseClosed, oldOpen = door->IsOpen();
333 		int count;
334 		unsigned short *indices = tmm->GetDoorIndices(door->ID, &count, baseClosed);
335 		door->SetTiles(indices, count);
336 		// reset open state to the one in the old wed
337 		door->SetDoorOpen(oldOpen, false, 0);
338 	}
339 
340 	return true;
341 }
342 
343 // everything is the same up to DOOR_FOUND, but then it gets messy (see Door.h)
344 static const ieDword gemrbDoorFlags[6] = { DOOR_TRANSPARENT, DOOR_KEY, DOOR_SLIDE, DOOR_USEUPKEY, DOOR_LOCKEDINFOTEXT, DOOR_WARNINGINFOTEXT };
345 // the last two are 0, since they are outside the original bit range, so all the constants can coexist
346 static const ieDword iwd2DoorFlags[6] = { DOOR_LOCKEDINFOTEXT, DOOR_TRANSPARENT, DOOR_WARNINGINFOTEXT, DOOR_KEY, 0, 0 };
FixIWD2DoorFlags(ieDword Flags,bool reverse)347 inline ieDword FixIWD2DoorFlags(ieDword Flags, bool reverse)
348 {
349 	ieDword bit, otherbit, maskOff = 0, maskOn= 0;
350 	for (int i=0; i < 6; i++) {
351 		if (!reverse) {
352 			bit = gemrbDoorFlags[i];
353 			otherbit = iwd2DoorFlags[i];
354 		} else {
355 			bit = iwd2DoorFlags[i];
356 			otherbit = gemrbDoorFlags[i];
357 		}
358 		if (Flags & bit) {
359 			maskOff |= bit;
360 			maskOn |= otherbit;
361 		}
362 	}
363 	// delayed bad bit removal due to chain overlapping
364 	return (Flags & ~maskOff) | maskOn;
365 }
366 
SetupMainAmbients(Map * map,bool day_or_night)367 static Ambient* SetupMainAmbients(Map *map, bool day_or_night) {
368 	ieResRef *main1[2] = { &map->SongHeader.MainNightAmbient1, &map->SongHeader.MainDayAmbient1 };
369 	ieResRef *main2[2] = { &map->SongHeader.MainNightAmbient2, &map->SongHeader.MainDayAmbient2 };
370 	ieDword vol[2] = { map->SongHeader.MainNightAmbientVol, map->SongHeader.MainDayAmbientVol };
371 	ieResRef mainAmbient = "";
372 	if (*main1[day_or_night][0]) {
373 		CopyResRef(mainAmbient, *main1[day_or_night]);
374 	}
375 	// the second ambient is always longer, was meant as a memory optimisation w/ IE_AMBI_HIMEM
376 	// however that was implemented only for the normal ambients
377 	// nowadays we can just skip the first
378 	if (*main2[day_or_night][0]) {
379 		CopyResRef(mainAmbient, *main2[day_or_night]);
380 	}
381 	if (!mainAmbient[0]) return NULL;
382 
383 	Ambient *ambi = new Ambient();
384 	ambi->flags = IE_AMBI_ENABLED | IE_AMBI_LOOPING | IE_AMBI_MAIN | IE_AMBI_NOSAVE;
385 	ambi->gain = vol[day_or_night];
386 	// sounds and name
387 	char *sound = (char *) malloc(9);
388 	memcpy(sound, mainAmbient, 9);
389 	ambi->sounds.push_back(sound);
390 	memcpy(ambi->name, sound, 9);
391 	ambi->appearance = (1<<25) - 1; // default to all 24 bits enabled, one per hour
392 	ambi->radius = 50; // REFERENCE_DISTANCE
393 	return ambi;
394 }
395 
GetMap(const char * ResRef,bool day_or_night)396 Map* AREImporter::GetMap(const char *ResRef, bool day_or_night)
397 {
398 	unsigned int i,x;
399 
400 	// if this area does not have extended night, force it to day mode
401 	if (!(AreaFlags & AT_EXTENDED_NIGHT))
402 		day_or_night = true;
403 
404 	Map* map = new Map();
405 	if (!map) {
406 		Log(ERROR, "AREImporter", "Can't allocate map (out of memory).");
407 		return NULL;
408 	}
409 	if (core->SaveAsOriginal) {
410 		map->version = bigheader;
411 	}
412 
413 	map->AreaFlags = AreaFlags;
414 	map->Rain = WRain;
415 	map->Snow = WSnow;
416 	map->Fog = WFog;
417 	map->Lightning = WLightning;
418 	map->AreaType = AreaType;
419 	map->DayNight = day_or_night;
420 	map->AreaDifficulty = AreaDifficulty;
421 	strnlwrcpy( map->WEDResRef, WEDResRef, 8);
422 	strnlwrcpy( map->Dream[0], Dream1, 8);
423 	strnlwrcpy( map->Dream[1], Dream2, 8);
424 
425 	//we have to set this here because the actors will receive their
426 	//current area setting here, areas' 'scriptname' is their name
427 	map->SetScriptName( ResRef );
428 	int idx = GetTrackString( ResRef );
429 	if (idx>=0) {
430 		map->SetTrackString(tracks[idx].text, tracks[idx].trackFlag, tracks[idx].difficulty);
431 	} else {
432 		map->SetTrackString(ieStrRef(-1), false, 0);
433 	}
434 
435 	if (!core->IsAvailable( IE_WED_CLASS_ID )) {
436 		Log(ERROR, "AREImporter", "No tile map manager available.");
437 		delete map;
438 		return NULL;
439 	}
440 	ieResRef TmpResRef;
441 
442 	if (day_or_night) {
443 		memcpy( TmpResRef, WEDResRef, 9);
444 	} else {
445 		snprintf( TmpResRef, 9, "%.7sN", WEDResRef);
446 	}
447 
448 	PluginHolder<TileMapMgr> tmm(IE_WED_CLASS_ID);
449 	DataStream* wedfile = gamedata->GetResource( WEDResRef, IE_WED_CLASS_ID );
450 	tmm->Open( wedfile );
451 
452 	//there was no tilemap set yet, so lets just send a NULL
453 	TileMap* tm = tmm->GetTileMap(NULL);
454 	if (!tm) {
455 		Log(ERROR, "AREImporter", "No tile map available.");
456 		delete map;
457 		return NULL;
458 	}
459 
460 	// Small map for MapControl
461 	ResourceHolder<ImageMgr> sm = GetResourceHolder<ImageMgr>(TmpResRef);
462 	if (!sm) {
463 		//fall back to day minimap
464 		sm = GetResourceHolder<ImageMgr>(map->WEDResRef);
465 	}
466 
467 	//if the Script field is empty, the area name will be copied into it on first load
468 	//this works only in the iwd branch of the games
469 	if (!Script[0] && core->HasFeature(GF_FORCE_AREA_SCRIPT) ) {
470 		memcpy(Script, ResRef, sizeof(ieResRef) );
471 	}
472 
473 	if (Script[0]) {
474 		//for some reason the area's script is run from the last slot
475 		//at least one area script depends on this, if you need something
476 		//more customisable, add a game flag
477 		map->Scripts[MAX_SCRIPTS-1] = new GameScript( Script, map );
478 	}
479 
480 	if (day_or_night) {
481 		snprintf( TmpResRef, 9, "%.6sLM", WEDResRef);
482 	} else {
483 		snprintf( TmpResRef, 9, "%.6sLN", WEDResRef);
484 	}
485 
486 	ResourceHolder<ImageMgr> lm = GetResourceHolder<ImageMgr>(TmpResRef);
487 	if (!lm) {
488 		Log(ERROR, "AREImporter", "No lightmap available.");
489 		return NULL;
490 	}
491 
492 	snprintf( TmpResRef, 9, "%.6sSR", WEDResRef);
493 
494 	ResourceHolder<ImageMgr> sr = GetResourceHolder<ImageMgr>(TmpResRef);
495 	if (!sr) {
496 		Log(ERROR, "AREImporter", "No searchmap available.");
497 		return NULL;
498 	}
499 
500 	snprintf( TmpResRef, 9, "%.6sHT", WEDResRef);
501 
502 	ResourceHolder<ImageMgr> hm = GetResourceHolder<ImageMgr>(TmpResRef);
503 	if (!hm) {
504 		Log(ERROR, "AREImporter", "No heightmap available.");
505 		return NULL;
506 	}
507 
508 	map->AddTileMap( tm, lm->GetImage(), sr->GetBitmap(), sm ? sm->GetSprite2D() : NULL, hm->GetBitmap() );
509 
510 	Log(DEBUG, "AREImporter", "Loading songs");
511 	str->Seek( SongHeader, GEM_STREAM_START );
512 	//5 is the number of song indices
513 	for (i = 0; i < MAX_RESCOUNT; i++) {
514 		str->ReadDword( map->SongHeader.SongList + i );
515 	}
516 
517 	str->ReadResRef(map->SongHeader.MainDayAmbient1);
518 	str->ReadResRef(map->SongHeader.MainDayAmbient2);
519 	str->ReadDword(&map->SongHeader.MainDayAmbientVol);
520 
521 	str->ReadResRef(map->SongHeader.MainNightAmbient1);
522 	str->ReadResRef(map->SongHeader.MainNightAmbient2);
523 	str->ReadDword(&map->SongHeader.MainNightAmbientVol);
524 
525 	// check for existence of main ambients (bg1)
526 	#define DAY_BITS (((1<<18) - 1) ^ ((1<<6) - 1)) // day: bits 6-18 per DLTCEP
527 	Ambient *ambi = SetupMainAmbients(map, true);
528 	if (ambi) {
529 		// schedule for day/night
530 		// if the two ambients are the same, just add one, so there's no restart
531 		if (memcmp(map->SongHeader.MainDayAmbient2, map->SongHeader.MainNightAmbient2, 8)) {
532 			ambi->appearance = DAY_BITS;
533 			map->AddAmbient(ambi);
534 			// night
535 			ambi = SetupMainAmbients(map, false);
536 			if (ambi) {
537 				ambi->appearance ^= DAY_BITS; // night: bits 0-5 + 19-23, [dusk till dawn]
538 			}
539 		}
540 		// bgt ar7300 has a nigth ambient only in the first slot
541 		if (ambi) {
542 			map->AddAmbient(ambi);
543 		}
544 	}
545 
546 	if (core->HasFeature(GF_PST_STATE_FLAGS)) {
547 		str->ReadDword(&map->SongHeader.reverbID);
548 	} else {
549 		// all data has it at 0, so we don't bother reading
550 		map->SongHeader.reverbID = EFX_PROFILE_REVERB_INVALID;
551 	}
552 	map->SetupReverbInfo();
553 
554 	str->Seek( RestHeader + 32, GEM_STREAM_START );
555 	for (i = 0; i < MAX_RESCOUNT; i++) {
556 		str->ReadDword( map->RestHeader.Strref + i );
557 	}
558 	for (i = 0; i < MAX_RESCOUNT; i++) {
559 		str->ReadResRef( map->RestHeader.CreResRef[i] );
560 	}
561 	str->ReadWord( &map->RestHeader.CreatureNum );
562 	if( map->RestHeader.CreatureNum>MAX_RESCOUNT ) {
563 		map->RestHeader.CreatureNum = MAX_RESCOUNT;
564 	}
565 	str->ReadWord( &map->RestHeader.Difficulty);  //difficulty?
566 	str->ReadDword( &map->RestHeader.sduration);  //spawn duration
567 	str->ReadWord( &map->RestHeader.rwdist);      //random walk distance
568 	str->ReadWord( &map->RestHeader.owdist);      //other walk distance
569 	str->ReadWord( &map->RestHeader.Maximum);     //maximum number of creatures
570 	str->ReadWord( &map->RestHeader.Enabled);
571 	str->ReadWord( &map->RestHeader.DayChance );
572 	str->ReadWord( &map->RestHeader.NightChance );
573 
574 	Log(DEBUG, "AREImporter", "Loading regions");
575 	core->LoadProgress(70);
576 	//Loading InfoPoints
577 	for (i = 0; i < InfoPointsCount; i++) {
578 		str->Seek( InfoPointsOffset + ( i * 0xC4 ), GEM_STREAM_START );
579 		ieWord Type, VertexCount;
580 		ieDword FirstVertex, Cursor, Flags;
581 		ieWord TrapDetDiff, TrapRemDiff, Trapped, TrapDetected;
582 		ieWord LaunchX, LaunchY;
583 		ieWord PosX, PosY;
584 		ieWord TalkX, TalkY;
585 		ieVariable Name, Entrance;
586 		ieResRef Script, KeyResRef, Destination;
587 		ieResRef DialogResRef, WavResRef; //adopted pst specific fields
588 		ieStrRef DialogName;
589 		str->Read( Name, 32 );
590 		Name[32] = 0;
591 		str->ReadWord( &Type );
592 		Region bbox;
593 		ieWord tmp;
594 		str->ReadWord( &tmp );
595 		bbox.x = tmp;
596 		str->ReadWord( &tmp );
597 		bbox.y = tmp;
598 		str->ReadWord( &tmp );
599 		bbox.w = tmp - bbox.x;
600 		str->ReadWord( &tmp );
601 		bbox.h = tmp - bbox.y;
602 		str->ReadWord( &VertexCount );
603 		str->ReadDword( &FirstVertex );
604 		ieDword tmp2;
605 		str->ReadDword( &tmp2 ); //named triggerValue in the IE source
606 		str->ReadDword( &Cursor );
607 		str->ReadResRef( Destination );
608 		str->Read( Entrance, 32 );
609 		Entrance[32] = 0;
610 		str->ReadDword( &Flags );
611 		ieStrRef StrRef;
612 		str->ReadDword( &StrRef );
613 		str->ReadWord( &TrapDetDiff );
614 		str->ReadWord( &TrapRemDiff );
615 		str->ReadWord( &Trapped );
616 		str->ReadWord( &TrapDetected );
617 		str->ReadWord( &LaunchX );
618 		str->ReadWord( &LaunchY );
619 		str->ReadResRef( KeyResRef );
620 		str->ReadResRef( Script );
621 		str->ReadWord( &PosX);
622 		str->ReadWord( &PosY);
623 		/* ARE 9.1: 4B per position after that, but let's just try the lower two ones. */
624 		if (16 == map->version) {
625 			str->ReadWord(&PosX);
626 			str->Seek(2, GEM_CURRENT_POS);
627 			str->ReadWord(&PosY);
628 			str->Seek(30, GEM_CURRENT_POS);
629 		} else {
630 			//maybe we have to store this
631 			str->Seek( 36, GEM_CURRENT_POS );
632 		}
633 
634 		if (core->HasFeature(GF_INFOPOINT_DIALOGS)) {
635 			str->ReadResRef( WavResRef );
636 			str->ReadWord( &TalkX);
637 			str->ReadWord( &TalkY);
638 			str->ReadDword( &DialogName );
639 			str->ReadResRef( DialogResRef );
640 		} else {
641 			memset(WavResRef, 0, sizeof(WavResRef));
642 			TalkX = 0;
643 			TalkY = 0;
644 			DialogName = -1;
645 			memset(DialogResRef, 0, sizeof(DialogResRef));
646 		}
647 
648 		InfoPoint* ip = nullptr;
649 		str->Seek( VerticesOffset + ( FirstVertex * 4 ), GEM_STREAM_START );
650 		if (VertexCount <= 1) {
651 			// this is exactly the same as bbox.Origin()
652 			if (VertexCount == 1) {
653 				str->ReadWord(&tmp);
654 				assert(tmp == bbox.x);
655 				str->ReadWord(&tmp);
656 				assert(tmp == bbox.y);
657 			}
658 
659 			if (bbox.Dimensions().IsEmpty()) {
660 				// we approximate a bounding box equivalent to a small radius
661 				// we copied this from the Container code that seems to indicate
662 				// this is how the originals behave. It is probably "good enough"
663 				bbox.x = PosX - 7;
664 				bbox.y = PosY - 5;
665 				bbox.w = 16;
666 				bbox.h = 12;
667 			}
668 
669 			ip = tm->AddInfoPoint( Name, Type, nullptr );
670 			ip->BBox = bbox;
671 		} else if (VertexCount == 2) {
672 #define MSG "Encounted a bogus polygon with 2 verticies"
673 #if NDEBUG
674 			Log(ERROR, "AREImporter", MSG);
675 			continue;
676 #else // make this fatal on debug builds
677 			error("AREImporter", MSG);
678 #endif
679 #undef MSG
680 		} else {
681 			Point* points = ( Point* ) malloc( VertexCount*sizeof( Point ) );
682 			for (x = 0; x < VertexCount; x++) {
683 				str->ReadWord( (ieWord*) &points[x].x );
684 				str->ReadWord( (ieWord*) &points[x].y );
685 			}
686 			auto poly = std::make_shared<Gem_Polygon>(points, VertexCount, &bbox);
687 			free( points );
688 			ip = tm->AddInfoPoint( Name, Type, poly );
689 		}
690 
691 		ip->TrapDetectionDiff = TrapDetDiff;
692 		ip->TrapRemovalDiff = TrapRemDiff;
693 		ip->Trapped = Trapped;
694 		ip->TrapDetected = TrapDetected;
695 		ip->TrapLaunch.x = LaunchX;
696 		ip->TrapLaunch.y = LaunchY;
697 		// translate door cursor on infopoint to correct cursor
698 		if (Cursor == IE_CURSOR_DOOR) Cursor = IE_CURSOR_PASS;
699 		ip->Cursor = Cursor;
700 		String* str = core->GetString( StrRef );
701 		ip->SetOverheadText(*str, false);
702 		delete str;
703 		ip->StrRef = StrRef; //we need this when saving area
704 		ip->SetMap(map);
705 		ip->Flags = Flags;
706 		ip->UsePoint.x = PosX;
707 		ip->UsePoint.y = PosY;
708 		//FIXME: PST doesn't use this field
709 		if (ip->GetUsePoint()) {
710 			ip->Pos = ip->UsePoint;
711 		} else {
712 			ip->Pos.x = bbox.x + ( bbox.w / 2 );
713 			ip->Pos.y = bbox.y + ( bbox.h / 2 );
714 		}
715 		memcpy( ip->Destination, Destination, sizeof(Destination) );
716 		memcpy( ip->EntranceName, Entrance, sizeof(Entrance) );
717 		memcpy( ip->KeyResRef, KeyResRef, sizeof(KeyResRef) );
718 
719 		//these appear only in PST, but we could support them everywhere
720 		// HOWEVER they did not use them as witnessed in ar0101 (0101prt1 and 0101prt2) :(
721 		if (core->HasFeature(GF_PST_STATE_FLAGS)) {
722 			TalkX = ip->Pos.x;
723 			TalkY = ip->Pos.y;
724 		}
725 		ip->TalkPos.x=TalkX;
726 		ip->TalkPos.y=TalkY;
727 		ip->DialogName=DialogName;
728 		ip->SetDialog(DialogResRef);
729 		ip->SetEnter(WavResRef);
730 
731 		if (Script[0]) {
732 			ip->Scripts[0] = new GameScript( Script, ip );
733 		} else {
734 			ip->Scripts[0] = NULL;
735 		}
736 	}
737 
738 	Log(DEBUG, "AREImporter", "Loading containers");
739 	for (i = 0; i < ContainersCount; i++) {
740 		str->Seek( ContainersOffset + ( i * 0xC0 ), GEM_STREAM_START );
741 		ieVariable Name;
742 		ieWord Type, LockDiff;
743 		ieDword Flags;
744 		ieWord TrapDetDiff, TrapRemDiff, Trapped, TrapDetected;
745 		ieWord XPos, YPos;
746 		ieWord LaunchX, LaunchY;
747 		ieDword ItemIndex, ItemCount;
748 		ieResRef KeyResRef;
749 		ieStrRef OpenFail;
750 
751 		str->Read( Name, 32 );
752 		Name[32] = 0;
753 		str->ReadWord( &XPos );
754 		str->ReadWord( &YPos );
755 		str->ReadWord( &Type );
756 		str->ReadWord( &LockDiff );
757 		str->ReadDword( &Flags );
758 		str->ReadWord( &TrapDetDiff );
759 		str->ReadWord( &TrapRemDiff );
760 		str->ReadWord( &Trapped );
761 		str->ReadWord( &TrapDetected );
762 		str->ReadWord( &LaunchX );
763 		str->ReadWord( &LaunchY );
764 		Region bbox;
765 		ieWord tmp;
766 		str->ReadWord( &tmp );
767 		bbox.x = tmp;
768 		str->ReadWord( &tmp );
769 		bbox.y = tmp;
770 		str->ReadWord( &tmp );
771 		bbox.w = tmp - bbox.x;
772 		str->ReadWord( &tmp );
773 		bbox.h = tmp - bbox.y;
774 		str->ReadDword( &ItemIndex );
775 		str->ReadDword( &ItemCount );
776 		str->ReadResRef( Script );
777 		ieDword firstIndex;
778 		ieWord vertCount, unknown;
779 		str->ReadDword( &firstIndex );
780 		//the vertex count is only 16 bits, there is a weird flag
781 		//after it, which is usually 0, but sometimes set to 1
782 		str->ReadWord( &vertCount );
783 		str->ReadWord( &unknown );   //trigger range
784 		//str->Read( Name, 32 );     //owner's scriptname
785 		str->Seek( 32, GEM_CURRENT_POS);
786 		str->ReadResRef( KeyResRef);
787 		str->Seek( 4, GEM_CURRENT_POS); //break difficulty
788 		str->ReadDword( &OpenFail );
789 
790 		str->Seek( VerticesOffset + ( firstIndex * 4 ), GEM_STREAM_START );
791 
792 		Container* c = nullptr;
793 		if (vertCount == 0) {
794 			/* piles have no polygons and no bounding box in some areas,
795 			 * but bg2 gives them this bounding box at first load,
796 			 * should we specifically check for Type==IE_CONTAINER_PILE? */
797 			if (bbox.Dimensions().IsEmpty()) {
798 				bbox.x = XPos - 7;
799 				bbox.y = YPos - 5;
800 				bbox.w = 16;
801 				bbox.h = 12;
802 			}
803 			c = map->AddContainer( Name, Type, nullptr );
804 			c->BBox = bbox;
805 		} else {
806 			Point* points = ( Point* ) malloc( vertCount*sizeof( Point ) );
807 			for (x = 0; x < vertCount; x++) {
808 				ieWord tmp;
809 				str->ReadWord( &tmp );
810 				points[x].x = tmp;
811 				str->ReadWord( &tmp );
812 				points[x].y = tmp;
813 			}
814 			auto poly = std::make_shared<Gem_Polygon>( points, vertCount, &bbox );
815 			c = map->AddContainer( Name, Type, poly );
816 			free( points );
817 		}
818 
819 		//c->SetMap(map);
820 		c->Pos.x = XPos;
821 		c->Pos.y = YPos;
822 		c->LockDifficulty = LockDiff;
823 		c->Flags = Flags;
824 		c->TrapDetectionDiff = TrapDetDiff;
825 		c->TrapRemovalDiff = TrapRemDiff;
826 		c->Trapped = Trapped;
827 		c->TrapDetected = TrapDetected;
828 		c->TrapLaunch.x = LaunchX;
829 		c->TrapLaunch.y = LaunchY;
830 		//reading items into a container
831 		str->Seek( ItemsOffset+( ItemIndex * 0x14 ), GEM_STREAM_START);
832 		while(ItemCount--) {
833 			//cannot add directly to inventory (ground piles)
834 			c->AddItem( core->ReadItem(str));
835 		}
836 
837 		if (Type==IE_CONTAINER_PILE)
838 			Script[0]=0;
839 
840 		if (Script[0]) {
841 			c->Scripts[0] = new GameScript( Script, c );
842 		} else {
843 			c->Scripts[0] = NULL;
844 		}
845 		strnlwrcpy(c->KeyResRef, KeyResRef, 8);
846 		if (!OpenFail) OpenFail = ieStrRef(-1); // rewrite 0 to -1
847 		c->OpenFail = OpenFail;
848 	}
849 
850 	Log(DEBUG, "AREImporter", "Loading doors");
851 	for (i = 0; i < DoorsCount; i++) {
852 		str->Seek( DoorsOffset + ( i * 0xc8 ), GEM_STREAM_START );
853 		int count;
854 		ieDword Flags;
855 		ieDword OpenFirstVertex, ClosedFirstVertex;
856 		ieDword OpenFirstImpeded, ClosedFirstImpeded;
857 		ieWord OpenVerticesCount, ClosedVerticesCount;
858 		ieWord OpenImpededCount, ClosedImpededCount;
859 		ieVariable LongName, LinkedInfo;
860 		ieResRef ShortName;
861 		ieWord minX, maxX, minY, maxY;
862 		ieDword cursor;
863 		ieResRef KeyResRef, Script;
864 		ieWord TrapDetect, TrapRemoval;
865 		ieWord Trapped, TrapDetected;
866 		ieWord LaunchX, LaunchY;
867 		ieDword DiscoveryDiff, LockRemoval;
868 		Region BBClosed, BBOpen;
869 		ieStrRef OpenStrRef;
870 		ieStrRef NameStrRef;
871 		ieResRef Dialog;
872 		ieWord hp, ac;
873 
874 		str->Read( LongName, 32 );
875 		LongName[32] = 0;
876 		str->ReadResRef( ShortName );
877 		str->ReadDword( &Flags );
878 		if (map->version == 16) {
879 			Flags = FixIWD2DoorFlags(Flags, false);
880 		}
881 		if (AreaType & AT_OUTDOOR) Flags |= DOOR_TRANSPARENT; // actually true only for fog-of-war, excluding other actors
882 		str->ReadDword( &OpenFirstVertex );
883 		str->ReadWord( &OpenVerticesCount );
884 		str->ReadWord( &ClosedVerticesCount );
885 		str->ReadDword( &ClosedFirstVertex );
886 		str->ReadWord( &minX );
887 		str->ReadWord( &minY );
888 		str->ReadWord( &maxX );
889 		str->ReadWord( &maxY );
890 		BBOpen.x = minX;
891 		BBOpen.y = minY;
892 		BBOpen.w = maxX - minX;
893 		BBOpen.h = maxY - minY;
894 		str->ReadWord( &minX );
895 		str->ReadWord( &minY );
896 		str->ReadWord( &maxX );
897 		str->ReadWord( &maxY );
898 		BBClosed.x = minX;
899 		BBClosed.y = minY;
900 		BBClosed.w = maxX - minX;
901 		BBClosed.h = maxY - minY;
902 		str->ReadDword( &OpenFirstImpeded );
903 		str->ReadWord( &OpenImpededCount );
904 		str->ReadWord( &ClosedImpededCount );
905 		str->ReadDword( &ClosedFirstImpeded );
906 		str->ReadWord(&hp); // hitpoints
907 		str->ReadWord(&ac); // AND armorclass, according to IE dev info
908 		ieResRef OpenResRef, CloseResRef;
909 		str->ReadResRef( OpenResRef );
910 		str->ReadResRef( CloseResRef );
911 		str->ReadDword( &cursor );
912 		str->ReadWord( &TrapDetect );
913 		str->ReadWord( &TrapRemoval );
914 		str->ReadWord( &Trapped );
915 		str->ReadWord( &TrapDetected );
916 		str->ReadWord( &LaunchX );
917 		str->ReadWord( &LaunchY );
918 		str->ReadResRef( KeyResRef );
919 		str->ReadResRef( Script );
920 		str->ReadDword( &DiscoveryDiff );
921 		str->ReadDword( &LockRemoval );
922 		Point toOpen[2];
923 		str->ReadWord( &minX );
924 		toOpen[0].x = minX;
925 		str->ReadWord( &minY );
926 		toOpen[0].y = minY;
927 		str->ReadWord( &maxX );
928 		toOpen[1].x = maxX;
929 		str->ReadWord( &maxY );
930 		toOpen[1].y = maxY;
931 		str->ReadDword( &OpenStrRef);
932 		if (core->HasFeature(GF_AUTOMAP_INI) ) {
933 			str->Read( LinkedInfo, 24);
934 			LinkedInfo[24] = 0; // LinkedInfo unused in pst anyway?
935 		} else {
936 			str->Read( LinkedInfo, 32);
937 		}
938 		str->ReadDword( &NameStrRef);
939 		str->ReadResRef( Dialog );
940 		if (core->HasFeature(GF_AUTOMAP_INI) ) {
941 			// maybe this is important? but seems not
942 			str->Seek( 8, GEM_CURRENT_POS );
943 		}
944 
945 		//Reading Open Polygon
946 		std::shared_ptr<Gem_Polygon> open = nullptr;
947 		str->Seek( VerticesOffset + ( OpenFirstVertex * 4 ), GEM_STREAM_START );
948 		if (OpenVerticesCount) {
949 			Point* points = (Point*)malloc( OpenVerticesCount*sizeof( Point ) );
950 			for (x = 0; x < OpenVerticesCount; x++) {
951 				str->ReadWord( &minX );
952 				points[x].x = minX;
953 				str->ReadWord( &minY );
954 				points[x].y = minY;
955 			}
956 			open = std::make_shared<Gem_Polygon>( points, OpenVerticesCount, &BBOpen );
957 			free( points );
958 		}
959 
960 		//Reading Closed Polygon
961 		std::shared_ptr<Gem_Polygon> closed = nullptr;
962 		str->Seek( VerticesOffset + ( ClosedFirstVertex * 4 ),
963 				GEM_STREAM_START );
964 		if (ClosedVerticesCount) {
965 			Point* points = ( Point * ) malloc( ClosedVerticesCount * sizeof( Point ) );
966 			for (x = 0; x < ClosedVerticesCount; x++) {
967 				str->ReadWord( &minX );
968 				points[x].x = minX;
969 				str->ReadWord( &minY );
970 				points[x].y = minY;
971 			}
972 			closed = std::make_shared<Gem_Polygon>( points, ClosedVerticesCount, &BBClosed );
973 			free( points );
974 		}
975 
976 		//Getting Door Information from the WED File
977 		bool BaseClosed;
978 		unsigned short * indices = tmm->GetDoorIndices( ShortName, &count, BaseClosed );
979 		if (core->HasFeature(GF_REVERSE_DOOR)) {
980 			BaseClosed = !BaseClosed;
981 		}
982 
983 		auto closedPolys = tmm->ClosedDoorPolygons();
984 		auto openPolys = tmm->OpenDoorPolygons();
985 
986 		DoorTrigger dt(open, std::move(openPolys), closed, std::move(closedPolys));
987 		Door* door = tm->AddDoor( ShortName, LongName, Flags, BaseClosed,
988 					indices, count, std::move(dt) );
989 		door->OpenBBox = BBOpen;
990 		door->ClosedBBox = BBClosed;
991 
992 		//Reading Open Impeded blocks
993 		str->Seek( VerticesOffset + ( OpenFirstImpeded * 4 ),
994 				GEM_STREAM_START );
995 		Point* points = ( Point * ) malloc( OpenImpededCount * sizeof( Point ) );
996 		for (x = 0; x < OpenImpededCount; x++) {
997 			str->ReadWord( &minX );
998 			points[x].x = minX;
999 			str->ReadWord( &minY );
1000 			points[x].y = minY;
1001 		}
1002 		door->open_ib = points;
1003 		door->oibcount = OpenImpededCount;
1004 
1005 		//Reading Closed Impeded blocks
1006 		str->Seek( VerticesOffset + ( ClosedFirstImpeded * 4 ),
1007 				GEM_STREAM_START );
1008 		points = ( Point * ) malloc( ClosedImpededCount * sizeof( Point ) );
1009 		for (x = 0; x < ClosedImpededCount; x++) {
1010 			str->ReadWord( &minX );
1011 			points[x].x = minX;
1012 			str->ReadWord( &minY );
1013 			points[x].y = minY;
1014 		}
1015 		door->closed_ib = points;
1016 		door->cibcount = ClosedImpededCount;
1017 		door->SetMap(map);
1018 
1019 		door->hp = hp;
1020 		door->ac = ac;
1021 		door->TrapDetectionDiff = TrapDetect;
1022 		door->TrapRemovalDiff = TrapRemoval;
1023 		door->Trapped = Trapped;
1024 		door->TrapDetected = TrapDetected;
1025 		door->TrapLaunch.x = LaunchX;
1026 		door->TrapLaunch.y = LaunchY;
1027 
1028 		door->Cursor = cursor;
1029 		memcpy( door->KeyResRef, KeyResRef, sizeof(KeyResRef) );
1030 		if (Script[0]) {
1031 			door->Scripts[0] = new GameScript( Script, door );
1032 		} else {
1033 			door->Scripts[0] = NULL;
1034 		}
1035 
1036 		door->toOpen[0] = toOpen[0];
1037 		door->toOpen[1] = toOpen[1];
1038 		//Leave the default sound untouched
1039 		if (OpenResRef[0])
1040 			memcpy( door->OpenSound, OpenResRef, sizeof(OpenResRef) );
1041 		else {
1042 			if (Flags & DOOR_SECRET)
1043 				memcpy( door->OpenSound, Sounds[DEF_HOPEN], 9 );
1044 			else
1045 				memcpy( door->OpenSound, Sounds[DEF_OPEN], 9 );
1046 		}
1047 		if (CloseResRef[0])
1048 			memcpy( door->CloseSound, CloseResRef, sizeof(CloseResRef) );
1049 		else {
1050 			if (Flags & DOOR_SECRET)
1051 				memcpy( door->CloseSound, Sounds[DEF_HCLOSE], 9 );
1052 			else
1053 				memcpy( door->CloseSound, Sounds[DEF_CLOSE], 9 );
1054 		}
1055 		door->DiscoveryDiff=DiscoveryDiff;
1056 		door->LockDifficulty=LockRemoval;
1057 		if (!OpenStrRef) OpenStrRef = ieStrRef(-1); // rewrite 0 to -1
1058 		door->OpenStrRef=OpenStrRef;
1059 		strnspccpy(door->LinkedInfo, LinkedInfo, 32);
1060 		//these 2 fields are not sure
1061 		door->NameStrRef=NameStrRef;
1062 		door->SetDialog(Dialog);
1063 	}
1064 
1065 	Log(DEBUG, "AREImporter", "Loading spawnpoints");
1066 	for (i = 0; i < SpawnCount; i++) {
1067 		str->Seek( SpawnOffset + (i*0xc8), GEM_STREAM_START );
1068 		ieVariable Name;
1069 		ieWord XPos, YPos;
1070 		ieWord Count, Difficulty, Frequency, Method;
1071 		ieWord Maximum, Enabled;
1072 		ieResRef creatures[MAX_RESCOUNT];
1073 		ieWord DayChance, NightChance;
1074 		ieDword Schedule;
1075 		ieDword sduration;
1076 		ieWord rwdist, owdist;
1077 
1078 		str->Read( Name, 32 );
1079 		Name[32] = 0;
1080 		str->ReadWord( &XPos );
1081 		str->ReadWord( &YPos );
1082 		for (unsigned int j = 0;j < MAX_RESCOUNT; j++) {
1083 			str->ReadResRef( creatures[j] );
1084 		}
1085 		str->ReadWord( &Count);
1086 		str->ReadWord( &Difficulty);
1087 		str->ReadWord( &Frequency );
1088 		str->ReadWord( &Method);
1089 		str->ReadDword( &sduration); //time to live for spawns
1090 		str->ReadWord( &rwdist);     //random walk distance (0 is unlimited)
1091 		str->ReadWord( &owdist);     //other walk distance (inactive in all engines?)
1092 		str->ReadWord( &Maximum);
1093 		str->ReadWord( &Enabled);
1094 		str->ReadDword( &Schedule);
1095 		str->ReadWord( &DayChance);
1096 		str->ReadWord( &NightChance);
1097 
1098 		Spawn *sp = map->AddSpawn(Name, XPos, YPos, creatures, Count);
1099 		sp->Difficulty = Difficulty;
1100 		//this value is used in a division, better make it nonzero now
1101 		//this will fix any old gemrb saves vs. the original engine
1102 		if (!Frequency) {
1103 			Frequency = 1;
1104 		}
1105 		sp->Frequency = Frequency;
1106 		sp->Method = Method;
1107 		sp->sduration = sduration;
1108 		sp->rwdist = rwdist;
1109 		sp->owdist = owdist;
1110 		sp->Maximum = Maximum;
1111 		sp->Enabled = Enabled;
1112 		sp->appearance = Schedule;
1113 		sp->DayChance = DayChance;
1114 		sp->NightChance = NightChance;
1115 		//the rest is not read, we seek for every record
1116 	}
1117 
1118 	int pst = core->HasFeature(GF_AUTOMAP_INI);
1119 
1120 	core->LoadProgress(75);
1121 	Log(DEBUG, "AREImporter", "Loading actors");
1122 	str->Seek( ActorOffset, GEM_STREAM_START );
1123 	if (!core->IsAvailable( IE_CRE_CLASS_ID )) {
1124 		Log(WARNING, "AREImporter", "No Actor Manager Available, skipping actors");
1125 	} else {
1126 		PluginHolder<ActorMgr> actmgr(IE_CRE_CLASS_ID);
1127 		for (i = 0; i < ActorCount; i++) {
1128 			ieVariable DefaultName;
1129 			ieResRef CreResRef;
1130 			ieDword TalkCount;
1131 			ieDword Orientation, Schedule, RemovalTime;
1132 			ieWord XPos, YPos, XDes, YDes, MaxDistance, Spawned;
1133 			ieResRef Dialog;
1134 			ieResRef Scripts[8]; //the original order is shown in scrlev.ids
1135 			ieDword Flags;
1136 			ieByte DifficultyMargin;
1137 
1138 			str->Read( DefaultName, 32);
1139 			DefaultName[32]=0;
1140 			str->ReadWord( &XPos );
1141 			str->ReadWord( &YPos );
1142 			str->ReadWord( &XDes );
1143 			str->ReadWord( &YDes );
1144 			str->ReadDword( &Flags );
1145 			str->ReadWord( &Spawned );
1146 			str->Seek( 1, GEM_CURRENT_POS ); // one letter of a ResRef, changed to * at runtime, purpose unknown (portraits?), but not needed either
1147 			str->Read( &DifficultyMargin, 1 ); // iwd2 only
1148 			str->Seek( 4, GEM_CURRENT_POS ); //actor animation, unused
1149 			str->ReadDword( &Orientation );
1150 			str->ReadDword( &RemovalTime );
1151 			str->ReadWord( &MaxDistance );
1152 			str->Seek( 2, GEM_CURRENT_POS ); // apparently unused https://gibberlings3.net/forums/topic/21724-a
1153 			str->ReadDword( &Schedule );
1154 			str->ReadDword( &TalkCount );
1155 			str->ReadResRef( Dialog );
1156 
1157 			memset(Scripts, 0, sizeof(Scripts));
1158 			str->ReadResRef( Scripts[SCR_OVERRIDE] );
1159 			str->ReadResRef( Scripts[SCR_GENERAL] );
1160 			str->ReadResRef( Scripts[SCR_CLASS] );
1161 			str->ReadResRef( Scripts[SCR_RACE] );
1162 			str->ReadResRef( Scripts[SCR_DEFAULT] );
1163 			str->ReadResRef( Scripts[SCR_SPECIFICS] );
1164 			str->ReadResRef( CreResRef );
1165 			DataStream* crefile;
1166 			Actor *ab;
1167 			ieDword CreOffset, CreSize;
1168 			str->ReadDword( &CreOffset );
1169 			str->ReadDword( &CreSize );
1170 			// another iwd2 script slot
1171 			str->ReadResRef( Scripts[SCR_AREA] );
1172 			str->Seek( 120, GEM_CURRENT_POS );
1173 			//not iwd2, this field is garbage
1174 			if (!core->HasFeature(GF_IWD2_SCRIPTNAME)) {
1175 				Scripts[SCR_AREA][0]=0;
1176 			}
1177 			//actually, Flags&1 signs that the creature
1178 			//is not loaded yet, so !(Flags&1) means it is embedded
1179 			if (CreOffset != 0 && !(Flags&1) ) {
1180 				crefile = SliceStream( str, CreOffset, CreSize, true );
1181 			} else {
1182 				crefile = gamedata->GetResource( CreResRef, IE_CRE_CLASS_ID );
1183 			}
1184 			if(!actmgr->Open(crefile)) {
1185 				Log(ERROR, "AREImporter", "Couldn't read actor: %s!", CreResRef);
1186 				continue;
1187 			}
1188 			ab = actmgr->GetActor(0);
1189 			if(!ab)
1190 				continue;
1191 			// PST generally doesn't appear to use the starting MC_ bits, but for some reason
1192 			// there's a coaxmetal copy in the mortuary with both KEEP and REMOVE corpse
1193 			// set that should never be seen. The actor is also already dead, so we don't end
1194 			// up doing any of the regular cleanup on it (it's mrtghost.cre). Banish it instead.
1195 			if (pst && ab->GetBase(IE_STATE_ID) & STATE_DEAD && ab->GetBase(IE_MC_FLAGS) & MC_REMOVE_CORPSE) {
1196 				continue;
1197 			}
1198 			map->AddActor(ab, false);
1199 			ab->Pos.x = XPos;
1200 			ab->Pos.y = YPos;
1201 			ab->Destination.x = XPos;
1202 			ab->Destination.y = YPos;
1203 			ab->HomeLocation.x = XDes;
1204 			ab->HomeLocation.y = YDes;
1205 			ab->maxWalkDistance = MaxDistance;
1206 			ab->Spawned = Spawned;
1207 			ab->appearance = Schedule;
1208 			//copying the scripting name into the actor
1209 			//if the CreatureAreaFlag was set to 8
1210 			if ((Flags&AF_NAME_OVERRIDE) || (core->HasFeature(GF_IWD2_SCRIPTNAME)) ) {
1211 				ab->SetScriptName(DefaultName);
1212 			}
1213 			//IWD2 specific hacks
1214 			if (core->HasFeature(GF_3ED_RULES)) {
1215 				//This flag is used for something else in IWD2
1216 				if (Flags&AF_NAME_OVERRIDE) {
1217 					ab->BaseStats[IE_EA]=EA_EVILCUTOFF;
1218 				}
1219 				if (Flags&AF_SEEN_PARTY) {
1220 					ab->SetMCFlag(MC_SEENPARTY,OP_OR);
1221 				}
1222 				if (Flags&AF_INVULNERABLE) {
1223 					ab->SetMCFlag(MC_INVULNERABLE,OP_OR);
1224 				}
1225 				if (!(Flags&AF_ENABLED)) {
1226 					// DifficultyMargin - only enable actors that are difficult enough vs the area difficulty
1227 					// 1 - area difficulty 1
1228 					// 2 - area difficulty 2
1229 					// 4 - area difficulty 3
1230 					if (DifficultyMargin && !(DifficultyMargin & map->AreaDifficulty)) {
1231 						ab->DestroySelf();
1232 					}
1233 				}
1234 			}
1235 			ab->DifficultyMargin = DifficultyMargin;
1236 
1237 			if (Dialog[0]) {
1238 				ab->SetDialog(Dialog);
1239 			}
1240 			for (int j=0;j<8;j++) {
1241 				if (Scripts[j][0]) {
1242 					ab->SetScript(Scripts[j],j);
1243 				}
1244 			}
1245 			ab->SetOrientation( Orientation,0 );
1246 			ab->TalkCount = TalkCount;
1247 			ab->RemovalTime = RemovalTime;
1248 			ab->RefreshEffects(NULL);
1249 		}
1250 	}
1251 
1252 	core->LoadProgress(90);
1253 	Log(DEBUG, "AREImporter", "Loading animations");
1254 	str->Seek( AnimOffset, GEM_STREAM_START );
1255 	if (!core->IsAvailable( IE_BAM_CLASS_ID )) {
1256 		Log(WARNING, "AREImporter", "No Animation Manager Available, skipping animations");
1257 	} else {
1258 		for (i = 0; i < AnimCount; i++) {
1259 			AreaAnimation* anim = new AreaAnimation();
1260 			str->Read(anim->Name, 32);
1261 			ieWord animX, animY, startFrameRange;
1262 			str->ReadWord( &animX );
1263 			str->ReadWord( &animY );
1264 			anim->Pos.x=animX;
1265 			anim->Pos.y=animY;
1266 			str->ReadDword( &anim->appearance );
1267 			str->ReadResRef( anim->BAM );
1268 			str->ReadWord( &anim->sequence );
1269 			str->ReadWord( &anim->frame );
1270 			str->ReadDword( &anim->Flags );
1271 			anim->originalFlags = anim->Flags;
1272 			str->ReadWordSigned( &anim->height );
1273 			if (core->HasFeature(GF_IMPLICIT_AREAANIM_BACKGROUND) && anim->height <= 0) {
1274 				anim->height = ANI_PRI_BACKGROUND;
1275 				anim->Flags |= A_ANI_NO_WALL;
1276 			}
1277 			str->ReadWord( &anim->transparency );
1278 			str->ReadWord( &startFrameRange );
1279 			str->Read( &anim->startchance,1 );
1280 			if (anim->startchance<=0) {
1281 				anim->startchance=100; //percentage of starting a cycle
1282 			}
1283 			if (startFrameRange && (anim->Flags&A_ANI_RANDOM_START) ) {
1284 				anim->frame = RAND(0, startFrameRange - 1);
1285 			}
1286 			anim->startFrameRange = 0; //this will never get resaved (iirc)
1287 			str->Read( &anim->skipcycle,1 ); //how many cycles are skipped	(100% skippage)
1288 			str->ReadResRef( anim->PaletteRef );
1289 			// TODO: EE: word with anim width for PVRZ/WBM resources (if flag bits are set, see A_ANI_ defines)
1290 			// 0x4a holds the height
1291 			str->ReadDword( &anim->unknown48 );
1292 
1293 			if (pst) {
1294 				AdjustPSTFlags(anim);
1295 			}
1296 
1297 			//set up the animation, it cannot be done here
1298 			//because a StaticSequence action can change
1299 			//it later
1300 			map->AddAnimation( anim );
1301 			//the animation was safely transferred to internal memory
1302 			delete anim;
1303 		}
1304 	}
1305 
1306 	Log(DEBUG, "AREImporter", "Loading entrances");
1307 	str->Seek( EntrancesOffset, GEM_STREAM_START );
1308 	for (i = 0; i < EntrancesCount; i++) {
1309 		ieVariable Name;
1310 		ieWord XPos, YPos, Face;
1311 		str->Read( Name, 32 );
1312 		Name[32] = 0;
1313 		str->ReadWord( &XPos );
1314 		str->ReadWord( &YPos );
1315 		str->ReadWord( &Face );
1316 		str->Seek( 66, GEM_CURRENT_POS );
1317 		map->AddEntrance( Name, XPos, YPos, Face );
1318 	}
1319 
1320 	Log(DEBUG, "AREImporter", "Loading variables");
1321 	map->locals->LoadInitialValues(ResRef);
1322 	str->Seek( VariablesOffset, GEM_STREAM_START );
1323 	for (i = 0; i < VariablesCount; i++) {
1324 		ieVariable Name;
1325 		ieDword Value;
1326 		str->Read( Name, 32 );
1327 		Name[32] = 0;
1328 		str->Seek( 8, GEM_CURRENT_POS );
1329 		str->ReadDword( &Value );
1330 		str->Seek( 40, GEM_CURRENT_POS );
1331 		map->locals->SetAt( Name, Value );
1332 	}
1333 
1334 	Log(DEBUG, "AREImporter", "Loading ambients");
1335 	str->Seek( AmbiOffset, GEM_STREAM_START );
1336 	for (i = 0; i < AmbiCount; i++) {
1337 		int j;
1338 		ieResRef sounds[MAX_RESCOUNT];
1339 		ieWord tmpWord;
1340 
1341 		Ambient *ambi = new Ambient();
1342 		str->Read( &ambi->name, 32 );
1343 		str->ReadWord( &tmpWord );
1344 		ambi->origin.x = tmpWord;
1345 		str->ReadWord( &tmpWord );
1346 		ambi->origin.y = tmpWord;
1347 		str->ReadWord( &ambi->radius );
1348 		str->Seek( 2, GEM_CURRENT_POS );
1349 		str->ReadDword( &ambi->pitchVariance );
1350 		str->ReadWord( &ambi->gainVariance );
1351 		str->ReadWord( &ambi->gain );
1352 		for (j = 0;j < MAX_RESCOUNT; j++) {
1353 			str->ReadResRef( sounds[j] );
1354 		}
1355 		str->ReadWord( &tmpWord );
1356 		str->Seek( 2, GEM_CURRENT_POS );
1357 		str->ReadDword( &ambi->interval );
1358 		str->ReadDword( &ambi->intervalVariance );
1359 		// schedule bits
1360 		str->ReadDword( &ambi->appearance );
1361 		str->ReadDword( &ambi->flags );
1362 		str->Seek( 64, GEM_CURRENT_POS );
1363 		//this is a physical limit
1364 		if (tmpWord>MAX_RESCOUNT) {
1365 			tmpWord=MAX_RESCOUNT;
1366 		}
1367 		for (j = 0; j < tmpWord; j++) {
1368 			char *sound = (char *) malloc(9);
1369 			memcpy(sound, sounds[j], 9);
1370 			ambi->sounds.push_back(sound);
1371 		}
1372 		map->AddAmbient(ambi);
1373 	}
1374 
1375 	Log(DEBUG, "AREImporter", "Loading automap notes");
1376 	str->Seek( NoteOffset, GEM_STREAM_START );
1377 
1378 	Point point;
1379 	//Don't bother with autonote.ini if the area has autonotes (ie. it is a saved area)
1380 
1381 	if (pst) {
1382 		AnimationFactory* flags = (AnimationFactory*)gamedata->GetFactoryResource("FLAG1", IE_BAM_CLASS_ID, IE_NORMAL);
1383 		if (flags == NULL) {
1384 			ResourceHolder<ImageMgr> roimg = GetResourceHolder<ImageMgr>("RONOTE");
1385 			ResourceHolder<ImageMgr> userimg = GetResourceHolder<ImageMgr>("USERNOTE");
1386 
1387 			CycleEntry rocycle = {1, 0};
1388 			flags = new AnimationFactory("FLAG1");
1389 			flags->AddCycle(rocycle);
1390 			flags->AddFrame(roimg->GetSprite2D());
1391 			CycleEntry usercycle = {1, 1};
1392 			flags->AddCycle(usercycle);
1393 			flags->AddFrame(userimg->GetSprite2D());
1394 
1395 			ieWord flt[2] = {0, 1};
1396 			flags->LoadFLT(flt, 2);
1397 			gamedata->AddFactoryResource(flags);
1398 		}
1399 
1400 		if (!NoteCount) {
1401 			if( !INInote ) {
1402 				ReadAutonoteINI();
1403 			}
1404 
1405 			//add autonote.ini entries
1406 			if( INInote ) {
1407 				const char *scriptName = map->GetScriptName();
1408 				int count = INInote->GetKeyAsInt(scriptName, "count", 0);
1409 				while (count) {
1410 					char key[32];
1411 					int value;
1412 					snprintf(key, sizeof(key), "xPos%d",count);
1413 					value = INInote->GetKeyAsInt(scriptName, key, 0);
1414 					point.x = value;
1415 					snprintf(key, sizeof(key), "yPos%d",count);
1416 					value = INInote->GetKeyAsInt(scriptName, key, 0);
1417 					point.y = value;
1418 					snprintf(key, sizeof(key), "text%d",count);
1419 					value = INInote->GetKeyAsInt(scriptName, key, 0);
1420 					map->AddMapNote(point, 0, value, true);
1421 					count--;
1422 				}
1423 			}
1424 		} else {
1425 			for (i = 0; i < NoteCount; i++) {
1426 				ieDword px,py;
1427 				str->ReadDword(&px);
1428 				str->ReadDword(&py);
1429 
1430 				// in PST the coordinates are stored in small map space
1431 				// our MapControl wants them in large map space so we must convert
1432 				// its what other games use and its what our custom map note code uses
1433 				const Size mapsize = map->GetSize();
1434 				point.x = px * double(mapsize.w) / map->SmallMap->Frame.w;
1435 				point.y = py * double(mapsize.h) / map->SmallMap->Frame.h;
1436 
1437 				char bytes[501]; // 500 + null
1438 				str->Read(bytes, 500 );
1439 				bytes[500] = '\0';
1440 				String* text = StringFromCString(bytes);
1441 				ieDword readonly;
1442 				str->ReadDword(&readonly); //readonly == 1
1443 				if (readonly) {
1444 					map->AddMapNote(point, 0, text, true);
1445 				} else {
1446 					map->AddMapNote(point, 1, text, false);
1447 				}
1448 				str->Seek(20, GEM_CURRENT_POS);
1449 			}
1450 		}
1451 	} else {
1452 		for (i = 0; i < NoteCount; i++) {
1453 			ieWord px,py;
1454 
1455 			str->ReadWord( &px );
1456 			str->ReadWord( &py );
1457 			point.x=px;
1458 			point.y=py;
1459 			ieStrRef strref = 0;
1460 			str->ReadDword( &strref );
1461 			ieWord location; // (0=Extenal (TOH/TOT), 1=Internal (TLK)
1462 			str->ReadWord( &location );
1463 			ieWord color;
1464 			str->ReadWord( &color );
1465 			str->Seek( 40, GEM_CURRENT_POS );
1466 			// FIXME: do any other games have read only notes?
1467 			// BG2 allows editing the builtin notes, PST does not, what about others?
1468 			map->AddMapNote(point, color, strref, false);
1469 		}
1470 	}
1471 
1472 	//this is a ToB feature (saves the unexploded projectiles)
1473 	Log(DEBUG, "AREImporter", "Loading traps");
1474 	for (i = 0; i < TrapCount; i++) {
1475 		ieResRef TrapResRef;
1476 		ieDword TrapEffOffset;
1477 		ieWord TrapSize, ProID;
1478 		ieWord X,Y,Z;
1479 		ieDword Ticks;
1480 		ieByte TargetType;
1481 		ieByte Owner;
1482 
1483 		str->Seek( TrapOffset + ( i * 0x1c ), GEM_STREAM_START );
1484 
1485 		str->ReadResRef( TrapResRef );
1486 		str->ReadDword( &TrapEffOffset );
1487 		str->ReadWord( &TrapSize );
1488 		str->ReadWord( &ProID );
1489 		str->ReadDword( &Ticks );  //actually, delaycount/repetitioncount
1490 		str->ReadWord( &X );
1491 		str->ReadWord( &Y );
1492 		str->ReadWord( &Z );
1493 		str->Read(&TargetType, 1); //according to dev info, this is 'targettype'; "Enemy-ally targetting" on IESDP
1494 		str->Read(&Owner, 1); // party member index that created this projectile (0-5)
1495 		int TrapEffectCount = TrapSize/0x108;
1496 		if(TrapEffectCount*0x108!=TrapSize) {
1497 			Log(ERROR, "AREImporter", "TrapEffectSize in game: %d != %d. Clearing it",
1498 				TrapSize, TrapEffectCount*0x108);
1499 				continue;
1500 		}
1501 		//The projectile is always created, the worst that can happen
1502 		//is a dummy projectile
1503 		//The projectile ID is 214 for TRAPSNAR
1504 		//It is off by one compared to projectl.ids, but the same as missile.ids
1505 		Projectile *pro = core->GetProjectileServer()->GetProjectileByIndex(ProID-1);
1506 
1507 		//This could be wrong on msvc7 with its separate memory managers
1508 		EffectQueue *fxqueue = new EffectQueue();
1509 		DataStream *fs = new SlicedStream( str, TrapEffOffset, TrapSize);
1510 
1511 		ReadEffects(fs, fxqueue, TrapEffectCount);
1512 		Actor *caster = core->GetGame()->FindPC(Owner + 1);
1513 		pro->SetEffects(fxqueue);
1514 		if (caster) {
1515 			// Since the level info isn't stored, we assume it's the same as if the trap was just placed.
1516 			// It matters for the normal thief traps (they scale with level 4 times), while the rest don't scale.
1517 			// To be more flexible and better handle disabled dualclasses, we don't hardcode it to the thief level.
1518 			// Perhaps simplify and store the level in Z? Would need a check in the original (don't break saves).
1519 			ieDword level = caster->GetThiefLevel();
1520 			pro->SetCaster(caster->GetGlobalID(), level ? level : caster->GetXPLevel(false));
1521 		}
1522 		Point pos(X,Y);
1523 		map->AddProjectile( pro, pos, pos);
1524 	}
1525 
1526 	Log(DEBUG, "AREImporter", "Loading tiles");
1527 	//Loading Tiled objects (if any)
1528 	str->Seek( TileOffset, GEM_STREAM_START );
1529 	for (i = 0; i < TileCount; i++) {
1530 		ieVariable Name;
1531 		ieResRef ID;
1532 		ieDword Flags;
1533 		// these fields could be different size: ieDword ClosedCount, OpenCount;
1534 		ieWord ClosedCount, OpenCount;
1535 		ieDword ClosedIndex, OpenIndex;
1536 		str->Read( Name, 32 );
1537 		Name[32] = 0;
1538 		str->ReadResRef( ID );
1539 		str->ReadDword( &Flags );
1540 		//IE dev info says this:
1541 		str->ReadDword( &OpenIndex );
1542 		str->ReadWord( &OpenCount );
1543 		str->ReadWord( &ClosedCount );
1544 		str->ReadDword( &ClosedIndex );
1545 		//end of disputed section
1546 
1547 		str->Seek( 48, GEM_CURRENT_POS );
1548 		//absolutely no idea where these 'tile indices' are stored
1549 		//are they tileset tiles or impeded block tiles
1550 		map->TMap->AddTile( ID, Name, Flags, NULL,0, NULL, 0 );
1551 	}
1552 
1553 	Log(DEBUG, "AREImporter", "Loading explored bitmap");
1554 	i = map->GetExploredMapSize();
1555 	if (ExploredBitmapSize==i) {
1556 		map->ExploredBitmap = (ieByte *) malloc(i);
1557 		str->Seek( ExploredBitmapOffset, GEM_STREAM_START );
1558 		str->Read( map->ExploredBitmap, i );
1559 	}
1560 	else {
1561 		if( ExploredBitmapSize ) {
1562 			Log(ERROR, "AREImporter", "ExploredBitmapSize in game: %d != %d. Clearing it",
1563 				ExploredBitmapSize, i);
1564 		}
1565 		ExploredBitmapSize = i;
1566 		map->ExploredBitmap = (ieByte *) calloc(i, 1);
1567 	}
1568 	map->VisibleBitmap = (ieByte *) calloc(i, 1);
1569 
1570 	Log(DEBUG, "AREImporter", "Loading wallgroups");
1571 	map->SetWallGroups(tmm->GetWallGroups());
1572 	//setting up doors
1573 	for (i=0;i<DoorsCount;i++) {
1574 		Door *door = tm->GetDoor(i);
1575 		door->SetDoorOpen(door->IsOpen(), false, 0);
1576 	}
1577 
1578 	return map;
1579 }
1580 
AdjustPSTFlags(AreaAnimation * areaAnim)1581 void AREImporter::AdjustPSTFlags(AreaAnimation *areaAnim) {
1582 	/**
1583 	 * For PST, map animation flags work differently to a degree that they
1584 	 * should not be mixed together with the rest as they even tend to
1585 	 * break things (like stopping early, hiding under FoW).
1586 	 *
1587 	 * So far, a better approximation towards handling animations is:
1588 	 * - zero everything
1589 	 * - always set A_ANI_SYNC
1590 	 * - copy/map known flags (A_ANI_ACTIVE, A_ANI_NO_WALL, A_ANI_BLEND)
1591 	 *
1592 	 * Note that WF_COVERANIMS is enabled by default for PST, so ANI_NO_WALL
1593 	 *   is important.
1594 	 *
1595 	 * The actual use of bits in PST map anims isn't fully solved here.
1596 	 */
1597 
1598 	#define PST_ANI_NO_WALL 0x0008  // A_ANI_PLAYONCE in other games
1599 	#define PST_ANI_BLEND 0x0100  // A_ANI_BACKGROUND in other games
1600 
1601 	areaAnim->Flags = 0;             // Clear everything
1602 
1603 	// Set default-on flags (currently only A_ANI_SYNC)
1604 	areaAnim->Flags |= A_ANI_SYNC;
1605 
1606 	// Copy still-relevant A_ANI_* flags
1607 	areaAnim->Flags |= areaAnim->originalFlags & A_ANI_ACTIVE;
1608 
1609 	// Map known flags
1610 	if (areaAnim->originalFlags & PST_ANI_BLEND) {
1611 		areaAnim->Flags |= A_ANI_BLEND;
1612 	}
1613 	if (areaAnim->originalFlags & PST_ANI_NO_WALL) {
1614 		areaAnim->Flags |= A_ANI_NO_WALL;
1615 	}
1616 }
1617 
ReadEffects(DataStream * ds,EffectQueue * fxqueue,ieDword EffectsCount)1618 void AREImporter::ReadEffects(DataStream *ds, EffectQueue *fxqueue, ieDword EffectsCount)
1619 {
1620 	unsigned int i;
1621 
1622 	PluginHolder<EffectMgr> eM(IE_EFF_CLASS_ID);
1623 	eM->Open(ds);
1624 
1625 	for (i = 0; i < EffectsCount; i++) {
1626 		Effect fx;
1627 
1628 		eM->GetEffectV20( &fx );
1629 		// NOTE: AddEffect() allocates a new effect
1630 		fxqueue->AddEffect( &fx );
1631 	}
1632 }
1633 
GetStoredFileSize(Map * map)1634 int AREImporter::GetStoredFileSize(Map *map)
1635 {
1636 	unsigned int i;
1637 	int headersize = map->version+0x11c;
1638 	ActorOffset = headersize;
1639 
1640 	//get only saved actors (no familiars or partymembers)
1641 	//summons?
1642 	ActorCount = (ieWord) map->GetActorCount(false);
1643 	headersize += ActorCount * 0x110;
1644 
1645 	PluginHolder<ActorMgr> am(IE_CRE_CLASS_ID);
1646 	EmbeddedCreOffset = headersize;
1647 
1648 	for (i=0;i<ActorCount;i++) {
1649 		headersize += am->GetStoredFileSize(map->GetActor(i, false) );
1650 	}
1651 
1652 	InfoPointsOffset = headersize;
1653 
1654 	InfoPointsCount = (ieWord) map->TMap->GetInfoPointCount();
1655 	headersize += InfoPointsCount * 0xc4;
1656 	SpawnOffset = headersize;
1657 
1658 	SpawnCount = map->GetSpawnCount();
1659 	headersize += SpawnCount * 0xc8;
1660 	EntrancesOffset = headersize;
1661 
1662 	EntrancesCount = (ieDword) map->GetEntranceCount();
1663 	headersize += EntrancesCount * 0x68;
1664 	ContainersOffset = headersize;
1665 
1666 	//this one removes empty heaps and counts items, should be before
1667 	//getting ContainersCount
1668 	ItemsCount = (ieDword) map->ConsolidateContainers();
1669 	ContainersCount = (ieDword) map->TMap->GetContainerCount();
1670 	headersize += ContainersCount * 0xc0;
1671 	ItemsOffset = headersize;
1672 	headersize += ItemsCount * 0x14;
1673 	DoorsOffset = headersize;
1674 
1675 	DoorsCount = (ieDword) map->TMap->GetDoorCount();
1676 	headersize += DoorsCount * 0xc8;
1677 	VerticesOffset = headersize;
1678 
1679 	VerticesCount = 0;
1680 	for(i=0;i<InfoPointsCount;i++) {
1681 		InfoPoint *ip=map->TMap->GetInfoPoint(i);
1682 		if (ip->outline) {
1683 			VerticesCount+=ip->outline->Count();
1684 		} else {
1685 			VerticesCount++;
1686 		}
1687 	}
1688 	for(i=0;i<ContainersCount;i++) {
1689 		Container *c=map->TMap->GetContainer(i);
1690 		if (c->outline)
1691 			VerticesCount+=c->outline->Count();
1692 	}
1693 	for(i=0;i<DoorsCount;i++) {
1694 		Door *d=map->TMap->GetDoor(i);
1695 		auto open = d->OpenTriggerArea();
1696 		auto closed = d->ClosedTriggerArea();
1697 		if (open)
1698 			VerticesCount += open->Count();
1699 		if (closed)
1700 			VerticesCount += closed->Count();
1701 
1702 		VerticesCount += d->oibcount+d->cibcount;
1703 	}
1704 	headersize += VerticesCount * 4;
1705 	AmbiOffset = headersize;
1706 
1707 	AmbiCount = map->GetAmbientCount(true);
1708 	headersize += AmbiCount * 0xd4;
1709 	VariablesOffset = headersize;
1710 
1711 	VariablesCount = (ieDword) map->locals->GetCount();
1712 	headersize += VariablesCount * 0x54;
1713 	AnimOffset = headersize;
1714 
1715 	AnimCount = (ieDword) map->GetAnimationCount();
1716 	headersize += AnimCount * 0x4c;
1717 	TileOffset = headersize;
1718 
1719 	TileCount = (ieDword) map->TMap->GetTileCount();
1720 	headersize += TileCount * 0x6c;
1721 	ExploredBitmapOffset = headersize;
1722 
1723 	ExploredBitmapSize = map->GetExploredMapSize();
1724 	headersize += ExploredBitmapSize;
1725 	EffectOffset = headersize;
1726 
1727 	TrapCount = (ieDword) map->GetTrapCount(piter);
1728 	for(i=0;i<TrapCount;i++) {
1729 		const Projectile *pro = map->GetNextTrap(piter);
1730 		if (pro) {
1731 			EffectQueue *fxqueue = pro->GetEffects();
1732 			if (fxqueue) {
1733 				headersize += fxqueue->GetSavedEffectsCount() * 0x108;
1734 			}
1735 		}
1736 	}
1737 
1738 	TrapOffset = headersize;
1739 	headersize += TrapCount * 0x1c;
1740 	NoteOffset = headersize;
1741 
1742 	int pst = core->HasFeature( GF_AUTOMAP_INI );
1743 	NoteCount = map->GetMapNoteCount();
1744 	headersize += NoteCount * (pst?0x214: 0x34);
1745 	SongHeader = headersize;
1746 
1747 	headersize += 0x90;
1748 	RestHeader = headersize;
1749 
1750 	headersize += 0xe4;
1751 	return headersize;
1752 }
1753 
PutHeader(DataStream * stream,const Map * map)1754 int AREImporter::PutHeader(DataStream *stream, const Map *map)
1755 {
1756 	char Signature[56];
1757 	ieDword tmpDword = 0;
1758 	ieWord tmpWord = 0;
1759 	int pst = core->HasFeature( GF_AUTOMAP_INI );
1760 
1761 	memcpy( Signature, "AREAV1.0", 8);
1762 	if (map->version==16) {
1763 		Signature[5]='9';
1764 		Signature[7]='1';
1765 	}
1766 	stream->Write( Signature, 8);
1767 	stream->WriteResRef( map->WEDResRef);
1768 	uint32_t time = core->GetGame()->GameTime;
1769 	stream->WriteDword(&time); //lastsaved
1770 	stream->WriteDword( &map->AreaFlags);
1771 
1772 	memset(Signature, 0, sizeof(Signature)); //8 bytes 0
1773 	stream->Write( Signature, 8); //northref
1774 	stream->WriteDword( &tmpDword);
1775 	stream->Write( Signature, 8); //westref
1776 	stream->WriteDword( &tmpDword);
1777 	stream->Write( Signature, 8); //southref
1778 	stream->WriteDword( &tmpDword);
1779 	stream->Write( Signature, 8); //eastref
1780 	stream->WriteDword( &tmpDword);
1781 
1782 	stream->WriteWord( &map->AreaType);
1783 	stream->WriteWord( &map->Rain);
1784 	stream->WriteWord( &map->Snow);
1785 	stream->WriteWord( &map->Fog);
1786 	stream->WriteWord( &map->Lightning);
1787 	stream->WriteWord( &tmpWord);
1788 
1789 	if (map->version == 16) { //writing 14 bytes of 0's
1790 		char tmp[1] = { '0' };
1791 		if (map->AreaDifficulty == 2) {
1792 			tmp[0] = 1;
1793 		}
1794 		stream->Write( tmp, 1);
1795 		tmp[0] = 0;
1796 		if (map->AreaDifficulty == 4) {
1797 			tmp[0] = 1;
1798 		}
1799 		stream->Write( tmp, 1);
1800 		stream->Write( Signature, 6);
1801 		stream->Write( Signature, 8);
1802 	}
1803 
1804 	stream->WriteDword( &ActorOffset);
1805 	stream->WriteWord( &ActorCount);
1806 	stream->WriteWord( &InfoPointsCount );
1807 	stream->WriteDword( &InfoPointsOffset );
1808 	stream->WriteDword( &SpawnOffset );
1809 	stream->WriteDword( &SpawnCount );
1810 	stream->WriteDword( &EntrancesOffset );
1811 	stream->WriteDword( &EntrancesCount );
1812 	stream->WriteDword( &ContainersOffset );
1813 	stream->WriteWord( &ContainersCount );
1814 	stream->WriteWord( &ItemsCount );
1815 	stream->WriteDword( &ItemsOffset );
1816 	stream->WriteDword( &VerticesOffset );
1817 	stream->WriteWord( &VerticesCount );
1818 	stream->WriteWord( &AmbiCount );
1819 	stream->WriteDword( &AmbiOffset );
1820 	stream->WriteDword( &VariablesOffset );
1821 	stream->WriteDword( &VariablesCount );
1822 	stream->WriteDword( &tmpDword);
1823 
1824 	//the saved area script is in the last script slot!
1825 	const GameScript *s = map->Scripts[MAX_SCRIPTS - 1];
1826 	if (s) {
1827 		stream->WriteResRef( s->GetName() );
1828 	} else {
1829 		stream->Write( Signature, 8);
1830 	}
1831 	stream->WriteDword( &ExploredBitmapSize);
1832 	stream->WriteDword( &ExploredBitmapOffset);
1833 	stream->WriteDword( &DoorsCount );
1834 	stream->WriteDword( &DoorsOffset );
1835 	stream->WriteDword( &AnimCount );
1836 	stream->WriteDword( &AnimOffset );
1837 	stream->WriteDword( &TileCount);
1838 	stream->WriteDword( &TileOffset);
1839 	stream->WriteDword( &SongHeader);
1840 	stream->WriteDword( &RestHeader);
1841 	//an empty dword for pst
1842 	int i;
1843 	if (pst) {
1844 		tmpDword = 0xffffffff;
1845 		stream->WriteDword( &tmpDword);
1846 		i=52;
1847 	} else {
1848 		i=56;
1849 	}
1850 	stream->WriteDword( &NoteOffset );
1851 	stream->WriteDword( &NoteCount );
1852 	stream->WriteDword( &TrapOffset );
1853 	stream->WriteDword( &TrapCount );
1854 	stream->WriteResRef( map->Dream[0] );
1855 	stream->WriteResRef( map->Dream[1] );
1856 	//usually 56 empty bytes (but pst used up 4 elsewhere)
1857 	stream->Write( Signature, i);
1858 	return 0;
1859 }
1860 
PutDoors(DataStream * stream,const Map * map,ieDword & VertIndex)1861 int AREImporter::PutDoors(DataStream *stream, const Map *map, ieDword &VertIndex)
1862 {
1863 	char filling[8];
1864 	ieWord tmpWord = 0;
1865 
1866 	memset(filling,0,sizeof(filling) );
1867 	for (unsigned int i=0;i<DoorsCount;i++) {
1868 		Door *d = map->TMap->GetDoor(i);
1869 
1870 		stream->Write( d->GetScriptName(), 32);
1871 		stream->WriteResRef( d->ID);
1872 		if (map->version == 16) {
1873 			d->Flags = FixIWD2DoorFlags(d->Flags, true);
1874 		}
1875 		stream->WriteDword( &d->Flags);
1876 		stream->WriteDword( &VertIndex);
1877 		auto open = d->OpenTriggerArea();
1878 		tmpWord = open ? open->Count() : 0;
1879 		stream->WriteWord( &tmpWord);
1880 		VertIndex += tmpWord;
1881 		auto closed = d->ClosedTriggerArea();
1882 		tmpWord = closed ? closed->Count() : 0;
1883 		stream->WriteWord( &tmpWord);
1884 		stream->WriteDword( &VertIndex);
1885 		VertIndex += tmpWord;
1886 		//open bounding box
1887 		tmpWord = (ieWord) d->OpenBBox.x;
1888 		stream->WriteWord( &tmpWord);
1889 		tmpWord = (ieWord) d->OpenBBox.y;
1890 		stream->WriteWord( &tmpWord);
1891 		tmpWord = (ieWord) (d->OpenBBox.x+d->OpenBBox.w);
1892 		stream->WriteWord( &tmpWord);
1893 		tmpWord = (ieWord) (d->OpenBBox.y+d->OpenBBox.h);
1894 		stream->WriteWord( &tmpWord);
1895 		//closed bounding box
1896 		tmpWord = (ieWord) d->ClosedBBox.x;
1897 		stream->WriteWord( &tmpWord);
1898 		tmpWord = (ieWord) d->ClosedBBox.y;
1899 		stream->WriteWord( &tmpWord);
1900 		tmpWord = (ieWord) (d->ClosedBBox.x+d->ClosedBBox.w);
1901 		stream->WriteWord( &tmpWord);
1902 		tmpWord = (ieWord) (d->ClosedBBox.y+d->ClosedBBox.h);
1903 		stream->WriteWord( &tmpWord);
1904 		//open and closed impeded blocks
1905 		stream->WriteDword( &VertIndex);
1906 		tmpWord = (ieWord) d->oibcount;
1907 		stream->WriteWord( &tmpWord);
1908 		VertIndex += tmpWord;
1909 		tmpWord = (ieWord) d->cibcount;
1910 		stream->WriteWord( &tmpWord);
1911 		stream->WriteDword( &VertIndex);
1912 		VertIndex += tmpWord;
1913 		stream->WriteWord( &d->hp);
1914 		stream->WriteWord( &d->ac);
1915 		stream->WriteResRef( d->OpenSound);
1916 		stream->WriteResRef( d->CloseSound);
1917 		stream->WriteDword( &d->Cursor);
1918 		stream->WriteWord( &d->TrapDetectionDiff);
1919 		stream->WriteWord( &d->TrapRemovalDiff);
1920 		stream->WriteWord( &d->Trapped);
1921 		stream->WriteWord( &d->TrapDetected);
1922 		tmpWord = (ieWord) d->TrapLaunch.x;
1923 		stream->WriteWord( &tmpWord);
1924 		tmpWord = (ieWord) d->TrapLaunch.y;
1925 		stream->WriteWord( &tmpWord);
1926 		stream->WriteResRef( d->KeyResRef);
1927 		const GameScript *s = d->Scripts[0];
1928 		if (s) {
1929 			stream->WriteResRef( s->GetName() );
1930 		} else {
1931 			stream->Write( filling, 8);
1932 		}
1933 		stream->WriteDword( &d->DiscoveryDiff);
1934 		//lock difficulty field
1935 		stream->WriteDword( &d->LockDifficulty);
1936 		//opening locations
1937 		tmpWord = (ieWord) d->toOpen[0].x;
1938 		stream->WriteWord( &tmpWord);
1939 		tmpWord = (ieWord) d->toOpen[0].y;
1940 		stream->WriteWord( &tmpWord);
1941 		tmpWord = (ieWord) d->toOpen[1].x;
1942 		stream->WriteWord( &tmpWord);
1943 		tmpWord = (ieWord) d->toOpen[1].y;
1944 		stream->WriteWord( &tmpWord);
1945 		stream->WriteDword( &d->OpenStrRef);
1946 		if (core->HasFeature(GF_AUTOMAP_INI) ) {
1947 			stream->Write( d->LinkedInfo, 24);
1948 		} else {
1949 			stream->Write( d->LinkedInfo, 32);
1950 		}
1951 		stream->WriteDword( &d->NameStrRef);
1952 		stream->WriteResRef( d->GetDialog());
1953 		if (core->HasFeature(GF_AUTOMAP_INI) ) {
1954 			stream->Write( filling, 8);
1955 		}
1956 	}
1957 	return 0;
1958 }
1959 
PutPoints(DataStream * stream,const std::vector<Point> & p)1960 int AREImporter::PutPoints(DataStream *stream, const std::vector<Point>& p)
1961 {
1962 	return PutPoints(stream, &p[0], p.size());
1963 }
1964 
PutPoints(DataStream * stream,const Point * p,size_t count)1965 int AREImporter::PutPoints( DataStream *stream, const Point *p, size_t count)
1966 {
1967 	ieWord tmpWord;
1968 
1969 	for(size_t j=0;j<count;j++) {
1970 		tmpWord = p[j].x;
1971 		stream->WriteWord( &tmpWord);
1972 		tmpWord = p[j].y;
1973 		stream->WriteWord( &tmpWord);
1974 	}
1975 	return 0;
1976 }
1977 
PutVertices(DataStream * stream,const Map * map)1978 int AREImporter::PutVertices(DataStream *stream, const Map *map)
1979 {
1980 	unsigned int i;
1981 
1982 	//regions
1983 	for(i=0;i<InfoPointsCount;i++) {
1984 		InfoPoint *ip = map->TMap->GetInfoPoint(i);
1985 		if (ip->outline) {
1986 			PutPoints(stream, ip->outline->vertices);
1987 		} else {
1988 			Point origin = ip->BBox.Origin();
1989 			PutPoints(stream, &origin, 1);
1990 		}
1991 	}
1992 	//containers
1993 	for(i=0;i<ContainersCount;i++) {
1994 		const Container *c = map->TMap->GetContainer(i);
1995 		if (c->outline) {
1996 			PutPoints(stream, c->outline->vertices);
1997 		}
1998 	}
1999 	//doors
2000 	for(i=0;i<DoorsCount;i++) {
2001 		const Door *d = map->TMap->GetDoor(i);
2002 		auto open = d->OpenTriggerArea();
2003 		auto closed = d->ClosedTriggerArea();
2004 		if (open)
2005 			PutPoints(stream, open->vertices);
2006 		if (closed)
2007 			PutPoints(stream, closed->vertices);
2008 		PutPoints(stream, d->open_ib, d->oibcount);
2009 		PutPoints(stream, d->closed_ib, d->cibcount);
2010 	}
2011 	return 0;
2012 }
2013 
PutItems(DataStream * stream,const Map * map)2014 int AREImporter::PutItems(DataStream *stream, const Map *map)
2015 {
2016 	for (unsigned int i=0;i<ContainersCount;i++) {
2017 		const Container *c = map->TMap->GetContainer(i);
2018 
2019 		for(int j=0;j<c->inventory.GetSlotCount();j++) {
2020 			const CREItem *ci = c->inventory.GetSlotItem(j);
2021 
2022 			stream->WriteResRef( ci->ItemResRef);
2023 			stream->WriteWord( &ci->Expired);
2024 			stream->WriteWord( &ci->Usages[0]);
2025 			stream->WriteWord( &ci->Usages[1]);
2026 			stream->WriteWord( &ci->Usages[2]);
2027 			stream->WriteDword( &ci->Flags);
2028 		}
2029 	}
2030 	return 0;
2031 }
2032 
PutContainers(DataStream * stream,const Map * map,ieDword & VertIndex)2033 int AREImporter::PutContainers(DataStream *stream, const Map *map, ieDword &VertIndex)
2034 {
2035 	char filling[56];
2036 	ieDword ItemIndex = 0;
2037 	ieDword tmpDword;
2038 	ieWord tmpWord;
2039 
2040 	memset(filling,0,sizeof(filling) );
2041 	for (unsigned int i=0;i<ContainersCount;i++) {
2042 		const Container *c = map->TMap->GetContainer(i);
2043 
2044 		//this is the editor name
2045 		stream->Write( c->GetScriptName(), 32);
2046 		tmpWord = (ieWord) c->Pos.x;
2047 		stream->WriteWord( &tmpWord);
2048 		tmpWord = (ieWord) c->Pos.y;
2049 		stream->WriteWord( &tmpWord);
2050 		stream->WriteWord( &c->Type);
2051 		stream->WriteWord( &c->LockDifficulty);
2052 		stream->WriteDword( &c->Flags);
2053 		stream->WriteWord( &c->TrapDetectionDiff);
2054 		stream->WriteWord( &c->TrapRemovalDiff);
2055 		stream->WriteWord( &c->Trapped);
2056 		stream->WriteWord( &c->TrapDetected);
2057 		tmpWord = (ieWord) c->TrapLaunch.x;
2058 		stream->WriteWord( &tmpWord);
2059 		tmpWord = (ieWord) c->TrapLaunch.y;
2060 		stream->WriteWord( &tmpWord);
2061 		//outline bounding box
2062 		tmpWord = (ieWord) c->BBox.x;
2063 		stream->WriteWord( &tmpWord);
2064 		tmpWord = (ieWord) c->BBox.y;
2065 		stream->WriteWord( &tmpWord);
2066 		tmpWord = (ieWord) (c->BBox.x + c->BBox.w);
2067 		stream->WriteWord( &tmpWord);
2068 		tmpWord = (ieWord) (c->BBox.y + c->BBox.h);
2069 		stream->WriteWord( &tmpWord);
2070 		//item index and offset
2071 		tmpDword = c->inventory.GetSlotCount();
2072 		stream->WriteDword( &ItemIndex);
2073 		stream->WriteDword( &tmpDword);
2074 		ItemIndex +=tmpDword;
2075 		const GameScript *s = c->Scripts[0];
2076 		if (s) {
2077 			stream->WriteResRef( s->GetName() );
2078 		} else {
2079 			stream->Write( filling, 8);
2080 		}
2081 		//outline polygon index and count
2082 		tmpWord = (c->outline) ? c->outline->Count() : 0;
2083 		stream->WriteDword( &VertIndex);
2084 		stream->WriteWord( &tmpWord);
2085 		VertIndex +=tmpWord;
2086 		tmpWord = 0;
2087 		stream->WriteWord( &tmpWord); //vertex count is made short
2088 		//this is the real scripting name
2089 		stream->Write( c->GetScriptName(), 32);
2090 		stream->WriteResRef( c->KeyResRef);
2091 		stream->WriteDword( &tmpDword); //unknown80
2092 		stream->WriteDword( &c->OpenFail);
2093 		stream->Write( filling, 56); //unknown or unused stuff
2094 	}
2095 	return 0;
2096 }
2097 
PutRegions(DataStream * stream,const Map * map,ieDword & VertIndex)2098 int AREImporter::PutRegions(DataStream *stream, const Map *map, ieDword &VertIndex)
2099 {
2100 	ieDword tmpDword = 0;
2101 	ieWord tmpWord;
2102 	char filling[36];
2103 
2104 	memset(filling,0,sizeof(filling) );
2105 	for (unsigned int i=0;i<InfoPointsCount;i++) {
2106 		const InfoPoint *ip = map->TMap->GetInfoPoint(i);
2107 
2108 		stream->Write( ip->GetScriptName(), 32);
2109 		//this is a hack, we abuse a coincidence
2110 		//ST_PROXIMITY = 1, ST_TRIGGER = 2, ST_TRAVEL = 3
2111 		//translates to trap = 0, info = 1, travel = 2
2112 		tmpWord = ((ieWord) ip->Type) - 1;
2113 		stream->WriteWord( &tmpWord);
2114 		//outline bounding box
2115 		tmpWord = (ieWord) ip->BBox.x;
2116 		stream->WriteWord( &tmpWord);
2117 		tmpWord = (ieWord) ip->BBox.y;
2118 		stream->WriteWord( &tmpWord);
2119 		tmpWord = (ieWord) (ip->BBox.x + ip->BBox.w);
2120 		stream->WriteWord( &tmpWord);
2121 		tmpWord = (ieWord) (ip->BBox.y + ip->BBox.h);
2122 		stream->WriteWord( &tmpWord);
2123 		tmpWord = (ip->outline) ? ip->outline->Count() : 1;
2124 		stream->WriteWord( &tmpWord);
2125 		stream->WriteDword( &VertIndex);
2126 		VertIndex += tmpWord;
2127 		stream->WriteDword( &tmpDword); //unknown30
2128 		stream->WriteDword( &ip->Cursor);
2129 		stream->WriteResRef( ip->Destination);
2130 		stream->Write( ip->EntranceName, 32);
2131 		stream->WriteDword( &ip->Flags);
2132 		stream->WriteDword( &ip->StrRef);
2133 		stream->WriteWord( &ip->TrapDetectionDiff);
2134 		stream->WriteWord( &ip->TrapRemovalDiff);
2135 		stream->WriteWord( &ip->Trapped); //unknown???
2136 		stream->WriteWord( &ip->TrapDetected);
2137 		tmpWord = (ieWord) ip->TrapLaunch.x;
2138 		stream->WriteWord( &tmpWord);
2139 		tmpWord = (ieWord) ip->TrapLaunch.y;
2140 		stream->WriteWord( &tmpWord);
2141 		stream->WriteResRef( ip->KeyResRef);
2142 		const GameScript *s = ip->Scripts[0];
2143 		if (s) {
2144 			stream->WriteResRef( s->GetName() );
2145 		} else {
2146 			stream->Write( filling, 8);
2147 		}
2148 		tmpWord = (ieWord) ip->UsePoint.x;
2149 		ieDword tmpDword2 = ip->UsePoint.x;
2150 		stream->WriteWord( &tmpWord);
2151 		tmpWord = (ieWord) ip->UsePoint.y;
2152 		tmpDword = ip->UsePoint.y;
2153 		stream->WriteWord( &tmpWord);
2154 		if (16 == map->version) {
2155 			stream->WriteDword(&tmpDword2);
2156 			stream->WriteDword(&tmpDword);
2157 			stream->Write(filling, 28); //unknown
2158 		} else {
2159 			stream->Write(filling, 36); //unknown
2160 		}
2161 		//these are probably only in PST
2162 		stream->WriteResRef( ip->EnterWav);
2163 		tmpWord = (ieWord) ip->TalkPos.x;
2164 		stream->WriteWord( &tmpWord);
2165 		tmpWord = (ieWord) ip->TalkPos.y;
2166 		stream->WriteWord( &tmpWord);
2167 		stream->WriteDword( &ip->DialogName);
2168 		stream->WriteResRef( ip->GetDialog());
2169 	}
2170 	return 0;
2171 }
2172 
PutSpawns(DataStream * stream,const Map * map)2173 int AREImporter::PutSpawns(DataStream *stream, const Map *map)
2174 {
2175 	ieWord tmpWord;
2176 	char filling[56];
2177 
2178 	memset(filling,0,sizeof(filling) );
2179 	for (unsigned int i=0;i<SpawnCount;i++) {
2180 		const Spawn *sp = map->GetSpawn(i);
2181 
2182 		stream->Write( sp->Name, 32);
2183 		tmpWord = (ieWord) sp->Pos.x;
2184 		stream->WriteWord( &tmpWord);
2185 		tmpWord = (ieWord) sp->Pos.y;
2186 		stream->WriteWord( &tmpWord);
2187 		tmpWord = sp->GetCreatureCount();
2188 		int j;
2189 		for (j = 0;j < tmpWord; j++) {
2190 			stream->WriteResRef( sp->Creatures[j] );
2191 		}
2192 		while( j++<MAX_RESCOUNT) {
2193 			stream->Write( filling, 8);
2194 		}
2195 		stream->WriteWord( &tmpWord );
2196 		stream->WriteWord( &sp->Difficulty);
2197 		stream->WriteWord( &sp->Frequency);
2198 		stream->WriteWord( &sp->Method);
2199 		stream->WriteDword( &sp->sduration); //spawn duration
2200 		stream->WriteWord( &sp->rwdist);     //random walk distance
2201 		stream->WriteWord( &sp->owdist);     //other walk distance
2202 		stream->WriteWord( &sp->Maximum);
2203 		stream->WriteWord( &sp->Enabled);
2204 		stream->WriteDword( &sp->appearance);
2205 		stream->WriteWord( &sp->DayChance);
2206 		stream->WriteWord( &sp->NightChance);
2207 		stream->Write( filling, 56); //most likely unused crap
2208 	}
2209 	return 0;
2210 }
2211 
PutScript(DataStream * stream,const Actor * ac,unsigned int index)2212 void AREImporter::PutScript(DataStream *stream, const Actor *ac, unsigned int index)
2213 {
2214 	char filling[8];
2215 
2216 	const GameScript *s = ac->Scripts[index];
2217 	if (s) {
2218 		stream->WriteResRef( s->GetName() );
2219 	} else {
2220 		memset(filling,0,sizeof(filling));
2221 		stream->Write( filling, 8);
2222 	}
2223 }
2224 
PutActors(DataStream * stream,const Map * map)2225 int AREImporter::PutActors(DataStream *stream, const Map *map)
2226 {
2227 	ieDword tmpDword = 0;
2228 	ieWord tmpWord;
2229 	ieByte tmpByte;
2230 	ieDword CreatureOffset = EmbeddedCreOffset;
2231 	char filling[120];
2232 	unsigned int i;
2233 
2234 	PluginHolder<ActorMgr> am(IE_CRE_CLASS_ID);
2235 	memset(filling,0,sizeof(filling) );
2236 	for (i=0;i<ActorCount;i++) {
2237 		Actor *ac = map->GetActor(i, false);
2238 
2239 		stream->Write( ac->GetScriptName(), 32);
2240 		tmpWord = (ieWord) ac->Pos.x;
2241 		stream->WriteWord( &tmpWord);
2242 		tmpWord = (ieWord) ac->Pos.y;
2243 		stream->WriteWord( &tmpWord);
2244 		tmpWord = (ieWord) ac->HomeLocation.x;
2245 		stream->WriteWord( &tmpWord);
2246 		tmpWord = (ieWord) ac->HomeLocation.y;
2247 		stream->WriteWord( &tmpWord);
2248 
2249 		stream->WriteDword( &tmpDword); //used fields flag always 0 for saved areas
2250 		tmpWord = ac->Spawned;
2251 		stream->WriteWord( &tmpWord);
2252 		stream->Write(filling, 1); // letter
2253 		tmpByte = ac->DifficultyMargin;
2254 		stream->Write( &tmpByte, 1 );
2255 		stream->WriteDword( &tmpDword); //actor animation, unused
2256 		tmpWord = ac->GetOrientation();
2257 		stream->WriteWord( &tmpWord);
2258 		tmpWord = 0;
2259 		stream->WriteWord( &tmpWord); //unknown
2260 		stream->WriteDword( &ac->RemovalTime);
2261 		stream->WriteWord( &ac->maxWalkDistance);
2262 		stream->WriteWord( &tmpWord); //more unknowns
2263 		stream->WriteDword( &ac->appearance);
2264 		stream->WriteDword( &ac->TalkCount);
2265 		stream->WriteResRef( ac->GetDialog());
2266 		PutScript(stream, ac, SCR_OVERRIDE);
2267 		PutScript(stream, ac, SCR_GENERAL);
2268 		PutScript(stream, ac, SCR_CLASS);
2269 		PutScript(stream, ac, SCR_RACE);
2270 		PutScript(stream, ac, SCR_DEFAULT);
2271 		PutScript(stream, ac, SCR_SPECIFICS);
2272 		//creature reference is empty because we are embedding it
2273 		//the original engine used a '*'
2274 		stream->Write( filling, 8);
2275 		stream->WriteDword( &CreatureOffset);
2276 		ieDword CreatureSize = am->GetStoredFileSize(ac);
2277 		stream->WriteDword( &CreatureSize);
2278 		CreatureOffset += CreatureSize;
2279 		PutScript(stream, ac, SCR_AREA);
2280 		stream->Write( filling, 120);
2281 	}
2282 
2283 	CreatureOffset = EmbeddedCreOffset;
2284 	for (i=0;i<ActorCount;i++) {
2285 		assert(stream->GetPos() == CreatureOffset);
2286 		Actor *ac = map->GetActor(i, false);
2287 
2288 		//reconstructing offsets again
2289 		CreatureOffset += am->GetStoredFileSize(ac);
2290 		am->PutActor( stream, ac);
2291 	}
2292 	assert(stream->GetPos() == CreatureOffset);
2293 
2294 	return 0;
2295 }
2296 
PutAnimations(DataStream * stream,const Map * map)2297 int AREImporter::PutAnimations(DataStream *stream, const Map *map)
2298 {
2299 	ieWord tmpWord;
2300 
2301 	aniIterator iter = map->GetFirstAnimation();
2302 	while(const AreaAnimation *an = map->GetNextAnimation(iter)) {
2303 		stream->Write( an->Name, 32);
2304 		tmpWord = (ieWord) an->Pos.x;
2305 		stream->WriteWord( &tmpWord);
2306 		tmpWord = (ieWord) an->Pos.y;
2307 		stream->WriteWord( &tmpWord);
2308 		stream->WriteDword( &an->appearance);
2309 		stream->WriteResRef( an->BAM);
2310 		stream->WriteWord( &an->sequence);
2311 		stream->WriteWord( &an->frame);
2312 
2313 		if (core->HasFeature(GF_AUTOMAP_INI)) {
2314 			/* PST toggles the active bit only, and we need to keep the rest. */
2315 			ieDword flags = (an->originalFlags & ~A_ANI_ACTIVE) | (an->Flags & A_ANI_ACTIVE);
2316 			stream->WriteDword(&flags);
2317 		} else {
2318 			stream->WriteDword(&an->Flags);
2319 		}
2320 
2321 		stream->WriteWord((const ieWord *) &an->height);
2322 		stream->WriteWord( &an->transparency);
2323 		stream->WriteWord( &an->startFrameRange); //used by A_ANI_RANDOM_START
2324 		stream->Write( &an->startchance,1);
2325 		stream->Write( &an->skipcycle,1);
2326 		stream->WriteResRef( an->PaletteRef);
2327 		stream->WriteDword( &an->unknown48);//seems utterly unused
2328 	}
2329 	return 0;
2330 }
2331 
PutEntrances(DataStream * stream,const Map * map)2332 int AREImporter::PutEntrances(DataStream *stream, const Map *map)
2333 {
2334 	ieWord tmpWord;
2335 	char filling[66];
2336 
2337 	memset(filling,0,sizeof(filling) );
2338 	for (unsigned int i=0;i<EntrancesCount;i++) {
2339 		const Entrance *e = map->GetEntrance(i);
2340 
2341 		stream->Write( e->Name, 32);
2342 		tmpWord = (ieWord) e->Pos.x;
2343 		stream->WriteWord( &tmpWord);
2344 		tmpWord = (ieWord) e->Pos.y;
2345 		stream->WriteWord( &tmpWord);
2346 		stream->WriteWord( &e->Face);
2347 		//a large empty piece of crap
2348 		stream->Write( filling, 66);
2349 	}
2350 	return 0;
2351 }
2352 
PutVariables(DataStream * stream,const Map * map)2353 int AREImporter::PutVariables(DataStream *stream, const Map *map)
2354 {
2355 	char filling[40];
2356 	Variables::iterator pos=NULL;
2357 	const char *name;
2358 	ieDword value;
2359 
2360 	memset(filling,0,sizeof(filling) );
2361 	for (unsigned int i=0;i<VariablesCount;i++) {
2362 		pos=map->locals->GetNextAssoc( pos, name, value);
2363 		//name isn't necessarily 32 bytes long, so we play safe
2364 		strncpy(filling, name, 32);
2365 		stream->Write( filling, 40);
2366 		//clearing up after the strncpy so we'll write 0's next
2367 		memset(filling,0,sizeof(filling) );
2368 		stream->WriteDword( &value);
2369 		//40 bytes of empty crap
2370 		stream->Write( filling, 40);
2371 	}
2372 	return 0;
2373 }
2374 
PutAmbients(DataStream * stream,const Map * map)2375 int AREImporter::PutAmbients(DataStream *stream, const Map *map)
2376 {
2377 	char filling[64];
2378 	ieWord tmpWord;
2379 
2380 	memset(filling,0,sizeof(filling) );
2381 	ieWord realCount = map->GetAmbientCount();
2382 	for (ieWord i = 0; i < realCount; i++) {
2383 		const Ambient *am = map->GetAmbient(i);
2384 		if (am->flags & IE_AMBI_NOSAVE) continue;
2385 		stream->Write( am->name, 32 );
2386 		tmpWord = (ieWord) am->origin.x;
2387 		stream->WriteWord( &tmpWord );
2388 		tmpWord = (ieWord) am->origin.y;
2389 		stream->WriteWord( &tmpWord );
2390 		stream->WriteWord( &am->radius );
2391 		stream->Write( filling, 2 );
2392 		stream->WriteDword( &am->pitchVariance );
2393 		stream->WriteWord( &am->gainVariance );
2394 		stream->WriteWord( &am->gain );
2395 		tmpWord = (ieWord) am->sounds.size();
2396 		int j;
2397 		for (j = 0;j < tmpWord; j++) {
2398 			stream->WriteResRef( am->sounds[j] );
2399 		}
2400 		while( j++<MAX_RESCOUNT) {
2401 			stream->Write( filling, 8);
2402 		}
2403 		stream->WriteWord( &tmpWord );
2404 		stream->Write( filling, 2 );
2405 		stream->WriteDword( &am->interval );
2406 		stream->WriteDword( &am->intervalVariance );
2407 		stream->WriteDword( &am->appearance );
2408 		stream->WriteDword( &am->flags );
2409 		stream->Write( filling, 64);
2410 	}
2411 	return 0;
2412 }
2413 
PutMapnotes(DataStream * stream,const Map * map)2414 int AREImporter::PutMapnotes(DataStream *stream, const Map *map)
2415 {
2416 	char filling[8];
2417 	ieDword tmpDword;
2418 	ieWord tmpWord;
2419 
2420 	//different format
2421 	int pst = core->HasFeature( GF_AUTOMAP_INI );
2422 
2423 	memset(filling,0,sizeof(filling) );
2424 	for (unsigned int i=0;i<NoteCount;i++) {
2425 		const MapNote& mn = map->GetMapNote(i);
2426 
2427 		if (pst) {
2428 			// in PST the coordinates are stored in small map space
2429 			const Size& mapsize = map->GetSize();
2430 			tmpDword = mn.Pos.x * double(map->SmallMap->Frame.w) / mapsize.w;
2431 			stream->WriteDword( &tmpDword );
2432 			tmpDword = mn.Pos.y * double(map->SmallMap->Frame.h) / mapsize.h;
2433 			stream->WriteDword( &tmpDword );
2434 
2435 			int len = 0;
2436 			if (mn.text) {
2437 				// limited to 500 *bytes* of text, convert to a multibyte encoding.
2438 				// we convert to MB because it fits more than if we wrote the wide characters
2439 				char* mbstring = MBCStringFromString(*mn.text);
2440 				// FIXME: depends on locale blah blah (see MBCStringFromString definition)
2441 				if (mbstring) {
2442 					// only care about number of bytes before null so strlen is what we want despite being MB string
2443 					len = std::min(static_cast<int>(strlen(mbstring)), 500);
2444 					stream->Write( mbstring, len);
2445 					free(mbstring);
2446 				} else {
2447 					Log(WARNING, "AREImporter", "MapNote converted to an invalid multibyte sequence; cannot write it to file.\nFailed Note: %ls", mn.text->c_str());
2448 				}
2449 			}
2450 
2451 			// pad the remaining space
2452 			int x = 500 - len;
2453 			for (int j=0;j<x/8;j++) {
2454 				stream->Write( filling, 8);
2455 			}
2456 			x = x%8;
2457 			if (x) {
2458 				stream->Write( filling, x);
2459 			}
2460 			tmpDword = (ieDword) mn.readonly;
2461 			stream->WriteDword(&tmpDword);
2462 			for (x=0;x<5;x++) { //5 empty dwords
2463 				stream->Write( filling, 4);
2464 			}
2465 		} else {
2466 			tmpWord = (ieWord) mn.Pos.x;
2467 			stream->WriteWord( &tmpWord );
2468 			tmpWord = (ieWord) mn.Pos.y;
2469 			stream->WriteWord( &tmpWord );
2470 			stream->WriteDword( &mn.strref);
2471 			stream->WriteWord( &tmpWord );
2472 			stream->WriteWord( &mn.color );
2473 			tmpDword = 1;
2474 			stream->WriteDword( &tmpDword );
2475 			for (int x = 0; x < 9; ++x) { //9 empty dwords
2476 				stream->Write( filling, 4);
2477 			}
2478 		}
2479 	}
2480 	return 0;
2481 }
2482 
PutEffects(DataStream * stream,const EffectQueue * fxqueue)2483 int AREImporter::PutEffects(DataStream *stream, const EffectQueue *fxqueue)
2484 {
2485 	PluginHolder<EffectMgr> eM(IE_EFF_CLASS_ID);
2486 	assert(eM != nullptr);
2487 
2488 	std::list< Effect* >::const_iterator f=fxqueue->GetFirstEffect();
2489 	ieDword EffectsCount = fxqueue->GetSavedEffectsCount();
2490 	for(unsigned int i=0;i<EffectsCount;i++) {
2491 		const Effect *fx = fxqueue->GetNextSavedEffect(f);
2492 
2493 		assert(fx!=NULL);
2494 
2495 		eM->PutEffectV2(stream, fx);
2496 	}
2497 	return 0;
2498 }
2499 
PutTraps(DataStream * stream,const Map * map)2500 int AREImporter::PutTraps( DataStream *stream, const Map *map)
2501 {
2502 	ieDword Offset;
2503 	ieDword tmpDword;
2504 	ieResRef name;
2505 	ieWord type = 0;
2506 	Point dest(0,0);
2507 
2508 	Offset = EffectOffset;
2509 	ieDword i = map->GetTrapCount(piter);
2510 	while(i--) {
2511 		ieWord tmpWord = 0;
2512 		ieByte tmpByte = 0xff;
2513 		Projectile *pro = map->GetNextTrap(piter);
2514 		if (pro) {
2515 			//The projectile ID is based on missile.ids which is
2516 			//off by one compared to projectl.ids
2517 			type = pro->GetType()+1;
2518 			dest = pro->GetDestination();
2519 			strnuprcpy(name, pro->GetName(), 8);
2520 			const EffectQueue *fxqueue = pro->GetEffects();
2521 			if (fxqueue) {
2522 				tmpWord = fxqueue->GetSavedEffectsCount();
2523 			}
2524 			ieDword ID = pro->GetCaster();
2525 			// lookup caster via Game, since the the current map can already be empty when switching them
2526 			const Actor *actor = core->GetGame()->GetActorByGlobalID(ID);
2527 			//0xff if not in party
2528 			//party slot if in party
2529 			if (actor) tmpByte = (ieByte) (actor->InParty-1);
2530 		}
2531 
2532 		stream->WriteResRef( name );
2533 		stream->WriteDword( &Offset );
2534 		//size of fxqueue;
2535 		assert(tmpWord<256);
2536 		tmpWord *= 0x108;
2537 		Offset += tmpWord;
2538 		stream->WriteWord( &tmpWord );  //size in bytes
2539 		stream->WriteWord( &type );     //missile.ids
2540 		tmpDword = 0;
2541 		stream->WriteDword(&tmpDword); // unknown field, Ticks
2542 		tmpWord = (ieWord) dest.x;
2543 		stream->WriteWord( &tmpWord );
2544 		tmpWord = (ieWord) dest.y;
2545 		stream->WriteWord( &tmpWord );
2546 		tmpWord = 0;
2547 		stream->WriteWord(&tmpWord); // unknown field, Z
2548 		stream->Write(&tmpByte, 1);   // unknown field, TargetType
2549 		stream->Write(&tmpByte, 1);   // Owner
2550 	}
2551 	return 0;
2552 }
2553 
PutExplored(DataStream * stream,const Map * map)2554 int AREImporter::PutExplored(DataStream *stream, const Map *map)
2555 {
2556 	stream->Write( map->ExploredBitmap, ExploredBitmapSize);
2557 	return 0;
2558 }
2559 
PutTiles(DataStream * stream,const Map * map)2560 int AREImporter::PutTiles(DataStream *stream, const Map *map)
2561 {
2562 	char filling[48];
2563 	ieDword tmpDword = 0;
2564 
2565 	memset(filling,0,sizeof(filling) );
2566 	for (unsigned int i=0;i<TileCount;i++) {
2567 		const TileObject *am = map->TMap->GetTile(i);
2568 		stream->Write( am->Name, 32 );
2569 		stream->WriteResRef( am->Tileset );
2570 		stream->WriteDword( &am->Flags);
2571 		stream->WriteDword( &am->opencount);
2572 		//can't write tiles, otherwise now we should write a tile index
2573 		stream->WriteDword( &tmpDword);
2574 		stream->WriteDword( &am->closedcount);
2575 		//can't write tiles otherwise now we should write a tile index
2576 		stream->WriteDword( &tmpDword);
2577 		stream->Write( filling, 48);
2578 	}
2579 	return 0;
2580 }
2581 
PutSongHeader(DataStream * stream,const Map * map)2582 int AREImporter::PutSongHeader(DataStream *stream, const Map *map)
2583 {
2584 	int i;
2585 	char filling[8];
2586 	ieDword tmpDword = 0;
2587 
2588 	memset(filling,0,sizeof(filling) );
2589 	for(i=0;i<MAX_RESCOUNT;i++) {
2590 		stream->WriteDword( &map->SongHeader.SongList[i]);
2591 	}
2592 	//day
2593 	stream->WriteResRef(map->SongHeader.MainDayAmbient1);
2594 	stream->WriteResRef(map->SongHeader.MainDayAmbient2);
2595 	stream->WriteDword(&map->SongHeader.MainDayAmbientVol);
2596 	//night
2597 	stream->WriteResRef(map->SongHeader.MainNightAmbient1);
2598 	stream->WriteResRef(map->SongHeader.MainNightAmbient2);
2599 	stream->WriteDword(&map->SongHeader.MainNightAmbientVol);
2600 	//song flag
2601 	stream->WriteDword(&map->SongHeader.reverbID);
2602 	//lots of empty crap (15x4)
2603 	for(i=0;i<15;i++) {
2604 		stream->WriteDword( &tmpDword);
2605 	}
2606 	return 0;
2607 }
2608 
PutRestHeader(DataStream * stream,const Map * map)2609 int AREImporter::PutRestHeader(DataStream *stream, const Map *map)
2610 {
2611 	int i;
2612 	ieDword tmpDword = 0;
2613 
2614 	char filling[32];
2615 	memset(filling,0,sizeof(filling) );
2616 	stream->Write( filling, 32); //empty label
2617 	for(i=0;i<MAX_RESCOUNT;i++) {
2618 		stream->WriteDword( &map->RestHeader.Strref[i]);
2619 	}
2620 	for(i=0;i<MAX_RESCOUNT;i++) {
2621 		stream->WriteResRef( map->RestHeader.CreResRef[i]);
2622 	}
2623 	stream->WriteWord( &map->RestHeader.CreatureNum);
2624 	stream->WriteWord( &map->RestHeader.Difficulty);
2625 	stream->WriteDword( &map->RestHeader.sduration);
2626 	stream->WriteWord( &map->RestHeader.rwdist);
2627 	stream->WriteWord( &map->RestHeader.owdist);
2628 	stream->WriteWord( &map->RestHeader.Maximum);
2629 	stream->WriteWord( &map->RestHeader.Enabled);
2630 	stream->WriteWord( &map->RestHeader.DayChance);
2631 	stream->WriteWord( &map->RestHeader.NightChance);
2632 	for(i=0;i<14;i++) {
2633 		stream->WriteDword( &tmpDword);
2634 	}
2635 	return 0;
2636 }
2637 
2638 /* no saving of tiled objects, are they used anywhere? */
PutArea(DataStream * stream,Map * map)2639 int AREImporter::PutArea(DataStream *stream, Map *map)
2640 {
2641 	ieDword VertIndex = 0;
2642 	int ret;
2643 
2644 	if (!stream || !map) {
2645 		return -1;
2646 	}
2647 
2648 	ret = PutHeader( stream, map);
2649 	if (ret) {
2650 		return ret;
2651 	}
2652 
2653 	ret = PutActors( stream, map);
2654 	if (ret) {
2655 		return ret;
2656 	}
2657 
2658 	ret = PutRegions( stream, map, VertIndex);
2659 	if (ret) {
2660 		return ret;
2661 	}
2662 
2663 	ret = PutSpawns( stream, map);
2664 	if (ret) {
2665 		return ret;
2666 	}
2667 
2668 	ret = PutEntrances( stream, map);
2669 	if (ret) {
2670 		return ret;
2671 	}
2672 
2673 	ret = PutContainers( stream, map, VertIndex);
2674 	if (ret) {
2675 		return ret;
2676 	}
2677 
2678 	ret = PutItems( stream, map);
2679 	if (ret) {
2680 		return ret;
2681 	}
2682 
2683 	ret = PutDoors( stream, map, VertIndex);
2684 	if (ret) {
2685 		return ret;
2686 	}
2687 
2688 	ret = PutVertices( stream, map);
2689 	if (ret) {
2690 		return ret;
2691 	}
2692 
2693 	ret = PutAmbients( stream, map);
2694 	if (ret) {
2695 		return ret;
2696 	}
2697 
2698 	ret = PutVariables( stream, map);
2699 	if (ret) {
2700 		return ret;
2701 	}
2702 
2703 	ret = PutAnimations( stream, map);
2704 	if (ret) {
2705 		return ret;
2706 	}
2707 
2708 	ret = PutTiles( stream, map);
2709 	if (ret) {
2710 		return ret;
2711 	}
2712 
2713 	ret = PutExplored( stream, map);
2714 	if (ret) {
2715 		return ret;
2716 	}
2717 
2718 	ieDword i = map->GetTrapCount(piter);
2719 	while(i--) {
2720 		const Projectile *trap = map->GetNextTrap(piter);
2721 		if (!trap) {
2722 			continue;
2723 		}
2724 
2725 		const EffectQueue *fxqueue = trap->GetEffects();
2726 
2727 		if (!fxqueue) {
2728 			continue;
2729 		}
2730 
2731 		ret = PutEffects( stream, fxqueue);
2732 		if (ret) {
2733 			return ret;
2734 		}
2735 	}
2736 
2737 	ret = PutTraps( stream, map);
2738 	if (ret) {
2739 		return ret;
2740 	}
2741 
2742 	ret = PutMapnotes( stream, map);
2743 	if (ret) {
2744 		return ret;
2745 	}
2746 
2747 	ret = PutSongHeader( stream, map);
2748 	if (ret) {
2749 		return ret;
2750 	}
2751 
2752 	ret = PutRestHeader( stream, map);
2753 
2754 	return ret;
2755 }
2756 
2757 #include "plugindef.h"
2758 
2759 GEMRB_PLUGIN(0x145B60F0, "ARE File Importer")
2760 PLUGIN_CLASS(IE_ARE_CLASS_ID, AREImporter)
2761 PLUGIN_CLEANUP(ReleaseMemory)
2762 END_PLUGIN()
2763