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 "WorldMap.h"
22
23 #include "Game.h"
24 #include "Interface.h"
25 #include "TableMgr.h"
26 #include "Video.h"
27 #include "RNG.h"
28
29 #include <list>
30
31 namespace GemRB {
32
WMPAreaEntry()33 WMPAreaEntry::WMPAreaEntry()
34 {
35 MapIcon = NULL;
36 StrCaption = NULL;
37 StrTooltip = NULL;
38 SingleFrame = false;
39 AreaName[0] = AreaLongName[0] = AreaResRef[0] = 0;
40 LoadScreenResRef[0] = 0;
41 LocCaptionName = LocTooltipName = 0;
42 AreaLinksCount[0] = AreaLinksIndex[0] = 0;
43 IconSeq = AreaStatus = 0;
44 }
45
~WMPAreaEntry()46 WMPAreaEntry::~WMPAreaEntry()
47 {
48 delete StrCaption;
49 free(StrTooltip);
50 }
51
SetAreaStatus(ieDword arg,int op)52 void WMPAreaEntry::SetAreaStatus(ieDword arg, int op)
53 {
54 SetBits(AreaStatus, arg, op);
55 MapIcon = nullptr;
56 }
57
GetCaption()58 const String* WMPAreaEntry::GetCaption()
59 {
60 if (!StrCaption) {
61 StrCaption = core->GetString(LocCaptionName);
62 }
63 return StrCaption;
64 }
65
GetTooltip()66 const char* WMPAreaEntry::GetTooltip()
67 {
68 if (!StrTooltip) {
69 StrTooltip = core->GetCString(LocTooltipName);
70 }
71 return StrTooltip;
72 }
73
GetMapIcon(const AnimationFactory * bam)74 Holder<Sprite2D> WMPAreaEntry::GetMapIcon(const AnimationFactory *bam)
75 {
76 if (!bam || IconSeq == (ieDword) -1) {
77 return NULL;
78 }
79 if (!MapIcon) {
80 int frame = 0;
81 switch (AreaStatus&(WMP_ENTRY_ACCESSIBLE|WMP_ENTRY_VISITED))
82 {
83 case WMP_ENTRY_ACCESSIBLE: frame = 0; break;
84 case WMP_ENTRY_VISITED: frame = 4; break;
85 case WMP_ENTRY_ACCESSIBLE|WMP_ENTRY_VISITED: frame = 1; break;
86 case 0: frame = 2; break;
87 }
88
89 // iwd1, bg1 and pst all have this format
90 if (bam->GetCycleSize(IconSeq)<5) {
91 SingleFrame = true;
92 frame = 0;
93 }
94 MapIcon = bam->GetFrame((ieWord) frame, (ieByte) IconSeq);
95 if (!MapIcon) {
96 Log(ERROR, "WMPAreaEntry", "GetMapIcon failed for frame %d, seq %d", frame, IconSeq);
97 return NULL;
98 }
99 }
100 return MapIcon;
101 }
102
GetAreaStatus()103 ieDword WMPAreaEntry::GetAreaStatus()
104 {
105 ieDword tmp = AreaStatus;
106 if (core->HasFeature(GF_KNOW_WORLD) ) {
107 tmp |=WMP_ENTRY_VISITED;
108 }
109 return tmp;
110 }
111
WorldMap(void)112 WorldMap::WorldMap(void)
113 {
114 MapMOS = NULL;
115 Distances = NULL;
116 GotHereFrom = NULL;
117 bam = NULL;
118 encounterArea = -1;
119 Width = Height = 0;
120 MapNumber = AreaName = 0;
121 Flags = 0;
122 unknown1 = unknown2 = 0;
123 }
124
125 //Allocate AE and AL only in Core, otherwise Win32 will
126 //be buggy
AddAreaEntry(WMPAreaEntry * ae)127 void WorldMap::AddAreaEntry(WMPAreaEntry *ae)
128 {
129 area_entries.push_back(ae);
130 }
131
AddAreaLink(WMPAreaLink * al)132 void WorldMap::AddAreaLink(WMPAreaLink *al)
133 {
134 area_links.push_back(al);
135 }
136
GetNewAreaEntry() const137 WMPAreaEntry *WorldMap::GetNewAreaEntry() const
138 {
139 return new WMPAreaEntry();
140 }
141
SetAreaEntry(unsigned int x,WMPAreaEntry * ae)142 void WorldMap::SetAreaEntry(unsigned int x, WMPAreaEntry *ae)
143 {
144 //if index is too large, we break
145 if (x>area_entries.size()) {
146 error("WorldMap", "Trying to set invalid entry (%d/%d)\n", x, (int)area_entries.size());
147 }
148 //altering an existing entry
149 if (x<area_entries.size()) {
150 if (area_entries[x]) {
151 delete area_entries[x];
152 }
153 area_entries[x]=ae;
154 return;
155 }
156 //adding a new entry
157 area_entries.push_back(ae);
158 }
159
InsertAreaLink(unsigned int areaidx,unsigned int dir,WMPAreaLink * arealink)160 void WorldMap::InsertAreaLink(unsigned int areaidx, unsigned int dir, WMPAreaLink *arealink)
161 {
162 unsigned int pos;
163 WMPAreaEntry *ae;
164
165 WMPAreaLink *al = new WMPAreaLink();
166 memcpy(al, arealink, sizeof(WMPAreaLink) );
167 unsigned int idx = area_entries[areaidx]->AreaLinksIndex[dir];
168 area_links.insert(area_links.begin()+idx,al);
169
170 unsigned int max = area_entries.size();
171 for(pos = 0; pos<max; pos++) {
172 ae = area_entries[pos];
173 for (unsigned int k=0;k<4;k++) {
174 if ((pos==areaidx) && (k==dir)) {
175 ae->AreaLinksCount[k]++;
176 continue;
177 }
178 if(ae->AreaLinksIndex[k]>=idx) {
179 ae->AreaLinksIndex[k]++;
180 }
181 }
182 }
183 }
184
SetAreaLink(unsigned int x,WMPAreaLink * arealink)185 void WorldMap::SetAreaLink(unsigned int x, WMPAreaLink *arealink)
186 {
187 WMPAreaLink *al =new WMPAreaLink();
188
189 //change this to similar code as above if WMPAreaLink gets non-struct members
190 memcpy( al,arealink,sizeof(WMPAreaLink) );
191
192 //if index is too large, we break
193 if (x>area_links.size()) {
194 error("WorldMap", "Trying to set invalid link (%d/%d)", x, (int)area_links.size());
195 }
196 //altering an existing link
197 if (x<area_links.size()) {
198 if (area_links[x]) {
199 delete area_links[x];
200 }
201 area_links[x]=al;
202 return;
203 }
204 //adding a new link
205 area_links.push_back(al);
206 }
207
~WorldMap(void)208 WorldMap::~WorldMap(void)
209 {
210 unsigned int i;
211
212 for (i = 0; i < area_entries.size(); i++) {
213 delete( area_entries[i] );
214 }
215 for (i = 0; i < area_links.size(); i++) {
216 delete( area_links[i] );
217 }
218 if (Distances) {
219 free(Distances);
220 }
221 if (GotHereFrom) {
222 free(GotHereFrom);
223 }
224 if (bam) bam = NULL;
225 }
226
SetMapIcons(AnimationFactory * newicons)227 void WorldMap::SetMapIcons(AnimationFactory *newicons)
228 {
229 bam = newicons;
230 }
231
SetMapMOS(Holder<Sprite2D> newmos)232 void WorldMap::SetMapMOS(Holder<Sprite2D> newmos)
233 {
234 MapMOS = newmos;
235 }
236
GetArea(const ieResRef AreaName,unsigned int & i) const237 WMPAreaEntry* WorldMap::GetArea(const ieResRef AreaName, unsigned int &i) const
238 {
239 i=(unsigned int) area_entries.size();
240 while (i--) {
241 if (!strnicmp(AreaName, area_entries[i]->AreaName,8)) {
242 return area_entries[i];
243 }
244 }
245 return NULL;
246 }
247
248 //Find Worldmap location by nearest area with a smaller number
249 //Counting backwards, stop at 1000 boundaries.
250 //It is not possible to simply round to 1000, because there are
251 //WMP entries like AR8001, and we need to find the best match
FindNearestEntry(const ieResRef AreaName,unsigned int & i) const252 WMPAreaEntry* WorldMap::FindNearestEntry(const ieResRef AreaName, unsigned int &i) const
253 {
254 int value = 0;
255 ieResRef tmp;
256
257 sscanf(&AreaName[2],"%4d", &value);
258 do {
259 snprintf(tmp, 9, "%.2s%04d", AreaName, value);
260 WMPAreaEntry* ret = GetArea(tmp, i);
261 if (ret) {
262 return ret;
263 }
264 if (value%1000 == 0) break;
265 value--;
266 }
267 while(1); //value%1000 should protect us from infinite loops
268 i = -1;
269 return NULL;
270 }
271
272 //this is a pathfinding algorithm
273 //you have to find the optimal path
CalculateDistances(const ieResRef AreaName,int direction)274 int WorldMap::CalculateDistances(const ieResRef AreaName, int direction)
275 {
276 //first, update reachable/visible areas by worlde.2da if exists
277 UpdateReachableAreas();
278 UpdateAreaVisibility(AreaName, direction);
279 if (direction==-1) {
280 return 0;
281 }
282
283 if (direction<0 || direction>3) {
284 Log(ERROR, "WorldMap", "CalculateDistances for invalid direction: %s", AreaName);
285 return -1;
286 }
287
288 unsigned int i;
289 if (!GetArea(AreaName, i)) {
290 Log(ERROR, "WorldMap", "CalculateDistances for invalid Area: %s", AreaName);
291 return -1;
292 }
293 if (Distances) {
294 free(Distances);
295 }
296 if (GotHereFrom) {
297 free(GotHereFrom);
298 }
299
300 Log(MESSAGE, "WorldMap", "CalculateDistances for Area: %s", AreaName);
301
302 size_t memsize =sizeof(int) * area_entries.size();
303 Distances = (int *) malloc( memsize );
304 GotHereFrom = (int *) malloc( memsize );
305 memset( Distances, -1, memsize );
306 memset( GotHereFrom, -1, memsize );
307 Distances[i] = 0; //setting our own distance
308 GotHereFrom[i] = -1; //we didn't move
309
310 int *seen_entry = (int *) malloc( memsize );
311
312 std::list<int> pending;
313 pending.push_back(i);
314 while(!pending.empty()) {
315 i=pending.front();
316 pending.pop_front();
317 WMPAreaEntry* ae=area_entries[i];
318 memset( seen_entry, -1, memsize );
319 //all directions should be used
320 for(int d=0;d<4;d++) {
321 int j=ae->AreaLinksIndex[d];
322 int k=j+ae->AreaLinksCount[d];
323 if ((size_t) k>area_links.size()) {
324 Log(ERROR, "WorldMap", "The worldmap file is corrupted... and it would crash right now! Entry #: %d Direction: %d",
325 i, d);
326 break;
327 }
328 for(;j<k;j++) {
329 WMPAreaLink* al = area_links[j];
330 WMPAreaEntry* ae2 = area_entries[al->AreaIndex];
331 unsigned int mydistance = (unsigned int) Distances[i];
332
333 // we must only process the FIRST seen link to each area from this one
334 if (seen_entry[al->AreaIndex] != -1) continue;
335 seen_entry[al->AreaIndex] = 0;
336 /*
337 if ( ( (ae->GetAreaStatus() & WMP_ENTRY_PASSABLE) == WMP_ENTRY_PASSABLE) &&
338 ( (ae2->GetAreaStatus() & WMP_ENTRY_WALKABLE) == WMP_ENTRY_WALKABLE)
339 */
340 if ( (ae2->GetAreaStatus() & WMP_ENTRY_WALKABLE) == WMP_ENTRY_WALKABLE) {
341 // al->Flags is the entry direction
342 mydistance += al->DistanceScale * 4;
343 //nonexisting distance is the biggest!
344 if ((unsigned) Distances[al->AreaIndex] > mydistance) {
345 Distances[al->AreaIndex] = mydistance;
346 GotHereFrom[al->AreaIndex] = j;
347 pending.push_back(al->AreaIndex);
348 }
349 }
350 }
351 }
352 }
353
354 free(seen_entry);
355 return 0;
356 }
357
358 //returns the index of the area owning this link
WhoseLinkAmI(int link_index) const359 unsigned int WorldMap::WhoseLinkAmI(int link_index) const
360 {
361 unsigned int cnt = GetEntryCount();
362 for (unsigned int i = 0; i < cnt; i++) {
363 WMPAreaEntry *ae=area_entries[i];
364 for (int direction=0;direction<4;direction++)
365 {
366 int j=ae->AreaLinksIndex[direction];
367 if (link_index>=j) {
368 j+=ae->AreaLinksCount[direction];
369 if(link_index<j) {
370 return i;
371 }
372 }
373 }
374 }
375 return (ieDword) -1;
376 }
377
GetLink(const ieResRef A,const ieResRef B) const378 WMPAreaLink *WorldMap::GetLink(const ieResRef A, const ieResRef B) const
379 {
380 unsigned int i,j,k;
381
382 WMPAreaEntry *ae=GetArea( A, i );
383 if (!ae) {
384 return NULL;
385 }
386 //looking for destination area, returning the first link found
387 for (i=0;i<4;i++) {
388 j=ae->AreaLinksCount[i];
389 k=ae->AreaLinksIndex[i];
390 while(j--) {
391 WMPAreaLink *al = area_links[k++];
392 WMPAreaEntry *ae2 = area_entries[al->AreaIndex];
393 //or arearesref?
394 if (strnicmp(ae2->AreaName, B, 8)==0) {
395 return al;
396 }
397 }
398 }
399 return NULL;
400 }
401
402 //call this function to find out which area we fall into
403 //not necessarily the target area
404 //if it isn't the same, then a random encounter happened!
GetEncounterLink(const ieResRef AreaName,bool & encounter) const405 WMPAreaLink *WorldMap::GetEncounterLink(const ieResRef AreaName, bool &encounter) const
406 {
407 if (!GotHereFrom) {
408 return NULL;
409 }
410 unsigned int i;
411 WMPAreaEntry *ae=GetArea( AreaName, i ); //target area
412 if (!ae) {
413 Log(ERROR, "WorldMap", "No such area: %s", AreaName);
414 return NULL;
415 }
416 std::list<WMPAreaLink*> walkpath;
417 Log(DEBUG, "WorldMap", "Gathering path information for: %s", AreaName);
418 while (GotHereFrom[i]!=-1) {
419 Log(DEBUG, "WorldMap", "Adding path to %d", i);
420 walkpath.push_back(area_links[GotHereFrom[i]]);
421 i = WhoseLinkAmI(GotHereFrom[i]);
422 if (i==(ieDword) -1) {
423 error("WorldMap", "Something has been screwed up here (incorrect path)!\n");
424 }
425 }
426
427 Log(DEBUG, "WorldMap", "Walkpath size is: %d", (int) walkpath.size());
428 if (walkpath.empty()) {
429 return NULL;
430 }
431 std::list<WMPAreaLink*>::reverse_iterator p=walkpath.rbegin();
432 WMPAreaLink *lastpath;
433 encounter=false;
434 do {
435 lastpath = *p;
436 if (lastpath->EncounterChance > (unsigned int) RAND(0, 99)) {
437 encounter=true;
438 break;
439 }
440 ++p;
441 }
442 while(p!=walkpath.rend() );
443 return lastpath;
444 }
445
446 //adds a temporary AreaEntry to the world map
447 //this entry has two links for each direction, leading to the two areas
448 //we were travelling between when using the supplied link
SetEncounterArea(const ieResRef area,WMPAreaLink * link)449 void WorldMap::SetEncounterArea(const ieResRef area, WMPAreaLink *link) {
450 unsigned int i;
451 if (GetArea(area, i)) {
452 return;
453 }
454
455 //determine the area the link came from
456 unsigned int j, cnt = GetLinkCount();
457 for (j = 0; j < cnt; ++j) {
458 if (link == area_links[j]) {
459 break;
460 }
461 }
462
463 i = WhoseLinkAmI(j);
464 if (i == (unsigned int) -1) {
465 Log(ERROR, "WorldMap", "Could not add encounter area");
466 return;
467 }
468
469 WMPAreaEntry *ae = GetNewAreaEntry();
470 ae->SetAreaStatus(WMP_ENTRY_VISIBLE|WMP_ENTRY_ACCESSIBLE|WMP_ENTRY_VISITED, OP_SET);
471 CopyResRef(ae->AreaName, area);
472 CopyResRef(ae->AreaResRef, area);
473 ae->LocCaptionName = -1;
474 ae->LocTooltipName = -1;
475 ae->IconSeq = -1;
476 CopyResRef(ae->LoadScreenResRef, "");
477
478 WMPAreaEntry *src = area_entries[i];
479 WMPAreaEntry *dest = area_entries[link->AreaIndex];
480 ae->pos.x = src->pos.x + (dest->pos.x - src->pos.x) / 2;
481 ae->pos.y = src->pos.y + (dest->pos.y - src->pos.y) / 2;
482
483 //setup the area links
484 WMPAreaLink *ldest = new WMPAreaLink();
485 memcpy(ldest, link, sizeof(WMPAreaLink));
486 ldest->DistanceScale /= 2;
487 ldest->EncounterChance = 0;
488
489 link = GetLink(dest->AreaName, src->AreaName);
490 if (!link) {
491 Log(ERROR, "WorldMap", "Could not find link from %s to %s",
492 dest->AreaName, src->AreaName);
493 delete ae;
494 delete ldest;
495 return;
496 }
497
498 WMPAreaLink *lsrc = new WMPAreaLink();
499 memcpy(lsrc, link, sizeof(WMPAreaLink));
500 lsrc->DistanceScale /= 2;
501 lsrc->EncounterChance = 0;
502
503 unsigned int idx = area_links.size();
504 AddAreaLink(ldest);
505 AddAreaLink(lsrc);
506
507 for (i = 0; i < 4; ++i) {
508 ae->AreaLinksCount[i] = 2;
509 ae->AreaLinksIndex[i] = idx;
510 }
511
512 encounterArea = area_entries.size();
513 AddAreaEntry(ae);
514 }
515
ClearEncounterArea()516 void WorldMap::ClearEncounterArea()
517 {
518 if (encounterArea == -1) {
519 return;
520 }
521
522 WMPAreaEntry *ea = area_entries[encounterArea];
523 area_entries.erase(area_entries.begin() + encounterArea);
524
525 WMPAreaLink *l = area_links[ea->AreaLinksIndex[0]];
526 delete l;
527 l = area_links[ea->AreaLinksIndex[0] + 1];
528 delete l;
529
530 //NOTE: if anything else added links after us we'd have to globally
531 //update all link indices, but since ambush areas do not allow
532 //saving/loading we should be okay with this
533 area_links.erase(area_links.begin() + ea->AreaLinksIndex[0],
534 area_links.begin() + ea->AreaLinksIndex[0] + ea->AreaLinksCount[0]);
535
536 delete ea;
537 encounterArea = -1;
538 }
539
GetDistance(const ieResRef AreaName) const540 int WorldMap::GetDistance(const ieResRef AreaName) const
541 {
542 if (!Distances) {
543 return -1;
544 }
545 unsigned int i;
546 if (GetArea( AreaName, i )) {
547 return Distances[i];
548 }
549 return -1;
550 }
551
UpdateAreaVisibility(const ieResRef AreaName,int direction)552 void WorldMap::UpdateAreaVisibility(const ieResRef AreaName, int direction)
553 {
554 unsigned int i;
555
556 WMPAreaEntry* ae=GetArea(AreaName,i);
557 if (!ae)
558 return;
559 //we are here, so we visited and it is visible too (i guess)
560 Log(DEBUG, "WorldMap", "Updated Area visibility: %s (visited, accessible and visible)", AreaName);
561
562 ae->SetAreaStatus(WMP_ENTRY_VISITED|WMP_ENTRY_VISIBLE|WMP_ENTRY_ACCESSIBLE, OP_OR);
563 if (direction<0 || direction>3)
564 return;
565 i=ae->AreaLinksCount[direction];
566 while (i--) {
567 WMPAreaLink* al = area_links[ae->AreaLinksIndex[direction]+i];
568 WMPAreaEntry* ae2 = area_entries[al->AreaIndex];
569 if (ae2->GetAreaStatus()&WMP_ENTRY_ADJACENT) {
570 Log(DEBUG, "WorldMap", "Updated Area visibility: %s (accessible and visible)", ae2->AreaName);
571 ae2->SetAreaStatus(WMP_ENTRY_VISIBLE|WMP_ENTRY_ACCESSIBLE, OP_OR);
572 }
573 }
574 }
575
SetAreaStatus(const ieResRef AreaName,int Bits,int Op)576 void WorldMap::SetAreaStatus(const ieResRef AreaName, int Bits, int Op)
577 {
578 unsigned int i;
579 WMPAreaEntry* ae=GetArea(AreaName,i);
580 if (!ae)
581 return;
582 ae->SetAreaStatus(Bits, Op);
583 }
584
UpdateReachableAreas()585 void WorldMap::UpdateReachableAreas()
586 {
587 AutoTable tab("worlde", true);
588 if (!tab) {
589 return;
590 }
591 Game *game = core->GetGame();
592 if (!game) {
593 return;
594 }
595 int idx = tab->GetRowCount();
596 while (idx--) {
597 // 2da rows in format <name> <variable name> <area>
598 // we set the first three flags for <area> if <variable name> is set
599 ieDword varval = 0;
600 const char *varname = tab->QueryField(idx, 0);
601 if (game->locals->Lookup(varname, varval) && varval) {
602 const char *areaname = tab->QueryField(idx, 1);
603 SetAreaStatus(areaname, WMP_ENTRY_VISIBLE | WMP_ENTRY_ADJACENT | WMP_ENTRY_ACCESSIBLE, OP_OR);
604 }
605 }
606 }
607
608 /****************** WorldMapArray *******************/
WorldMapArray(unsigned int count)609 WorldMapArray::WorldMapArray(unsigned int count)
610 {
611 CurrentMap = 0;
612 MapCount = count;
613 all_maps = (WorldMap **) calloc(count, sizeof(WorldMap *) );
614 single = true;
615 }
616
~WorldMapArray()617 WorldMapArray::~WorldMapArray()
618 {
619 unsigned int i;
620
621 for (i = 0; i<MapCount; i++) {
622 if (all_maps[i]) {
623 delete all_maps[i];
624 }
625 }
626 free( all_maps );
627 }
628
FindAndSetCurrentMap(const ieResRef area)629 unsigned int WorldMapArray::FindAndSetCurrentMap(const ieResRef area)
630 {
631 unsigned int i, idx;
632
633 for (i = CurrentMap; i<MapCount; i++) {
634 if (all_maps[i]->GetArea (area, idx) ) {
635 CurrentMap = i;
636 return i;
637 }
638 }
639 for (i = 0; i<CurrentMap; i++) {
640 if (all_maps[i]->GetArea (area, idx) ) {
641 CurrentMap = i;
642 return i;
643 }
644 }
645 return CurrentMap;
646 }
647
SetWorldMap(WorldMap * m,unsigned int index)648 void WorldMapArray::SetWorldMap(WorldMap *m, unsigned int index)
649 {
650 if (index<MapCount) {
651 all_maps[index]=m;
652 }
653 }
654
NewWorldMap(unsigned int index)655 WorldMap *WorldMapArray::NewWorldMap(unsigned int index)
656 {
657 if (all_maps[index]) {
658 delete all_maps[index];
659 }
660 all_maps[index] = new WorldMap();
661 return all_maps[index];
662 }
663
664 }
665