mirror of
https://github.com/evennia/evennia.git
synced 2026-03-21 23:36:30 +01:00
Added a wilderness area contrib
This commit is contained in:
parent
a9f08760cc
commit
bd27a7eabc
2 changed files with 841 additions and 0 deletions
|
|
@ -298,4 +298,122 @@ class TestBarter(CommandTest):
|
|||
self.call(barter.CmdFinish(), ": Ending.", "You say, \"Ending.\"\n [You aborted trade. No deal was made.]")
|
||||
|
||||
|
||||
from evennia.contrib import wilderness
|
||||
from evennia import DefaultCharacter
|
||||
|
||||
|
||||
class TestWilderness(EvenniaTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestWilderness, self).setUp()
|
||||
self.char1 = create_object(DefaultCharacter, key="char1")
|
||||
self.char2 = create_object(DefaultCharacter, key="char2")
|
||||
|
||||
def get_wilderness_script(self, name="default"):
|
||||
w = wilderness.WildernessScript.objects.get("default")
|
||||
return w
|
||||
|
||||
def test_create_wilderness_default_name(self):
|
||||
wilderness.create_wilderness()
|
||||
w = self.get_wilderness_script()
|
||||
self.assertIsNotNone(w)
|
||||
|
||||
def test_create_wilderness_custom_name(self):
|
||||
name = "customname"
|
||||
wilderness.create_wilderness(name)
|
||||
w = self.get_wilderness_script(name)
|
||||
self.assertIsNotNone(w)
|
||||
|
||||
def test_enter_wilderness(self):
|
||||
wilderness.create_wilderness()
|
||||
wilderness.enter_wilderness(self.char1)
|
||||
self.assertIsInstance(self.char1.location, wilderness.WildernessRoom)
|
||||
w = self.get_wilderness_script()
|
||||
self.assertEquals(w.db.itemlocations[self.char1], (0, 0))
|
||||
|
||||
def test_enter_wilderness_custom_location(self):
|
||||
wilderness.create_wilderness()
|
||||
wilderness.enter_wilderness(self.char1, location=(1, 2))
|
||||
self.assertIsInstance(self.char1.location, wilderness.WildernessRoom)
|
||||
w = self.get_wilderness_script()
|
||||
self.assertEquals(w.db.itemlocations[self.char1], (1, 2))
|
||||
|
||||
def test_enter_wilderness_custom_name(self):
|
||||
name = "customnname"
|
||||
wilderness.create_wilderness(name)
|
||||
wilderness.enter_wilderness(self.char1, name=name)
|
||||
self.assertIsInstance(self.char1.location, wilderness.WildernessRoom)
|
||||
|
||||
def test_wilderness_correct_exits(self):
|
||||
wilderness.create_wilderness()
|
||||
wilderness.enter_wilderness(self.char1)
|
||||
|
||||
# By default we enter at a corner (0, 0), so only a few exits should
|
||||
# be visible / traversable
|
||||
exits = [i for i in self.char1.location.contents
|
||||
if i.destination and (
|
||||
i.access(self.char1, "view") or
|
||||
i.access(self.char1, "traverse"))]
|
||||
|
||||
self.assertEquals(len(exits), 3)
|
||||
exitsok = ["north", "northeast", "east"]
|
||||
for exit in exitsok:
|
||||
self.assertTrue(any([e for e in exits if e.key == exit]))
|
||||
|
||||
# If we move to another location not on an edge, then all directions
|
||||
# should be visible / traversable
|
||||
wilderness.enter_wilderness(self.char1, location=(1, 1))
|
||||
exits = [i for i in self.char1.location.contents
|
||||
if i.destination and (
|
||||
i.access(self.char1, "view") or
|
||||
i.access(self.char1, "traverse"))]
|
||||
self.assertEquals(len(exits), 8)
|
||||
exitsok = ["north", "northeast", "east", "southeast", "south",
|
||||
"southwest", "west", "northwest"]
|
||||
for exit in exitsok:
|
||||
self.assertTrue(any([e for e in exits if e.key == exit]))
|
||||
|
||||
def test_room_creation(self):
|
||||
# Pretend that both char1 and char2 are connected...
|
||||
self.char1.sessions.add(1)
|
||||
self.char2.sessions.add(1)
|
||||
self.assertTrue(self.char1.has_player)
|
||||
self.assertTrue(self.char2.has_player)
|
||||
|
||||
wilderness.create_wilderness()
|
||||
w = self.get_wilderness_script()
|
||||
|
||||
# We should have no unused room after moving the first player in.
|
||||
self.assertEquals(len(w.db.unused_rooms), 0)
|
||||
w.move_obj(self.char1, (0, 0))
|
||||
self.assertEquals(len(w.db.unused_rooms), 0)
|
||||
|
||||
# And also no unused room after moving the second one in.
|
||||
w.move_obj(self.char2, (1, 1))
|
||||
self.assertEquals(len(w.db.unused_rooms), 0)
|
||||
|
||||
# But if char2 moves into char1's room, we should have one unused room
|
||||
# Which should be char2's old room that got created.
|
||||
w.move_obj(self.char2, (0, 0))
|
||||
self.assertEquals(len(w.db.unused_rooms), 1)
|
||||
self.assertEquals(self.char1.location, self.char2.location)
|
||||
|
||||
# And if char2 moves back out, that unused room should be put back to
|
||||
# use again.
|
||||
w.move_obj(self.char2, (1, 1))
|
||||
self.assertNotEquals(self.char1.location, self.char2.location)
|
||||
self.assertEquals(len(w.db.unused_rooms), 0)
|
||||
|
||||
def test_get_new_location(self):
|
||||
loc = (1, 1)
|
||||
directions = {"north": (1, 2),
|
||||
"northeast": (2, 2),
|
||||
"east": (2, 1),
|
||||
"southeast": (2, 0),
|
||||
"south": (1, 0),
|
||||
"southwest": (0, 0),
|
||||
"west": (0, 1),
|
||||
"northwest": (0, 2)}
|
||||
for direction, correct_loc in directions.iteritems():
|
||||
new_loc = wilderness.get_new_location(loc, direction)
|
||||
self.assertEquals(new_loc, correct_loc, direction)
|
||||
|
|
|
|||
723
evennia/contrib/wilderness.py
Normal file
723
evennia/contrib/wilderness.py
Normal file
|
|
@ -0,0 +1,723 @@
|
|||
"""
|
||||
Wilderness system
|
||||
|
||||
Evennia contrib - titeuf87 2017
|
||||
|
||||
This contrib provides a wilderness map. This is an area that can be huge where
|
||||
the rooms are mostly similar, except for some small cosmetic changes like the
|
||||
room name.
|
||||
|
||||
Usage:
|
||||
|
||||
This contrib does not provide any commands. Instead the @py command can be
|
||||
used.
|
||||
|
||||
A wilderness map needs to created first. There can be different maps, all
|
||||
with their own name. If no name is provided, then a default one is used.
|
||||
|
||||
@py from evennia.contrib import wilderness; wilderness.create_wilderness()
|
||||
|
||||
Once created, it is possible to move into that wilderness map:
|
||||
@py from evennia.contrib import wilderness; wilderness.enter_wilderness(me)
|
||||
|
||||
All coordinates used by the wilderness map are in the format of (x, y)
|
||||
tuples. x goes from left to right and y goes from bottom to top. So x = 0
|
||||
is on the left and y = 0 is at the bottom of the map.
|
||||
|
||||
|
||||
Customisation:
|
||||
|
||||
The defaults, while useable, are meant to be customised. When creating a
|
||||
new wilderness map it is possible to give a "map provider": this is a
|
||||
python object that is smart enough to create the map.
|
||||
|
||||
The default provider, WildernessMapProvider, just creates a grid area that
|
||||
is unlimited in size.
|
||||
This WildernessMapProvider can be subclassed to create more interesting
|
||||
maps and also to customize the room/exit typeclass used.
|
||||
|
||||
There is also no command that allows players to enter the wilderness. This
|
||||
still needs to be added: it can be a command or an exit, depending on your
|
||||
needs.
|
||||
|
||||
Customisation example:
|
||||
|
||||
To give an example of how to customize, we will create a very simple (and
|
||||
small) wilderness map that is shaped like a pyramid. The map will be
|
||||
provided as a string: a "." symbol is a location we can walk on.
|
||||
|
||||
Let's create a file world/pyramid.py:
|
||||
|
||||
```python
|
||||
map_str = \"\"\"
|
||||
.
|
||||
...
|
||||
.....
|
||||
.......
|
||||
\"\"\"
|
||||
|
||||
from evennia.contrib import wilderness
|
||||
|
||||
class PyramidMapProvider(wilderness.WildernessMapProvider):
|
||||
|
||||
def is_valid_coordinates(self, wilderness, coordinates):
|
||||
x, y = coordinates
|
||||
try:
|
||||
lines = map_str.split("\n")
|
||||
# The reverse is needed because otherwise the pyramid will be
|
||||
# upside down
|
||||
lines.reverse()
|
||||
line = lines[y]
|
||||
column = line[x]
|
||||
return column == "."
|
||||
except IndexError:
|
||||
return False
|
||||
|
||||
def get_location_name(self, coordinates):
|
||||
x, y = coordinates
|
||||
if y == 3:
|
||||
return "Atop the pyramid."
|
||||
else:
|
||||
return "Inside a pyramid."
|
||||
```
|
||||
|
||||
Now we can use our new pyramid-shaped wilderness map. From inside Evennia:
|
||||
|
||||
```
|
||||
@py from world import pyramid as p; p.wilderness.create_wilderness(mapprovider=p.PyramidMapProvider())
|
||||
|
||||
@py from evennia.contrib import wilderness; wilderness.enter_wilderness(me, coordinates=(4, 1))
|
||||
```
|
||||
|
||||
Implementation details:
|
||||
|
||||
When a character moves into the wilderness, they get their own room. If
|
||||
they move, instead of moving the character, the room changes to match the
|
||||
new coordinates.
|
||||
If a character meets another character in the wilderness, then their room
|
||||
merges. When one of the character leaves again, they each get their own
|
||||
separate rooms.
|
||||
Rooms are created as needed. Unneeded rooms are stored away to avoid the
|
||||
overhead cost of creating new rooms again in the future.
|
||||
|
||||
"""
|
||||
|
||||
from evennia import DefaultRoom, DefaultExit, DefaultScript
|
||||
from evennia import create_object, create_script
|
||||
from evennia.utils import inherits_from
|
||||
|
||||
|
||||
def create_wilderness(name="default", mapprovider=None):
|
||||
"""
|
||||
Creates a new wilderness map. Does nothing if a wilderness map already
|
||||
exists with the same name.
|
||||
|
||||
Args:
|
||||
name (str, optional): the name to use for that wilderness map
|
||||
mapprovider (WildernessMap instance, optional): an instance of a
|
||||
WildernessMap class (or subclass) that will be used to provide the
|
||||
layout of this wilderness map. If none is provided, the default
|
||||
infinite grid map will be used.
|
||||
|
||||
"""
|
||||
if WildernessScript.objects.filter(db_key=name).exists():
|
||||
# Don't create two wildernesses with the same name
|
||||
return
|
||||
|
||||
if not mapprovider:
|
||||
mapprovider = WildernessMapProvider()
|
||||
script = create_script(WildernessScript, key=name)
|
||||
script.db.mapprovider = mapprovider
|
||||
|
||||
|
||||
def enter_wilderness(obj, coordinates=(0, 0), name="default"):
|
||||
"""
|
||||
Moves obj into the wilderness. The wilderness needs to exist first and the
|
||||
provided coordinates needs to be valid inside that wilderness.
|
||||
|
||||
Args:
|
||||
obj (object): the object to move into the wilderness
|
||||
coordinates (tuple), optional): the coordinates to move obj to into
|
||||
the wilderness. If not provided, defaults (0, 0)
|
||||
name (str, optional): name of the wilderness map, if not using the
|
||||
default one
|
||||
|
||||
Returns:
|
||||
bool: True if obj succesfully moved into the wilderness.
|
||||
"""
|
||||
if not WildernessScript.objects.filter(db_key=name).exists():
|
||||
return False
|
||||
|
||||
script = WildernessScript.objects.get(db_key=name)
|
||||
if script.is_valid_coordinates(coordinates):
|
||||
script.move_obj(obj, coordinates)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_new_coordinates(coordinates, direction):
|
||||
"""
|
||||
Returns the coordinates of direction applied to the provided coordinates.
|
||||
|
||||
Args:
|
||||
coordinates: tuple of (x, y)
|
||||
direction: a direction string (like "northeast")
|
||||
|
||||
Returns:
|
||||
tuple: tuple of (x, y) coordinates
|
||||
"""
|
||||
x, y = coordinates
|
||||
|
||||
if direction in ("north", "northwest", "northeast"):
|
||||
y += 1
|
||||
if direction in ("south", "southwest", "southeast"):
|
||||
y -= 1
|
||||
if direction in ("northwest", "west", "southwest"):
|
||||
x -= 1
|
||||
if direction in ("northeast", "east", "southeast"):
|
||||
x += 1
|
||||
|
||||
return (x, y)
|
||||
|
||||
|
||||
class WildernessRoom(DefaultRoom):
|
||||
"""
|
||||
This is a single room inside the wilderness. This room provides a "view"
|
||||
into the wilderness map. When a player moves around, instead of going to
|
||||
another room as with traditional rooms, they stay in the same room but the
|
||||
room itself changes to display another area of the wilderness.
|
||||
"""
|
||||
|
||||
@property
|
||||
def wilderness(self):
|
||||
"""
|
||||
Shortcut property to the wilderness script this room belongs to.
|
||||
|
||||
Returns:
|
||||
WildernessScript: the WildernessScript attached to this room
|
||||
"""
|
||||
return self.ndb.wildernessscript
|
||||
|
||||
@property
|
||||
def location_name(self):
|
||||
"""
|
||||
Returns the name of the wilderness at this room's coordinates.
|
||||
|
||||
Returns:
|
||||
name (str)
|
||||
"""
|
||||
return self.wilderness.mapprovider.get_location_name(
|
||||
self.coordinates)
|
||||
|
||||
@property
|
||||
def coordinates(self):
|
||||
"""
|
||||
Returns the coordinates of this room into the wilderness.
|
||||
|
||||
Returns:
|
||||
tuple: (x, y) coordinates of where this room is inside the
|
||||
wilderness.
|
||||
"""
|
||||
return self.ndb.active_coordinates
|
||||
|
||||
def at_object_receive(self, moved_obj, source_location):
|
||||
"""
|
||||
Called after an object has been moved into this object. This is a
|
||||
default Evennia hook.
|
||||
|
||||
Args:
|
||||
moved_obj (Object): The object moved into this one.
|
||||
source_location (Object): Where `moved_obj` came from.
|
||||
"""
|
||||
if moved_obj.destination and moved_obj.destination == moved_obj.location:
|
||||
# Ignore exits looping back to themselves: those are the regular
|
||||
# n, ne, ... exits.
|
||||
return
|
||||
|
||||
itemcoords = self.wilderness.db.itemcoordinates
|
||||
if moved_obj in itemcoords:
|
||||
# This object was already in the wilderness. We need to make sure
|
||||
# it goes to the correct room it belongs to.
|
||||
# Otherwise the following issue can come up:
|
||||
# 1) Player 1 and Player 2 share a room
|
||||
# 2) Player 1 disconnects
|
||||
# 3) Player 2 moves around
|
||||
# 4) Player 1 reconnects
|
||||
# Player 1 will end up in player 2's room, which has the wrong
|
||||
# coordinates
|
||||
|
||||
coordinates = itemcoords[moved_obj]
|
||||
# Setting the location to None is important here so that we always
|
||||
# get a "fresh" room
|
||||
moved_obj.location = None
|
||||
self.wilderness.move_obj(moved_obj, coordinates)
|
||||
else:
|
||||
# This object wasn't in the wilderness yet. Let's add it.
|
||||
itemcoords[moved_obj] = self.coordinates
|
||||
|
||||
def at_object_leave(self, moved_obj, target_location):
|
||||
"""
|
||||
Called just before an object leaves from inside this object. This is a
|
||||
default Evennia hook.
|
||||
|
||||
Args:
|
||||
moved_obj (Object): The object leaving
|
||||
target_location (Object): Where `moved_obj` is going.
|
||||
|
||||
"""
|
||||
self.wilderness.at_after_object_leave(moved_obj)
|
||||
|
||||
def set_active_coordinates(self, new_coordinates):
|
||||
"""
|
||||
Changes this room to show the wilderness map from other coordinates.
|
||||
|
||||
Args:
|
||||
new_coordinates (tuple): coordinates as tuple of (x, y)
|
||||
"""
|
||||
# Remove the reference for the old coordinates...
|
||||
rooms = self.wilderness.db.rooms
|
||||
del rooms[self.coordinates]
|
||||
# ...and add it for the new coordinates.
|
||||
self.ndb.active_coordinates = new_coordinates
|
||||
rooms[self.coordinates] = self
|
||||
|
||||
# Every obj inside this room will get its location set to None
|
||||
for item in self.contents:
|
||||
if not item.destination or item.destination != item.location:
|
||||
item.location = None
|
||||
# And every obj matching the new coordinates will get its location set
|
||||
# to this room
|
||||
for item in self.wilderness.get_objs_at_coordinates(new_coordinates):
|
||||
item.location = self
|
||||
|
||||
# Fix the lockfuncs for the exit so we can't go where we're not
|
||||
# supposed to go
|
||||
for exit in self.exits:
|
||||
if exit.destination != self:
|
||||
continue
|
||||
x, y = get_new_coordinates(new_coordinates, exit.key)
|
||||
valid = self.wilderness.is_valid_coordinates((x, y))
|
||||
|
||||
if valid:
|
||||
exit.locks.add("traverse:true();view:true()")
|
||||
else:
|
||||
exit.locks.add("traverse:false();view:false()")
|
||||
|
||||
def get_display_name(self, looker, **kwargs):
|
||||
"""
|
||||
Displays the name of the object in a viewer-aware manner.
|
||||
|
||||
Args:
|
||||
looker (TypedObject): The object or player that is looking
|
||||
at/getting inforamtion for this object.
|
||||
|
||||
Returns:
|
||||
name (str): A string containing the name of the object,
|
||||
including the DBREF if this user is privileged to control
|
||||
said object and also its coordinates into the wilderness map.
|
||||
|
||||
Notes:
|
||||
This function could be extended to change how object names
|
||||
appear to users in character, but be wary. This function
|
||||
does not change an object's keys or aliases when
|
||||
searching, and is expected to produce something useful for
|
||||
builders.
|
||||
"""
|
||||
if self.locks.check_lockstring(looker, "perm(Builders)"):
|
||||
name = "{}(#{})".format(self.location_name, self.id)
|
||||
else:
|
||||
name = self.location_name
|
||||
|
||||
name += " {0}".format(self.coordinates)
|
||||
return name
|
||||
|
||||
|
||||
class WildernessExit(DefaultExit):
|
||||
"""
|
||||
This is an Exit object used inside a WildernessRoom. Instead of changing
|
||||
the location of an Object traversing through it (like a traditional exit
|
||||
would do) it changes the coordinates of that traversing Object inside
|
||||
the wilderness map.
|
||||
"""
|
||||
|
||||
@property
|
||||
def wilderness(self):
|
||||
"""
|
||||
Shortcut property to the wilderness script.
|
||||
|
||||
Returns:
|
||||
WildernessScript: the WildernessScript attached to this exit's room
|
||||
"""
|
||||
return self.location.wilderness
|
||||
|
||||
@property
|
||||
def mapprovider(self):
|
||||
"""
|
||||
Shortcut property to the map provider.
|
||||
|
||||
Returns:
|
||||
MapProvider object: the mapprovider object used with this
|
||||
wilderness map.
|
||||
"""
|
||||
return self.wilderness.mapprovider
|
||||
|
||||
def at_traverse_coordinates(self, traversing_object, current_coordinates,
|
||||
new_coordinates):
|
||||
"""
|
||||
Called when an object wants to travel from one place inside the
|
||||
wilderness to another place inside the wilderness.
|
||||
|
||||
If this returns True, then the traversing can happen. Otherwise it will
|
||||
be blocked.
|
||||
|
||||
This method is similar how the `at_traverse` works on normal exits.
|
||||
|
||||
Args:
|
||||
traversing_object (Object): The object doing the travelling.
|
||||
current_coordinates (tuple): (x, y) coordinates where
|
||||
`traversing_object` currently is.
|
||||
new_coordinates (tuple): (x, y) coordinates of where
|
||||
`traversing_object` wants to travel to.
|
||||
|
||||
Returns:
|
||||
bool: True if traversing_object is allowed to traverse
|
||||
"""
|
||||
return True
|
||||
|
||||
def at_traverse(self, traversing_object, target_location):
|
||||
"""
|
||||
This implements the actual traversal. The traverse lock has
|
||||
already been checked (in the Exit command) at this point.
|
||||
|
||||
Args:
|
||||
traversing_object (Object): Object traversing us.
|
||||
target_location (Object): Where target is going.
|
||||
|
||||
Returns:
|
||||
bool: True if the traverse is allowed to happen
|
||||
|
||||
"""
|
||||
itemcoordinates = self.location.wilderness.db.itemcoordinates
|
||||
|
||||
current_coordinates = itemcoordinates[traversing_object]
|
||||
new_coordinates = get_new_coordinates(current_coordinates, self.key)
|
||||
|
||||
if not self.at_traverse_coordinates(traversing_object,
|
||||
current_coordinates,
|
||||
new_coordinates):
|
||||
return False
|
||||
|
||||
if not traversing_object.at_before_move(None):
|
||||
return False
|
||||
traversing_object.location.msg_contents("{} leaves to {}".format(
|
||||
traversing_object.key, new_coordinates),
|
||||
exclude=[traversing_object])
|
||||
|
||||
self.location.wilderness.move_obj(traversing_object, new_coordinates)
|
||||
|
||||
traversing_object.location.msg_contents("{} arrives from {}".format(
|
||||
traversing_object.key, current_coordinates),
|
||||
exclude=[traversing_object])
|
||||
|
||||
traversing_object.at_after_move(None)
|
||||
return True
|
||||
|
||||
|
||||
class WildernessMapProvider(object):
|
||||
"""
|
||||
Default Wilderness Map provider.
|
||||
|
||||
This is a simple provider that just creates an infite large grid area.
|
||||
"""
|
||||
room_typeclass = WildernessRoom
|
||||
exit_typeclass = WildernessExit
|
||||
|
||||
def is_valid_coordinates(self, wilderness, coordinates):
|
||||
"""Returns True if coordinates is valid and can be walked to.
|
||||
|
||||
Args:
|
||||
wilderness: the wilderness script
|
||||
coordinates (tuple): the coordinates to check as (x, y) tuple.
|
||||
|
||||
Returns:
|
||||
bool: True if the coordinates are valid
|
||||
"""
|
||||
x, y = coordinates
|
||||
if x < 0:
|
||||
return False
|
||||
if y < 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_location_name(self, coordinates):
|
||||
"""
|
||||
Returns a name for the position at coordinates.
|
||||
|
||||
Args:
|
||||
coordinates (tuple): the coordinates as (x, y) tuple.
|
||||
|
||||
Returns:
|
||||
name (str)
|
||||
"""
|
||||
return "The wilderness"
|
||||
|
||||
|
||||
class WildernessScript(DefaultScript):
|
||||
"""
|
||||
This is the main "handler" for the wilderness system: inside here the
|
||||
coordinates of every item currently inside the wilderness is stored. This
|
||||
script is responsible for creating rooms as needed and storing rooms away
|
||||
into storage when they are not needed anymore.
|
||||
"""
|
||||
|
||||
def at_script_creation(self):
|
||||
"""
|
||||
Only called once, when the script is created. This is a default Evennia
|
||||
hook.
|
||||
"""
|
||||
self.persistent = True
|
||||
|
||||
# Store the coordinates of every item that is inside the wilderness
|
||||
# Key: object, Value: (x, y)
|
||||
self.db.itemcoordinates = {}
|
||||
|
||||
# Store the rooms that are used as views into the wilderness
|
||||
# Key: (x, y), Value: room object
|
||||
self.db.rooms = {}
|
||||
|
||||
# Created rooms that are not needed anymore are stored there. This
|
||||
# allows quick retrieval if a new room is needed without having to
|
||||
# create it.
|
||||
self.db.unused_rooms = []
|
||||
|
||||
@property
|
||||
def mapprovider(self):
|
||||
"""
|
||||
Shortcut property to the map provider.
|
||||
|
||||
Returns:
|
||||
MapProvider: the mapprovider used with this wilderness
|
||||
"""
|
||||
return self.db.mapprovider
|
||||
|
||||
@property
|
||||
def itemcoordinates(self):
|
||||
"""
|
||||
Returns a dictionary with the coordinates of every item inside this
|
||||
wilderness map. The key is the item, the value are the coordinates as
|
||||
(x, y) tuple.
|
||||
|
||||
Returns:
|
||||
{item: coordinates}
|
||||
"""
|
||||
return self.db.itemcoordinates
|
||||
|
||||
def at_start(self):
|
||||
"""
|
||||
Called when the script is started and also after server reloads.
|
||||
"""
|
||||
for coordinates, room in self.db.rooms.items():
|
||||
room.ndb.wildernessscript = self
|
||||
room.ndb.active_coordinates = coordinates
|
||||
for item in self.db.itemcoordinates.keys():
|
||||
item.ndb.wilderness = self
|
||||
|
||||
def is_valid_coordinates(self, coordinates):
|
||||
"""
|
||||
Returns True if coordinates are valid (and can be travelled to).
|
||||
Otherwise returns False
|
||||
|
||||
Args:
|
||||
coordinates (tuple): coordinates as (x, y) tuple
|
||||
|
||||
Returns:
|
||||
bool: True if the coordinates are valid
|
||||
"""
|
||||
return self.mapprovider.is_valid_coordinates(self, coordinates)
|
||||
|
||||
def get_obj_coordinates(self, obj):
|
||||
"""
|
||||
Returns the coordinates of obj in the wilderness.
|
||||
|
||||
Returns (x, y)
|
||||
|
||||
Args:
|
||||
obj (object): an object inside the wilderness
|
||||
|
||||
Returns:
|
||||
tuple: (x, y) tuple of where obj is located
|
||||
"""
|
||||
return self.itemcoordinates[obj]
|
||||
|
||||
def get_objs_at_coordinates(self, coordinates):
|
||||
"""
|
||||
Returns a list of every object at certain coordinates.
|
||||
|
||||
Imeplementation detail: this uses a naive iteration through every
|
||||
object inside the wilderness which could cause slow downs when there
|
||||
are a lot of objects in the map.
|
||||
|
||||
Args:
|
||||
coordinates (tuple): a coordinate tuple like (x, y)
|
||||
|
||||
Returns:
|
||||
[Object, ]: list of Objects at coordinates
|
||||
"""
|
||||
result = []
|
||||
for item, item_coordinates in self.itemcoordinates.items():
|
||||
if coordinates == item_coordinates:
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
def move_obj(self, obj, new_coordinates):
|
||||
"""
|
||||
Moves obj to new coordinates in this wilderness.
|
||||
|
||||
Args:
|
||||
obj (object): the object to move
|
||||
new_coordinates (tuple): tuple of (x, y) where to move obj to.
|
||||
"""
|
||||
# Update the position of this obj in the wilderness
|
||||
self.itemcoordinates[obj] = new_coordinates
|
||||
old_room = obj.location
|
||||
|
||||
# Remove the obj's location. This is needed so that the object does not
|
||||
# appear in its old room should that room be deleted.
|
||||
obj.location = None
|
||||
|
||||
try:
|
||||
# See if we already have a room for that location
|
||||
room = self.db.rooms[new_coordinates]
|
||||
# There is. Try to destroy the old_room if it is not needed anymore
|
||||
self._destroy_room(old_room)
|
||||
except KeyError:
|
||||
# There is no room yet at new_location
|
||||
if (old_room and not inherits_from(old_room, WildernessRoom)) or \
|
||||
(not old_room):
|
||||
# Obj doesn't originally come from a wilderness room.
|
||||
# We'll create a new one then.
|
||||
room = self._create_room(new_coordinates, obj)
|
||||
else:
|
||||
# Obj does come from another wilderness room
|
||||
create_new_room = False
|
||||
|
||||
if old_room.wilderness != self:
|
||||
# ... but that other wilderness room belongs to another
|
||||
# wilderness map
|
||||
create_new_room = True
|
||||
old_room.wilderness.at_after_object_leave(obj)
|
||||
else:
|
||||
for item in old_room.contents:
|
||||
if item.has_player:
|
||||
# There is still a player in the old room.
|
||||
# Let's create a new room and not touch that old
|
||||
# room.
|
||||
create_new_room = True
|
||||
break
|
||||
|
||||
if create_new_room:
|
||||
# Create a new room to hold obj, not touching any obj's in
|
||||
# the old room
|
||||
room = self._create_room(new_coordinates, obj)
|
||||
else:
|
||||
# The old_room is empty: we are just going to reuse that
|
||||
# room instead of creating a new one
|
||||
room = old_room
|
||||
|
||||
room.set_active_coordinates(new_coordinates)
|
||||
obj.location = room
|
||||
obj.ndb.wilderness = self
|
||||
|
||||
def _create_room(self, coordinates, report_to):
|
||||
"""
|
||||
Gets a new WildernessRoom to be used for the provided coordinates.
|
||||
|
||||
It first tries to retrieve a room out of storage. If there are no rooms
|
||||
left a new one will be created.
|
||||
|
||||
Args:
|
||||
coordinates (tuple): coordinate tuple of (x, y)
|
||||
report_to (object): the obj to return error messages to
|
||||
"""
|
||||
if self.db.unused_rooms:
|
||||
# There is still unused rooms stored in storage, let's get one of
|
||||
# those
|
||||
room = self.db.unused_rooms.pop()
|
||||
else:
|
||||
# No more unused rooms...time to make a new one.
|
||||
|
||||
# First, create the room
|
||||
room = create_object(typeclass=self.mapprovider.room_typeclass,
|
||||
key="Wilderness",
|
||||
report_to=report_to)
|
||||
|
||||
# Then the exits
|
||||
exits = [("north", "n"),
|
||||
("northeast", "ne"),
|
||||
("east", "e"),
|
||||
("southeast", "se"),
|
||||
("south", "s"),
|
||||
("southwest", "sw"),
|
||||
("west", "w"),
|
||||
("northwest", "nw")]
|
||||
for key, alias in exits:
|
||||
create_object(typeclass=self.mapprovider.exit_typeclass,
|
||||
key=key,
|
||||
aliases=[alias],
|
||||
location=room,
|
||||
destination=room,
|
||||
report_to=report_to)
|
||||
|
||||
room.ndb.active_coordinates = coordinates
|
||||
room.ndb.wildernessscript = self
|
||||
self.db.rooms[coordinates] = room
|
||||
|
||||
return room
|
||||
|
||||
def _destroy_room(self, room):
|
||||
"""
|
||||
Moves a room back to storage. If room is not a WildernessRoom or there
|
||||
is a player inside the room, then this does nothing.
|
||||
|
||||
Args:
|
||||
room (WildernessRoom): the room to put in storage
|
||||
"""
|
||||
if not room or not inherits_from(room, WildernessRoom):
|
||||
return
|
||||
|
||||
for item in room.contents:
|
||||
if item.has_player:
|
||||
# There is still a character in that room. We can't get rid of
|
||||
# it just yet
|
||||
break
|
||||
else:
|
||||
# No characters left in the room.
|
||||
|
||||
# Clear the location of every obj in that room first
|
||||
for item in room.contents:
|
||||
if item.destination and item.destination == room:
|
||||
# Ignore the exits, they stay in the room
|
||||
continue
|
||||
item.location = None
|
||||
|
||||
# Then delete its reference
|
||||
del self.db.rooms[room.ndb.active_coordinates]
|
||||
# And finally put this room away in storage
|
||||
self.db.unused_rooms.append(room)
|
||||
|
||||
def at_after_object_leave(self, obj):
|
||||
"""
|
||||
Called after an object left this wilderness map. Used for cleaning up.
|
||||
|
||||
Args:
|
||||
obj (object): the object that left
|
||||
"""
|
||||
# Remove that obj from the wilderness's coordinates dict
|
||||
loc = self.db.itemcoordinates[obj]
|
||||
del self.db.itemcoordinates[obj]
|
||||
|
||||
# And see if we can put that room away into storage.
|
||||
room = self.db.rooms[loc]
|
||||
self._destroy_room(room)
|
||||
Loading…
Add table
Add a link
Reference in a new issue