entities without definitions in script will be removed from game; it is now possible...
[k8-i-v-a-n.git] / src / game / worldmap.cpp
blobca233d06806dd1431612e8f1ba1429de6675b362
1 /*
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
8 * See LICENSING which should be included
9 * along with this file for more details
13 /* Compiled through wmapset.cpp */
15 #define MAX_TEMPERATURE 27 //increase for a warmer world
16 #define LATITUDE_EFFECT 40 //increase for more effect
17 #define ALTITUDE_EFFECT 0.02
19 #define COLD 10
20 #define MEDIUM 12
21 #define WARM 17
22 #define HOT 19
24 static const int DirX[8] = { -1, -1, -1, 0, 0, 1, 1, 1 };
25 static const int DirY[8] = { -1, 0, 1, -1, 1, -1, 0, 1 };
28 #define OceanType ocean::ProtoType.GetIndex()
29 #define SnowType snow::ProtoType.GetIndex()
30 #define GlacierType glacier::ProtoType.GetIndex()
31 #define EGForestType evergreenforest::ProtoType.GetIndex()
32 #define LForestType leafyforest::ProtoType.GetIndex()
33 #define SteppeType steppe::ProtoType.GetIndex()
34 #define DesertType desert::ProtoType.GetIndex()
35 #define JungleType jungle::ProtoType.GetIndex()
38 worldmap::worldmap () {}
39 continent *worldmap::GetContinentUnder (v2 Pos) const { return Continent[ContinentBuffer[Pos.X][Pos.Y]]; }
40 v2 worldmap::GetEntryPos (ccharacter *, int I) const { return EntryMap.find(I)->second; }
41 continent *worldmap::GetContinent (int I) const { return Continent[I]; }
42 int worldmap::GetAltitude (v2 Pos) { return AltitudeBuffer[Pos.X][Pos.Y]; }
43 charactervector &worldmap::GetPlayerGroup () { return PlayerGroup; }
44 character *worldmap::GetPlayerGroupMember (int c) { return PlayerGroup[c]; }
47 worldmap::worldmap (int XSize, int YSize) : area(XSize, YSize) {
48 Map = reinterpret_cast<wsquare ***>(area::Map);
49 for (int x = 0; x < XSize; ++x) {
50 for (int y = 0; y < YSize; ++y) {
51 //Map[x][y] = new wsquare(this, v2(x, y));
52 //Map[x][y]->SetGWTerrain(ocean::Spawn());
53 Map[x][y] = nullptr;
56 TypeBuffer = nullptr;
57 AltitudeBuffer = nullptr;
58 ContinentBuffer = nullptr;
59 continent::TypeBuffer = nullptr;
60 continent::AltitudeBuffer = nullptr;
61 continent::ContinentBuffer = nullptr;
65 worldmap::~worldmap () {
66 resetItAll(false);
67 delete [] TypeBuffer;
68 delete [] AltitudeBuffer;
69 delete [] ContinentBuffer;
70 for (uInt c = 1; c < Continent.size(); ++c) delete Continent[c];
71 for (uInt c = 0; c < PlayerGroup.size(); ++c) delete PlayerGroup[c];
72 Continent.resize(1, 0);
73 PlayerGroup.clear();
74 continent::TypeBuffer = nullptr;
75 continent::AltitudeBuffer = nullptr;
76 continent::ContinentBuffer = nullptr;
80 void worldmap::resetItAll (truth recreate) {
81 Map = reinterpret_cast<wsquare ***>(area::Map);
82 for (int x = 0; x < XSize; ++x) {
83 for (int y = 0; y < YSize; ++y) {
84 if (Map[x][y]) delete Map[x][y];
85 if (recreate) {
86 Map[x][y] = new wsquare(this, v2(x, y));
87 Map[x][y]->SetGWTerrain(ocean::Spawn());
88 } else {
89 Map[x][y] = nullptr;
93 ClearEntryPoints();
94 if (recreate) {
95 if (TypeBuffer) delete [] TypeBuffer;
96 if (AltitudeBuffer) delete [] AltitudeBuffer;
97 if (ContinentBuffer) delete [] ContinentBuffer;
98 Alloc2D(TypeBuffer, XSize, YSize);
99 Alloc2D(AltitudeBuffer, XSize, YSize);
100 Alloc2D(ContinentBuffer, XSize, YSize);
101 continent::TypeBuffer = TypeBuffer;
102 continent::AltitudeBuffer = AltitudeBuffer;
103 continent::ContinentBuffer = ContinentBuffer;
108 void worldmap::Save (outputfile &SaveFile) const {
109 area::Save(SaveFile);
110 SaveFile.Write(reinterpret_cast<char*>(TypeBuffer[0]), XSizeTimesYSize*sizeof(uChar));
111 SaveFile.Write(reinterpret_cast<char*>(AltitudeBuffer[0]), XSizeTimesYSize*sizeof(short));
112 SaveFile.Write(reinterpret_cast<char*>(ContinentBuffer[0]), XSizeTimesYSize*sizeof(uChar));
113 for (feuLong c = 0; c < XSizeTimesYSize; ++c) Map[0][c]->Save(SaveFile);
114 SaveFile << Continent << PlayerGroup;
118 void worldmap::Load (inputfile &SaveFile) {
119 area::Load(SaveFile);
120 Map = reinterpret_cast<wsquare ***>(area::Map);
121 Alloc2D(TypeBuffer, XSize, YSize);
122 Alloc2D(AltitudeBuffer, XSize, YSize);
123 Alloc2D(ContinentBuffer, XSize, YSize);
124 SaveFile.Read(reinterpret_cast<char*>(TypeBuffer[0]), XSizeTimesYSize*sizeof(uChar));
125 SaveFile.Read(reinterpret_cast<char*>(AltitudeBuffer[0]), XSizeTimesYSize*sizeof(short));
126 SaveFile.Read(reinterpret_cast<char*>(ContinentBuffer[0]), XSizeTimesYSize*sizeof(uChar));
127 continent::TypeBuffer = TypeBuffer;
128 continent::AltitudeBuffer = AltitudeBuffer;
129 continent::ContinentBuffer = ContinentBuffer;
130 for (int x = 0; x < XSize; ++x) {
131 for (int y = 0; y < YSize; ++y) {
132 delete Map[x][y];
133 Map[x][y] = new wsquare(this, v2(x, y));
136 for (int x = 0; x < XSize; ++x) {
137 for (int y = 0; y < YSize; ++y) {
138 game::SetSquareInLoad(Map[x][y]);
139 Map[x][y]->Load(SaveFile);
142 CalculateNeighbourBitmapPoses();
143 SaveFile >> Continent >> PlayerGroup;
147 void worldmap::poiReset () {
148 for (int f = 0; f < game::poiCount(); ++f) {
149 auto terra = game::poiByIndex(f);
150 terra->SetRevealed(false);
151 terra->SetPlaced(false);
152 terra->SetGenerated(false);
153 auto cfg = terra->GetConfig();
154 // fix obvious scripting bugs
155 if (cfg == NEW_ATTNAM || cfg == UNDER_WATER_TUNNEL || cfg == UNDER_WATER_TUNNEL_EXIT || cfg == ELPURI_CAVE) {
156 terra->MustBeSkipped = false;
157 } else {
158 //fprintf(stderr, "terra #%d prob is %d\n", terra->GetConfig(), terra->GetProbability());
159 terra->MustBeSkipped = (terra->GetProbability() < RAND()%100+1);
160 //if (terra->MustBeSkipped) fprintf(stderr, "worldmap::poiReset: skipped POI with config #%d\n", terra->GetConfig());
166 void worldmap::poiPlaceAtMap (owterrain *terra, truth forceReveal) {
167 if (!terra) ABORT("cannot place nothing on worldmap!");
168 if (!terra->IsGenerated()) ABORT("cannot place ungenerated something on worldmap!");
169 if (terra->MustBeSkipped) ABORT("cannot place skipped something on worldmap!");
170 // place it, if it is not placed yet
171 if (!terra->IsPlaced()) {
172 terra->SetPlaced(true);
173 GetWSquare(terra->GetPosition())->ChangeOWTerrain(terra->Clone());
174 SetEntryPos(terra->GetConfig(), terra->GetPosition());
175 //fprintf(stderr, "POI #%d placed at (%d,%d); attached dungeon is %d\n", terra->GetConfig(), terra->GetPosition().X, terra->GetPosition().Y, terra->GetAttachedDungeon());
176 } else {
177 //fprintf(stderr, "already placed...\n");
179 if (!IsValidPos(terra->GetPosition())) ABORT("cannot place something on invalid worldmap position!");
180 if (forceReveal || (terra->RevealEnvironmentInitially() && !terra->IsRevealed())) {
181 //if (terra->IsRevealed()) fprintf(stderr, "re-revealing...\n");
182 terra->SetRevealed(true);
183 RevealEnvironment(terra->GetPosition(), 1);
188 ContinentVector worldmap::poiFindPlacesFor (owterrain *terra, truth shuffle) {
189 ContinentVector list;
190 if (terra) {
191 for (uInt c = 1; c < Continent.size(); ++c) {
192 if (terra->MustBeSkipped) continue;
193 //fprintf(stderr, " trying continent %u for %d...\n", c, terra->GetConfig());
194 if (terra->IsSuitableContinent(Continent[c])) {
195 //fprintf(stderr, " FOUND!\n");
196 list.push_back(Continent[c]);
199 if (shuffle && list.size() > 1) {
200 for (uInt f = 0; f < list.size(); ++f) {
201 uInt swp = (uInt)RAND()%list.size();
202 if (swp != f) {
203 continent *tmp = list[f];
204 list[f] = list[swp];
205 list[swp] = tmp;
210 //fprintf(stderr, ">>>%u continent(s) for %d...\n", list.size(), terra->GetConfig());
211 return list;
215 static ContinentVector createShuffledContinentList (const ContinentVector &list) {
216 ContinentVector res;
217 for (auto &cont : list) res.push_back(cont);
218 if (res.size() > 1) {
219 for (uInt f = 0; f < res.size(); ++f) {
220 uInt swp = (uInt)RAND()%res.size();
221 if (swp != f) {
222 continent *tmp = res[f];
223 res[f] = res[swp];
224 res[swp] = tmp;
228 return res;
232 truth worldmap::poiIsOccupied (v2 pos) {
233 //fprintf(stderr, "checking...\n");
234 for (int f = 0; f < game::poiCount(); ++f) {
235 auto terra = game::poiByIndex(f);
236 if (!terra) ABORT("Somone stole our terrain!");
237 if (!terra->IsGenerated()) continue;
238 //fprintf(stderr, " checking #%d...\n", f);
239 if (terra->GetPosition() == pos) {
240 //fprintf(stderr, " CONFLICT!\n");
241 return true;
244 //fprintf(stderr, "checking complete!\n");
245 return false;
249 truth worldmap::poiPlaceAttnamsAndUT (continent *PetrusLikes) {
250 if (!PetrusLikes) return false; // oops
251 if (PetrusLikes->GetSize() < 8) return false; // oops
253 if (!attnam) ABORT("Who stole my Attnam?!");
254 if (!newattnam) ABORT("Who stole my New Attnam?!");
255 if (!underwatertunnel) ABORT("Who stole my UC Entry?!");
256 if (!underwatertunnelexit) ABORT("Who stole my UC Exit?!");
258 v2 newattnamPos = ERROR_V2, tunnelEntryPos = ERROR_V2, tunnelExitPos = ERROR_V2;
260 // place tunnel exit
261 truth Correct = false;
262 for (int c1 = 0; c1 < 25; ++c1) {
263 game::BusyAnimation();
264 for (int c2 = 1; c2 < 50; ++c2) {
265 tunnelExitPos = PetrusLikes->GetRandomMember(-1, &Correct);
266 if (!Correct) return false; // no room, oops
267 Correct = false;
268 for (int d1 = 0; d1 < 8; ++d1) {
269 v2 Pos = tunnelExitPos+game::GetMoveVector(d1);
270 if (IsValidPos(Pos) && AltitudeBuffer[Pos.X][Pos.Y] <= 0) {
271 int Distance = 3+(RAND()&3);
272 truth Error = false;
273 int x, y;
274 int Counter = 0;
276 tunnelEntryPos = Pos;
277 for (int c2 = 0; c2 < Distance; ++c2) {
278 tunnelEntryPos += game::GetMoveVector(d1);
279 if (!IsValidPos(tunnelEntryPos) || AltitudeBuffer[tunnelEntryPos.X][tunnelEntryPos.Y] > 0) { Error = true; break; }
281 if (Error) continue;
283 for (x = tunnelEntryPos.X-3; x <= tunnelEntryPos.X+3; ++x) {
284 for (y = tunnelEntryPos.Y-3; y <= tunnelEntryPos.Y+3; ++y, ++Counter) {
285 if (Counter != 0 && Counter != 6 && Counter != 42 && Counter != 48 &&
286 (!IsValidPos(x, y) || AltitudeBuffer[x][y] > 0 || AltitudeBuffer[x][y] < -350)) {
287 Error = true;
288 break;
291 if (Error) break;
293 if (Error) continue;
295 Error = true;
296 for (x = 0; x < XSize; ++x) if (TypeBuffer[x][tunnelEntryPos.Y] == JungleType) { Error = false; break; }
297 if (Error) continue;
299 Counter = 0;
300 for (x = tunnelEntryPos.X - 2; x <= tunnelEntryPos.X+2; ++x) {
301 for (y = tunnelEntryPos.Y - 2; y <= tunnelEntryPos.Y+2; ++y, ++Counter) {
302 if (Counter != 0 && Counter != 4 && Counter != 20 && Counter != 24) AltitudeBuffer[x][y] /= 2;
306 AltitudeBuffer[tunnelEntryPos.X][tunnelEntryPos.Y] = 1+RAND()%50;
307 TypeBuffer[tunnelEntryPos.X][tunnelEntryPos.Y] = JungleType;
308 GetWSquare(tunnelEntryPos)->ChangeGWTerrain(jungle::Spawn());
310 int NewAttnamIndex;
311 for (NewAttnamIndex = RAND()&7; NewAttnamIndex == 7-d1; NewAttnamIndex = RAND()&7) {}
312 newattnamPos = tunnelEntryPos+game::GetMoveVector(NewAttnamIndex);
314 static const int DiagonalDir[4] = { 0, 2, 5, 7 };
315 static const int NotDiagonalDir[4] = { 1, 3, 4, 6 };
316 static const int AdjacentDir[4][2] = { { 0, 1 }, { 0, 2 }, { 1, 3 }, { 2, 3 } };
317 truth Raised[] = { false, false, false, false };
319 for (int d2 = 0; d2 < 4; ++d2) {
320 if (NotDiagonalDir[d2] != 7-d1 && (NotDiagonalDir[d2] == NewAttnamIndex || !(RAND()&2))) {
321 v2 Pos = tunnelEntryPos+game::GetMoveVector(NotDiagonalDir[d2]);
322 AltitudeBuffer[Pos.X][Pos.Y] = 1+RAND()%50;
323 TypeBuffer[Pos.X][Pos.Y] = JungleType;
324 GetWSquare(Pos)->ChangeGWTerrain(jungle::Spawn());
325 Raised[d2] = true;
329 for (int d2 = 0; d2 < 4; ++d2) {
330 if (DiagonalDir[d2] != 7-d1 &&
331 (DiagonalDir[d2] == NewAttnamIndex ||
332 (Raised[AdjacentDir[d2][0]] && Raised[AdjacentDir[d2][1]] && !(RAND()&2)))) {
333 v2 Pos = tunnelEntryPos+game::GetMoveVector(DiagonalDir[d2]);
335 AltitudeBuffer[Pos.X][Pos.Y] = 1+RAND()%50;
336 TypeBuffer[Pos.X][Pos.Y] = JungleType;
337 GetWSquare(Pos)->ChangeGWTerrain(jungle::Spawn());
340 Correct = true;
341 break;
344 if (Correct) break;
346 if (Correct) break;
348 if (!Correct) return false;
349 if (newattnamPos == ERROR_V2 || tunnelEntryPos == ERROR_V2 || tunnelExitPos == ERROR_V2) return false;
351 //fprintf(stderr, "UC and New Attnam were successfully placed...\n");
352 // tunnel entry, tunnel exit and New Attnam are ok, find a place for Attnam
353 game::BusyAnimation();
355 // spawn and reveal all special places
356 for (int f = 0; f < game::poiCount(); ++f) {
357 auto terra = game::poiByIndex(f);
358 if (terra->GetConfig() == NEW_ATTNAM) { terra->SetGenerated(true); terra->SetPosition(newattnamPos); poiPlaceAtMap(terra); }
359 else if (terra->GetConfig() == UNDER_WATER_TUNNEL) { terra->SetGenerated(true); terra->SetPosition(tunnelEntryPos); poiPlaceAtMap(terra); }
360 else if (terra->GetConfig() == UNDER_WATER_TUNNEL_EXIT) { terra->SetGenerated(true); terra->SetPosition(tunnelExitPos); poiPlaceAtMap(terra); }
363 // done
364 return true;
368 void worldmap::Generate () {
369 Alloc2D(OldAltitudeBuffer, XSize, YSize);
370 Alloc2D(OldTypeBuffer, XSize, YSize);
372 //continent* poiContinents[CONFIG_TABLE_SIZE]; // max number of configs
374 for (;;) {
375 //fprintf(stderr, "generating new planet...\n");
376 resetItAll(true); // recreate
378 RandomizeAltitude();
379 SmoothAltitude();
380 GenerateClimate();
381 SmoothClimate();
382 CalculateContinents();
384 if (Continent.size() < 2) ABORT("Strange things happens in Universe...");
385 //fprintf(stderr, "%u continents generated...\n", Continent.size());
387 //fprintf(stderr, "resetting POI info...\n");
388 poiReset(); // this also sets `owterrain::MustBeSkipped`, using spawn probabilities
389 //memset(poiContinents, 0, sizeof(poiContinents));
391 // find place for attnam
392 if (!attnam) ABORT("Who stole my Attnam?!");
394 game::BusyAnimation();
395 auto PerfectForAttnam = poiFindPlacesFor(attnam, true); // shuffle results
396 if (PerfectForAttnam.size() == 0) {
397 //fprintf(stderr, "no country for old man...\n");
398 continue;
401 //fprintf(stderr, "trying to find room for other POIs...\n");
402 // try to place all initial places, and check if other places are (roughly) ok
403 continent *PetrusLikes = nullptr;
404 truth success = false;
405 for (auto &cont : PerfectForAttnam) {
406 //fprintf(stderr, "trying to place special pois...\n");
407 if (!poiPlaceAttnamsAndUT(cont)) continue; // alas
408 // WARNING! from here, we cannot continue checking continents on failure!
409 //fprintf(stderr, "checking other pois...\n");
410 success = true;
411 for (int f = 0; f < game::poiCount(); ++f) {
412 auto terra = game::poiByIndex(f);
413 if (terra->MustBeSkipped) continue;
414 if (terra->IsGenerated()) continue;
415 if (terra->GetWantContinentWith() != attnam->GetConfig()) continue;
416 //fprintf(stderr, " f=%d; cidx=%d (%d)\n", f, terra->GetWantContinentWith(), attnam->GetConfig());
417 if (!terra->IsSuitableContinent(cont)) {
418 //fprintf(stderr, " OOPS!(0)\n");
419 if (!terra->CanBeSkipped()) {
420 //fprintf(stderr, " OOPS!(1)\n");
421 success = false;
422 break;
424 terra->MustBeSkipped = true;
425 } else {
426 //poiContinents[f] = cont;
429 // we can't continue looping here
430 if (success) PetrusLikes = cont; // ok, this continent can be used for Petrus' needs
431 break;
433 if (!PetrusLikes) continue; // alas
435 //TODO: check and assign other continents
436 // here, we should sort POI list in order of placement, pick continents, and so on
437 // but i'll leave that for some indefinite future
439 for (;;) {
440 success = false;
441 for (int f = 0; f < game::poiCount(); ++f) {
442 auto terra = game::poiByIndex(f);
443 if (terra->MustBeSkipped) continue;
444 if (terra->IsGenerated()) continue;
445 auto wantc = terra->GetWantContinentWith();
446 // Attnam continent?
447 if (wantc == attnam->GetConfig()) {
448 // yep, already checked
449 poiContinents[f] = PetrusLikes;
450 continue;
452 // New Attnam or UC Entry island is too small to place anything
453 if (wantc == newattnam->GetConfig() || wantc == underwatertunnel->GetConfig()) {
454 ABORT("Cannot place anything near New Attnam!");
456 // list of suitable continents
457 ContinentVector clist;
458 // 0: pick random continent
459 if (wantc == 0) {
460 clist = createShuffledContinentList(Continent);
461 } else {
462 // check if we
468 //fprintf(stderr, "spawining other pois...\n");
469 // spawn others
470 for (int f = 0; f < game::poiCount(); ++f) {
471 auto terra = game::poiByIndex(f);
472 if (terra->MustBeSkipped) continue;
473 if (terra->IsGenerated()) continue;
474 //fprintf(stderr, "trying poicfg #%d...\n", terra->GetConfig());
475 //FIXME: find continent for this place
476 continent *cont = nullptr;
477 auto wantc = terra->GetWantContinentWith();
478 // Attnam continent?
479 if (wantc == attnam->GetConfig() || wantc == underwatertunnel->GetConfig()) {
480 // yep, already checked
481 cont = PetrusLikes;
482 } else if (wantc == 0) {
483 // pick random continent
484 auto clist = createShuffledContinentList(Continent);
485 for (auto &cc : clist) if (terra->IsSuitableContinent(cc)) { cont = cc; break; }
486 } else if (wantc == newattnam->GetConfig() || wantc == underwatertunnel->GetConfig()) {
487 // New Attnam or UC Entry island is too small to place anything
488 ABORT("Cannot place anything near New Attnam!");
490 // found something?
491 if (!cont) {
492 // if we can skip this dungeon, then skip it, otherwise signal failure
493 if (!terra->CanBeSkipped()) { success = false; break; }
494 terra->MustBeSkipped = true;
495 success = true; // in case this is last POI
496 continue;
498 // get random position for this poi
499 v2 poipos;
500 game::BusyAnimation();
501 auto possiblePlaces = cont->GetShuffledMembers(terra->CanBeOnAnyTerrain() ? -1 : terra->GetNativeGTerrainType());
502 success = false;
503 for (auto &ppos : possiblePlaces) {
504 if (!poiIsOccupied(ppos)) { poipos = ppos; success = true; break; } // ok
506 if (!success) {
507 // if we can skip this dungeon, then skip it, otherwise signal failure
508 if (!terra->CanBeSkipped()) break;
509 terra->MustBeSkipped = true;
510 success = true; // in case this is last POI
511 continue;
513 // ok, we can place it
514 //fprintf(stderr, "place poicfg #%d at (%d,%d)...\n", terra->GetConfig(), poipos.X, poipos.Y);
515 terra->SetGenerated(true);
516 terra->SetPosition(poipos);
517 if (terra->PlaceInitially()) poiPlaceAtMap(terra);
520 // if something's failed, do it all again
521 if (!success) continue;
523 // player just exited new attnam
524 PLAYER->PutTo(newattnam->GetPosition());
526 CalculateLuminances();
527 CalculateNeighbourBitmapPoses();
528 // break infinite loop, we're done
529 break;
532 // done
533 delete [] OldAltitudeBuffer;
534 delete [] OldTypeBuffer;
538 void worldmap::RandomizeAltitude () {
539 game::BusyAnimation();
540 for (int x = 0; x < XSize; ++x) {
541 for (int y = 0; y < YSize; ++y) {
542 AltitudeBuffer[x][y] = 4000-RAND()%8000;
548 void worldmap::SmoothAltitude () {
549 for (int c = 0; c < 10; ++c) {
550 game::BusyAnimation();
551 int x, y;
552 for (y = 0; y < YSize; ++y) SafeSmooth(0, y);
553 for (x = 1; x < XSize - 1; ++x) {
554 SafeSmooth(x, 0);
555 for (y = 1; y < YSize - 1; ++y) FastSmooth(x, y);
556 SafeSmooth(x, YSize - 1);
558 for (y = 0; y < YSize; ++y) SafeSmooth(XSize - 1, y);
563 void worldmap::FastSmooth (int x, int y) {
564 sLong HeightNear = 0;
565 int d;
566 for (d = 0; d < 4; ++d) HeightNear += OldAltitudeBuffer[x + DirX[d]][y + DirY[d]];
567 for (d = 4; d < 8; ++d) HeightNear += AltitudeBuffer[x + DirX[d]][y + DirY[d]];
568 OldAltitudeBuffer[x][y] = AltitudeBuffer[x][y];
569 AltitudeBuffer[x][y] = HeightNear >> 3;
573 void worldmap::SafeSmooth (int x, int y) {
574 sLong HeightNear = 0;
575 int d, SquaresNear = 0;
576 for (d = 0; d < 4; ++d) {
577 int X = x + DirX[d];
578 int Y = y + DirY[d];
579 if (IsValidPos(X, Y)) {
580 HeightNear += OldAltitudeBuffer[X][Y];
581 ++SquaresNear;
584 for (d = 4; d < 8; ++d) {
585 int X = x + DirX[d];
586 int Y = y + DirY[d];
587 if (IsValidPos(X, Y)) {
588 HeightNear += AltitudeBuffer[X][Y];
589 ++SquaresNear;
592 OldAltitudeBuffer[x][y] = AltitudeBuffer[x][y];
593 AltitudeBuffer[x][y] = HeightNear / SquaresNear;
597 void worldmap::GenerateClimate () {
598 game::BusyAnimation();
599 for (int y = 0; y < YSize; ++y) {
600 double DistanceFromEquator = fabs(double(y) / YSize - 0.5);
601 truth LatitudeRainy = DistanceFromEquator <= 0.05 || (DistanceFromEquator > 0.25 && DistanceFromEquator <= 0.45);
602 for (int x = 0; x < XSize; ++x) {
603 if (AltitudeBuffer[x][y] <= 0) {
604 TypeBuffer[x][y] = OceanType;
605 continue;
607 truth Rainy = LatitudeRainy;
608 if (!Rainy) {
609 for(int d = 0; d < 8; ++d) {
610 v2 Pos = v2(x, y) + game::GetMoveVector(d);
611 if (IsValidPos(Pos) && AltitudeBuffer[Pos.X][Pos.Y] <= 0) {
612 Rainy = true;
613 break;
617 int Temperature = int(MAX_TEMPERATURE-DistanceFromEquator*LATITUDE_EFFECT-AltitudeBuffer[x][y]*ALTITUDE_EFFECT);
618 int Type = 0;
619 if (Temperature <= COLD) Type = Rainy ? SnowType : GlacierType;
620 else if (Temperature <= MEDIUM) Type = Rainy ? EGForestType : SnowType;
621 else if (Temperature <= WARM) Type = Rainy ? LForestType : SteppeType;
622 else if (Temperature <= HOT) Type = Rainy ? LForestType : DesertType;
623 else Type = Rainy ? JungleType : DesertType;
624 TypeBuffer[x][y] = Type;
630 void worldmap::SmoothClimate () {
631 for (int c = 0; c < 3; ++c) {
632 game::BusyAnimation();
633 for (int x = 0; x < XSize; ++x) {
634 for (int y = 0; y < YSize; ++y) {
635 if ((OldTypeBuffer[x][y] = TypeBuffer[x][y]) != OceanType) {
636 TypeBuffer[x][y] = WhatTerrainIsMostCommonAroundCurrentTerritorySquareIncludingTheSquareItself(x, y);
641 game::BusyAnimation();
642 for (int x = 0; x < XSize; ++x) {
643 for (int y = 0; y < YSize; ++y) {
644 auto terraProto = protocontainer<gwterrain>::GetProto(TypeBuffer[x][y]);
645 if (!terraProto) ABORT("Oops! No gwterrain prototype for type #%d!\n", TypeBuffer[x][y]);
646 Map[x][y]->ChangeGWTerrain(terraProto->Spawn());
652 /* Evil... */
653 #define ANALYZE_TYPE(type) {\
654 int T = type;\
655 for (c = 0; c < u; ++c) if (T == UsedType[c]) { ++TypeAmount[c]; break; }\
656 if (c == u) { UsedType[u] = T; TypeAmount[u++] = 1; }\
660 /* k8: WOW! */
661 int worldmap::WhatTerrainIsMostCommonAroundCurrentTerritorySquareIncludingTheSquareItself (int x, int y) {
662 int UsedType[9];
663 int TypeAmount[9];
664 int c, d, u = 1;
665 UsedType[0] = TypeBuffer[x][y];
666 TypeAmount[0] = 1;
667 for (d = 0; d < 4; ++d) {
668 int X = x+DirX[d];
669 int Y = y+DirY[d];
670 if (IsValidPos(X, Y)) ANALYZE_TYPE(OldTypeBuffer[X][Y]);
672 for (d = 4; d < 8; ++d) {
673 int X = x+DirX[d];
674 int Y = y+DirY[d];
675 if (IsValidPos(X, Y)) ANALYZE_TYPE(TypeBuffer[X][Y]);
677 int MostCommon = 0;
678 for (c = 1; c < u; ++c) if (TypeAmount[c] > TypeAmount[MostCommon] && UsedType[c] != OceanType) MostCommon = c;
679 return UsedType[MostCommon];
683 void worldmap::CalculateContinents () {
684 for (uInt c = 1; c < Continent.size(); ++c) delete Continent[c];
685 Continent.resize(1, 0);
686 memset(ContinentBuffer[0], 0, XSizeTimesYSize*sizeof(uChar));
687 game::BusyAnimation();
688 for (int x = 0; x < XSize; ++x) {
689 for (int y = 0; y < YSize; ++y) {
690 if (AltitudeBuffer[x][y] > 0) {
691 truth Attached = false;
692 for (int d = 0; d < 8; ++d) {
693 v2 Pos = v2(x, y)+game::GetMoveVector(d);
694 if (IsValidPos(Pos)) {
695 cint NearCont = ContinentBuffer[Pos.X][Pos.Y];
696 if (NearCont) {
697 cint ThisCont = ContinentBuffer[x][y];
698 if (ThisCont) {
699 if (ThisCont != NearCont) {
700 if (Continent[ThisCont]->GetSize() < Continent[NearCont]->GetSize()) {
701 Continent[ThisCont]->AttachTo(Continent[NearCont]);
702 } else {
703 Continent[NearCont]->AttachTo(Continent[ThisCont]);
706 } else {
707 Continent[NearCont]->Add(v2(x, y));
709 Attached = true;
713 if (!Attached) {
714 if (Continent.size() == 255) {
715 RemoveEmptyContinents();
716 if (Continent.size() == 255) ABORT("Valpurus shall not carry more continents!");
718 continent *NewContinent = new continent(Continent.size());
719 NewContinent->Add(v2(x, y));
720 Continent.push_back(NewContinent);
725 RemoveEmptyContinents();
726 for (uInt c = 1; c < Continent.size(); ++c) Continent[c]->GenerateInfo();
730 void worldmap::RemoveEmptyContinents () {
731 for (uInt c = 1; c < Continent.size(); ++c) {
732 if (!Continent[c]->GetSize()) {
733 for (uInt i = Continent.size()-1; i >= c; --i) {
734 if (Continent[i]->GetSize()) {
735 Continent[i]->AttachTo(Continent[c]);
736 delete Continent[i];
737 Continent.pop_back();
738 break;
739 } else {
740 delete Continent[i];
741 Continent.pop_back();
749 void worldmap::Draw (truth) const {
750 cint XMin = Max(game::GetCamera().X, 0);
751 cint YMin = Max(game::GetCamera().Y, 0);
752 cint XMax = Min(XSize, game::GetCamera().X+game::GetScreenXSize());
753 cint YMax = Min(YSize, game::GetCamera().Y+game::GetScreenYSize());
754 blitdata BlitData = {
755 DOUBLE_BUFFER,
756 { 0, 0 },
757 { 0, 0 },
758 { TILE_SIZE, TILE_SIZE },
759 { 0 },
760 TRANSPARENT_COLOR,
761 ALLOW_ANIMATE|ALLOW_ALPHA
763 if (!game::GetSeeWholeMapCheatMode()) {
764 for (int x = XMin; x < XMax; ++x) {
765 BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
766 wsquare **Square = &Map[x][YMin];
767 for (int y = YMin; y < YMax; ++y, ++Square, BlitData.Dest.Y += TILE_SIZE) {
768 if ((*Square)->LastSeen) (*Square)->Draw(BlitData);
771 } else {
772 for (int x = XMin; x < XMax; ++x) {
773 BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
774 wsquare **Square = &Map[x][YMin];
775 for (int y = YMin; y < YMax; ++y, ++Square, BlitData.Dest.Y += TILE_SIZE) (*Square)->Draw(BlitData);
781 void worldmap::CalculateLuminances () {
782 for (feuLong c = 0; c < XSizeTimesYSize; ++c) Map[0][c]->CalculateLuminance();
786 void worldmap::CalculateNeighbourBitmapPoses () {
787 for (feuLong c = 0; c < XSizeTimesYSize; ++c) Map[0][c]->GetGWTerrain()->CalculateNeighbourBitmapPoses();
791 wsquare *worldmap::GetNeighbourWSquare (v2 Pos, int I) const {
792 Pos += game::GetMoveVector(I);
793 if (Pos.X >= 0 && Pos.Y >= 0 && Pos.X < XSize && Pos.Y < YSize) return Map[Pos.X][Pos.Y];
794 return 0;
798 void worldmap::RevealEnvironment (v2 Pos, int Radius) {
799 rect Rect;
800 femath::CalculateEnvironmentRectangle(Rect, Border, Pos, Radius);
801 for (int x = Rect.X1; x <= Rect.X2; ++x)
802 for (int y = Rect.Y1; y <= Rect.Y2; ++y)
803 Map[x][y]->SignalSeen();
807 outputfile &operator << (outputfile &SaveFile, const worldmap *WorldMap) {
808 WorldMap->Save(SaveFile);
809 return SaveFile;
813 inputfile &operator >> (inputfile &SaveFile, worldmap *&WorldMap) {
814 WorldMap = new worldmap;
815 WorldMap->Load(SaveFile);
816 return SaveFile;
820 void worldmap::UpdateLOS () {
821 game::RemoveLOSUpdateRequest();
822 int Radius = PLAYER->GetLOSRange();
823 sLong RadiusSquare = Radius*Radius;
824 v2 Pos = PLAYER->GetPos();
825 rect Rect;
826 femath::CalculateEnvironmentRectangle(Rect, Border, Pos, Radius);
827 for (int x = Rect.X1; x <= Rect.X2; ++x)
828 for (int y = Rect.Y1; y <= Rect.Y2; ++y)
829 if (sLong(HypotSquare(Pos.X-x, Pos.Y-y)) <= RadiusSquare) Map[x][y]->SignalSeen();