Added loci, and made dialog processing internal to the engine.
This commit is contained in:
parent
5010042430
commit
9cda61a895
11 changed files with 1860 additions and 1269 deletions
953
gamebase.py
953
gamebase.py
File diff suppressed because it is too large
Load diff
|
@ -63,30 +63,41 @@ class NoOpEvent(GameEvent):
|
||||||
|
|
||||||
class GoEvent(GameEvent):
|
class GoEvent(GameEvent):
|
||||||
|
|
||||||
def __init__(self, actor, x, y):
|
def __init__(self, actor, path, speed, action = None, locus = None, timeTaken = 0):
|
||||||
super(GoEvent, self).__init__('go')
|
super(GoEvent, self).__init__('go')
|
||||||
self.actor = actor
|
self.actor = actor
|
||||||
self.x = x
|
self.path = path
|
||||||
self.y = y
|
self.speed = speed
|
||||||
|
self.action = action
|
||||||
|
self.locus = locus
|
||||||
|
self.timeTaken = timeTaken
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pos(self):
|
||||||
|
return self.path[0]
|
||||||
|
|
||||||
class ArriveEvent(GameEvent):
|
class ArriveEvent(GameEvent):
|
||||||
|
|
||||||
def __init__(self, actor, x, y, t):
|
def __init__(self, actor, pos, speed, action = None, locus = None, timeTaken = 0):
|
||||||
super(ArriveEvent, self).__init__('arrive')
|
super(ArriveEvent, self).__init__('arrive')
|
||||||
self.actor = actor
|
self.actor = actor
|
||||||
self.x = x
|
self.pos = pos
|
||||||
self.y = y
|
self.speed = speed
|
||||||
self.t = t
|
self.action = action
|
||||||
|
self.locus = locus
|
||||||
|
self.timeTaken = timeTaken
|
||||||
|
|
||||||
class UseEvent(GameEvent):
|
class UseEvent(GameEvent):
|
||||||
def __init__(self, thing, args):
|
def __init__(self, actor, thing, args):
|
||||||
super(UseEvent, self).__init__('use')
|
super(UseEvent, self).__init__('use')
|
||||||
|
self.actor = actor
|
||||||
self.thing = thing
|
self.thing = thing
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
class UseOnEvent(GameEvent):
|
class UseOnEvent(GameEvent):
|
||||||
def __init__(self, item, thing, args):
|
def __init__(self, actor, item, thing, args):
|
||||||
super(UseOnEvent, self).__init__('useon')
|
super(UseOnEvent, self).__init__('useon')
|
||||||
|
self.actor = actor
|
||||||
self.thing = thing # thing can be a coordinate pair?
|
self.thing = thing # thing can be a coordinate pair?
|
||||||
self.item = item
|
self.item = item
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
336
gamelocus.py
Normal file
336
gamelocus.py
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
# gamelocus.py
|
||||||
|
|
||||||
|
import math as _mt
|
||||||
|
|
||||||
|
class Locus(object):
|
||||||
|
"""Abstract base class for locus objects."""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""Base locus constructor."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
"""Is this point within the locus?"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Locus()"
|
||||||
|
|
||||||
|
class PointLocus(Locus):
|
||||||
|
"""A locus that defines a single tile."""
|
||||||
|
|
||||||
|
def __init__(self, x, y, **kwargs):
|
||||||
|
"""Take the coordinates for a point."""
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||||
|
raise ValueError("Item must be a tuple or a list.")
|
||||||
|
return item[0] == self.x and item[1] == self.y
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(((self.x, self.y),)) # just an iterator of the one tile
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "PointLocus({}, {})".format(self.x, self.y)
|
||||||
|
|
||||||
|
class LineLocus(Locus):
|
||||||
|
"""A locus that defines all square tiles that intersect a given line segment."""
|
||||||
|
|
||||||
|
def __init__(self, x1, y1, x2, y2, thick = False, **kwargs):
|
||||||
|
"""Take the coordinates for the two endpoints."""
|
||||||
|
self.x1 = x1
|
||||||
|
self.y1 = y1
|
||||||
|
self.x2 = x2
|
||||||
|
self.y2 = y2
|
||||||
|
self.thick = thick
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||||
|
raise ValueError("Item must be a tuple or a list.")
|
||||||
|
x, y = item
|
||||||
|
# Is it outside the bounding box for this line?
|
||||||
|
if x > max(self.x1, self.x2) or x < min(self.x1, self.x2) or y > max(self.y1, self.y2) or y < min(self.y1, self.y2):
|
||||||
|
return False
|
||||||
|
# Is this line straight up and down, or left and right?
|
||||||
|
elif self.x1 == self.x2 or self.y1 == self.y2:
|
||||||
|
# In this case, the line takes up the whole bounding box, so it must
|
||||||
|
# be true since it passed the first step to get here.
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
slope = (self.y2 - self.y1) / (self.x2 - self.x1)
|
||||||
|
intercept = (self.y1 + 0.5) - (self.x1 + 0.5) * slope
|
||||||
|
f = lambda z: z * slope + intercept
|
||||||
|
if self.thick:
|
||||||
|
if slope > 0:
|
||||||
|
return _mt.ceil(f(x)-1) <= y and _mt.floor(f(x+1)+1) > y
|
||||||
|
else:
|
||||||
|
return _mt.floor(f(x)+1) > y and _mt.ceil(f(x+1)-1) <= y
|
||||||
|
else:
|
||||||
|
if slope > 0:
|
||||||
|
return _mt.floor(f(x)) <= y and _mt.ceil(f(x+1)) > y
|
||||||
|
else:
|
||||||
|
return _mt.ceil(f(x)) > y and _mt.floor(f(x+1)) <= y
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
# avoid infinite slope case
|
||||||
|
if self.x1 == self.x2:
|
||||||
|
miny, maxy = min(self.y1, self.y2), max(self.y1, self.y2) + 1
|
||||||
|
ret = [(self.x1, y) for y in range(miny, maxy)]
|
||||||
|
return iter(ret)
|
||||||
|
# for convenience: it's easy to calculate if it's horizontal too.
|
||||||
|
elif self.y1 == self.y2:
|
||||||
|
minx, maxx = min(self.x1, self.x2), max(self.x1, self.x2) + 1
|
||||||
|
ret = [(x, self.y1) for x in range(minx, maxx)]
|
||||||
|
return iter(ret)
|
||||||
|
else:
|
||||||
|
slope = (self.y2 - self.y1) / (self.x2 - self.x1)
|
||||||
|
intercept = (self.y1 + 0.5) - (self.x1 + 0.5) * slope
|
||||||
|
f = lambda x: x * slope + intercept
|
||||||
|
lx, ly = min((self.x1, self.y1), (self.x2, self.y2))
|
||||||
|
rx, ry = max((self.x1, self.y1), (self.x2, self.y2))
|
||||||
|
ret = []
|
||||||
|
# edge case 1: the first half column
|
||||||
|
if slope > 0:
|
||||||
|
maxy = _mt.ceil(f(lx+1))
|
||||||
|
if self.thick:
|
||||||
|
maxy = _mt.floor(f(lx+1)+1)
|
||||||
|
for y in range(ly, maxy):
|
||||||
|
ret.append((lx, y))
|
||||||
|
else:
|
||||||
|
maxy = _mt.floor(f(lx+1))-1
|
||||||
|
if self.thick:
|
||||||
|
maxy = _mt.ceil(f(lx+1)-2)
|
||||||
|
for y in range(ly, maxy, -1):
|
||||||
|
ret.append((lx, y))
|
||||||
|
# Usual case: the line between the end points
|
||||||
|
for x in range(lx+1, rx):
|
||||||
|
if slope > 0:
|
||||||
|
miny = _mt.floor(f(x))
|
||||||
|
maxy = _mt.ceil(f(x+1))
|
||||||
|
if self.thick:
|
||||||
|
miny = _mt.ceil(f(x)-1)
|
||||||
|
maxy = _mt.floor(f(x+1)+1)
|
||||||
|
for y in range(miny, maxy):
|
||||||
|
ret.append((x, y))
|
||||||
|
else:
|
||||||
|
miny = _mt.ceil(f(x)-1)
|
||||||
|
maxy = _mt.floor(f(x+1))-1
|
||||||
|
if self.thick:
|
||||||
|
miny = _mt.floor(f(x))
|
||||||
|
maxy = _mt.ceil(f(x+1)-2)
|
||||||
|
for y in range(miny, maxy, -1):
|
||||||
|
ret.append((x, y))
|
||||||
|
# edge case 2: the last half column
|
||||||
|
if slope > 0:
|
||||||
|
miny = _mt.floor(f(rx))
|
||||||
|
if self.thick:
|
||||||
|
miny = _mt.ceil(f(rx)-1)
|
||||||
|
for y in range(miny, ry+1):
|
||||||
|
ret.append((rx, y))
|
||||||
|
else:
|
||||||
|
miny = _mt.ceil(f(rx)-1)
|
||||||
|
if self.thick:
|
||||||
|
miny = _mt.floor(f(rx))
|
||||||
|
for y in range(miny, ry-1, -1):
|
||||||
|
ret.append((rx, y))
|
||||||
|
return iter(ret)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "LineLocus({}, {}, {}, {}, thick={})".format(self.x1, self.y1, self.x2, self.y2, self.thick)
|
||||||
|
|
||||||
|
class RectLocus(Locus):
|
||||||
|
"""A locus that defines the outline of a rectangle."""
|
||||||
|
|
||||||
|
def __init__(self, x1, y1, x2, y2, **kwargs):
|
||||||
|
self.lx = min(x1, x2)
|
||||||
|
self.rx = max(x1, x2)
|
||||||
|
self.ly = min(y1, y2)
|
||||||
|
self.ry = max(y1, y2)
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||||
|
raise ValueError("Item must be a tuple or a list.")
|
||||||
|
x, y = item
|
||||||
|
if x < self.lx or x > self.rx or y < self.ly or y > self.ry:
|
||||||
|
return False
|
||||||
|
elif x == self.lx or x == self.rx or y == self.ry or y == self.ly:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
ret = [(x, self.ly) for x in range(self.lx, self.rx)]
|
||||||
|
ret.extend([(self.rx, y) for y in range(self.ly, self.ry)])
|
||||||
|
ret.extend([(x, self.ry) for x in range(self.rx, self.lx, -1)])
|
||||||
|
ret.extend([(self.lx, y) for y in range(self.ry, self.ly, -1)])
|
||||||
|
return iter(ret)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "RectLocus({}, {}, {}, {})".format(self.lx, self.ly, self.rx, self.ry)
|
||||||
|
|
||||||
|
class FilledRectLocus(Locus):
|
||||||
|
"""A locus that defines all points within a rectangle."""
|
||||||
|
|
||||||
|
def __init__(self, x1, y1, x2, y2, **kwargs):
|
||||||
|
self.lx = min(x1, x2)
|
||||||
|
self.rx = max(x1, x2)
|
||||||
|
self.ly = min(y1, y2)
|
||||||
|
self.ry = max(y1, y2)
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||||
|
raise ValueError("Item must be a tuple or a list.")
|
||||||
|
x, y = item
|
||||||
|
return x >= self.lx and x <= self.rx and y >= self.ly and y <= self.ry
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter([(x, y) for y in range(self.ly, self.ry+1) for x in range(self.lx, self.rx+1)])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "FilledRectLocus({}, {}, {}, {})".format(self.lx, self.ly, self.rx, self.ry)
|
||||||
|
|
||||||
|
class CircleLocus(Locus):
|
||||||
|
"""A locus that defines the outline of a circle."""
|
||||||
|
|
||||||
|
def __init__(self, x, y, radius, **kwargs):
|
||||||
|
self.x = x + 0.5
|
||||||
|
self.y = y + 0.5
|
||||||
|
self.r = radius
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||||
|
raise ValueError("Item must be a tuple or a list.")
|
||||||
|
x, y = item
|
||||||
|
if self.x == x + 0.5 and (y == _mt.floor(self.y + self.r) or y == _mt.floor(self.y - self.r)):
|
||||||
|
return True
|
||||||
|
elif self.y == y + 0.5 and (x == _mt.floor(self.x + self.r) or x == _mt.floor(self.x - self.r)):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# Edge case: very small circle
|
||||||
|
if self.r <= _mt.sqrt(2) * 0.5:
|
||||||
|
# In this case, the circle is small enough that the previous
|
||||||
|
# checks would have cought every possible tile other than the center.
|
||||||
|
return x == _mt.floor(self.x) and y == _mt.floor(self.y)
|
||||||
|
if y + 0.5 > self.y:
|
||||||
|
if x + 0.5 > self.x:
|
||||||
|
return _mt.sqrt((x - self.x)**2 + (y - self.y)**2) < self.r and _mt.sqrt((x+1 - self.x)**2 + (y+1 - self.y)**2) > self.r
|
||||||
|
else:
|
||||||
|
return _mt.sqrt((x+1 - self.x)**2 + (y - self.y)**2) < self.r and _mt.sqrt((x - self.x)**2 + (y+1 - self.y)**2) > self.r
|
||||||
|
else:
|
||||||
|
if x + 0.5 > self.x:
|
||||||
|
return _mt.sqrt((x - self.x)**2 + (y+1 - self.y)**2) < self.r and _mt.sqrt((x+1 - self.x)**2 + (y - self.y)**2) > self.r
|
||||||
|
else:
|
||||||
|
return _mt.sqrt((x+1 - self.x)**2 + (y+1 - self.y)**2) < self.r and _mt.sqrt((x - self.x)**2 + (y - self.y)**2) > self.r
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
# Edge case: very small circle
|
||||||
|
if self.r <= _mt.sqrt(2) * 0.5:
|
||||||
|
if self.r > 0.5:
|
||||||
|
ret = ((_mt.floor(self.x), _mt.floor(self.y)),
|
||||||
|
(_mt.floor(self.x) + 1, _mt.floor(self.y)),
|
||||||
|
(_mt.floor(self.x), _mt.floor(self.y) + 1),
|
||||||
|
(_mt.floor(self.x) - 1, _mt.floor(self.y)),
|
||||||
|
(_mt.floor(self.x), _mt.floor(self.y) - 1))
|
||||||
|
return iter(ret)
|
||||||
|
else:
|
||||||
|
return iter(((_mt.floor(self.x), _mt.floor(self.y)),))
|
||||||
|
else:
|
||||||
|
f = lambda z: _mt.sqrt(self.r * self.r - (z - self.x)**2) + self.y
|
||||||
|
# The topmost square: the 'keystone' if you will.
|
||||||
|
ret = [(_mt.floor(self.x), _mt.floor(self.y + self.r))]
|
||||||
|
# All squares between the keystone and the rightmost column.
|
||||||
|
for x in range(_mt.ceil(self.x), _mt.floor(self.x + self.r)):
|
||||||
|
ly = _mt.floor(f(x))
|
||||||
|
ry = _mt.floor(f(x+1)) - 1
|
||||||
|
for y in range(ly, ry, -1):
|
||||||
|
ret.append((x, y))
|
||||||
|
# The last column, minus the bottom square
|
||||||
|
x = _mt.floor(self.x + self.r)
|
||||||
|
for y in range(_mt.floor(f(x)), _mt.floor(self.y), -1):
|
||||||
|
ret.append((x, y))
|
||||||
|
# Finish the circle by copying the coordinates we already have.
|
||||||
|
ret = ret + [(_mt.floor(self.x) + (y - _mt.floor(self.y)), _mt.floor(self.y) - (x - _mt.floor(self.x))) for x, y in ret]
|
||||||
|
return iter(ret + [(_mt.floor(self.x) - (x - _mt.floor(self.x)), _mt.floor(self.y) - (y - _mt.floor(self.y))) for x, y in ret])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "CircleLocus({}, {}, {})".format(_mt.floor(self.x), _mt.floor(self.y), self.r)
|
||||||
|
|
||||||
|
class FilledCircleLocus(Locus):
|
||||||
|
"""A locus that defines all points within a circle."""
|
||||||
|
|
||||||
|
def __init__(self, x, y, radius, **kwargs):
|
||||||
|
self.x = x + 0.5
|
||||||
|
self.y = y + 0.5
|
||||||
|
self.r = radius
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||||
|
raise ValueError("Item must be a tuple or a list.")
|
||||||
|
x, y = item
|
||||||
|
if self.x == x + 0.5 and (y <= _mt.floor(self.y + self.r) and y >= _mt.floor(self.y - self.r)):
|
||||||
|
return True
|
||||||
|
elif self.y == y + 0.5 and (x <= _mt.floor(self.x + self.r) and x >= _mt.floor(self.x - self.r)):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# Edge case: very small circle
|
||||||
|
if self.r <= _mt.sqrt(2) * 0.5:
|
||||||
|
# In this case, the circle is small enough that the previous
|
||||||
|
# checks would have cought every possible tile other than the center.
|
||||||
|
return x == _mt.floor(self.x) and y == _mt.floor(self.y)
|
||||||
|
if y + 0.5 > self.y:
|
||||||
|
if x + 0.5 > self.x:
|
||||||
|
return _mt.sqrt((x - self.x)**2 + (y - self.y)**2) < self.r
|
||||||
|
else:
|
||||||
|
return _mt.sqrt((x+1 - self.x)**2 + (y - self.y)**2) < self.r
|
||||||
|
else:
|
||||||
|
if x + 0.5 > self.x:
|
||||||
|
return _mt.sqrt((x - self.x)**2 + (y+1 - self.y)**2) < self.r
|
||||||
|
else:
|
||||||
|
return _mt.sqrt((x+1 - self.x)**2 + (y+1 - self.y)**2) < self.r
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
# Edge case: very small circle
|
||||||
|
if self.r <= _mt.sqrt(2) * 0.5:
|
||||||
|
if self.r > 0.5:
|
||||||
|
ret = ((_mt.floor(self.x), _mt.floor(self.y)),
|
||||||
|
(_mt.floor(self.x) + 1, _mt.floor(self.y)),
|
||||||
|
(_mt.floor(self.x), _mt.floor(self.y) + 1),
|
||||||
|
(_mt.floor(self.x) - 1, _mt.floor(self.y)),
|
||||||
|
(_mt.floor(self.x), _mt.floor(self.y) - 1))
|
||||||
|
return iter(ret)
|
||||||
|
else:
|
||||||
|
return iter(((_mt.floor(self.x), _mt.floor(self.y)),))
|
||||||
|
else:
|
||||||
|
f = lambda z: _mt.sqrt(self.r * self.r - (z - self.x)**2) + self.y
|
||||||
|
# The topmost square: the 'keystone' if you will.
|
||||||
|
ret = [(_mt.floor(self.x), y) for y in range(_mt.floor(self.y + self.r), _mt.floor(self.y), -1)]
|
||||||
|
# All squares between the keystone and the rightmost column.
|
||||||
|
ry = _mt.floor(self.y)
|
||||||
|
for x in range(_mt.ceil(self.x), _mt.floor(self.x + self.r)):
|
||||||
|
ly = _mt.floor(f(x))
|
||||||
|
for y in range(ly, ry, -1):
|
||||||
|
ret.append((x, y))
|
||||||
|
# The last column, minus the bottom square
|
||||||
|
x = _mt.floor(self.x + self.r)
|
||||||
|
for y in range(_mt.floor(f(x)), _mt.floor(self.y), -1):
|
||||||
|
ret.append((x, y))
|
||||||
|
# Finish the circle by copying the coordinates we already have.
|
||||||
|
ret = ret + [(_mt.floor(self.x) + (y - _mt.floor(self.y)), _mt.floor(self.y) - (x - _mt.floor(self.x))) for x, y in ret]
|
||||||
|
return iter(ret + [(_mt.floor(self.x) - (x - _mt.floor(self.x)), _mt.floor(self.y) - (y - _mt.floor(self.y))) for x, y in ret] + [(_mt.floor(self.x), _mt.floor(self.y))])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "FilledCircleLocus({}, {}, {})".format(_mt.floor(self.x), _mt.floor(self.y), self.r)
|
||||||
|
|
||||||
|
class SetLocus(set, Locus):
|
||||||
|
"""A locus that defines a set of given arbitrary points."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(SetLocus, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "SetLocus({})".format(tuple(self))
|
685
gamemap.py
685
gamemap.py
|
@ -1,511 +1,10 @@
|
||||||
#gamemap.py
|
#gamemap.py
|
||||||
import re
|
import re
|
||||||
import heapq
|
import heapq
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
import ruamel.yaml
|
import ruamel.yaml
|
||||||
import math as _mt
|
import math as _mt
|
||||||
from ruamel.yaml.comments import CommentedMap # for loading classes
|
import gamethings as _gt
|
||||||
|
import gamelocus as _gl
|
||||||
class Thing(object):
|
|
||||||
|
|
||||||
def __init__(self, thingType: str, name: str, x: int, y: int, description: str, flags: int, playerx = None, playery = None):
|
|
||||||
self.thingType = thingType
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
self.playerx = x
|
|
||||||
self.playery = y
|
|
||||||
self.prevx = x # if an area gets double-occupied, a thing can get pushed back.
|
|
||||||
self.prevy = y
|
|
||||||
if playerx:
|
|
||||||
self.playerx = playerx
|
|
||||||
if playery:
|
|
||||||
self.playery = playery
|
|
||||||
self.passable = bool(flags & 1)
|
|
||||||
self.talkable = bool(flags & 2)
|
|
||||||
self.lookable = bool(flags & 4)
|
|
||||||
self.takeable = bool(flags & 8)
|
|
||||||
self.useable = bool(flags & 16)
|
|
||||||
self.graphic = ('clear', '#7F7F7F', ' ')
|
|
||||||
self.thingID = -1 # ID not assigned
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""__str__ is used for look."""
|
|
||||||
return self.description
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, Thing):
|
|
||||||
return False
|
|
||||||
return self.name == other.name
|
|
||||||
|
|
||||||
class Item(Thing):
|
|
||||||
yaml_flag = u'!Item'
|
|
||||||
defaultGraphic = ('clear', '#00BF00', '^')
|
|
||||||
|
|
||||||
def __init__(self, name, x: int, y: int, description: str, useFunc: str, useOnFunc: str, customValues: dict, ranged: bool, graphic = defaultGraphic):
|
|
||||||
super(Item, self).__init__('i', name, x, y, description, 13)
|
|
||||||
self.useFunc = useFunc
|
|
||||||
self.useOnFunc = useOnFunc
|
|
||||||
self.customValues = customValues
|
|
||||||
self.ranged = ranged
|
|
||||||
self.graphic = graphic
|
|
||||||
|
|
||||||
def use(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_yaml(cls, representer, node):
|
|
||||||
# save usual things
|
|
||||||
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
|
|
||||||
# save graphic
|
|
||||||
graphic = {}
|
|
||||||
if node.graphic[0] != Item.defaultGraphic[0]:
|
|
||||||
graphic['bgc'] = node.graphic[0]
|
|
||||||
if node.graphic[1] != Item.defaultGraphic[1]:
|
|
||||||
graphic['fgc'] = node.graphic[1]
|
|
||||||
if node.graphic[2] != Item.defaultGraphic[2]:
|
|
||||||
graphic['shape'] = node.graphic[2]
|
|
||||||
if len(graphic) > 0:
|
|
||||||
ret['graphic'] = graphic
|
|
||||||
# save use functions
|
|
||||||
if node.useFunc != '':
|
|
||||||
ret['useFunc'] = node.useFunc
|
|
||||||
if node.useOnFunc != '':
|
|
||||||
ret['useOnFunc'] = node.useOnFunc
|
|
||||||
if len(node.customValues) > 0:
|
|
||||||
ret['customValues'] = node.customValues
|
|
||||||
if node.ranged:
|
|
||||||
ret['ranged'] = node.ranged
|
|
||||||
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_yaml(cls, constructor, node):
|
|
||||||
parts = CommentedMap()
|
|
||||||
constructor.construct_mapping(node, parts, True)
|
|
||||||
# set default values for optional arguments
|
|
||||||
useFunc = ''
|
|
||||||
useOnFunc = ''
|
|
||||||
customValues = {}
|
|
||||||
ranged = False
|
|
||||||
bgc = Item.defaultGraphic[0]
|
|
||||||
fgc = Item.defaultGraphic[1]
|
|
||||||
shape = Item.defaultGraphic[2]
|
|
||||||
# load graphic
|
|
||||||
if 'graphic' in parts:
|
|
||||||
if 'bgc' in parts['graphic']:
|
|
||||||
bgc = parts['graphic']['bgc']
|
|
||||||
if 'fgc' in parts['graphic']:
|
|
||||||
fgc = parts['graphic']['fgc']
|
|
||||||
if 'shape' in parts['graphic']:
|
|
||||||
shape = parts['graphic']['shape']
|
|
||||||
graphic = (bgc, fgc, shape)
|
|
||||||
# load use functions
|
|
||||||
if 'useFunc' in parts:
|
|
||||||
useFunc = parts['useFunc']
|
|
||||||
if 'useOnFunc' in parts:
|
|
||||||
useOnFunc = parts['useOnFunc']
|
|
||||||
if 'customValues' in parts:
|
|
||||||
customValues = dict(parts['customValues'])
|
|
||||||
for v in customValues:
|
|
||||||
if isinstance(customValues[v], tuple):
|
|
||||||
customValues[v] = list(customValues[v])
|
|
||||||
if 'ranged' in parts:
|
|
||||||
useOnFunc = parts['ranged']
|
|
||||||
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
||||||
parts['description'], useFunc, useOnFunc, customValues, ranged, graphic)
|
|
||||||
|
|
||||||
class Useable(Thing):
|
|
||||||
yaml_flag = u'!Useable'
|
|
||||||
defaultGraphic = ('clear', '#0000FF', '#')
|
|
||||||
|
|
||||||
def __init__(self, name, x: int, y: int, description: str, useFunc: str, customValues: dict, playerx = None, playery = None, graphic = defaultGraphic):
|
|
||||||
super(Useable, self).__init__('u', name, x, y, description, 16, playerx, playery)
|
|
||||||
self.useFunc = useFunc
|
|
||||||
self.customValues = customValues
|
|
||||||
self.graphic = graphic
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_yaml(cls, representer, node):
|
|
||||||
# save usual things
|
|
||||||
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
|
|
||||||
# save graphic
|
|
||||||
graphic = {}
|
|
||||||
if node.graphic[0] != Useable.defaultGraphic[0]:
|
|
||||||
graphic['bgc'] = node.graphic[0]
|
|
||||||
if node.graphic[1] != Useable.defaultGraphic[1]:
|
|
||||||
graphic['fgc'] = node.graphic[1]
|
|
||||||
if node.graphic[2] != Useable.defaultGraphic[2]:
|
|
||||||
graphic['shape'] = node.graphic[2]
|
|
||||||
if len(graphic) > 0:
|
|
||||||
ret['graphic'] = graphic
|
|
||||||
# save use functions
|
|
||||||
if node.useFunc != '':
|
|
||||||
ret['useFunc'] = node.useFunc
|
|
||||||
if len(node.customValues) > 0:
|
|
||||||
ret['customValues'] = node.customValues
|
|
||||||
if node.x != node.playerx or node.y != node.playery:
|
|
||||||
ret['useLocation'] = (node.playerx, node.playery)
|
|
||||||
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_yaml(cls, constructor, node):
|
|
||||||
parts = CommentedMap()
|
|
||||||
constructor.construct_mapping(node, parts, True)
|
|
||||||
# set default values for optional arguments
|
|
||||||
useFunc = ''
|
|
||||||
customValues = {}
|
|
||||||
playerx, playery = parts['location']
|
|
||||||
bgc = Useable.defaultGraphic[0]
|
|
||||||
fgc = Useable.defaultGraphic[1]
|
|
||||||
shape = Useable.defaultGraphic[2]
|
|
||||||
# load graphic
|
|
||||||
if 'graphic' in parts:
|
|
||||||
if 'bgc' in parts['graphic']:
|
|
||||||
bgc = parts['graphic']['bgc']
|
|
||||||
if 'fgc' in parts['graphic']:
|
|
||||||
fgc = parts['graphic']['fgc']
|
|
||||||
if 'shape' in parts['graphic']:
|
|
||||||
shape = parts['graphic']['shape']
|
|
||||||
graphic = (bgc, fgc, shape)
|
|
||||||
# load use functions
|
|
||||||
if 'useFunc' in parts:
|
|
||||||
useFunc = parts['useFunc']
|
|
||||||
if 'customValues' in parts:
|
|
||||||
customValues = dict(parts['customValues'])
|
|
||||||
for v in customValues:
|
|
||||||
if isinstance(customValues[v], tuple):
|
|
||||||
customValues[v] = list(customValues[v])
|
|
||||||
if 'useLocation' in parts:
|
|
||||||
playerx, playery = parts['useLocation']
|
|
||||||
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
||||||
parts['description'], useFunc, customValues, playerx, playery, graphic)
|
|
||||||
|
|
||||||
class Character(Thing):
|
|
||||||
defaultGraphic = ('clear', '#000000', 'o')
|
|
||||||
|
|
||||||
def __init__(self, thingType: str, name: str, x: int, y: int,
|
|
||||||
description: str, inventory: dict, customValues: dict,
|
|
||||||
flags: int, playerx = None, playery = None, graphic = defaultGraphic):
|
|
||||||
super(Character, self).__init__(thingType, name, x, y, description, flags)
|
|
||||||
if inventory == None:
|
|
||||||
inventory = {} # create a new dict for the inventory.
|
|
||||||
# This couldn't be in the NPC constructor because
|
|
||||||
# then all characters would share a reference to
|
|
||||||
# the same empty inventory.
|
|
||||||
self.__inventory = inventory
|
|
||||||
self.customValues = customValues
|
|
||||||
self.graphic = graphic
|
|
||||||
self.thingNames = {}
|
|
||||||
# set up inventory shtuff
|
|
||||||
for i in self.__inventory:
|
|
||||||
if self.__inventory[i].name in self.thingNames:
|
|
||||||
self.thingNames[self.__inventory[i].name].append(i)
|
|
||||||
else:
|
|
||||||
self.thingNames[self.__inventory[i].name] = [i]
|
|
||||||
|
|
||||||
def addThing(self, thing):
|
|
||||||
if not isinstance(thing, Item):
|
|
||||||
raise TypeError("Only items can be added to a character's inventory.")
|
|
||||||
self.__inventory[thing.thingID] = thing
|
|
||||||
if thing.name in self.thingNames:
|
|
||||||
self.thingNames[thing.name].append(thing.thingID)
|
|
||||||
else:
|
|
||||||
self.thingNames[thing.name] = [thing.thingID]
|
|
||||||
|
|
||||||
def getThingByID(self, thingID):
|
|
||||||
return self.__inventory[thingID]
|
|
||||||
|
|
||||||
def getThingByName(self, name):
|
|
||||||
if name in self.thingNames:
|
|
||||||
return self.__inventory[self.thingNames[name][0]]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def removeThingByID(self, thingID):
|
|
||||||
ret = self.__inventory[thingID]
|
|
||||||
self.thingNames[ret.name].remove(thingID)
|
|
||||||
if len(self.thingNames[ret.name]) == 0:
|
|
||||||
del self.thingNames[ret.name]
|
|
||||||
del self.__inventory[thingID]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def removeThingByName(self, name):
|
|
||||||
ret = self.getThingByName(name)
|
|
||||||
self.thingNames[ret.name].remove(thingID)
|
|
||||||
if len(self.thingNames[ret.name]) == 0:
|
|
||||||
del self.thingNames[ret.name]
|
|
||||||
del self.__inventory[thingID]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def removeThing(self, ret):
|
|
||||||
self.thingNames[ret.name].remove(ret.thingID)
|
|
||||||
if len(self.thingNames[ret.name]) == 0:
|
|
||||||
del self.thingNames[ret.name]
|
|
||||||
del self.__inventory[ret.thingID]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inventory(self):
|
|
||||||
"""Get the inventory as a list."""
|
|
||||||
return list(self.__inventory.values())
|
|
||||||
|
|
||||||
class NPC(Character):
|
|
||||||
yaml_flag = u'!NPC'
|
|
||||||
defaultGraphic = ('clear', '#000000', 'o')
|
|
||||||
|
|
||||||
def __init__(self, name, x: int, y: int, description: str, behavior: str, inventory: list, customValues: dict, playerx = None, playery = None, graphic = defaultGraphic):
|
|
||||||
super(NPC, self).__init__('n', name, x, y, description, None, customValues, 6, playerx, playery, graphic)
|
|
||||||
self.behavior = behavior
|
|
||||||
self.behaveEvent = None
|
|
||||||
self.tempInventory = inventory # should be deleted once NPC is loaded
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_yaml(cls, representer, node):
|
|
||||||
# save usual things
|
|
||||||
ret = {'name': node.name, 'location': (node.x, node.y),
|
|
||||||
'description': node.description, 'behavior': node.behavior}
|
|
||||||
# save graphic
|
|
||||||
graphic = {}
|
|
||||||
if node.graphic[0] != NPC.defaultGraphic[0]:
|
|
||||||
graphic['bgc'] = node.graphic[0]
|
|
||||||
if node.graphic[1] != NPC.defaultGraphic[1]:
|
|
||||||
graphic['fgc'] = node.graphic[1]
|
|
||||||
if node.graphic[2] != NPC.defaultGraphic[2]:
|
|
||||||
graphic['shape'] = node.graphic[2]
|
|
||||||
if len(graphic) > 0:
|
|
||||||
ret['graphic'] = graphic
|
|
||||||
# save use functions
|
|
||||||
if len(node.inventory) > 0:
|
|
||||||
ret['inventory'] = node.inventory
|
|
||||||
if len(node.customValues) > 0:
|
|
||||||
ret['customValues'] = node.customValues
|
|
||||||
if node.x != node.playerx or node.y != node.playery:
|
|
||||||
ret['useLocation'] = (node.playerx, node.playery)
|
|
||||||
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_yaml(cls, constructor, node):
|
|
||||||
parts = CommentedMap()
|
|
||||||
constructor.construct_mapping(node, parts, True)
|
|
||||||
# set default values for optional arguments
|
|
||||||
following = False
|
|
||||||
inventory = []
|
|
||||||
customValues = {}
|
|
||||||
playerx, playery = parts['location']
|
|
||||||
bgc = NPC.defaultGraphic[0]
|
|
||||||
fgc = NPC.defaultGraphic[1]
|
|
||||||
shape = NPC.defaultGraphic[2]
|
|
||||||
# load graphic
|
|
||||||
if 'graphic' in parts:
|
|
||||||
if 'bgc' in parts['graphic']:
|
|
||||||
bgc = parts['graphic']['bgc']
|
|
||||||
if 'fgc' in parts['graphic']:
|
|
||||||
fgc = parts['graphic']['fgc']
|
|
||||||
if 'shape' in parts['graphic']:
|
|
||||||
shape = parts['graphic']['shape']
|
|
||||||
graphic = (bgc, fgc, shape)
|
|
||||||
# load use functions
|
|
||||||
if 'inventory' in parts:
|
|
||||||
inventory = parts['inventory']
|
|
||||||
if 'customValues' in parts:
|
|
||||||
customValues = dict(parts['customValues'])
|
|
||||||
for v in customValues:
|
|
||||||
if isinstance(customValues[v], tuple):
|
|
||||||
customValues[v] = list(customValues[v])
|
|
||||||
if 'useLocation' in parts:
|
|
||||||
playerx, playery = parts['useLocation']
|
|
||||||
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
||||||
parts['description'], parts['behavior'], inventory, customValues,
|
|
||||||
playerx, playery, graphic)
|
|
||||||
|
|
||||||
class Door(Thing):
|
|
||||||
yaml_flag = u'!Door'
|
|
||||||
defaultGraphic = ('clear', '#7F3F00', '#')
|
|
||||||
|
|
||||||
def __init__(self, name, x: int, y: int, locked: bool, description = None, key = None, graphic = defaultGraphic):
|
|
||||||
self.descBase = description
|
|
||||||
if description == None:
|
|
||||||
if locked:
|
|
||||||
description = "The {0} is locked.".format(name)
|
|
||||||
else:
|
|
||||||
description = "The {0} is unlocked.".format(name)
|
|
||||||
else:
|
|
||||||
if locked:
|
|
||||||
description += " It is locked.".format(name)
|
|
||||||
else:
|
|
||||||
description += " It is unlocked.".format(name)
|
|
||||||
super(Door, self).__init__('d', name, x, y, description, 1)
|
|
||||||
self.passable = not locked
|
|
||||||
self.key = key
|
|
||||||
self.graphic = graphic
|
|
||||||
|
|
||||||
def lock(self, key = None):
|
|
||||||
if key == self.key:
|
|
||||||
self.passable = not self.passable
|
|
||||||
if self.descBase == None:
|
|
||||||
if self.passable:
|
|
||||||
self.description = "The {0} is unlocked.".format(self.name)
|
|
||||||
else:
|
|
||||||
self.description = "The {0} is locked.".format(self.name)
|
|
||||||
else:
|
|
||||||
if self.passable:
|
|
||||||
self.description += " It is unlocked.".format(self.name)
|
|
||||||
else:
|
|
||||||
self.description += " It is locked.".format(self.name)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_yaml(cls, representer, node):
|
|
||||||
# save usual things
|
|
||||||
ret = {'name': node.name, 'location': (node.x, node.y)}
|
|
||||||
# save graphic
|
|
||||||
graphic = {}
|
|
||||||
if node.graphic[0] != Door.defaultGraphic[0]:
|
|
||||||
graphic['bgc'] = node.graphic[0]
|
|
||||||
if node.graphic[1] != Door.defaultGraphic[1]:
|
|
||||||
graphic['fgc'] = node.graphic[1]
|
|
||||||
if node.graphic[2] != Door.defaultGraphic[2]:
|
|
||||||
graphic['shape'] = node.graphic[2]
|
|
||||||
if len(graphic) > 0:
|
|
||||||
ret['graphic'] = graphic
|
|
||||||
# save door state
|
|
||||||
if node.passable:
|
|
||||||
ret['locked'] = not node.passable
|
|
||||||
if node.descBase != None:
|
|
||||||
ret['description'] = node.descBase
|
|
||||||
if node.key != None:
|
|
||||||
ret['key'] = node.key
|
|
||||||
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_yaml(cls, constructor, node):
|
|
||||||
parts = CommentedMap()
|
|
||||||
constructor.construct_mapping(node, parts, True)
|
|
||||||
# set default values for optional arguments
|
|
||||||
description = None
|
|
||||||
locked = False
|
|
||||||
key = None
|
|
||||||
bgc = Door.defaultGraphic[0]
|
|
||||||
fgc = Door.defaultGraphic[1]
|
|
||||||
shape = Door.defaultGraphic[2]
|
|
||||||
# load graphic
|
|
||||||
if 'graphic' in parts:
|
|
||||||
if 'bgc' in parts['graphic']:
|
|
||||||
bgc = parts['graphic']['bgc']
|
|
||||||
if 'fgc' in parts['graphic']:
|
|
||||||
fgc = parts['graphic']['fgc']
|
|
||||||
if 'shape' in parts['graphic']:
|
|
||||||
shape = parts['graphic']['shape']
|
|
||||||
graphic = (bgc, fgc, shape)
|
|
||||||
# load door state
|
|
||||||
if 'description' in parts:
|
|
||||||
description = parts['description']
|
|
||||||
if 'locked' in parts:
|
|
||||||
locked = parts['locked']
|
|
||||||
if 'key' in parts:
|
|
||||||
key = parts['key']
|
|
||||||
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
||||||
locked, description, key, graphic)
|
|
||||||
|
|
||||||
class MapExit(Thing):
|
|
||||||
yaml_flag = u'!MapExit'
|
|
||||||
defaultGraphic = ('clear', '#FF0000', 'x')
|
|
||||||
|
|
||||||
def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix = None, onUse = '', key = True, graphic = defaultGraphic):
|
|
||||||
description = name
|
|
||||||
if prefix:
|
|
||||||
description = "{0} {1}".format(prefix, name)
|
|
||||||
super(MapExit, self).__init__('x', name, x, y, description, 5)
|
|
||||||
self.exitid = exitid
|
|
||||||
self.destination = destination
|
|
||||||
self.prefix = prefix
|
|
||||||
self.onUse = onUse
|
|
||||||
self.key = key
|
|
||||||
self.graphic = graphic
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_yaml(cls, representer, node):
|
|
||||||
# save usual things
|
|
||||||
ret = {'name': node.name, 'location': (node.x, node.y), 'id': node.exitid, 'destination': node.destination}
|
|
||||||
# save graphic
|
|
||||||
graphic = {}
|
|
||||||
if node.graphic[0] != MapExit.defaultGraphic[0]:
|
|
||||||
graphic['bgc'] = node.graphic[0]
|
|
||||||
if node.graphic[1] != MapExit.defaultGraphic[1]:
|
|
||||||
graphic['fgc'] = node.graphic[1]
|
|
||||||
if node.graphic[2] != MapExit.defaultGraphic[2]:
|
|
||||||
graphic['shape'] = node.graphic[2]
|
|
||||||
if len(graphic) > 0:
|
|
||||||
ret['graphic'] = graphic
|
|
||||||
if node.prefix != None:
|
|
||||||
ret['prefix'] = node.prefix
|
|
||||||
if node.onUse != '':
|
|
||||||
ret['onUse'] = node.onUse
|
|
||||||
if node.key != True:
|
|
||||||
ret['key'] = node.key
|
|
||||||
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_yaml(cls, constructor, node):
|
|
||||||
parts = CommentedMap()
|
|
||||||
constructor.construct_mapping(node, parts, True)
|
|
||||||
# set default values for optional arguments
|
|
||||||
prefix = None
|
|
||||||
onUse = ''
|
|
||||||
key = True
|
|
||||||
bgc = MapExit.defaultGraphic[0]
|
|
||||||
fgc = MapExit.defaultGraphic[1]
|
|
||||||
shape = MapExit.defaultGraphic[2]
|
|
||||||
# load graphic
|
|
||||||
if 'graphic' in parts:
|
|
||||||
if 'bgc' in parts['graphic']:
|
|
||||||
bgc = parts['graphic']['bgc']
|
|
||||||
if 'fgc' in parts['graphic']:
|
|
||||||
fgc = parts['graphic']['fgc']
|
|
||||||
if 'shape' in parts['graphic']:
|
|
||||||
shape = parts['graphic']['shape']
|
|
||||||
graphic = (bgc, fgc, shape)
|
|
||||||
if 'prefix' in parts:
|
|
||||||
prefix = parts['prefix']
|
|
||||||
if 'onUse' in parts:
|
|
||||||
onUse = parts['onUse']
|
|
||||||
if 'key' in parts:
|
|
||||||
key = parts['key']
|
|
||||||
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
||||||
parts['id'], parts['destination'], prefix, onUse, key, graphic)
|
|
||||||
|
|
||||||
class MapEntrance(Thing):
|
|
||||||
yaml_flag = u'!MapEntrance'
|
|
||||||
defaultGraphic = ('clear', 'clear', 'x')
|
|
||||||
# no graphic - should not be drawn
|
|
||||||
|
|
||||||
def __init__(self, x: int, y: int, exitid: int, name = None):
|
|
||||||
if name == None:
|
|
||||||
name = 'entrance {}'.format(exitid)
|
|
||||||
super(MapEntrance, self).__init__('a', name, x, y, '', 1)
|
|
||||||
self.exitid = exitid
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_yaml(cls, representer, node):
|
|
||||||
# save usual things
|
|
||||||
ret = {'location': (node.x, node.y), 'id': node.exitid}
|
|
||||||
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_yaml(cls, constructor, node):
|
|
||||||
parts = CommentedMap()
|
|
||||||
constructor.construct_mapping(node, parts, True)
|
|
||||||
# set default values for optional arguments
|
|
||||||
return cls(parts['location'][0], parts['location'][1], parts['id'])
|
|
||||||
|
|
||||||
class PlayerCharacter(Character):
|
|
||||||
"""Player object. Cannot be created with yaml."""
|
|
||||||
defaultGraphic = ('clear', '#0000FF', 'o')
|
|
||||||
|
|
||||||
def __init__(self, x: int, y: int, description: str, inventory: dict, customValues: dict, name = 'You', graphic = defaultGraphic):
|
|
||||||
super(PlayerCharacter, self).__init__('p', name, x, y, description, inventory, customValues, 5, graphic=graphic)
|
|
||||||
|
|
||||||
class MapError(RuntimeError):
|
class MapError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
@ -518,6 +17,13 @@ class GameMap(object):
|
||||||
# regular expressions
|
# regular expressions
|
||||||
tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])')
|
tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])')
|
||||||
matrixRegex = re.compile(r'(?:[ \t]*(?:[a-z ](?:[0-9]+|[ ]))+(\n))+')
|
matrixRegex = re.compile(r'(?:[ \t]*(?:[a-z ](?:[0-9]+|[ ]))+(\n))+')
|
||||||
|
yaml = ruamel.yaml.YAML()
|
||||||
|
yaml.register_class(_gt.Item)
|
||||||
|
yaml.register_class(_gt.Useable)
|
||||||
|
yaml.register_class(_gt.NPC)
|
||||||
|
yaml.register_class(_gt.Door)
|
||||||
|
yaml.register_class(_gt.MapExit)
|
||||||
|
yaml.register_class(_gt.MapEntrance)
|
||||||
|
|
||||||
def __init__(self, name, graph, matrix, dimensions):
|
def __init__(self, name, graph, matrix, dimensions):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -534,6 +40,7 @@ class GameMap(object):
|
||||||
self.wallColors = []
|
self.wallColors = []
|
||||||
self.persistent = []
|
self.persistent = []
|
||||||
self.enterScript = ''
|
self.enterScript = ''
|
||||||
|
self.version = 'square 1'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __cleanStr(text: str, end = '\n'):
|
def __cleanStr(text: str, end = '\n'):
|
||||||
|
@ -562,23 +69,24 @@ it will read from stdin. Otherwise, it should be a valid file name.
|
||||||
Entering a map through stdin will be obsolete once testing is over."""
|
Entering a map through stdin will be obsolete once testing is over."""
|
||||||
info = None
|
info = None
|
||||||
tryToRead = True
|
tryToRead = True
|
||||||
yaml = ruamel.yaml.YAML()
|
|
||||||
yaml.register_class(Item)
|
|
||||||
yaml.register_class(Useable)
|
|
||||||
yaml.register_class(NPC)
|
|
||||||
yaml.register_class(Door)
|
|
||||||
yaml.register_class(MapExit)
|
|
||||||
yaml.register_class(MapEntrance)
|
|
||||||
if infile != None:
|
if infile != None:
|
||||||
try:
|
try:
|
||||||
with open(infile, 'r') as f:
|
with open(infile, 'r') as f:
|
||||||
info = yaml.load(f)
|
info = GameMap.yaml.load(f)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print("The file could not be read.")
|
print("The file could not be read.")
|
||||||
return None, nextThing
|
return None, nextThing
|
||||||
else:
|
else:
|
||||||
raise MapError("No file was specified for loading.")
|
raise MapError("No file was specified for loading.")
|
||||||
|
|
||||||
|
# Future feature: different map standards, with cool special features
|
||||||
|
# like hex tiles, or walls in-between tiles.
|
||||||
|
# For now just 'square 1': square tiles, version 1.
|
||||||
|
version = 'square 1'
|
||||||
|
if 'version' in info:
|
||||||
|
if info['version'] in ('square 1'):
|
||||||
|
version = info['version']
|
||||||
|
|
||||||
# Now what we do with the data
|
# Now what we do with the data
|
||||||
mat = None
|
mat = None
|
||||||
if 'layout' not in info:
|
if 'layout' not in info:
|
||||||
|
@ -735,7 +243,7 @@ list of lists of tuples."""
|
||||||
return nextThing
|
return nextThing
|
||||||
|
|
||||||
def addThingRecursive(self, container, nextThing = 0):
|
def addThingRecursive(self, container, nextThing = 0):
|
||||||
if isinstance(container, Thing):
|
if isinstance(container, _gt.Thing):
|
||||||
if container.thingID == -1:
|
if container.thingID == -1:
|
||||||
container.thingID = nextThing
|
container.thingID = nextThing
|
||||||
nextThing = self.addThingRecursive(container.customValues, nextThing)
|
nextThing = self.addThingRecursive(container.customValues, nextThing)
|
||||||
|
@ -777,42 +285,55 @@ list of lists of tuples."""
|
||||||
else:
|
else:
|
||||||
raise ValueError('Thing cannot be found by {}.'.format(str(kwargs)))
|
raise ValueError('Thing cannot be found by {}.'.format(str(kwargs)))
|
||||||
|
|
||||||
def path(self, x1, y1, x2, y2, closeEnough = True):
|
def path(self, x1, y1, loc, closeEnough = True):
|
||||||
startThing = self.getThingAtCoords(x1, y1)
|
startThing = self.getThingAtCoords(x1, y1)
|
||||||
if not closeEnough:
|
#if not closeEnough:
|
||||||
if startThing and not startThing.passable:
|
# if startThing and not startThing.passable:
|
||||||
return -1, [] # meaning you can't get there
|
# return -1, [], -1 # meaning you can't get there
|
||||||
dist, prev = self.dijkstra(x1, y1, closeEnough)
|
dist, prev, endPoint = self.dijkstra(x1, y1, loc, closeEnough)
|
||||||
endPoint = self.coordsToInt(x2, y2)
|
#endPoint = self.coordsToInt(x2, y2)
|
||||||
numVertex = self.dimensions[0] * self.dimensions[1]
|
numVertex = self.dimensions[0] * self.dimensions[1]
|
||||||
if dist[endPoint] < numVertex + 1:
|
if endPoint > -1 and dist[endPoint] < numVertex + 1:
|
||||||
return dist[endPoint], prev
|
pathList = [endPoint]
|
||||||
|
nextPoint = prev[endPoint]
|
||||||
|
while nextPoint != -1:
|
||||||
|
pathList.append(nextPoint)
|
||||||
|
nextPoint = prev[nextPoint]
|
||||||
|
pathList.reverse()
|
||||||
|
return dist[endPoint], pathList[1:], endPoint
|
||||||
else:
|
else:
|
||||||
return -1, [] # meaning you can't get there
|
return -1, [], -1 # meaning you can't get there
|
||||||
|
|
||||||
def dijkstra(self, x1, y1, closeEnough = True):
|
def dijkstra(self, x1, y1, loc = None, closeEnough = True):
|
||||||
"""Uses Dijkstra's Algorithm to find the shortest path from (x1, y1) to (x2, y2)
|
"""Uses Dijkstra's Algorithm to find the shortest path from (x1, y1) to (x2, y2)
|
||||||
The closeEnough parameter will create a path that lands beside the source if necessary."""
|
The closeEnough parameter will create a path that lands beside the source if
|
||||||
|
necessary. The loc parameter is an optional locus which will cause the function
|
||||||
|
to return once it finds a point that's in the locus."""
|
||||||
# first test to see that the start point is passable
|
# first test to see that the start point is passable
|
||||||
startThing = self.getThingAtCoords(x1, y1)
|
startThing = self.getThingAtCoords(x1, y1)
|
||||||
startPoint = self.coordsToInt(x1, y1)
|
startPoint = self.coordsToInt(x1, y1)
|
||||||
#endPoint = self.coordsToInt(x2, y2)
|
endPoint = -1 # until one matches the locus, which it might not.
|
||||||
numVertex = self.dimensions[0] * self.dimensions[1]
|
numVertex = self.dimensions[0] * self.dimensions[1]
|
||||||
dist = [numVertex + 1 for i in range(numVertex)]
|
dist = [numVertex + 1 for i in range(numVertex)]
|
||||||
prev = [-1 for i in range(numVertex)]
|
prev = [-1 for i in range(numVertex)]
|
||||||
dist[startPoint] = 0
|
dist[startPoint] = 0
|
||||||
if closeEnough:
|
#if closeEnough:
|
||||||
if startThing and not startThing.passable:
|
# if startThing and not startThing.passable:
|
||||||
dist[startPoint] = -1
|
# dist[startPoint] = -1 # This is so it doesn't path into a non-passable end point.
|
||||||
queue = []
|
queue = []
|
||||||
heapq.heappush(queue, (dist[startPoint], startPoint))
|
heapq.heappush(queue, (dist[startPoint], startPoint))
|
||||||
|
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
u = heapq.heappop(queue)[1]
|
u = heapq.heappop(queue)[1]
|
||||||
|
if loc != None and self.intToCoords(u) in loc:
|
||||||
|
return dist, prev, u
|
||||||
for v in self.mapGraph[u]:
|
for v in self.mapGraph[u]:
|
||||||
thing = self.getThingAtPos(v)
|
thing = self.getThingAtPos(v)
|
||||||
if thing and not thing.passable:
|
if thing and not thing.passable:
|
||||||
continue
|
if closeEnough and self.intToCoords(v) in loc:
|
||||||
|
return dist, prev, u
|
||||||
|
else:
|
||||||
|
continue
|
||||||
tempDist = dist[u] + 1
|
tempDist = dist[u] + 1
|
||||||
if tempDist < dist[v]:
|
if tempDist < dist[v]:
|
||||||
dist[v] = tempDist
|
dist[v] = tempDist
|
||||||
|
@ -820,7 +341,7 @@ The closeEnough parameter will create a path that lands beside the source if nec
|
||||||
prev[v] = u
|
prev[v] = u
|
||||||
heapq.heappush(queue, (dist[v], v))
|
heapq.heappush(queue, (dist[v], v))
|
||||||
|
|
||||||
return dist, prev
|
return dist, prev, endPoint
|
||||||
#if dist[endPoint] < numVertex + 1:
|
#if dist[endPoint] < numVertex + 1:
|
||||||
# return dist[endPoint], prev
|
# return dist[endPoint], prev
|
||||||
#else:
|
#else:
|
||||||
|
@ -831,37 +352,13 @@ The closeEnough parameter will create a path that lands beside the source if nec
|
||||||
# Trivial case first:
|
# Trivial case first:
|
||||||
if abs(x1 - x2) <= 1 and abs(y1 - y2) <= 1:
|
if abs(x1 - x2) <= 1 and abs(y1 - y2) <= 1:
|
||||||
return True
|
return True
|
||||||
Dx = x2 - x1
|
|
||||||
Dy = y2 - y1
|
# Common case second:
|
||||||
y = y1 + 0.5
|
lst = list(_gl.LineLocus(x1, y1, x2, y2, False))[1:-1]
|
||||||
x = x1 + 0.5
|
|
||||||
lst = []
|
|
||||||
if abs(Dx) >= abs(Dy):
|
|
||||||
if Dx < 0:
|
|
||||||
x = x2 + 0.5
|
|
||||||
y = y2 + 0.5
|
|
||||||
dy = Dy / Dx
|
|
||||||
while(x < max(x2, x1) - 0.5):
|
|
||||||
x += 1
|
|
||||||
lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y)))
|
|
||||||
if _mt.floor(y) != _mt.floor(y + dy):
|
|
||||||
lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y + dy)))
|
|
||||||
y += dy
|
|
||||||
elif abs(Dx) < abs(Dy):
|
|
||||||
if Dy < 0:
|
|
||||||
x = x2 + 0.5
|
|
||||||
y = y2 + 0.5
|
|
||||||
dx = Dx / Dy
|
|
||||||
while(y < max(y2, y1) - 0.5):
|
|
||||||
y += 1
|
|
||||||
lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y)))
|
|
||||||
if _mt.floor(x) != _mt.floor(x + dx):
|
|
||||||
lst.append(self.coordsToInt(_mt.floor(x + dx), _mt.floor(y)))
|
|
||||||
x += dx
|
|
||||||
|
|
||||||
# Here is where we actually check:
|
# Here is where we actually check:
|
||||||
for space in lst:
|
for space in lst:
|
||||||
if not self.isPassable(space):
|
if not self.isPassable(self.coordsToInt(*space)):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -999,6 +496,8 @@ The closeEnough parameter will create a path that lands beside the source if nec
|
||||||
else:
|
else:
|
||||||
x, y = self.intToCoords(x)
|
x, y = self.intToCoords(x)
|
||||||
if thing != None:
|
if thing != None:
|
||||||
|
if thing.x == x and thing.y == y:
|
||||||
|
return # it's already there, so don't do anything.
|
||||||
oldPos = self.coordsToInt(thing.x, thing.y)
|
oldPos = self.coordsToInt(thing.x, thing.y)
|
||||||
if oldPos in self.thingPos:
|
if oldPos in self.thingPos:
|
||||||
self.thingPos[oldPos].remove(thing.thingID)
|
self.thingPos[oldPos].remove(thing.thingID)
|
||||||
|
@ -1015,3 +514,75 @@ The closeEnough parameter will create a path that lands beside the source if nec
|
||||||
else:
|
else:
|
||||||
raise MapError("There is nothing to move.")
|
raise MapError("There is nothing to move.")
|
||||||
|
|
||||||
|
class LoSLocus(_gl.Locus):
|
||||||
|
"""A locus that defines all points within line-of-sight of a given points."""
|
||||||
|
|
||||||
|
def __init__(self, x, y, level):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.level = level
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||||
|
raise ValueError("Item must be a tuple or a list.")
|
||||||
|
x, y = item
|
||||||
|
return self.level.lineOfSight(x, y, self.x, self.y)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
ret = [(self.x, self.y)]
|
||||||
|
mat = [[False for i in range(self.level.y)] for j in range(self.level.x)]
|
||||||
|
mat[self.x][self.y] = True
|
||||||
|
fringe = [(self.x, self.y + 1), (self.x + 1, self.y), (self.x, self.y - 1), (self.x - 1, self.y),
|
||||||
|
(self.x + 1, self.y + 1), (self.x + 1, self.y - 1), (self.x - 1, self.y - 1), (self.x - 1, self.y + 1)]
|
||||||
|
while len(fringe) > 0:
|
||||||
|
point = fringe.pop(0)
|
||||||
|
check = []
|
||||||
|
if abs(point[0] - self.x) > abs(point[1] - self.y):
|
||||||
|
if point[0] > self.x:
|
||||||
|
check.append((point[0] - 1, point[1]))
|
||||||
|
if point[1] > self.y:
|
||||||
|
check.append((point[0] - 1, point[1] - 1))
|
||||||
|
elif point[1] < self.y:
|
||||||
|
check.append((point[0] - 1, point[1] + 1))
|
||||||
|
else:
|
||||||
|
check.append((point[0] + 1, point[1]))
|
||||||
|
if point[1] > self.y:
|
||||||
|
check.append((point[0] + 1, point[1] - 1))
|
||||||
|
elif point[1] < self.y:
|
||||||
|
check.append((point[0] + 1, point[1] + 1))
|
||||||
|
elif abs(point[0] - self.x) < abs(point[1] - self.y):
|
||||||
|
if point[1] > self.y:
|
||||||
|
check.append((point[0], point[1] - 1))
|
||||||
|
if point[0] > self.x:
|
||||||
|
check.append((point[0] - 1, point[1] - 1))
|
||||||
|
elif point[0] < self.x:
|
||||||
|
check.append((point[0] + 1, point[1] - 1))
|
||||||
|
else:
|
||||||
|
check.append((point[0], point[1] + 1))
|
||||||
|
if point[0] > self.x:
|
||||||
|
check.append((point[0] - 1, point[1] + 1))
|
||||||
|
elif point[0] < self.x:
|
||||||
|
check.append((point[0] + 1, point[1] + 1))
|
||||||
|
else:
|
||||||
|
if point[0] > self.x:
|
||||||
|
if point[1] > self.y:
|
||||||
|
check.append((point[0] - 1, point[1] - 1))
|
||||||
|
else:
|
||||||
|
check.append((point[0] - 1, point[1] - 1))
|
||||||
|
else:
|
||||||
|
if point[1] > self.y:
|
||||||
|
check.append((point[0] + 1, point[1] - 1))
|
||||||
|
else:
|
||||||
|
check.append((point[0] + 1, point[1] - 1))
|
||||||
|
status = [mat[i[0]][i[1]] for i in check]
|
||||||
|
addIf = False
|
||||||
|
if True in status:
|
||||||
|
if False in status:
|
||||||
|
addIf = self.level.lineOfSight(point[0], point[1], self.x, self.y)
|
||||||
|
else:
|
||||||
|
addIf = True
|
||||||
|
if addIf:
|
||||||
|
mat[point[0]][point[1]] = self.level.isPassable(*point)
|
||||||
|
ret.append(point)
|
||||||
|
return iter(ret)
|
||||||
|
|
||||||
|
|
315
gamesequence.py
Normal file
315
gamesequence.py
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
# gamesequence.py
|
||||||
|
|
||||||
|
import re as _re
|
||||||
|
|
||||||
|
class SequenceError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ScriptBreak(object):
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
def getValueFromString(arg: str, env: dict):
|
||||||
|
#if env == None:
|
||||||
|
#env = self.customValues
|
||||||
|
val = None
|
||||||
|
validIdent = _re.match(r'[_A-Za-z][_0-9A-Za-z]*', arg)
|
||||||
|
if arg[0] in '"\'' and arg[-1] == arg[0]: # assume it's a string
|
||||||
|
val = arg[1:-1]
|
||||||
|
elif _re.match(r'[+-]?(?:[0-9]*[.])?[0-9]+', arg) != None:
|
||||||
|
if '.' in arg:
|
||||||
|
val = float(arg)
|
||||||
|
else:
|
||||||
|
val = int(arg)
|
||||||
|
elif arg.casefold() == 'true':
|
||||||
|
val = True
|
||||||
|
elif arg.casefold() == 'false':
|
||||||
|
val = False
|
||||||
|
#elif arg.casefold() == 'playerx' and self.player != None:
|
||||||
|
# val = self.player.x
|
||||||
|
#elif arg.casefold() == 'playery' and self.player != None:
|
||||||
|
# val = self.player.y
|
||||||
|
#elif arg.casefold() == 'playername' and self.player != None:
|
||||||
|
# val = self.player.name
|
||||||
|
#elif arg.casefold() == 'playerinv' and self.player != None:
|
||||||
|
# val = self.player.inventory
|
||||||
|
#elif arg.casefold() == 'playerdesc' and self.player != None:
|
||||||
|
# val = self.player.description
|
||||||
|
elif validIdent != None and env != None:
|
||||||
|
group = validIdent.group()
|
||||||
|
if 'scriptLocal' in env and group in env['scriptLocal']:
|
||||||
|
val = env['scriptLocal'][group]
|
||||||
|
elif group in env['global']:
|
||||||
|
val = env['global'][group]
|
||||||
|
else:
|
||||||
|
return False # for if statements; if a variable doesn't exist, should evaluate to False
|
||||||
|
# evaluate all values of all indecies
|
||||||
|
openBracket = validIdent.end()
|
||||||
|
if openBracket < len(arg):
|
||||||
|
if arg[openBracket] == '[':
|
||||||
|
ptr = openBracket
|
||||||
|
depth = 0
|
||||||
|
while ptr < len(arg):
|
||||||
|
if depth == 0 and arg[ptr] != '[':
|
||||||
|
raise SequenceError('Invalid value syntax: {}'.format(arg))
|
||||||
|
if arg[ptr] == '[':
|
||||||
|
depth += 1
|
||||||
|
elif arg[ptr] == ']':
|
||||||
|
depth -= 1
|
||||||
|
|
||||||
|
if depth == 0:
|
||||||
|
index = getValueFromString(arg[openBracket+1:ptr], env)
|
||||||
|
if index in val:
|
||||||
|
val = val[index]
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
openBracket = ptr + 1
|
||||||
|
ptr += 1
|
||||||
|
else:
|
||||||
|
raise SequenceError('Invalid value syntax: {}'.format(arg))
|
||||||
|
else:
|
||||||
|
raise SequenceError('Invalid argument to getValueFromString: {}'.format(arg))
|
||||||
|
return val
|
||||||
|
|
||||||
|
def compareValues(args: list, env: dict):
|
||||||
|
"""Generalized comparisons, may eventually be extended to other operators"""
|
||||||
|
if len(args) == 1:
|
||||||
|
return bool(getValueFromString(args[0]), env)
|
||||||
|
elif len(args) == 3 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'):
|
||||||
|
lval = getValueFromString(args[0], env)
|
||||||
|
operator = args[1]
|
||||||
|
rval = getValueFromString(args[2], env)
|
||||||
|
if operator == '==':
|
||||||
|
return lval == rval
|
||||||
|
elif operator == '!=':
|
||||||
|
return lval != rval
|
||||||
|
elif operator == '<=':
|
||||||
|
return lval <= rval
|
||||||
|
elif operator == '>=':
|
||||||
|
return lval >= rval
|
||||||
|
elif operator == '<':
|
||||||
|
return lval < rval
|
||||||
|
elif operator == '>':
|
||||||
|
return lval > rval
|
||||||
|
elif operator == 'in':
|
||||||
|
#if args[2].casefold() == 'playerinv':
|
||||||
|
# return lval in self.player.thingNames
|
||||||
|
#else:
|
||||||
|
return lval in rval
|
||||||
|
else:
|
||||||
|
raise SequenceError("Condition cannot be evaluated: {}".format(' '.join(args)))
|
||||||
|
|
||||||
|
def ifScript(args, env: dict, externalScripts: dict):
|
||||||
|
"""If statement: if [not] value [op value] script"""
|
||||||
|
if len(args) < 2:
|
||||||
|
raise GameError('Incomplete If statement: if {}'.format(' '.join(args)))
|
||||||
|
inverse = False
|
||||||
|
ret = False
|
||||||
|
if args[0] == 'not':
|
||||||
|
inverse = True
|
||||||
|
args.pop(0)
|
||||||
|
if len(args) < 2:
|
||||||
|
raise GameError('Incomplete If statement: if {}'.format(' '.join(args)))
|
||||||
|
|
||||||
|
# evaluate condition
|
||||||
|
if len(args) > 1 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'):
|
||||||
|
if len(args) < 4:
|
||||||
|
raise SequenceError('Incomplete If statement: if {}'.format(' '.join(args)))
|
||||||
|
ret = compareValues(args[:3], env)
|
||||||
|
args = args[3:]
|
||||||
|
else:
|
||||||
|
ret = bool(getValueFromString(args[0]), env)
|
||||||
|
args = args[1:]
|
||||||
|
if inverse:
|
||||||
|
ret = not ret
|
||||||
|
|
||||||
|
# if condition is true, evaluate further
|
||||||
|
if ret:
|
||||||
|
if isinstance(args[-1], list):
|
||||||
|
return runScript(args[-1], env, externalScripts)
|
||||||
|
else:
|
||||||
|
return runScript([args], env, externalScripts)
|
||||||
|
|
||||||
|
def getCustomValue(args, env: dict):
|
||||||
|
if env == None:
|
||||||
|
raise SequenceError("Cannot get a value from an empty environment.")
|
||||||
|
return getValueFromString(args[0], env)
|
||||||
|
|
||||||
|
def setCustomValue(args, env: dict):
|
||||||
|
"""takes [customValue, op, value]"""
|
||||||
|
if env == None:
|
||||||
|
raise SequenceError("Cannot set a value from an empty environment.")
|
||||||
|
scope = env['scriptLocal']
|
||||||
|
if len(args) > 0 and args[0] == 'global':
|
||||||
|
scope = env['global']
|
||||||
|
args.pop(0)
|
||||||
|
if len(args) < 3 or args[1] not in ('=', '+=', '-=', '*=', '/=', '%=', '//=', '**=', 'b=', '!=', '|=', '&=', '^='):
|
||||||
|
raise SequenceError('Arguments are not fit for the setCustomValue script.')
|
||||||
|
# This next line allows cvmod to use values in the thing's customValues dict,
|
||||||
|
# but doing this means that accessing a game CV requires setting it to a local var.
|
||||||
|
val = getValueFromString(args[2], env)
|
||||||
|
|
||||||
|
# set the value to a default value (0, 0.0, '', etc.) if not yet assigned
|
||||||
|
if args[0] not in scope and args[1] != '=':
|
||||||
|
if isinstance(val, int):
|
||||||
|
scope[args[0]] = 0
|
||||||
|
elif isinstance(val, float):
|
||||||
|
scope[args[0]] = 0.0
|
||||||
|
elif isinstance(val, str):
|
||||||
|
scope[args[0]] = ''
|
||||||
|
elif isinstance(val, bool):
|
||||||
|
scope[args[0]] = False
|
||||||
|
# Beyond this point are types that can only be found from other customValues
|
||||||
|
elif isinstance(val, tuple):
|
||||||
|
scope[args[0]] = ()
|
||||||
|
elif isinstance(val, list):
|
||||||
|
scope[args[0]] = []
|
||||||
|
elif isinstance(val, dict):
|
||||||
|
scope[args[0]] = {}
|
||||||
|
else:
|
||||||
|
raise SequenceError("Operation not supported for unassigned custom values of type {}: {}"
|
||||||
|
.format(type(val), args[1]))
|
||||||
|
|
||||||
|
# done parsing, evaluate
|
||||||
|
if args[1] == '=':
|
||||||
|
scope[args[0]] = val
|
||||||
|
elif args[1] == '+=':
|
||||||
|
scope[args[0]] += val
|
||||||
|
elif args[1] == '-=':
|
||||||
|
scope[args[0]] -= val
|
||||||
|
elif args[1] == '*=':
|
||||||
|
scope[args[0]] *= val
|
||||||
|
elif args[1] == '/=':
|
||||||
|
scope[args[0]] /= val
|
||||||
|
elif args[1] == '%=':
|
||||||
|
scope[args[0]] %= val
|
||||||
|
elif args[1] == '//=':
|
||||||
|
scope[args[0]] //= val
|
||||||
|
elif args[1] == '**=':
|
||||||
|
scope[args[0]] **= val
|
||||||
|
elif args[1] == 'b=':
|
||||||
|
scope[args[0]] = bool(val)
|
||||||
|
elif args[1] == '!=':
|
||||||
|
scope[args[0]] = not bool(val)
|
||||||
|
elif args[1] == '|=':
|
||||||
|
scope[args[0]] |= val
|
||||||
|
elif args[1] == '&=':
|
||||||
|
scope[args[0]] &= val
|
||||||
|
elif args[1] == '^=':
|
||||||
|
scope[args[0]] ^= val
|
||||||
|
return scope[args[0]]
|
||||||
|
|
||||||
|
def delCustomValue(args, env: dict):
|
||||||
|
"""To clean up after a map."""
|
||||||
|
if env == None:
|
||||||
|
raise SequenceError("Cannot delete a value from an empty environment.")
|
||||||
|
for i in args:
|
||||||
|
if i in env['global']:
|
||||||
|
del(env['global'][i])
|
||||||
|
|
||||||
|
_requireEnv = {'get' : getCustomValue, 'set' : setCustomValue, 'del' : delCustomValue}
|
||||||
|
_requireScripts = {'if' : ifScript}
|
||||||
|
|
||||||
|
def runScript(script: list, env: dict, externalScripts: dict):
|
||||||
|
"""run a script"""
|
||||||
|
ret = False
|
||||||
|
for line in script:
|
||||||
|
if len(line) == 0:
|
||||||
|
# empty line
|
||||||
|
continue
|
||||||
|
elif line[0].casefold() == 'playercmd':
|
||||||
|
# run a player command
|
||||||
|
self.getIO('playercmd')(line[1:])
|
||||||
|
elif line[0].casefold() in _requireScripts:
|
||||||
|
ret = _requireScripts[line[0]](line[1:], env, externalScripts)
|
||||||
|
elif line[0].casefold() in _requireEnv:
|
||||||
|
ret = _requireEnv[line[0]](line[1:], env)
|
||||||
|
elif line[0].casefold() == 'break':
|
||||||
|
# exit early
|
||||||
|
return _ScriptBreak(ret)
|
||||||
|
elif line[0] in externalScripts:
|
||||||
|
# run a script
|
||||||
|
ret = externalScripts[line[0]](line[1:])
|
||||||
|
else:
|
||||||
|
# conditional evaluation
|
||||||
|
compareValues(line, env)
|
||||||
|
|
||||||
|
if isinstance(ret, ScriptBreak):
|
||||||
|
return ret
|
||||||
|
# We specifically want the return value of the last line executed.
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def parseScript(instr: str, envGlobal: dict, externalScripts: dict):
|
||||||
|
"""parses then runs a script."""
|
||||||
|
#print('parseScript called with {}.'.format(instr))
|
||||||
|
if instr == '':
|
||||||
|
return # nothing to be done.
|
||||||
|
literalStr = list(instr)
|
||||||
|
inQuotes = False
|
||||||
|
script = []
|
||||||
|
stack = []
|
||||||
|
ret = []
|
||||||
|
argStart = 0
|
||||||
|
c = 0
|
||||||
|
l = 0
|
||||||
|
while c < len(instr):
|
||||||
|
if inQuotes:
|
||||||
|
if instr[c] == '"':
|
||||||
|
if instr[c-1] == '\\':
|
||||||
|
del literalStr[l-1]
|
||||||
|
l -= 1
|
||||||
|
else:
|
||||||
|
inQuotes = False
|
||||||
|
del literalStr[l]
|
||||||
|
l -= 1
|
||||||
|
else:
|
||||||
|
if instr[c] == '"': # quoted string
|
||||||
|
if l > 0 and instr[c-1] == '\\':
|
||||||
|
del literalStr[l-1]
|
||||||
|
l -= 1
|
||||||
|
else:
|
||||||
|
inQuotes = True
|
||||||
|
del literalStr[l]
|
||||||
|
l -= 1
|
||||||
|
elif instr[c] in ' \t\n;}{':
|
||||||
|
if l > 0 and instr[c-1] == '\\':
|
||||||
|
del literalStr[l-1]
|
||||||
|
l -= 1
|
||||||
|
else:
|
||||||
|
if argStart != l:
|
||||||
|
ret.append(''.join(literalStr[argStart:l]))
|
||||||
|
if instr[c] == ';':
|
||||||
|
if len(ret) > 0:
|
||||||
|
script.append(ret)
|
||||||
|
ret = []
|
||||||
|
elif instr[c] == '{': # block
|
||||||
|
stack.append(script)
|
||||||
|
script.append(ret)
|
||||||
|
script = []
|
||||||
|
ret.append(script)
|
||||||
|
ret = []
|
||||||
|
del literalStr[l]
|
||||||
|
l -= 1
|
||||||
|
elif instr[c] == '}': # close block
|
||||||
|
if len(ret) > 0:
|
||||||
|
script.append(ret)
|
||||||
|
ret = []
|
||||||
|
script = stack.pop()
|
||||||
|
del literalStr[l]
|
||||||
|
l -= 1
|
||||||
|
argStart = l + 1
|
||||||
|
c += 1
|
||||||
|
l += 1
|
||||||
|
if argStart != l:
|
||||||
|
ret.append(''.join(literalStr[argStart:l]))
|
||||||
|
if len(ret) > 0:
|
||||||
|
script.append(ret)
|
||||||
|
#print('After parsing: {}'.format(script))
|
||||||
|
env = {'global' : envGlobal, 'scriptLocal' : {}}
|
||||||
|
#self.customValues['scriptLocal'] = {}
|
||||||
|
ret = runScript(script, env, externalScripts)
|
||||||
|
if isinstance(ret, ScriptBreak):
|
||||||
|
ret = ret.value
|
||||||
|
return ret
|
114
gameshell.py
114
gameshell.py
|
@ -9,6 +9,7 @@ import heapq
|
||||||
import gameevents
|
import gameevents
|
||||||
import textwrap as _tw
|
import textwrap as _tw
|
||||||
from shutil import get_terminal_size as _gts
|
from shutil import get_terminal_size as _gts
|
||||||
|
import gameutil as _gu
|
||||||
#import random
|
#import random
|
||||||
|
|
||||||
TERM_SIZE = _gts()[0]
|
TERM_SIZE = _gts()[0]
|
||||||
|
@ -27,9 +28,16 @@ class GameShell(Shell):
|
||||||
self.outstream = _sys.stdout
|
self.outstream = _sys.stdout
|
||||||
self.gameBase = gameBase
|
self.gameBase = gameBase
|
||||||
self.colorMode = 0
|
self.colorMode = 0
|
||||||
|
data = self.gameBase.loadGameData('testing/testdata.yml')
|
||||||
self.gameTitle = 'Game Shell' # should be changed for actual games
|
self.gameTitle = 'Game Shell' # should be changed for actual games
|
||||||
|
if 'title' in data:
|
||||||
|
self.gameTitle = data['title']
|
||||||
self.startLevel = 'testing/test1.yml' # should be changed for actual games
|
self.startLevel = 'testing/test1.yml' # should be changed for actual games
|
||||||
|
if 'startLevel' in data:
|
||||||
|
self.gameTitle = data['startLevel']
|
||||||
self.openingText = '{}\nIn Development'
|
self.openingText = '{}\nIn Development'
|
||||||
|
if 'openingText' in data:
|
||||||
|
self.gameTitle = data['openingText']
|
||||||
self.ps2 = '?> '
|
self.ps2 = '?> '
|
||||||
self.__inGame = False
|
self.__inGame = False
|
||||||
|
|
||||||
|
@ -43,7 +51,10 @@ class GameShell(Shell):
|
||||||
self.registerCommand('help', self.man)
|
self.registerCommand('help', self.man)
|
||||||
self.registerCommand('new', self.newGame)
|
self.registerCommand('new', self.newGame)
|
||||||
self.gameBase.registerIO('container', self.container)
|
self.gameBase.registerIO('container', self.container)
|
||||||
self.gameBase.registerIO('dialog', self.dialog)
|
self.gameBase.registerIO('startDialog', self.startDialog)
|
||||||
|
self.gameBase.registerIO('openDialog', self.openDialog)
|
||||||
|
self.gameBase.registerIO('respondDialog', self.respondDialog)
|
||||||
|
self.gameBase.registerIO('endDialog', self.endDialog)
|
||||||
self.gameBase.registerIO('info', self.info)
|
self.gameBase.registerIO('info', self.info)
|
||||||
self.gameBase.registerIO('playercmd', self.playercmd)
|
self.gameBase.registerIO('playercmd', self.playercmd)
|
||||||
|
|
||||||
|
@ -136,7 +147,7 @@ class GameShell(Shell):
|
||||||
See a map of the local area. "ls" is an alias of map.
|
See a map of the local area. "ls" is an alias of map.
|
||||||
"l" and "legend" are aliases of -l.
|
"l" and "legend" are aliases of -l.
|
||||||
If -l is given, a map legend will be printed under the map."""
|
If -l is given, a map legend will be printed under the map."""
|
||||||
xAxis = ' ' + ''.join([self.gameBase.numberToLetter(i).ljust(2) for i in range(self.gameBase.level.dimensions[0])]) + '\n'
|
xAxis = ' ' + ''.join([_gu.numberToLetter(i).ljust(2) for i in range(self.gameBase.level.dimensions[0])]) + '\n'
|
||||||
rows = []
|
rows = []
|
||||||
index = 0
|
index = 0
|
||||||
exits = {}
|
exits = {}
|
||||||
|
@ -154,11 +165,6 @@ If -l is given, a map legend will be printed under the map."""
|
||||||
for x in range(level.dimensions[0]):
|
for x in range(level.dimensions[0]):
|
||||||
pos = level.mapMatrix[y][x]
|
pos = level.mapMatrix[y][x]
|
||||||
things = level.getThingsAtPos(index)
|
things = level.getThingsAtPos(index)
|
||||||
#if x == self.gameBase.playerx and y == self.gameBase.playery:
|
|
||||||
# if self.gameBase.player != textColor:
|
|
||||||
# textColor = '#0000FF'
|
|
||||||
# rows[-1].append(self.color(textColor[1:]))
|
|
||||||
# rows[-1].append('()')
|
|
||||||
if len(things) > 0:
|
if len(things) > 0:
|
||||||
# Prioritize types: p, n, i, u, d, x, a
|
# Prioritize types: p, n, i, u, d, x, a
|
||||||
thing = things[0]
|
thing = things[0]
|
||||||
|
@ -253,8 +259,8 @@ If -l is given, a map legend will be printed under the map."""
|
||||||
for i in self.gameBase.customValues:
|
for i in self.gameBase.customValues:
|
||||||
ret.append("{0:<22}: {1}".format(i, self.gameBase.customValues[i]))
|
ret.append("{0:<22}: {1}".format(i, self.gameBase.customValues[i]))
|
||||||
ret.append("Player name:{0:.>68}".format(self.gameBase.player.name))
|
ret.append("Player name:{0:.>68}".format(self.gameBase.player.name))
|
||||||
ret.append("Player position:{0:.>64}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.player.x), self.gameBase.player.y)))
|
ret.append("Player position:{0:.>64}".format("{0}{1}".format(_gu.numberToLetter(self.gameBase.player.x), self.gameBase.player.y)))
|
||||||
ret.append("Prev. position:{0:.>65}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.player.prevx), self.gameBase.player.prevy)))
|
ret.append("Prev. position:{0:.>65}".format("{0}{1}".format(_gu.numberToLetter(self.gameBase.player.prevx), self.gameBase.player.prevy)))
|
||||||
ret.append("Inventory:")
|
ret.append("Inventory:")
|
||||||
for i in self.gameBase.player.inventory:
|
for i in self.gameBase.player.inventory:
|
||||||
ret.append("{0:<8}: {1}".format(i.thingID, i.name))
|
ret.append("{0:<8}: {1}".format(i.thingID, i.name))
|
||||||
|
@ -303,6 +309,7 @@ If -l is given, a map legend will be printed under the map."""
|
||||||
"""Mode for in-game."""
|
"""Mode for in-game."""
|
||||||
if not self.__inGame:
|
if not self.__inGame:
|
||||||
self.registerCommand('map', self.showMap)
|
self.registerCommand('map', self.showMap)
|
||||||
|
self.registerCommand('wait', self.gameBase.wait)
|
||||||
self.registerCommand('ls', self.showMap)
|
self.registerCommand('ls', self.showMap)
|
||||||
self.registerCommand('go', self.gameBase.go)
|
self.registerCommand('go', self.gameBase.go)
|
||||||
self.registerCommand('move', self.gameBase.go)
|
self.registerCommand('move', self.gameBase.go)
|
||||||
|
@ -324,6 +331,7 @@ If -l is given, a map legend will be printed under the map."""
|
||||||
"""Mode for main menus and the like."""
|
"""Mode for main menus and the like."""
|
||||||
if self.__inGame:
|
if self.__inGame:
|
||||||
self.registerCommand('map', self.showMap)
|
self.registerCommand('map', self.showMap)
|
||||||
|
self.unregisterCommand('wait', self.gameBase.wait)
|
||||||
self.unRegisterCommand('ls', self.showMap)
|
self.unRegisterCommand('ls', self.showMap)
|
||||||
self.unRegisterCommand('go', self.gameBase.go)
|
self.unRegisterCommand('go', self.gameBase.go)
|
||||||
self.unRegisterCommand('move', self.gameBase.go)
|
self.unRegisterCommand('move', self.gameBase.go)
|
||||||
|
@ -435,78 +443,24 @@ Player is modified through side-effect."""
|
||||||
instr = input("Choose an item to view, or exit: ")
|
instr = input("Choose an item to view, or exit: ")
|
||||||
return charsRead / 27 # based on average 250 words per minute, and word length of 5.5 + 1 for space.
|
return charsRead / 27 # based on average 250 words per minute, and word length of 5.5 + 1 for space.
|
||||||
|
|
||||||
def dialog(self, dialogObj):
|
def startDialog(self):
|
||||||
if 'script' in dialogObj:
|
pass
|
||||||
self.gameBase.parseScript(dialogObj['script'])
|
|
||||||
if 'cond' in dialogObj:
|
|
||||||
cases = dialogObj['cond']
|
|
||||||
ret = 0
|
|
||||||
for case in cases:
|
|
||||||
cond = case['case'].split() # should be list like ["value", "==", 1]
|
|
||||||
if len(cond) == 1 and (cond[0] == 'else' or self.gameBase.getValueFromString(cond[0])):
|
|
||||||
ret = self.dialog(case)
|
|
||||||
break
|
|
||||||
elif len(cond) == 3 and self.gameBase.compareValues(cond[1], cond[0], cond[2]):
|
|
||||||
ret = self.dialog(case)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise RuntimeError("All routes are false: {}".format(testValue))
|
|
||||||
if ret > 1:
|
|
||||||
return ret - 1
|
|
||||||
else:
|
|
||||||
return ret
|
|
||||||
# if ret == 1 or ret == 0, go back again.
|
|
||||||
elif 'opener' in dialogObj:
|
|
||||||
toPrint = dialogObj['opener'].split('\n') # split by lines so that fill doesn't erase newlines
|
|
||||||
for line in toPrint:
|
|
||||||
print(_tw.fill(line, width = TERM_SIZE))
|
|
||||||
input('<ENTER>')
|
|
||||||
while isinstance(dialogObj, dict):
|
|
||||||
if 'action' in dialogObj:
|
|
||||||
action = dialogObj['action']
|
|
||||||
if action == 'answer':
|
|
||||||
answer = 0
|
|
||||||
skips = []
|
|
||||||
j = 0 # follower to i
|
|
||||||
if 'answers' in dialogObj and isinstance(dialogObj['answers'], list):
|
|
||||||
for i in range(len(dialogObj['answers'])):
|
|
||||||
ans = dialogObj['answers'][i]
|
|
||||||
if ans[0] == '?':
|
|
||||||
condEnd = ans.index(':')
|
|
||||||
cond = ans[1:condEnd].strip().split()
|
|
||||||
if self.gameBase.compareValues(cond):
|
|
||||||
print(_tw.fill('{}: {}'.format(j+1, ans[condEnd+1:].strip()), width = TERM_SIZE))
|
|
||||||
j += 1
|
|
||||||
else:
|
|
||||||
skips.append(i)
|
|
||||||
else:
|
|
||||||
print(_tw.fill('{}: {}'.format(j+1, ans), width = TERM_SIZE))
|
|
||||||
j += 1
|
|
||||||
answer = int(input(self.ps2)) - 1
|
|
||||||
# account for offset if there were answer options that didn't meet conditions
|
|
||||||
for i in skips:
|
|
||||||
if i <= answer:
|
|
||||||
answer += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if 'replies' in dialogObj and isinstance(dialogObj['replies'], list):
|
|
||||||
ret = self.dialog(dialogObj['replies'][answer])
|
|
||||||
if ret > 1:
|
|
||||||
return ret - 1
|
|
||||||
elif ret == 0:
|
|
||||||
return 0
|
|
||||||
# if ret == 1, then do this dialog again
|
|
||||||
|
|
||||||
elif len(action) >= 4 and action[:4] == 'back':
|
def openDialog(self, opener):
|
||||||
if len(action) == 4:
|
for line in opener:
|
||||||
return 1
|
print(_tw.fill(line, width = TERM_SIZE))
|
||||||
return int(action[4:])
|
input("...")
|
||||||
elif action == 'exit':
|
|
||||||
return 0
|
def respondDialog(self, options):
|
||||||
else:
|
for lineNo in range(len(options)):
|
||||||
raise RuntimeError('Malformed action: {}'.format(action))
|
print(_tw.fill('{}: {}'.format(lineNo+1, options[lineNo]), width = TERM_SIZE))
|
||||||
else:
|
answer = -1
|
||||||
raise RuntimeError("Dialog branch with neither switch nor openner.")
|
while answer < 0 or answer >= len(options):
|
||||||
|
answer = int(input(self.ps2)) - 1
|
||||||
|
return answer
|
||||||
|
|
||||||
|
def endDialog(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def playercmd(self, args):
|
def playercmd(self, args):
|
||||||
self.handleCommand(args)
|
self.handleCommand(args)
|
||||||
|
|
529
gamethings.py
Normal file
529
gamethings.py
Normal file
|
@ -0,0 +1,529 @@
|
||||||
|
#gamethings.py
|
||||||
|
|
||||||
|
import ruamel.yaml
|
||||||
|
from ruamel.yaml.comments import CommentedMap
|
||||||
|
|
||||||
|
class Thing(object):
|
||||||
|
|
||||||
|
def __init__(self, thingType: str, name: str, x: int, y: int, description: str, flags: int, playerx = None, playery = None, **kwargs):
|
||||||
|
self.thingType = thingType
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.playerx = x
|
||||||
|
self.playery = y
|
||||||
|
self.prevx = x # if an area gets double-occupied, a thing can get pushed back.
|
||||||
|
self.prevy = y
|
||||||
|
if playerx:
|
||||||
|
self.playerx = playerx
|
||||||
|
if playery:
|
||||||
|
self.playery = playery
|
||||||
|
self.passable = bool(flags & 1)
|
||||||
|
self.talkable = bool(flags & 2)
|
||||||
|
self.lookable = bool(flags & 4)
|
||||||
|
self.takeable = bool(flags & 8)
|
||||||
|
self.useable = bool(flags & 16)
|
||||||
|
self.graphic = ('clear', '#7F7F7F', ' ')
|
||||||
|
self.thingID = -1 # ID not assigned
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""__str__ is used for look."""
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, Thing):
|
||||||
|
return False
|
||||||
|
return self.name == other.name
|
||||||
|
|
||||||
|
class Observer(Thing):
|
||||||
|
"""ABC for things that have a dict of events that they should listen to."""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.behaviors = {} # {the name of the event : (behaviorQueue priority, the name of the behavior)}
|
||||||
|
self.busy = False # Prevents a new behavior until the current one is finished
|
||||||
|
self.behaviorQueue = [] # If you can't make it perfect, then make it adjustable.
|
||||||
|
# The behavior queue is for behaviors that need to execute once the current action is finished.
|
||||||
|
# If it's not considered important enough (behaviors[event][0] < 0), it's dropped.
|
||||||
|
|
||||||
|
class Item(Thing):
|
||||||
|
yaml_flag = u'!Item'
|
||||||
|
defaultGraphic = ('clear', '#00BF00', '^')
|
||||||
|
|
||||||
|
def __init__(self, name, x: int, y: int, description: str, useFunc: str, useOnFunc: str, customValues: dict, ranged: bool, graphic = defaultGraphic):
|
||||||
|
super(Item, self).__init__('i', name, x, y, description, 13)
|
||||||
|
self.useFunc = useFunc
|
||||||
|
self.useOnFunc = useOnFunc
|
||||||
|
self.customValues = customValues
|
||||||
|
self.ranged = ranged
|
||||||
|
self.graphic = graphic
|
||||||
|
|
||||||
|
def use(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(cls, representer, node):
|
||||||
|
# save usual things
|
||||||
|
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
|
||||||
|
# save graphic
|
||||||
|
graphic = {}
|
||||||
|
if node.graphic[0] != Item.defaultGraphic[0]:
|
||||||
|
graphic['bgc'] = node.graphic[0]
|
||||||
|
if node.graphic[1] != Item.defaultGraphic[1]:
|
||||||
|
graphic['fgc'] = node.graphic[1]
|
||||||
|
if node.graphic[2] != Item.defaultGraphic[2]:
|
||||||
|
graphic['shape'] = node.graphic[2]
|
||||||
|
if len(graphic) > 0:
|
||||||
|
ret['graphic'] = graphic
|
||||||
|
# save use functions
|
||||||
|
if node.useFunc != '':
|
||||||
|
ret['useFunc'] = node.useFunc
|
||||||
|
if node.useOnFunc != '':
|
||||||
|
ret['useOnFunc'] = node.useOnFunc
|
||||||
|
if len(node.customValues) > 0:
|
||||||
|
ret['customValues'] = node.customValues
|
||||||
|
if node.ranged:
|
||||||
|
ret['ranged'] = node.ranged
|
||||||
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(cls, constructor, node):
|
||||||
|
parts = CommentedMap()
|
||||||
|
constructor.construct_mapping(node, parts, True)
|
||||||
|
# set default values for optional arguments
|
||||||
|
useFunc = ''
|
||||||
|
useOnFunc = ''
|
||||||
|
customValues = {}
|
||||||
|
ranged = False
|
||||||
|
bgc = Item.defaultGraphic[0]
|
||||||
|
fgc = Item.defaultGraphic[1]
|
||||||
|
shape = Item.defaultGraphic[2]
|
||||||
|
# load graphic
|
||||||
|
if 'graphic' in parts:
|
||||||
|
if 'bgc' in parts['graphic']:
|
||||||
|
bgc = parts['graphic']['bgc']
|
||||||
|
if 'fgc' in parts['graphic']:
|
||||||
|
fgc = parts['graphic']['fgc']
|
||||||
|
if 'shape' in parts['graphic']:
|
||||||
|
shape = parts['graphic']['shape']
|
||||||
|
graphic = (bgc, fgc, shape)
|
||||||
|
# load use functions
|
||||||
|
if 'useFunc' in parts:
|
||||||
|
useFunc = parts['useFunc']
|
||||||
|
if 'useOnFunc' in parts:
|
||||||
|
useOnFunc = parts['useOnFunc']
|
||||||
|
if 'customValues' in parts:
|
||||||
|
customValues = dict(parts['customValues'])
|
||||||
|
for v in customValues:
|
||||||
|
if isinstance(customValues[v], tuple):
|
||||||
|
customValues[v] = list(customValues[v])
|
||||||
|
if 'ranged' in parts:
|
||||||
|
useOnFunc = parts['ranged']
|
||||||
|
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
||||||
|
parts['description'], useFunc, useOnFunc, customValues, ranged, graphic)
|
||||||
|
|
||||||
|
def use(self, user, gameBase, args) -> float:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def useOn(self, user, gameBase, thing, args) -> float:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Useable(Thing):
|
||||||
|
yaml_flag = u'!Useable'
|
||||||
|
defaultGraphic = ('clear', '#0000FF', '#')
|
||||||
|
|
||||||
|
def __init__(self, name, x: int, y: int, description: str, useFunc: str, customValues: dict, playerx = None, playery = None, graphic = defaultGraphic):
|
||||||
|
super(Useable, self).__init__('u', name, x, y, description, 16, playerx, playery)
|
||||||
|
self.useFunc = useFunc
|
||||||
|
self.customValues = customValues
|
||||||
|
self.graphic = graphic
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(cls, representer, node):
|
||||||
|
# save usual things
|
||||||
|
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
|
||||||
|
# save graphic
|
||||||
|
graphic = {}
|
||||||
|
if node.graphic[0] != Useable.defaultGraphic[0]:
|
||||||
|
graphic['bgc'] = node.graphic[0]
|
||||||
|
if node.graphic[1] != Useable.defaultGraphic[1]:
|
||||||
|
graphic['fgc'] = node.graphic[1]
|
||||||
|
if node.graphic[2] != Useable.defaultGraphic[2]:
|
||||||
|
graphic['shape'] = node.graphic[2]
|
||||||
|
if len(graphic) > 0:
|
||||||
|
ret['graphic'] = graphic
|
||||||
|
# save use functions
|
||||||
|
if node.useFunc != '':
|
||||||
|
ret['useFunc'] = node.useFunc
|
||||||
|
if len(node.customValues) > 0:
|
||||||
|
ret['customValues'] = node.customValues
|
||||||
|
if node.x != node.playerx or node.y != node.playery:
|
||||||
|
ret['useLocation'] = (node.playerx, node.playery)
|
||||||
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(cls, constructor, node):
|
||||||
|
parts = CommentedMap()
|
||||||
|
constructor.construct_mapping(node, parts, True)
|
||||||
|
# set default values for optional arguments
|
||||||
|
useFunc = ''
|
||||||
|
customValues = {}
|
||||||
|
playerx, playery = parts['location']
|
||||||
|
bgc = Useable.defaultGraphic[0]
|
||||||
|
fgc = Useable.defaultGraphic[1]
|
||||||
|
shape = Useable.defaultGraphic[2]
|
||||||
|
# load graphic
|
||||||
|
if 'graphic' in parts:
|
||||||
|
if 'bgc' in parts['graphic']:
|
||||||
|
bgc = parts['graphic']['bgc']
|
||||||
|
if 'fgc' in parts['graphic']:
|
||||||
|
fgc = parts['graphic']['fgc']
|
||||||
|
if 'shape' in parts['graphic']:
|
||||||
|
shape = parts['graphic']['shape']
|
||||||
|
graphic = (bgc, fgc, shape)
|
||||||
|
# load use functions
|
||||||
|
if 'useFunc' in parts:
|
||||||
|
useFunc = parts['useFunc']
|
||||||
|
if 'customValues' in parts:
|
||||||
|
customValues = dict(parts['customValues'])
|
||||||
|
for v in customValues:
|
||||||
|
if isinstance(customValues[v], tuple):
|
||||||
|
customValues[v] = list(customValues[v])
|
||||||
|
if 'useLocation' in parts:
|
||||||
|
playerx, playery = parts['useLocation']
|
||||||
|
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
||||||
|
parts['description'], useFunc, customValues, playerx, playery, graphic)
|
||||||
|
|
||||||
|
def use(self, user, gameBase, args) -> float:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Character(Thing):
|
||||||
|
defaultGraphic = ('clear', '#000000', 'o')
|
||||||
|
|
||||||
|
def __init__(self, inventory: dict, customValues: dict, graphic = defaultGraphic, **kwargs):
|
||||||
|
super(Character, self).__init__(**kwargs)
|
||||||
|
if inventory == None:
|
||||||
|
inventory = {} # create a new dict for the inventory.
|
||||||
|
# This couldn't be in the NPC constructor because
|
||||||
|
# then all characters would share a reference to
|
||||||
|
# the same empty inventory.
|
||||||
|
self.__inventory = inventory
|
||||||
|
self.customValues = customValues
|
||||||
|
self.graphic = graphic
|
||||||
|
self.thingNames = {}
|
||||||
|
# set up inventory shtuff
|
||||||
|
for i in self.__inventory:
|
||||||
|
if self.__inventory[i].name in self.thingNames:
|
||||||
|
self.thingNames[self.__inventory[i].name].append(i)
|
||||||
|
else:
|
||||||
|
self.thingNames[self.__inventory[i].name] = [i]
|
||||||
|
|
||||||
|
def addThing(self, thing):
|
||||||
|
if not isinstance(thing, Item):
|
||||||
|
raise TypeError("Only items can be added to a character's inventory.")
|
||||||
|
self.__inventory[thing.thingID] = thing
|
||||||
|
if thing.name in self.thingNames:
|
||||||
|
self.thingNames[thing.name].append(thing.thingID)
|
||||||
|
else:
|
||||||
|
self.thingNames[thing.name] = [thing.thingID]
|
||||||
|
|
||||||
|
def getThingByID(self, thingID):
|
||||||
|
return self.__inventory[thingID]
|
||||||
|
|
||||||
|
def getThingByName(self, name):
|
||||||
|
if name in self.thingNames:
|
||||||
|
return self.__inventory[self.thingNames[name][0]]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def removeThingByID(self, thingID):
|
||||||
|
ret = self.__inventory[thingID]
|
||||||
|
self.thingNames[ret.name].remove(thingID)
|
||||||
|
if len(self.thingNames[ret.name]) == 0:
|
||||||
|
del self.thingNames[ret.name]
|
||||||
|
del self.__inventory[thingID]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def removeThingByName(self, name):
|
||||||
|
ret = self.getThingByName(name)
|
||||||
|
self.thingNames[ret.name].remove(thingID)
|
||||||
|
if len(self.thingNames[ret.name]) == 0:
|
||||||
|
del self.thingNames[ret.name]
|
||||||
|
del self.__inventory[thingID]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def removeThing(self, ret):
|
||||||
|
self.thingNames[ret.name].remove(ret.thingID)
|
||||||
|
if len(self.thingNames[ret.name]) == 0:
|
||||||
|
del self.thingNames[ret.name]
|
||||||
|
del self.__inventory[ret.thingID]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inventory(self):
|
||||||
|
"""Get the inventory as a list."""
|
||||||
|
return list(self.__inventory.values())
|
||||||
|
|
||||||
|
class NPC(Character, Observer):
|
||||||
|
yaml_flag = u'!NPC'
|
||||||
|
defaultGraphic = ('clear', '#000000', 'o')
|
||||||
|
|
||||||
|
def __init__(self, behaviors: dict, tempInventory: list, **kwargs):
|
||||||
|
if 'graphic' not in kwargs:
|
||||||
|
kwargs['graphic'] = PlayerCharacter.defaultGraphic
|
||||||
|
super(NPC, self).__init__(thingType = 'n', inventory = {}, flags = 6, **kwargs)
|
||||||
|
self.behaviors = behaviors
|
||||||
|
self.behaveEvent = None
|
||||||
|
self.tempInventory = tempInventory # should be deleted once NPC is loaded
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(cls, representer, node):
|
||||||
|
# save usual things
|
||||||
|
ret = {'name': node.name, 'location': (node.x, node.y),
|
||||||
|
'description': node.description, 'behaviors': node.behaviors}
|
||||||
|
# save graphic
|
||||||
|
graphic = {}
|
||||||
|
if node.graphic[0] != NPC.defaultGraphic[0]:
|
||||||
|
graphic['bgc'] = node.graphic[0]
|
||||||
|
if node.graphic[1] != NPC.defaultGraphic[1]:
|
||||||
|
graphic['fgc'] = node.graphic[1]
|
||||||
|
if node.graphic[2] != NPC.defaultGraphic[2]:
|
||||||
|
graphic['shape'] = node.graphic[2]
|
||||||
|
if len(graphic) > 0:
|
||||||
|
ret['graphic'] = graphic
|
||||||
|
# save use functions
|
||||||
|
if len(node.inventory) > 0:
|
||||||
|
ret['inventory'] = node.inventory
|
||||||
|
if len(node.customValues) > 0:
|
||||||
|
ret['customValues'] = node.customValues
|
||||||
|
if node.x != node.playerx or node.y != node.playery:
|
||||||
|
ret['useLocation'] = (node.playerx, node.playery)
|
||||||
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(cls, constructor, node):
|
||||||
|
parts = CommentedMap()
|
||||||
|
constructor.construct_mapping(node, parts, True)
|
||||||
|
# set default values for optional arguments
|
||||||
|
following = False
|
||||||
|
minventory = []
|
||||||
|
mcustomValues = {}
|
||||||
|
mplayerx, mplayery = parts['location']
|
||||||
|
bgc = NPC.defaultGraphic[0]
|
||||||
|
fgc = NPC.defaultGraphic[1]
|
||||||
|
shape = NPC.defaultGraphic[2]
|
||||||
|
# load graphic
|
||||||
|
if 'graphic' in parts:
|
||||||
|
if 'bgc' in parts['graphic']:
|
||||||
|
bgc = parts['graphic']['bgc']
|
||||||
|
if 'fgc' in parts['graphic']:
|
||||||
|
fgc = parts['graphic']['fgc']
|
||||||
|
if 'shape' in parts['graphic']:
|
||||||
|
shape = parts['graphic']['shape']
|
||||||
|
mgraphic = (bgc, fgc, shape)
|
||||||
|
# load use functions
|
||||||
|
if 'inventory' in parts:
|
||||||
|
inventory = parts['inventory']
|
||||||
|
if 'customValues' in parts:
|
||||||
|
mcustomValues = dict(parts['customValues'])
|
||||||
|
for v in mcustomValues:
|
||||||
|
if isinstance(mcustomValues[v], tuple):
|
||||||
|
mcustomValues[v] = list(mcustomValues[v])
|
||||||
|
if 'useLocation' in parts:
|
||||||
|
playerx, playery = parts['useLocation']
|
||||||
|
return cls(name = parts['name'], x = parts['location'][0], y = parts['location'][1],
|
||||||
|
description = parts['description'], behaviors = parts['behaviors'], tempInventory = minventory, customValues = mcustomValues,
|
||||||
|
playerx = mplayerx, plyery = mplayery, graphic = mgraphic)
|
||||||
|
|
||||||
|
class Door(Thing):
|
||||||
|
yaml_flag = u'!Door'
|
||||||
|
defaultGraphic = ('clear', '#7F3F00', '#')
|
||||||
|
|
||||||
|
def __init__(self, name, x: int, y: int, locked: bool, description = None, key = None, graphic = defaultGraphic):
|
||||||
|
self.descBase = description
|
||||||
|
if description == None:
|
||||||
|
if locked:
|
||||||
|
description = "The {0} is locked.".format(name)
|
||||||
|
else:
|
||||||
|
description = "The {0} is unlocked.".format(name)
|
||||||
|
else:
|
||||||
|
if locked:
|
||||||
|
description += " It is locked.".format(name)
|
||||||
|
else:
|
||||||
|
description += " It is unlocked.".format(name)
|
||||||
|
super(Door, self).__init__('d', name, x, y, description, 1)
|
||||||
|
self.passable = not locked
|
||||||
|
self.key = key
|
||||||
|
self.graphic = graphic
|
||||||
|
|
||||||
|
def lock(self, key = None):
|
||||||
|
if key == self.key:
|
||||||
|
self.passable = not self.passable
|
||||||
|
if self.descBase == None:
|
||||||
|
if self.passable:
|
||||||
|
self.description = "The {0} is unlocked.".format(self.name)
|
||||||
|
else:
|
||||||
|
self.description = "The {0} is locked.".format(self.name)
|
||||||
|
else:
|
||||||
|
if self.passable:
|
||||||
|
self.description += " It is unlocked.".format(self.name)
|
||||||
|
else:
|
||||||
|
self.description += " It is locked.".format(self.name)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(cls, representer, node):
|
||||||
|
# save usual things
|
||||||
|
ret = {'name': node.name, 'location': (node.x, node.y)}
|
||||||
|
# save graphic
|
||||||
|
graphic = {}
|
||||||
|
if node.graphic[0] != Door.defaultGraphic[0]:
|
||||||
|
graphic['bgc'] = node.graphic[0]
|
||||||
|
if node.graphic[1] != Door.defaultGraphic[1]:
|
||||||
|
graphic['fgc'] = node.graphic[1]
|
||||||
|
if node.graphic[2] != Door.defaultGraphic[2]:
|
||||||
|
graphic['shape'] = node.graphic[2]
|
||||||
|
if len(graphic) > 0:
|
||||||
|
ret['graphic'] = graphic
|
||||||
|
# save door state
|
||||||
|
if node.passable:
|
||||||
|
ret['locked'] = not node.passable
|
||||||
|
if node.descBase != None:
|
||||||
|
ret['description'] = node.descBase
|
||||||
|
if node.key != None:
|
||||||
|
ret['key'] = node.key
|
||||||
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(cls, constructor, node):
|
||||||
|
parts = CommentedMap()
|
||||||
|
constructor.construct_mapping(node, parts, True)
|
||||||
|
# set default values for optional arguments
|
||||||
|
description = None
|
||||||
|
locked = False
|
||||||
|
key = None
|
||||||
|
bgc = Door.defaultGraphic[0]
|
||||||
|
fgc = Door.defaultGraphic[1]
|
||||||
|
shape = Door.defaultGraphic[2]
|
||||||
|
# load graphic
|
||||||
|
if 'graphic' in parts:
|
||||||
|
if 'bgc' in parts['graphic']:
|
||||||
|
bgc = parts['graphic']['bgc']
|
||||||
|
if 'fgc' in parts['graphic']:
|
||||||
|
fgc = parts['graphic']['fgc']
|
||||||
|
if 'shape' in parts['graphic']:
|
||||||
|
shape = parts['graphic']['shape']
|
||||||
|
graphic = (bgc, fgc, shape)
|
||||||
|
# load door state
|
||||||
|
if 'description' in parts:
|
||||||
|
description = parts['description']
|
||||||
|
if 'locked' in parts:
|
||||||
|
locked = parts['locked']
|
||||||
|
if 'key' in parts:
|
||||||
|
key = parts['key']
|
||||||
|
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
||||||
|
locked, description, key, graphic)
|
||||||
|
|
||||||
|
class MapExit(Thing):
|
||||||
|
yaml_flag = u'!MapExit'
|
||||||
|
defaultGraphic = ('clear', '#FF0000', 'x')
|
||||||
|
|
||||||
|
def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix = None, onUse = '', key = True, graphic = defaultGraphic):
|
||||||
|
description = name
|
||||||
|
if prefix:
|
||||||
|
description = "{0} {1}".format(prefix, name)
|
||||||
|
super(MapExit, self).__init__('x', name, x, y, description, 5)
|
||||||
|
self.exitid = exitid
|
||||||
|
self.destination = destination
|
||||||
|
self.prefix = prefix
|
||||||
|
self.onUse = onUse
|
||||||
|
self.key = key
|
||||||
|
self.graphic = graphic
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(cls, representer, node):
|
||||||
|
# save usual things
|
||||||
|
ret = {'name': node.name, 'location': (node.x, node.y), 'id': node.exitid, 'destination': node.destination}
|
||||||
|
# save graphic
|
||||||
|
graphic = {}
|
||||||
|
if node.graphic[0] != MapExit.defaultGraphic[0]:
|
||||||
|
graphic['bgc'] = node.graphic[0]
|
||||||
|
if node.graphic[1] != MapExit.defaultGraphic[1]:
|
||||||
|
graphic['fgc'] = node.graphic[1]
|
||||||
|
if node.graphic[2] != MapExit.defaultGraphic[2]:
|
||||||
|
graphic['shape'] = node.graphic[2]
|
||||||
|
if len(graphic) > 0:
|
||||||
|
ret['graphic'] = graphic
|
||||||
|
if node.prefix != None:
|
||||||
|
ret['prefix'] = node.prefix
|
||||||
|
if node.onUse != '':
|
||||||
|
ret['onUse'] = node.onUse
|
||||||
|
if node.key != True:
|
||||||
|
ret['key'] = node.key
|
||||||
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(cls, constructor, node):
|
||||||
|
parts = CommentedMap()
|
||||||
|
constructor.construct_mapping(node, parts, True)
|
||||||
|
# set default values for optional arguments
|
||||||
|
prefix = None
|
||||||
|
onUse = ''
|
||||||
|
key = True
|
||||||
|
bgc = MapExit.defaultGraphic[0]
|
||||||
|
fgc = MapExit.defaultGraphic[1]
|
||||||
|
shape = MapExit.defaultGraphic[2]
|
||||||
|
# load graphic
|
||||||
|
if 'graphic' in parts:
|
||||||
|
if 'bgc' in parts['graphic']:
|
||||||
|
bgc = parts['graphic']['bgc']
|
||||||
|
if 'fgc' in parts['graphic']:
|
||||||
|
fgc = parts['graphic']['fgc']
|
||||||
|
if 'shape' in parts['graphic']:
|
||||||
|
shape = parts['graphic']['shape']
|
||||||
|
graphic = (bgc, fgc, shape)
|
||||||
|
if 'prefix' in parts:
|
||||||
|
prefix = parts['prefix']
|
||||||
|
if 'onUse' in parts:
|
||||||
|
onUse = parts['onUse']
|
||||||
|
if 'key' in parts:
|
||||||
|
key = parts['key']
|
||||||
|
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
||||||
|
parts['id'], parts['destination'], prefix, onUse, key, graphic)
|
||||||
|
|
||||||
|
class MapEntrance(Thing):
|
||||||
|
yaml_flag = u'!MapEntrance'
|
||||||
|
defaultGraphic = ('clear', 'clear', 'x')
|
||||||
|
# no graphic - should not be drawn
|
||||||
|
|
||||||
|
def __init__(self, x: int, y: int, exitid: int, name = None):
|
||||||
|
if name == None:
|
||||||
|
name = 'entrance {}'.format(exitid)
|
||||||
|
super(MapEntrance, self).__init__('a', name, x, y, '', 1)
|
||||||
|
self.exitid = exitid
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(cls, representer, node):
|
||||||
|
# save usual things
|
||||||
|
ret = {'location': (node.x, node.y), 'id': node.exitid}
|
||||||
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(cls, constructor, node):
|
||||||
|
parts = CommentedMap()
|
||||||
|
constructor.construct_mapping(node, parts, True)
|
||||||
|
# set default values for optional arguments
|
||||||
|
return cls(parts['location'][0], parts['location'][1], parts['id'])
|
||||||
|
|
||||||
|
class PlayerCharacter(Character):
|
||||||
|
"""Player object. Cannot be created with yaml."""
|
||||||
|
defaultGraphic = ('clear', '#0000FF', 'o')
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
if 'name' not in kwargs:
|
||||||
|
kwargs['name'] == 'You'
|
||||||
|
if 'graphic' not in kwargs:
|
||||||
|
kwargs['graphic'] = PlayerCharacter.defaultGraphic
|
||||||
|
super(PlayerCharacter, self).__init__(thingType = 'p', flags = 5, **kwargs)
|
102
gameutil.py
Normal file
102
gameutil.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
# gameutil.py
|
||||||
|
|
||||||
|
# This is a home for nice helper functions that were useful, but also cluttered
|
||||||
|
# the GameBase class.
|
||||||
|
|
||||||
|
import re as _re
|
||||||
|
|
||||||
|
coordRegex = _re.compile(r'(?:(-?[a-zA-Z]+) ?(-?[0-9]+))|(?:(-?[0-9]+),? (-?[0-9]+))|(?:\(([0-9]+), ([0-9]+)\))')
|
||||||
|
|
||||||
|
def letterToNumber(letter):
|
||||||
|
if letter.isdecimal():
|
||||||
|
return int(letter)
|
||||||
|
ret = 0
|
||||||
|
sign = 1
|
||||||
|
start = 0
|
||||||
|
for i in range(len(letter)):
|
||||||
|
if letter[i] == '-':
|
||||||
|
sign = -sign
|
||||||
|
start -= 1
|
||||||
|
elif letter[i].isalpha():
|
||||||
|
if letter[i].isupper():
|
||||||
|
ret += (ord(letter[i]) - ord('A')) * (26**(i+start))
|
||||||
|
else:
|
||||||
|
ret += (ord(letter[i]) - ord('a')) * (26**(i+start))
|
||||||
|
else:
|
||||||
|
return ret * sign
|
||||||
|
return ret * sign
|
||||||
|
|
||||||
|
def numberToLetter(number):
|
||||||
|
if isinstance(number, str):
|
||||||
|
return number
|
||||||
|
ret = ''
|
||||||
|
sign = ''
|
||||||
|
if number == 0:
|
||||||
|
return 'A'
|
||||||
|
elif number < 0:
|
||||||
|
sign = '-'
|
||||||
|
number = -number
|
||||||
|
while number > 0:
|
||||||
|
ret += chr(ord('A') + number % 26)
|
||||||
|
number = int(number / 26)
|
||||||
|
return sign + ret
|
||||||
|
|
||||||
|
def parseCoords(level, args, usePlayerCoords = True, player = None):
|
||||||
|
"""Takes an argument list, and figures out what it's talking about.
|
||||||
|
Returns (thing, x, y). "Thing" can be None."""
|
||||||
|
if level == None:
|
||||||
|
raise ValueError('Coordinates cannot be parsed because there is no map.')
|
||||||
|
x = -1
|
||||||
|
y = -1
|
||||||
|
|
||||||
|
# first, try by name for objects in the map
|
||||||
|
name = ' '.join(args)
|
||||||
|
thing = level.getThingByName(name)
|
||||||
|
if thing != None:
|
||||||
|
if usePlayerCoords:
|
||||||
|
return thing, thing.playerx, thing.playery
|
||||||
|
else:
|
||||||
|
return thing, thing.x, thing.y
|
||||||
|
|
||||||
|
# Second, try by name for objects in the inventory
|
||||||
|
if player != None:
|
||||||
|
if name in player.thingNames:
|
||||||
|
return player.getThingByName(name), -1, -1
|
||||||
|
|
||||||
|
# Third, try by location
|
||||||
|
coordStr = args[0]
|
||||||
|
if len(args) > 1:
|
||||||
|
coordStr += ' ' + args[1]
|
||||||
|
match = coordRegex.match(coordStr)
|
||||||
|
if match != None: # if coordinates were given
|
||||||
|
groups = match.groups()
|
||||||
|
ind = 0
|
||||||
|
for i in range(len(groups)):
|
||||||
|
if groups[i] == None or groups[i] == match.group():
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
ind = i
|
||||||
|
break
|
||||||
|
x = letterToNumber(groups[ind])
|
||||||
|
y = letterToNumber(groups[ind+1])
|
||||||
|
thing = level.getThingAtCoords(x, y)
|
||||||
|
if thing != None and usePlayerCoords:
|
||||||
|
return thing, thing.playerx, thing.playery
|
||||||
|
else:
|
||||||
|
return thing, x, y
|
||||||
|
else: # if a name was given
|
||||||
|
return None, -1, -1
|
||||||
|
|
||||||
|
def startDialog(thing, outstream, dialog):
|
||||||
|
if 'dialogs' in thing.customValues:
|
||||||
|
if isinstance(thing.customValues['dialogs'], list):
|
||||||
|
if 'dialogPtr' in thing.customValues and isinstance(thing.customValues['dialogPtr'], int):
|
||||||
|
dialog(thing.customValues['dialogs'][thing.customValues['dialogPtr']], thing)
|
||||||
|
else:
|
||||||
|
dialog(thing.customValues['dialogs'][0], thing)
|
||||||
|
elif isinstance(thing.customValues['dialogs'], str):
|
||||||
|
dialog(thing.customValues['dialogs'], thing)
|
||||||
|
else:
|
||||||
|
raise GameError("Character '{}' has dialog of an unexpected type.".format(thing.name))
|
||||||
|
else:
|
||||||
|
print("{} has nothing to say to you.".format(thing.name), file = outstream)
|
44
locusdemo.py
Normal file
44
locusdemo.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
from gamelocus import *
|
||||||
|
|
||||||
|
def demo(loc):
|
||||||
|
print(loc)
|
||||||
|
allpoints = [i for i in loc]
|
||||||
|
print("Contains: Iter: ")
|
||||||
|
for row in range(10):
|
||||||
|
for col in range(10):
|
||||||
|
if (col, 9 - row) in loc:
|
||||||
|
print("##", end = "")
|
||||||
|
else:
|
||||||
|
print("..", end = "")
|
||||||
|
for col in range(10):
|
||||||
|
if (col, 9 - row) in allpoints:
|
||||||
|
print("##", end = "")
|
||||||
|
else:
|
||||||
|
print("..", end = "")
|
||||||
|
print()
|
||||||
|
|
||||||
|
demo(LineLocus(1, 1, 8, 8))
|
||||||
|
demo(LineLocus(1, 1, 8, 8, True))
|
||||||
|
demo(LineLocus(1, 8, 8, 1))
|
||||||
|
demo(LineLocus(1, 8, 8, 1, True))
|
||||||
|
demo(LineLocus(1, 3, 8, 6))
|
||||||
|
demo(LineLocus(1, 3, 8, 6, True))
|
||||||
|
demo(LineLocus(1, 6, 8, 3))
|
||||||
|
demo(LineLocus(1, 6, 8, 3, True))
|
||||||
|
demo(LineLocus(3, 1, 6, 8))
|
||||||
|
demo(LineLocus(3, 1, 6, 8, True))
|
||||||
|
demo(LineLocus(1, 5, 8, 5))
|
||||||
|
demo(LineLocus(1, 5, 8, 5, True))
|
||||||
|
demo(RectLocus(1, 1, 8, 8))
|
||||||
|
demo(FilledRectLocus(1, 1, 8, 8))
|
||||||
|
demo(RectLocus(1, 3, 8, 6))
|
||||||
|
demo(FilledRectLocus(1, 3, 8, 6))
|
||||||
|
demo(CircleLocus(4, 4, 4))
|
||||||
|
demo(CircleLocus(4, 4, 1))
|
||||||
|
demo(CircleLocus(4, 4, 0.6))
|
||||||
|
demo(CircleLocus(4, 4, 0.4))
|
||||||
|
demo(FilledCircleLocus(4, 4, 4))
|
||||||
|
demo(FilledCircleLocus(4, 4, 1))
|
||||||
|
demo(FilledCircleLocus(4, 4, 0.6))
|
||||||
|
demo(FilledCircleLocus(4, 4, 0.4))
|
||||||
|
demo(SetLocus(((1, 1), (1, 8), (8, 8), (8, 1))))
|
|
@ -47,7 +47,19 @@ loadAlways:
|
||||||
name: guy
|
name: guy
|
||||||
description: a guy
|
description: a guy
|
||||||
location: [4, 20]
|
location: [4, 20]
|
||||||
behavior: wander
|
behaviors:
|
||||||
|
none: none # might this work to prevent this character from doing anything?
|
||||||
customValues:
|
customValues:
|
||||||
dialogs: testing/testDialog.yml
|
dialogs: testing/testDialog.yml
|
||||||
|
- !NPC
|
||||||
|
name: follower
|
||||||
|
description: a follower
|
||||||
|
location: [6, 26]
|
||||||
|
behaviors:
|
||||||
|
go: [-1, follow]
|
||||||
|
arrive: [-1, follow]
|
||||||
|
customValues:
|
||||||
|
follow:
|
||||||
|
distance: 2
|
||||||
|
isFollowing: True
|
||||||
|
target: You # yes, YOU!
|
||||||
|
|
|
@ -15,7 +15,7 @@ replies:
|
||||||
opener: I would, but there's still a bunch of foreshadowing and stuff we
|
opener: I would, but there's still a bunch of foreshadowing and stuff we
|
||||||
have to get through first.
|
have to get through first.
|
||||||
action: back
|
action: back
|
||||||
script: set tellmore += 1
|
script: set global tellmore += 1
|
||||||
- case: else
|
- case: else
|
||||||
opener: You really want to know more, don't you?
|
opener: You really want to know more, don't you?
|
||||||
action: back
|
action: back
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue