Skip to content

Commit

Permalink
More map changes
Browse files Browse the repository at this point in the history
Spread the start locations out across the world.
hex map now includes wing and exit info as well as gate and startloc
info
Map renderer shows start hexes and gates as well as blocked edges
for the underworld.
  • Loading branch information
jt-traub committed Jul 5, 2024
1 parent 6d84f9e commit ccfc4ab
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ map_viewer/hexmap.json
map_viewer/.yarn
map_viewer/.pnp.*
map_viewer/.parcel-cache
map_viewer/dist
28 changes: 25 additions & 3 deletions game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,21 @@ int Game::ViewMap(const AString & typestr,const AString & mapfile)
if (type == 5) {
json worldmap;

std::vector<ARegion *> start_regions;
for (auto i = 0; i < regions.numLevels; i++) {
ARegionArray *pArr = regions.pRegionArrays[i];
if (pArr->levelType == ARegionArray::LEVEL_NEXUS) continue;
if (pArr->levelType == ARegionArray::LEVEL_NEXUS) {
if (!Globals->START_CITIES_EXIST) {
ARegion *nexus = pArr->GetRegion(0, 0);
forlist(&nexus->objects) {
Object *o = (Object *) elem;
if (o->inner != -1) {
start_regions.push_back(regions.GetRegion(o->inner));
}
}
}
continue;
};
string label = (pArr->strName ? (pArr->strName->const_str() + to_string(i-1)) : "surface");
worldmap[label] = json::array();

Expand All @@ -291,9 +303,19 @@ int Game::ViewMap(const AString & typestr,const AString & mapfile)
{ "yew", reg->produces_item(I_YEW) },
{ "mithril", reg->produces_item(I_MITHRIL) },
{ "admantium", reg->produces_item(I_ADMANTIUM) },
{ "floater", reg->produces_item(I_FLOATER) }

{ "floater", reg->produces_item(I_FLOATER) },
{ "wing", reg->produces_item(I_WHORSE) },
{ "gate", reg->gate != 0 },
{ "exits", json::array() },
{ "starting",
(start_regions.end() != std::find(start_regions.begin(), start_regions.end(), reg)) ||
reg->IsStartingCity()
}
};
// Fill in the exits
for (auto d = 0; d < NDIRS; d++) {
hexout["exits"].push_back(reg->neighbors[d] != nullptr);
}
if (reg->town) {
hexout["town"] = data["settlement"]["size"];
}
Expand Down
29 changes: 29 additions & 0 deletions map_viewer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,35 @@ function drawHexMap(col: number, row: number, level: string) {
}
ctx.closePath();
ctx.fill();

// Draw small circles for gate & starting area.
const point = layout.hexToPixel(p);
// Draw a circle for the gate (in white)
if (item['gate']) {
ctx.beginPath();
ctx.fillStyle = 'yellow';
ctx.arc(point.x - 3, point.y - 3, 2, 0, Math.PI*2);
ctx.fill();
}
if (item['starting']) {
// Draw a circle for starting area (in yellow)
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.arc(point.x + 3, point.y + 3, 2, 0, Math.PI*2);
ctx.fill();
}

// Draw the edges/exits
for (let n = 0; n < points.length; n++) {
if(item['exits'] && item['exits'][n] === false) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(points[n].x, points[n].y);
let n1 = (n + 1) % points.length;
ctx.lineTo(points[n1].x, points[n1].y);
ctx.stroke();
}
}
}
}

Expand Down
119 changes: 100 additions & 19 deletions neworigins/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2887,30 +2887,111 @@ void ARegionList::SetACNeighbors(int levelSrc, int levelTo, int maxX, int maxY)
}
else
{
// If we don't have starting cities, then put portals
// from the nexus to a variety of terrain types.
// These will transport the user to a randomly
// selected region of the chosen terrain type.
// The previous code would *always* choose the northernmost and westernmost location
// of the specific terrain. The new algorithm is going to be to
//
// 1. collect all the candidates of each terrain type
// a. a hex is a candidate if it is close (within 2hex) of a hex containing each of the following
// 1. wood
// 2. iron
// 3. stone
// 4. food (grain or livestock)
// 5. mounts (horse or camel)
// 2. choose a random candidate from each set of valid candidates.
// Hopefully we won't have a situation where the is no viable candidate. If we do, we will error out.

// First, we need to find the candidates
ARegionArray *to = GetRegionArray(levelTo);
ARegionGraph graph = ARegionGraph(to);
graph.setInclusion([](ARegion *current, ARegion *next) { return next->type != R_OCEAN; });

std::map<int, std::vector<ARegion *> > candidates;
for (int type = R_PLAIN; type <= R_TUNDRA; type++) {
int found = 0;
for (int x2 = 0; !found && x2 < maxX; x2++)
for (int y2 = 0; !found && y2 < maxY; y2++) {
for (int x2 = 0; x2 < maxX; x2++) {
for (int y2 = 0; y2 < maxY; y2++) {
ARegion *reg = to->GetRegion(x2, y2);
if (!reg)
continue;
if (!reg) continue;
if (reg->type == type) {
found = 1;
Object *o = new Object(AC);
o->num = AC->buildingseq++;
o->name = new AString(AString("Gateway to ") +
TerrainDefs[type].name + " [" + o->num + "]");
o->type = O_GATEWAY;
o->incomplete = 0;
o->inner = reg->num;
AC->objects.Add(o);
graphs::Location2D loc = { reg->xloc, reg->yloc };
auto result = graphs::breadthFirstSearch(graph, loc, 2);
// if hex is part of a landmass smaller than 10 hexes within 3 moves, skip it
if (result.size() < 10) continue;
// Now, check for the required items
std::map<int, bool> requiredItems = {
{I_WOOD, false},
{I_IRON, false},
{I_STONE, false},
{I_GRAIN, false},
{I_LIVESTOCK, false},
{I_HORSE, false},
{I_CAMEL, false}
};
for (auto &kv : result) {
ARegion *tReg = graph.get(kv.first);
if (tReg->produces_item(I_WOOD)) requiredItems[I_WOOD] = true;
if (tReg->produces_item(I_IRON)) requiredItems[I_IRON] = true;
if (tReg->produces_item(I_STONE)) requiredItems[I_STONE] = true;
if (tReg->produces_item(I_GRAIN)) requiredItems[I_GRAIN] = true;
if (tReg->produces_item(I_LIVESTOCK)) requiredItems[I_LIVESTOCK] = true;
if (tReg->produces_item(I_HORSE)) requiredItems[I_HORSE] = true;
if (tReg->produces_item(I_CAMEL)) requiredItems[I_CAMEL] = true;
}
if (requiredItems[I_WOOD] == false) continue;
if (requiredItems[I_IRON] == false) continue;
if (requiredItems[I_STONE] == false) continue;
if (requiredItems[I_GRAIN] == false && requiredItems[I_LIVESTOCK] == false) continue;
if (requiredItems[I_HORSE] == false && requiredItems[I_CAMEL] == false) continue;

candidates[type].push_back(reg);
}
}
}
}
// Now for each type, choose a random candidate
std::mt19937 gen{std::random_device{}()}; // generates random numbers
std::map<int, ARegion *> dests;
for (int type = R_PLAIN; type <= R_TUNDRA; type++) {
if (candidates[type].size() == 0) {
cout << "Error: No valid candidate found for gateway to " << TerrainDefs[type].name << "\n";
exit(1);
}
Awrite("Found " + std::to_string(candidates[type].size()) + " candidates for " +
TerrainDefs[type].name + " gateway");
std::uniform_int_distribution<std::size_t> dist(0, candidates[type].size() - 1);
int index = dist(gen);
ARegion *dest = candidates[type][index];
ARegion *best = dest;
int tries = 25;
while(tries--) {
int maxMin = -1;
// See if it's too close to any existing dest
int minDist = 1000;
for (int otherTypes = R_PLAIN; otherTypes < type; otherTypes++) {
ARegion *otherDest = dests[otherTypes];
int curDist = GetPlanarDistance(dest, otherDest, 0);
if (curDist < minDist) minDist = curDist;
}
if (minDist > 20) break;
if (minDist > maxMin) {
maxMin = minDist;
best = dest;
}
// too close, try again
index = dist(gen);
dest = candidates[type][index];
}
// store the best we have so far
dests[type] = best;
}

for (int type = R_PLAIN; type <= R_TUNDRA; type++) {
Object *o = new Object(AC);
o->num = AC->buildingseq++;
o->name = new AString(AString("Gateway to ") + TerrainDefs[type].name + " [" + o->num + "]");
o->type = O_GATEWAY;
o->incomplete = 0;
o->inner = dests[type]->num;
AC->objects.Add(o);
}
}
}
Expand All @@ -2932,7 +3013,7 @@ void ARegionList::InitSetupGates(int level)
int tempy = j*16 + getrandom(8)*2 + tempx%2;
ARegion *temp = pArr->GetRegion(tempx, tempy);
if (temp && TerrainDefs[temp->type].similar_type != R_OCEAN &&
temp->type != R_VOLCANO &&
temp->type != R_VOLCANO && temp->type != R_BARREN &&
temp->gate != -1) {
numberofgates++;
temp->gate = -1;
Expand Down

0 comments on commit ccfc4ab

Please sign in to comment.