/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/debug.h" #include "scumm/he/moonbase/map_spiff.h" namespace Scumm { SpiffGenerator::SpiffGenerator(int seed) { _seed = seed; } MapFile *SpiffGenerator::generateMap(int water, int tileset, int mapSize, int energy, int terrain) { _totalMapSizeG = mapSize; _energyAmountG = (2 + energy) * _totalMapSizeG * _totalMapSizeG; _islandsFlagG = pickFrom2(0, 1, water - 4, (water >= 5)); // 1 is large islands, 2 is small if (_islandsFlagG) { water -= 3; _energyAmountG = (int)(_energyAmountG * (5 - _islandsFlagG) / 6); // *2/3 or *1/2 } _waterAmountG = 4 * water; _cliffAmountG = 1 << terrain; _advancedMirrorOK_G = ((terrain > 1) && (water < 6)) || _islandsFlagG; _terrainSeedFlagG = 2 * water - terrain; int n = (int)(_energyAmountG / 2700); if (n > 12) { n = 12; } if (n < 1) { n = 1; } _numPoolsG = spiffRand((int)(_energyAmountG / 4000) + 1, n); if (_numPoolsG > 12) { _numPoolsG = 12; } generate(); // Populate MIF for map data generation: MIF mif = MIF(); int levelMap[MAXELEVVAL]; levelMap[kElevHigh] = 2; levelMap[kElevMedium] = 1; levelMap[kElevLow] = 0; mif._mapType = tileset; Common::sprintf_s(mif._name, "Spiff %04X", (uint16)_seed); mif._dimension = _totalMapSizeG; int y; int x; byte t; int XOffset = spiffRand(0, _totalMapSizeG-1); int YOffset = spiffRand(0, _totalMapSizeG-1); int newX; int newY; for (y = 0, newY = YOffset; y < _totalMapSizeG; ++y, ++newY) { for (x = 0, newX = XOffset; x < _totalMapSizeG; ++x, ++newX) { if (newX == _totalMapSizeG) newX = 0; if (newY == _totalMapSizeG) newY = 0; mif._cornerMap[newX][newY] = levelMap[_mapCorner[x][y]]; switch (_mapMiddle[x][y]) { case HUB: t = 0xff; break; case SMALLPOOL: t = 'S'; break; case MEDIUMPOOL: t = 'M'; break; case LARGEPOOLBOTTOM: t = 'L'; break; case WATER: t = 'W'; break; case UNASSIGNED: case LARGEPOOLTOP: t = '.'; break; default: t = '?'; } mif._centerMap[newX][newY] = t; } } // Generate new map: MapFile *map = new MapFile(); mif.generateMap(map); return map; } float SpiffGenerator::getRandomFloat() { // This is the exact linear congruential generator // algorithm used on MSVCRT (Windows Visual C++ Runtime), with // RAND_MAX being 0x7fff (32767). This is implemented here // to match the RNG between the original Moonbase Console // program and ScummVM. // // (Common::RandomSource uses Xorshift and uses unsigned // integers compared to MSVCRT's rand) _seed = _seed * 214013 + 2531011; return (float)((_seed >> 16) & 0x7fff) / 32767; } int SpiffGenerator::spiffRand(int min, int max) { // returns a random integer min to max inclusive return ((int)(getRandomFloat() * (max + 1 - min))) + min; } int SpiffGenerator::pickFrom2(int a, int probA, int b, int probB) { debug(3, "SpiffGenerator::pickFrom2(%d, %d, %d, %d)", a, probA, b, probB); float r = getRandomFloat() * (probA + probB); debug(3, " r = %f", r); if (r < probA) return a; else return b; } int SpiffGenerator::pickFrom3(int a, int probA, int b, int probB, int c, int probC) { debug(3, "SpiffGenerator::pickFrom3(%d, %d, %d, %d, %d, %d)", a, probA, b, probB, c, probC); float r = getRandomFloat() * (probA + probB + probC); debug(3, " r = %f", r); if (r < probA) return a; else if (r < probA + probB) return b; else return c; } int SpiffGenerator::pickFrom4(int a, int probA, int b, int probB, int c, int probC, int d, int probD) { debug(3, "SpiffGenerator::pickFrom4(%d, %d, %d, %d, %d, %d, %d, %d)", a, probA, b, probB, c, probC, d, probD); float r = getRandomFloat() * (probA + probB + probC + probD); debug(3, " r = %f", r); if (r < probA) return a; else if (r < probA + probB) return b; else if (r < probA + probB + probC) return c; else return d; } void SpiffGenerator::getSpecials() { // choose where the starting points and pools are int x, y, p, t; int edgeWaterA = (int)(_islandsFlagG * _totalMapSizeG / 16 + 0.5); int edgeWaterB = (int)(_islandsFlagG * _totalMapSizeG / 16); // don't put pools between islands // No matter what, they get a start hub spot. if (_mirrorTypeG == MAXDISTMIRROR) x = (int)((_totalMapSizeG * 3 + 8) / 16); else x = (int)(_mapMiddleMaxG / 2); y = x; _mapMiddle[x][y] = HUB; // hub start position for (p = 1; p <= _numPoolsG; ++p) { x = spiffRand(edgeWaterA, _mapMiddleMaxG - edgeWaterB); y = spiffRand(edgeWaterA, _mapMiddleMaxG - edgeWaterB); if (_mapMiddle[x][y] != UNASSIGNED) --p; // repick this pool else { t = pickFrom3(SMALLPOOL, 40000 * _numPoolsG, MEDIUMPOOL, 20000 * _numPoolsG + _energyAmountG, LARGEPOOLTOP, 2 * _energyAmountG); if (t == LARGEPOOLTOP) { if ((y == _mapMiddleMaxG - edgeWaterB) || (_mapMiddle[x][y + 1] != UNASSIGNED)) t = SMALLPOOL; // keep large pool from being too high or overlapping another pool or start else _mapMiddle[x][y + 1] = LARGEPOOLBOTTOM; } _mapMiddle[x][y] = t; } } } void SpiffGenerator::copyMap(int XOffset, int YOffset, int XDirection, int YDirection) { // copies the first quadrant of the map // XOffset and YOffset are the distances moved // XDirection and/or YDirection are/is -1 for mirrored, 1 for not mirrored int x, y, tempMiddle, newCX; int newCY = YOffset; int newMX, newMY; if (YDirection < 0) newCY += _mapCornerMaxG; for (y = 0; y <= _mapCornerMaxG; ++y) { if (newCY < 0) newCY += _totalMapSizeG; else if (newCY >= _totalMapSizeG) newCY -= _totalMapSizeG; newCX = XOffset; if (XDirection < 0) newCX += _mapCornerMaxG; for (x = 0; x <= _mapCornerMaxG; ++x) { if (newCX < 0) newCX += _totalMapSizeG; else if (newCX >= _totalMapSizeG) newCX -= _totalMapSizeG; _mapCorner[newCX][newCY] = _mapCorner[x][y]; if ((x != _mapCornerMaxG) && (y != _mapCornerMaxG)) { tempMiddle = _mapMiddle[x][y]; newMX = newCX; newMY = newCY; if (YDirection < 0) { newMY--; if (newMY == -1) newMY = _totalMapSizeG - 1; if (tempMiddle == LARGEPOOLTOP) tempMiddle = LARGEPOOLBOTTOM; else if (tempMiddle == LARGEPOOLBOTTOM) tempMiddle = LARGEPOOLTOP; } if (XDirection < 0) { newMX--; if (newMX == -1) newMX = _totalMapSizeG - 1; } _mapMiddle[newMX][newMY] = tempMiddle; } newCX += XDirection; } newCY += YDirection; } } void SpiffGenerator::mirrorMap() { // -------------------------------------------------------------- // mirror map // -------------------------------------------------------------- int swapXa = pickFrom2(-1, 1, 1, _advancedMirrorOK_G); int swapYa = pickFrom2(-1, _advancedMirrorOK_G, 1, 1); int swapXb = pickFrom2(-1, _advancedMirrorOK_G, 1, 1); int swapYb = pickFrom2(-1, 1, 1, _advancedMirrorOK_G); switch (_mirrorTypeG) { case NORMALMIRROR: // four quadrants // ABCBA // DEFED // GHIHG // DEFED // ABCBA copyMap(_mapCornerMaxG, 0, swapXa, swapYa); copyMap(0, _mapCornerMaxG, swapXb, swapYb); copyMap(_mapCornerMaxG, _mapCornerMaxG, swapXa * swapXb, swapYa * swapYb); break; case XOFFSETMIRROR: // Like normal, but one half is moved horizontally by 1/4 totalmapsize // ABABABABA // DEFGHGFED // CDCDCDCDC // FGHGFEDEF // ABABABABA if (swapYa == -1) // ensures fairness swapXb = -1; copyMap(_mapCornerMaxG, 0, 1, swapYa); copyMap(_mapCornerMaxG / 2, _mapCornerMaxG, swapXb, swapYb); copyMap(_mapCornerMaxG * 3 / 2, _mapCornerMaxG, swapXb, swapYa * swapYb); break; case YOFFSETMIRROR: // Like normal, but one half is moved vertically by 1/4 totalmapsize if (swapXb == -1) // ensures fairness swapYa = -1; copyMap(_mapCornerMaxG, _mapCornerMaxG / 2, swapXa, swapYa); copyMap(0, _mapCornerMaxG, swapXb, 1); copyMap(_mapCornerMaxG, _mapCornerMaxG * 3 / 2, swapXa * swapXb, swapYa); break; case MAXDISTMIRROR: // Allows maximum distance between starting points default: // ABCDCBA // E*GHIJE // HIJE*GH // DCBABCD // HG*EJIH // EJIHG*E // ABCDCBA copyMap(_mapCornerMaxG, 0, 1, -1); copyMap(0, _mapCornerMaxG, -1, 1); copyMap(_mapCornerMaxG, _mapCornerMaxG, -1, -1); } } void SpiffGenerator::errorCorrection() { // corrects errors caused by pool placement and mirroring // doesn't correct _mapCorner[x][_totalMapSizeG+1] or _mapCorner[_totalMapSizeG+1][y], since it isn't used // for any kElevHigh to kElevLow transitions, makes the kElevHigh kElevMedium // for pools on nonflat terrain, makes the terrain kElevMedium // removes invalid water int x; int y; int tempX; int tempY; int dx; int dy; int redo; int elev; for (y = 0; y < _totalMapSizeG; ++y) { for (x = 0; x < _totalMapSizeG; ++x) { if (_mapCorner[x][y] == kElevHigh) { for (dy = -1; dy <= 1; ++dy) { tempY = y + dy; if (tempY == _totalMapSizeG) { tempY = 0; } else if (tempY == -1) { tempY = _totalMapSizeG - 1; } for (dx = -1; dx <= 1; ++dx) { tempX = x + dx; if (tempX == _totalMapSizeG) { tempX = 0; } else if (tempX == -1) { tempX = _totalMapSizeG - 1; } if (_mapCorner[tempX][tempY] == kElevLow) { _mapCorner[x][y] = kElevMedium; } } } } else if ((_mapCorner[x][y] != kElevLow) && (_mapCorner[x][y] != kElevMedium)) { _mapCorner[x][y] = kElevMedium; // should not happen anymore } } } do { redo = 0; for (y = 0; y < _totalMapSizeG; ++y) { for (x = 0; x < _totalMapSizeG; ++x) { if (_mapMiddle[x][y] != UNASSIGNED) { tempY = y + 1; if (tempY == _totalMapSizeG) tempY = 0; tempX = x + 1; if (tempX == _totalMapSizeG) tempX = 0; elev = _mapCorner[x][y]; if ((_mapMiddle[x][y] == WATER) && (elev != kElevLow)) _mapMiddle[x][y] = UNASSIGNED; else if ((elev != _mapCorner[x][tempY]) || (elev != _mapCorner[tempX][y]) || (elev != _mapCorner[tempX][tempY])) { if (_mapMiddle[x][y] == WATER) _mapMiddle[x][y] = UNASSIGNED; else { _mapCorner[x][y] = kElevMedium; _mapCorner[x][tempY] = kElevMedium; _mapCorner[tempX][y] = kElevMedium; _mapCorner[tempX][tempY] = kElevMedium; redo = 1; } } } } } } while (redo); } void SpiffGenerator::generate() { // -------------------------------------------------------------- // initialize // -------------------------------------------------------------- int x; int y; int neighbors[MAXELEVVAL]; int a; int b; int tempElevation; int nextElevation; int special; _mapCornerMaxG = _totalMapSizeG / 2; _mapMiddleMaxG = _mapCornerMaxG - 1; for (y = 0; y <= _mapCornerMaxG; ++y) { // initialise map to UNASSIGNED tiles for (x = 0; x <= _mapCornerMaxG; ++x) { _mapCorner[x][y] = UNASSIGNED; _mapMiddle[x][y] = UNASSIGNED; } } if (_advancedMirrorOK_G) _mirrorTypeG = pickFrom4(NORMALMIRROR, 1, XOFFSETMIRROR, 2, YOFFSETMIRROR, 2, MAXDISTMIRROR, 4); else _mirrorTypeG = NORMALMIRROR; getSpecials(); // get start and pools // -------------------------------------------------------------- // loop through each square // -------------------------------------------------------------- _mapCorner[0][0] = pickFrom3(kElevLow, 1, kElevMedium, (_terrainSeedFlagG < 9), kElevHigh, (_terrainSeedFlagG < 8)); // seed // fill in the rest of the random map for (y = 0; y <= _mapCornerMaxG; ++y) { for (x = 0; x <= _mapCornerMaxG; ++x) { special = _mapMiddle[x][y]; // water wouldn't have been assigned yet, so must be pool, start, or UNASSIGNED // -------------------------------------------------------------- // check neighbors // -------------------------------------------------------------- if ((_mapCorner[x][y] != UNASSIGNED) && (_mapCorner[x][y] != LOW_OR_WATER)) nextElevation = _mapCorner[x][y]; // already defined because of a special or (0,0), so no change else { neighbors[kElevHigh] = 0; neighbors[kElevMedium] = 0; neighbors[kElevLow] = 0; neighbors[WATER] = 0; if (x > 0) { a = _mapCorner[x - 1][y]; if ((y > 1) && (_mapMiddle[x - 1][y - 2] == WATER)) ++neighbors[WATER]; if (y > 0) neighbors[_mapCorner[x - 1][y - 1]] += 3; } else { a = _mapCorner[x][y - 1]; } neighbors[a] += 3; if (y > 0) { b = _mapCorner[x][y - 1]; neighbors[b] += 3; if (x < _mapCornerMaxG) { ++neighbors[_mapCorner[x + 1][y - 1]]; // so this value can be ignored when choosing water if ((special != UNASSIGNED) && (x < _mapCornerMaxG - 1)) ++neighbors[_mapCorner[x + 2][y - 1]]; } if ((x > 1) && (_mapMiddle[x - 2][y - 1] == WATER)) ++neighbors[WATER]; } else { b = _mapCorner[x - 1][y]; // for probability equations for edges } // -------------------------------------------------------------- // pick new elevation // -------------------------------------------------------------- // neighbors possible new elevation // kElevHigh or kElevHigh with kElevMedium kElevHigh or kElevMedium // kElevMedium only kElevHigh, kElevMedium or kElevLow // kElevLow or WATER only kElevMedium, kElevLow or WATER // kElevMedium with kElevLow or WATER kElevMedium or kElevLow, possible WATER if no kElevMedium left, down, or down-left // kElevHigh with kElevLow or WATER kElevMedium const int highAmt = 105; const int mediumAmt = 100 + _waterAmountG; const int lowAmt = 105 + 3 * _waterAmountG; const int waterAmt = 15 * _waterAmountG; if (neighbors[kElevLow]) { if (neighbors[kElevHigh]) { // kElevHigh with kElevLow or WATER nextElevation = kElevMedium; } else if (neighbors[kElevMedium] >= 3) { // kElevMedium with kElevLow or WATER if (a != b) { nextElevation = pickFrom2(kElevLow, lowAmt, kElevMedium, mediumAmt); } else if (a == kElevLow) { nextElevation = pickFrom2(kElevLow, 100 * lowAmt, kElevMedium, mediumAmt * _cliffAmountG); } else { nextElevation = pickFrom2(kElevLow, lowAmt * _cliffAmountG, kElevMedium, 100 * mediumAmt); } } else { // kElevLow or WATER only, possibly kElevMedium down-right if (neighbors[WATER] == 1) { nextElevation = pickFrom3(WATER, 100 * waterAmt, kElevLow, 100 * lowAmt, kElevMedium, mediumAmt * _cliffAmountG); } else if (neighbors[WATER] == 0) { nextElevation = pickFrom3(WATER, waterAmt * _cliffAmountG, kElevLow, 100 * lowAmt, kElevMedium, mediumAmt * _cliffAmountG); } else { nextElevation = pickFrom3(WATER, 10000 * waterAmt, kElevLow, lowAmt * 100 * _cliffAmountG, kElevMedium, mediumAmt * _cliffAmountG * _cliffAmountG); } } } else { if (neighbors[kElevHigh]) { // kElevHigh or kElevHigh with kElevMedium if (a != b) { nextElevation = pickFrom2(kElevMedium, mediumAmt, kElevHigh, highAmt); } else if (a == kElevHigh) { nextElevation = pickFrom2(kElevMedium, mediumAmt * _cliffAmountG, kElevHigh, 100 * highAmt); } else { nextElevation = pickFrom2(kElevMedium, 100 * mediumAmt, kElevHigh, highAmt * _cliffAmountG); } } else { nextElevation = pickFrom3(kElevLow, lowAmt * _cliffAmountG, kElevMedium, 200 * mediumAmt, kElevHigh, highAmt * _cliffAmountG); } } // -------------------------------------------------------------- // set elevation // -------------------------------------------------------------- if ((_mapCorner[x][y] == LOW_OR_WATER) && (nextElevation != WATER)) { // bottom and left edges of a special on kElevLow ground there may only be kElevLow or WATER nextElevation = kElevLow; } if (nextElevation == WATER) { if ((x != 0) && (y != 0) && (_mapMiddle[x - 1][y - 1] == UNASSIGNED)) { _mapMiddle[x - 1][y - 1] = WATER; // set WATER } nextElevation = kElevLow; } _mapCorner[x][y] = nextElevation; // set elevation if (special != UNASSIGNED) { // if special, make flat spot (don't worry about going over map edge, will go into mirrored part) tempElevation = nextElevation; if (tempElevation == kElevLow) tempElevation = LOW_OR_WATER; // allow for water on left and bottom edges _mapCorner[x + 1][y + 1] = nextElevation; _mapCorner[x + 1][y] = tempElevation; _mapCorner[x][y + 1] = tempElevation; } } } } if (_islandsFlagG) { // replace borders with water, errorCorrection() finishes it. int edgeWaterA = (int)(_islandsFlagG * _totalMapSizeG / 16 + 0.5); int edgeWaterB = _mapMiddleMaxG - (int)(_islandsFlagG * _totalMapSizeG / 16); for (y = 0; y <= _mapCornerMaxG; ++y) { for (x = 0; x < edgeWaterA; ++x) { _mapCorner[x][y] = kElevLow; _mapMiddle[x][y] = WATER; } if (_mapCorner[edgeWaterA + 1][y] == kElevHigh) _mapCorner[edgeWaterA][y] = kElevMedium; for (x = _mapMiddleMaxG; x > edgeWaterB; --x) { _mapCorner[x + 1][y] = kElevLow; _mapMiddle[x][y] = WATER; } if (_mapCorner[edgeWaterB][y] == kElevHigh) { _mapCorner[edgeWaterB + 1][y] = kElevMedium; } } for (x = edgeWaterA; x <= edgeWaterB + 1; ++x) { for (y = 0; y < edgeWaterA; ++y) { _mapCorner[x][y] = kElevLow; _mapMiddle[x][y] = WATER; } if (_mapCorner[x][edgeWaterA + 1] == kElevHigh) _mapCorner[x][edgeWaterA] = kElevMedium; for (y = _mapMiddleMaxG; y > edgeWaterB; --y) { _mapCorner[x][y + 1] = kElevLow; _mapMiddle[x][y] = WATER; } if (_mapCorner[x][edgeWaterB] == kElevHigh) { _mapCorner[x][edgeWaterB + 1] = kElevMedium; } } if (_islandsFlagG == 2) { // add tiny islands to help bridge wide channels int j; for (int i = 0; i < _totalMapSizeG / 16; ++i) { x = (int)(_totalMapSizeG / 16 - .5); y = spiffRand(x, _totalMapSizeG / 2 - 1 - x); if (spiffRand(0, 1)) { x = _totalMapSizeG / 2 - 1 - x; } if (spiffRand(0, 1)) { _mapMiddle[x][y] = UNASSIGNED; for (j = 0; j < 4; ++j) { _mapMiddle[x + spiffRand(-1, 1)][y + spiffRand(-1, 1)] = UNASSIGNED; } } else { _mapMiddle[y][x] = UNASSIGNED; for (j = 0; j < 4; ++j) { _mapMiddle[y + spiffRand(-1, 1)][x + spiffRand(-1, 1)] = UNASSIGNED; } } } } } mirrorMap(); errorCorrection(); } } // End of namespace Scumm