matthewjwhite / crystal-skull

:skull: Highly-configurable, telnet-compatible, text-based RPG
MIT License
0 stars 0 forks source link

Implement initial exploration system #8

Closed matthewjwhite closed 3 years ago

matthewjwhite commented 4 years ago

What?

Exploration in this case refers to the navigation of land masses in the overall world,

A way to represent these land masses (cities, towns, dungeons, etc.) is required.

The goal here is to implement (at least) the initial exploration design/map system.

To Close

matthewjwhite commented 4 years ago

A grid-based platform seems too simplistic, ie. a square grid representing the entire world where each region/map is a square within that grid.

One possibility I have been toying with is:

For example, consider the WesternDungeon region/map:

With the above example, some regions could even be tunnels to other regions, as opposed to primary regions themselves.

As a side note, it seems too restrictive to place monsters at specific points in regions. I feel that this should be a randomly generated aspect of the game.

To maintain the user's current position, all that would need to be restored is the current region and relative position in that region.


Example configuration file format:

---
# westernDungeon.yaml
name: westernDungeon
width: 500
height: 300
keyPoints:
  - type: npc
    x: 70
    y: 30
  - type: item
    x: 20
    y: 20
exitRanges:
  - dest: easternDungeon # Eastern edge of Western Dungeon.
    xLow: 500
    xHigh: 500
    yLow: 20
    yHigh: 50
  - dest: anotherWorld # Teleportation point.
    xLow: 70
    xHigh: 70
    yLow: 60
    yHigh: 60
monsters:
  - hellHounds
  - massiveBugs

TL;DR:

The configuration file approach lends to the maximum configurability of this game. In other words, a server administrator should be able to tweak the game as they wish by simply adjusting configuration files.

matthewjwhite commented 3 years ago

Switching Directions

After giving it further thought, I think the idea of only having regions aware of their neighboring exit points/maps, as opposed to having a single, main grid that controls everything, is too complex.

Some of the major pitfalls:

Overall, having a single controller map is going to make things easier.

New Proposal

The new proposal is:

It's important to note that the idea of a controller map only matters when you're moving, to determine if you're entering a neighboring region and where you end up in that neighboring region. So, when moving, we need to invoke a relative -> absolute converter, to determine if we're: 1) breaching a boundary, and 2) where in the new map we are.

I think there are basically 3 pieces to implement here:

  1. Location conversion algorithm, invoked when moving.
  2. Individual map format.
  3. Controller map format.
  4. Additions to game loop.

Location Conversion

I think it would be something like this;

  1. User starts at WestDungeon, (5, 5) (relative point).
  2. User engages game loop, selecting a direction to go in.
  3. See if you're going to breach the edge of the map: are either the next relative X or Y coordinates equal to the height or width of the current map? If not, no need to involve the controller map, simply advance within the relative dimensions of the current map. if so, continue.
  4. Determine if there is even something on the other end (next steps), and advance into that region, continue. Otherwise, reject the mover, since you're at the edge with no region to jump to.
  5. Example, current map width is 200 (relative X range of 0 to 199), height 50 (relative Y range of 0 to 49), xMin 0, yMin 0. Traveling east, next relative point is (200, 40). The relative height is equal to the max height, so breach. There is a neighboring map with xMin 200, yMin 20, with a width of 300 and height of 200.
  6. Get the controller location by adding the current map's xMin (from controller) and xNext, same for Y. Ex. xMin 0 + 200 = 200, yMin 0 + 40 = (200, 40).
  7. Find the matching map by iterating over all maps and finding the one with the greatest xMin and yMin less than or equal to the controller location.
  8. Translate to the relative point in the new map. Add the current map's xMin to the next relative X and subtract the new map's xMin, ex. 0 + 200 - 200 = 0. Same for Y, ex. 0 + 40 - 20 = 20.
  9. Save location.

Individual Maps

name: westernDungeon
width: 500
height: 300
keyPoints:
  - type: npc
    x: 70
    y: 30
  - type: item
    x: 20
    y: 20
monsters:
  - hellHounds
  - massiveBugs
name: easternDungeon
width: 200
height: 200
monsters:
  - hellHounds

Controller Map

- map: westernDungeon
  xMin: 0
  yMin: 0
- map: easternDungeon
  xMin: 500
  yMin: 30

Notice no xMax or yMax - these can be inferred from the respective maps' dimensions; unfortunately, this requires careful coordination between the map definitions and controller, to ensure the width and height match up with the xMin and yMin of neighboring maps. Another option would be to not store any dimensions in the respective map definitions, and store the height and width in the controller, so it's all in one place.

matthewjwhite commented 3 years ago

A few things to note in beginning implementation:

Other interesting things being added/caught:

matthewjwhite commented 3 years ago

WIP branch: https://github.com/matthewjwhite/crystal-skull/compare/map?expand=1.

matthewjwhite commented 3 years ago

Went with a way simpler approach of finding the map you're moving into by checking for the map that has that point in its space (see Dim::contains). The other way was too complex and seemed to be fraught with unknown boundary cases.