mirror of
https://github.com/evennia/evennia.git
synced 2026-03-30 04:27:16 +02:00
Adding basic map contrib
This commit is contained in:
parent
e9cce46d66
commit
f9866ed9b5
3 changed files with 297 additions and 0 deletions
55
evennia/contrib/grid/basicmap/README.md
Normal file
55
evennia/contrib/grid/basicmap/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Basic Map
|
||||
|
||||
Contribution - helpme 2022
|
||||
|
||||
This adds a `map` to a given room which can be viewed with the `map` command. You can
|
||||
easily alter it to add special characters denoting environments, room colors and so on.
|
||||
If you don't expect the map to be updated frequently, you could choose to save the
|
||||
calculated map as a .ndb value on the room and render that instead of running mapping
|
||||
calculations anew each time.
|
||||
|
||||
## Installation:
|
||||
|
||||
Adding the `BasicMapCmdSet` to the default character cmdset will add the `map` command.
|
||||
|
||||
Specifically, in `mygame/commands/default_cmdsets.py`:
|
||||
|
||||
```python
|
||||
...
|
||||
from evennia.contrib.grid.basicmap import basicmap # <---
|
||||
|
||||
class CharacterCmdset(default_cmds.Character_CmdSet):
|
||||
...
|
||||
def at_cmdset_creation(self):
|
||||
...
|
||||
self.add(basicmap.BasicMapCmdSet) # <---
|
||||
|
||||
```
|
||||
|
||||
Then `reload` to make the new commands available.
|
||||
|
||||
## Settings:
|
||||
|
||||
In order to change your default map size, you can add to `mygame/server/settings.py`:
|
||||
|
||||
```python
|
||||
BASIC_MAP_SIZE = 5 # This changes the default map width/height.
|
||||
|
||||
```
|
||||
|
||||
## Features:
|
||||
|
||||
### ASCII map (and evennia supports UTF-8 characters and even emojis)
|
||||
|
||||
This produces an ASCII map for sighted players that can be as large as you have it
|
||||
set to.
|
||||
|
||||
### Screenreader-friendly map
|
||||
|
||||
The screenreader-friendly map notes if the player has their screenreader toggled on
|
||||
and if so, displays a screenreader-friendly descriptive map instead. For the sake of
|
||||
concision, it is less detailed than the ASCII map.
|
||||
|
||||
### New command
|
||||
|
||||
- `CmdMap` - view the map
|
||||
0
evennia/contrib/grid/basicmap/__init__.py
Normal file
0
evennia/contrib/grid/basicmap/__init__.py
Normal file
242
evennia/contrib/grid/basicmap/basicmap.py
Normal file
242
evennia/contrib/grid/basicmap/basicmap.py
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
"""
|
||||
Basic Map - helpme 2022
|
||||
|
||||
This adds an ascii `map` to a given room which can be viewed with the `map` command.
|
||||
You can easily alter it to add special characters, room colors etc.
|
||||
|
||||
If you don't expect the map to be updated frequently, you could choose to save the
|
||||
calculated map as a .ndb value on the room and render that instead of running mapping
|
||||
calculations anew each time.
|
||||
|
||||
This ignores non-ordinal exits (i.e. exits must be compass directions or up/down to be
|
||||
shown on the map).
|
||||
|
||||
An example map:
|
||||
```
|
||||
|
|
||||
-[-]-
|
||||
|
|
||||
|
|
||||
-[-]--[-]--[-]--[-]
|
||||
| | | |
|
||||
| | |
|
||||
-[-]--[-] [-]
|
||||
| \/ | |
|
||||
\ | /\ |
|
||||
-[-]--[-]
|
||||
```
|
||||
|
||||
Installation:
|
||||
|
||||
Adding the `BasicMapCmdSet` to the default character cmdset will add the `map` command.
|
||||
|
||||
Specifically, in `mygame/commands/default_cmdsets.py`:
|
||||
|
||||
```
|
||||
...
|
||||
from evennia.contrib.grid.basicmap import basicmap # <---
|
||||
|
||||
class CharacterCmdset(default_cmds.Character_CmdSet):
|
||||
...
|
||||
def at_cmdset_creation(self):
|
||||
...
|
||||
self.add(basicmap.BasicMapCmdSet) # <---
|
||||
|
||||
```
|
||||
|
||||
Then `reload` to make the new commands available.
|
||||
|
||||
Additional Settings:
|
||||
|
||||
In order to change your default map size, you can add to `mygame/server/settings.py`:
|
||||
|
||||
BASIC_MAP_SIZE = 5
|
||||
|
||||
This changes the default map width/height. 2-5 for most clients is sensible.
|
||||
|
||||
If you don't want the player to be able to specify the size of the map, ignore any
|
||||
arguments passed into the Map command.
|
||||
"""
|
||||
import time
|
||||
from django.conf import settings
|
||||
from evennia import CmdSet
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
|
||||
_BASIC_MAP_SIZE = settings.BASIC_MAP_SIZE if hasattr(settings, 'BASIC_MAP_SIZE') else 2
|
||||
|
||||
# _COMPASS_DIRECTIONS specifies which way to move the pointer on the x/y axes and what characters to use to depict the exits on the map.
|
||||
_COMPASS_DIRECTIONS = {
|
||||
'north': (0, -3, ' | '),
|
||||
'south': (0, 3, ' | '),
|
||||
'east': (3, 0, '-'),
|
||||
'west': (-3, 0, '-'),
|
||||
'northeast': (3, -3, '/'),
|
||||
'northwest': (-3, -3, '\\'),
|
||||
'southeast': (3, 3, '\\'),
|
||||
'southwest': (-3, 3, '/'),
|
||||
'up': (0, 0, '^'),
|
||||
'down': (0, 0, 'v')
|
||||
}
|
||||
|
||||
|
||||
class Map(object):
|
||||
def __init__(self, caller, size=_BASIC_MAP_SIZE, location=None):
|
||||
self.start_time = time.time()
|
||||
self.caller = caller
|
||||
self.max_width = int(size * 2 + 1) * 5 # This must be an odd number
|
||||
self.max_length = int(size * 2 + 1) * 3 # This must be an odd number
|
||||
self.has_mapped = {}
|
||||
self.curX = None
|
||||
self.curY = None
|
||||
self.size = size
|
||||
self.location = location or caller.location
|
||||
|
||||
def create_grid(self):
|
||||
# Create an empty grid of the configured size
|
||||
board = []
|
||||
for row in range(self.max_length):
|
||||
board.append([])
|
||||
for column in range(int(self.max_width/5)):
|
||||
board[row].extend([' ', ' ', ' '])
|
||||
return board
|
||||
|
||||
def exit_name_as_ordinal(self, ex):
|
||||
exit_name = ex.name
|
||||
if exit_name not in _COMPASS_DIRECTIONS:
|
||||
compass_aliases = [direction in ex.aliases.all() for direction in _COMPASS_DIRECTIONS.keys()]
|
||||
if compass_aliases[0]:
|
||||
exit_name = compass_aliases[0]
|
||||
if exit_name not in _COMPASS_DIRECTIONS:
|
||||
return ''
|
||||
return exit_name
|
||||
|
||||
def update_pos(self, room, exit_name):
|
||||
# Update the pointer
|
||||
self.curX, self.curY = self.has_mapped[room][0], self.has_mapped[room][1]
|
||||
|
||||
# Move the pointer depending on which direction the exit lies
|
||||
# exit_name has already been validated as an ordinal direction at this point
|
||||
self.curY += _COMPASS_DIRECTIONS[exit_name][0]
|
||||
self.curX += _COMPASS_DIRECTIONS[exit_name][1]
|
||||
|
||||
def has_drawn(self, room):
|
||||
return True if room in self.has_mapped.keys() else False
|
||||
|
||||
def draw_room_on_map(self, room, max_distance):
|
||||
self.draw(room)
|
||||
self.draw_exits(room)
|
||||
|
||||
if max_distance == 0:
|
||||
return
|
||||
|
||||
# Check if the caller has access to the room in question. If not, don't draw it.
|
||||
# Additionally, if the name of the exit is not ordinal but an alias of it is, use that.
|
||||
for ex in [x for x in room.exits if x.access(self.caller, "traverse")]:
|
||||
ex_name = self.exit_name_as_ordinal(ex)
|
||||
if not ex_name or ex_name in ['up', 'down']:
|
||||
continue
|
||||
if self.has_drawn(ex.destination):
|
||||
continue
|
||||
|
||||
self.update_pos(room, ex_name.lower())
|
||||
self.draw_room_on_map(ex.destination, max_distance - 1)
|
||||
|
||||
def draw_exits(self, room):
|
||||
x, y = self.curX, self.curY
|
||||
for ex in room.exits:
|
||||
ex_name = self.exit_name_as_ordinal(ex)
|
||||
|
||||
if not ex_name:
|
||||
continue
|
||||
|
||||
ex_character = _COMPASS_DIRECTIONS[ex_name][2]
|
||||
delta_x = int(_COMPASS_DIRECTIONS[ex_name][1]/3)
|
||||
delta_y = int(_COMPASS_DIRECTIONS[ex_name][0]/3)
|
||||
|
||||
# Make modifications if the exit has BOTH up and down exits
|
||||
if ex_name == 'up':
|
||||
if 'v' in self.grid[x][y]:
|
||||
self.render_room(room, x, y, p1='^', p2='v')
|
||||
else:
|
||||
self.render_room(room, x, y, here='^')
|
||||
elif ex_name == 'down':
|
||||
if '^' in self.grid[x][y]:
|
||||
self.render_room(room, x, y, p1='^', p2='v')
|
||||
else:
|
||||
self.render_room(room, x, y, here='v')
|
||||
else:
|
||||
self.grid[x + delta_x][y + delta_y] = ex_character
|
||||
|
||||
def draw(self, room):
|
||||
# draw initial caller location on map first!
|
||||
if room == self.location:
|
||||
self.start_loc_on_grid(room)
|
||||
self.has_mapped[room] = [self.curX, self.curY]
|
||||
else:
|
||||
# map all other rooms
|
||||
self.has_mapped[room] = [self.curX, self.curY]
|
||||
self.render_room(room, self.curX, self.curY)
|
||||
|
||||
def render_room(self, room, x, y, p1='[', p2=']', here=None):
|
||||
# Note: This is where you would set colors, symbols etc.
|
||||
# Render the room
|
||||
you = list("[ ]")
|
||||
|
||||
you[0] = f"{p1}|n"
|
||||
you[1] = f"{here if here else you[1]}"
|
||||
if room == self.caller.location:
|
||||
you[1] = '|[x|co|n' # Highlight the location you are currently in
|
||||
you[2] = f"{p2}|n"
|
||||
|
||||
self.grid[x][y] = "".join(you)
|
||||
|
||||
def start_loc_on_grid(self, room):
|
||||
x = int((self.max_width * 0.6 - 1) / 2)
|
||||
y = int((self.max_length - 1) / 2)
|
||||
|
||||
self.render_room(room, x, y)
|
||||
self.curX, self.curY = x, y
|
||||
|
||||
def show_map(self, debug=False):
|
||||
map_string = ""
|
||||
self.grid = self.create_grid()
|
||||
self.draw_room_on_map(self.location, self.size)
|
||||
|
||||
for row in self.grid:
|
||||
map_row = "".join(row)
|
||||
if map_row.strip() != "":
|
||||
map_string += f"{map_row}\n"
|
||||
|
||||
elapsed = time.time() - self.start_time
|
||||
if debug:
|
||||
map_string += f"\nTook {elapsed}ms to render the map.\n"
|
||||
|
||||
return "%s" % map_string
|
||||
|
||||
|
||||
class CmdMap(MuxCommand):
|
||||
"""
|
||||
Check the local map around you.
|
||||
|
||||
Usage: map (optional size)
|
||||
"""
|
||||
key = "map"
|
||||
|
||||
def func(self):
|
||||
size = _BASIC_MAP_SIZE
|
||||
if self.args.isnumeric():
|
||||
size = int(self.args)
|
||||
|
||||
# You can run show_map(debug=True) to see how long it takes.
|
||||
map_here = Map(self.caller, size=size).show_map()
|
||||
self.caller.msg((map_here, {"type": "map"}))
|
||||
|
||||
|
||||
# CmdSet for easily install all commands
|
||||
class BasicMapCmdSet(CmdSet):
|
||||
"""
|
||||
The map command.
|
||||
"""
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdMap)
|
||||
Loading…
Add table
Add a link
Reference in a new issue