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