""" This module holds a basic manual mapper. It can be linked to actions to provide a more automated mapping experience. This mapper has problems with areas that don't conform to a coordinate plane. However, this mapper also shows you the map of the area you've mapped out so far with a red * where you are in-game. That's pretty cool. It also works over telnet and maps are saveable into a mapfile. It also supports standard exits (n, e, s, w, ne, nw, sw, se) and allows you to enter notes for each room. todo: - add the ability to set one's coordinates - fix the save output so that it matches the atlas input - adjust to use the command_response hook instead of the commands thing Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Copyright 2004 Will Guaraldi """ __author__ = "Will Guaraldi <willg@bluesock.org>" __version__ = "0.4 (15 January, 2004)" __description__ = "Basic text-based mapper." from lyntin import exported, config, ansi, utils from lyntin.modules import modutils import commandresponse import re, cPickle DIRECTIONS = { "north": "n", "n": "n", "northeast": "ne", "ne": "ne", "east": "e", "e": "e", "southeast": "se", "se": "se", "south": "s", "s": "s", "southwest": "sw", "sw": "sw", "west": "w", "w": "w", "northwest": "nw", "nw": "nw"} REVERSE = {"n": "s", "ne": "sw", "e": "w", "se": "nw", "s": "n", "sw": "ne", "w": "e", "nw": "se" } SYMBOLLOOKUP = {"n": (0, 1, "|"), "ne": (1, 1, "/"), "e": (1, 0, "-"), "se": (1, -1, "\\"), "s": (0, -1, "|"), "sw": (-1, -1, "/"), "w": (-1, 0, "-"), "nw": (-1, 1, "\\")} DATADIR = config.options["datadir"] class Room: def __init__(self, x, y, symbol): self._x = x self._y = y self._notes = [] self._exits = [] self._symbol = symbol class Mapper: def __init__(self): self._enabled = 0 self._undo_list = [] self._redo_list = [] self._map = {} self._x = 0 self._y = 0 self._symbols = dict(SYMBOLLOOKUP) self._dsymbol = "o" def startMapping(self): self._enabled = 1 if not self._map.has_key((self._x, self._y)): self._map[(self._x, self._y)] = Room(self._x, self._y, self._dsymbol) def stopMapping(self): self._enabled = 0 def addRoom(self, coords): if not self._map.has_key(coords): self._map[coords] = Room(coords[0], coords[1], self._dsymbol) def removeRoom(self, coords): if self._map.has_key(coords): del self._map[coords] def move(self, direction): self._undo_list.append(direction) if len(self._undo_list) > 10: adj = len(self._undo_list) - 10 self._undo_list = self._undo_list[adj:] xoff, yoff, symbol = self._symbols[direction] self.addRoom((self._x + xoff, self._y + yoff)) self._x += xoff self._y += yoff def setexits(self, exits): if self._enabled == 1 and self._map.has_key((self._x, self._y)): self._map[(self._x, self._y)]._exits = exits return 1 return 0 def setsymbol(self, sym): if self._enabled == 1 and self._map.has_key((self._x, self._y)): self._map[(self._x, self._y)]._symbol = sym def setnotes(self, notes): if self._enabled == 1 and self._map.has_key((self._x, self._y)): self._map[(self._x, self._y)]._notes.append(notes) # self._map[(self._x, self._y)]._symbol = "n" def clear(self): self._map = {} self._undo_list = [] self.__list = [] self._x = 0 self._y = 0 def exportMap(self, name="unknown"): """ Gets a map for exporting to a file. """ mapstring = """mapname: """ + name + """ template: template.html maptype: thin exits: yes showgrid: no o = outdoor i = indoor g = grass r = road m = mountain a = marsh d = desert b = beach f = forest t = tundra BEGIN-MAP """ maptuple = self.getMap(color=0, withnotes=0, smallview=0, markme=0) xoff = maptuple[1] yoff = maptuple[2] mapstring = mapstring + maptuple[0] mapstring = mapstring + """END-MAP # DOTS # x y dot """ notes = [] for key in self._map.keys(): value = self._map[key] if value._notes: x = str(key[0] - xoff) y = str((key[1] - yoff) * -1) mapstring += "dot: %s %s notes\n" % (x, y) vnotes = list(value._notes) vnotes = [utils.wrap_text(m) for m in vnotes] notes.append("(x%s, y%s) %s" % (x, y, "\n ".join(vnotes))) mapstring += "\n\nBEGIN-NOTES\n" mapstring += "\n".join(notes) return mapstring def getMap(self, color=0, withnotes=0, smallview=0, markme=1): """ @param color: whether (1) or not (0) to do color markup @type color: boolean @param withnotes: whether (1) or not (0) to display notes @type withnotes: boolean @param smallview: whether (1) or not (0) to display the area around where we are @type smallview: boolean @param markme: whether (1) or not (0) to mark our position @type markme: boolean @returns: the map (as a big string) and the coordinate offset (x, y) @rtype: (string, int, int) """ keys = self._map.keys() if not keys: return "no map.", 0, 0 xlist = [mem[0] for mem in keys] xlist = dict(zip(xlist, range(len(xlist)))).keys() xlist.sort() if smallview == 1: i = xlist.index(self._x) if i + 8 < len(xlist): xlist = xlist[:i+8] if i > 8: xlist = xlist[i-8:] maxx = xlist[-1] + 1 minx = xlist[0] - 1 ylist = [mem[1] for mem in keys] ylist = dict(zip(ylist, range(len(ylist)))).keys() ylist.sort() if smallview == 1: i = ylist.index(self._y) if i + 4 < len(ylist): ylist = ylist[:i+4] if i > 4: ylist = ylist[i-4:] maxy = ylist[-1] + 1 miny = ylist[0] - 1 # initialize the map mymap = [] for mem in range(((maxy - miny) *2) +1): mymap.append(" " * (((maxx - minx) *2) +3)) def fixpart(part): if not part or part == []: return "" else: return part # go through all the rooms for y in ylist: for x in xlist: if not self._map.has_key((x, y)): continue yoff = y - miny xoff = x - minx room = self._map[(x,y)] line = mymap[(yoff*2)] if room._notes and markme: line = fixpart(line[:(xoff*2)]) + "n" + fixpart(line[(xoff*2)+1:]) else: line = fixpart(line[:(xoff*2)]) + room._symbol + fixpart(line[(xoff*2)+1:]) mymap[(yoff*2)] = line # handle exits here for mem in room._exits: yoffoff = 0 xoffoff = 0 symbol = "" xoffoff, yoffoff, symbol = self._symbols[mem] line = mymap[(yoff*2) + yoffoff] begin = fixpart(line[:(xoff*2) + xoffoff]) end = fixpart(line[(xoff*2)+1 + xoffoff:]) space = line[(xoff*2) + xoffoff] if ((space == "/" and symbol == "\\") or (space == "\\" and symbol == "/")): symbol = "x" line = begin + symbol + end mymap[(yoff*2) + yoffoff] = line # now we mark where we are if markme == 1: y = ((self._y - miny) *2) x = ((self._x - minx) *2) mymap[y] = mymap[y][:x] + "*" + mymap[y][x+1:] # flip the map over (because it's upside down right now) mymap.reverse() # y axis labels for i in range(len(mymap)): num = i+1 if num % 2 == 1: num = (num / 2) mymap[i] = "%s%s" % (str(num).rjust(2), mymap[i]) else: mymap[i] = " %s" % mymap[i] # x axis labels line = [] tenline = [] for i in range(len(mymap[0]) / 2): num = i - 1 if num != -1: one = num % 10 line.append("%s" % str(one).rjust(2)) if one == 0: ten = num / 10 tenline.append("%s" % (str(ten).rjust(2))) else: tenline.append(" ") else: line.append(" ") tenline.append(" ") mymap.insert(0, "".join(line)) mymap.insert(0, "".join(tenline)) # go through the map file and colorize the important letters if color == 1: mymap = [mem.replace("*", ansi.get_color("red") + "*" + ansi.get_color("default")) for mem in mymap] mymap = [mem.replace("n", ansi.get_color("blue") + "n" + ansi.get_color("default")) for mem in mymap] if withnotes == 1: mymap.append("") mymap.append("") for mem in self._map.keys(): y = (mem[1] - maxy) * -1 x = mem[0] - minx room = self._map[mem] if room._notes: mymap.append("(%d, %d)\n%s" % (x, y, "\n".join(room._notes))) return ("\n".join(mymap) + "\n", minx, maxy) def undo(self): global REVERSE if self._enabled == 1 and len(self._undo_list) > 0: temp = self._undo_list[-1] del self._undo_list[-1] self._redo_list.append(temp) reverse = REVERSE[temp] xoff, yoff, symbol = self._symbols[reverse] self.removeRoom((self._x, self._y)) self._x += xoff self._y += yoff return temp def redo(self): if self._enabled == 1 and len(self._redo_list) > 0: temp = self._redo_list[-1] del self._redo_list[-1] self._undo_list.append(temp) self.move(temp) return mapper = Mapper() commands_dict = {} EXITRE = re.compile(r"There (is|are) (.+?) obvious exit[s]?: (.+?)$") class lookprocesser(commandresponse.ProcessingTag): """ Pretty much just a wrapper for the process method which takes the response and pulls the gxp from it. """ def __init__(self): pass def process(self, ses, cmd, resp): """ We read through the response from doing "gscore" and pull the gxp value from it and toss it in our list of (time, gxp) tuples. Then we print everything. """ global mapper, DIRECTIONS if mapper._enabled == 1: exits = None resp = resp.splitlines() for i in range(0, len(resp)): exits = EXITRE.search(resp[i]) if exits: exits = exits.group(3) if i +1 < len(resp) and resp[i+1].startswith(" "): exits = exits + " " + resp[i+1].strip() break if not exits: exported.write_message("mapper: no exits found.") return exits = exits.replace(",", " ") exits = exits.replace("(", " ") exits = exits.replace(")", " ") exits = exits.replace(".", "") exits = exits.split() nexits = [DIRECTIONS[mem] for mem in exits if DIRECTIONS.has_key(mem)] nonexits = [mem for mem in exits if not DIRECTIONS.has_key(mem)] ret = mapper.setexits(nexits) if ret == 1: if "and" in nonexits: nonexits.remove("and") if nonexits: exported.write_error("mapper: %r aren't valid exits" % nonexits) mapper.setnotes("Additional exits: %r" % nonexits) exported.write_message("mapper: setting exits %r" % nexits) def look_cmd(ses, args, input): """ If mapping is enabled, this will perform a "look" and pull out interesting information. category: mapper """ global mapper if mapper._enabled == 1: ses.writeSocket("look\n", lookprocesser()) else: ses.writeSocket("look\n") commands_dict["look"] = (look_cmd, "") class goprocesser(commandresponse.ProcessingTag): def __init__(self, mapper, direction): self._mapper = mapper self._direction = direction def process(self, ses, cmd, resp): """ Checks to see if we couldn't go that way. If we couldn't then it does nothing. Otherwise it "moves" us that direction. """ global DIRECTIONS if resp.find("You cannot go " + self._direction) != -1: return if self._direction in DIRECTIONS.keys(): mapper.move(DIRECTIONS[self._direction]) def go_cmd(ses, args, input): """ If mapping is enabled, maps in a specific direction. category: mapper """ global mapper direction = args["direction"] if mapper._enabled == 1: ses.writeSocket(direction + "\n", goprocesser(mapper, direction)) else: ses.writeSocket(direction + "\n") commands_dict["go"] = (go_cmd, "direction") def mstart_cmd(ses, args, input): """ Starts the mapper and puts it in mapping mode. category: mapper """ global mapper mapper.startMapping() exported.write_message("mapper: starting.") commands_dict["mstart"] = (mstart_cmd, "") def mstop_cmd(ses, args, input): """ Stops the mapper and takes it out of mapping mode. category: mapper """ global mapper mapper.stopMapping() exported.write_message("mapper: stopping.") commands_dict["mstop"] = (mstop_cmd, "") def mmove_cmd(ses, args, input): """ Moves the marker on the map x spaces on the x axis and y spaces on the y axis. category: mapper """ global mapper x = args["x"] y = args["y"] * -1 x = mapper._x + x y = mapper._y + y if mapper._map.has_key((x, y)): mapper._x = x mapper._y = y exported.write_message("mapper: moving %d %d." % (args["x"], args["y"])) exported.lyntin_command("#mshow") else: exported.write_error("mapper: that space is not valid.") commands_dict["mmove"] = (mmove_cmd, "x:int y:int") def mdelete_cmd(ses, args, input): """ Removes a room and its exits from the map. category: mapper """ x = args["x"] y = args["y"] if mapper._map.has_key((x, y)): mapper.removeRoom((x, y)) exported.write_message("mapper: room (%d, %d) removed." % (x, y)) else: exported.write_error("mapper: that space is not valid.") commands_dict["mdelete"] = (mdelete_cmd, "x:int y:int") def madd_cmd(ses, args, input): """ Adds a room to the map. category: mapper """ x = args["x"] y = args["y"] if mapper._map.has_key((x, y)): exported.write_error("mapper: that room already exists.") else: mapper.addRoom((x, y)) exported.write_message("mapper: room (%d, %d) created." % (x, y)) commands_dict["madd"] = (madd_cmd, "x:int y:int") def mclear_cmd(ses, args, input): """ Clears the mapper and takes it out of mapping mode. category: mapper """ global mapper mapper.clear() exported.write_message("mapper: cleared map.") commands_dict["mclear"] = (mclear_cmd, "") def mshow_cmd(ses, args, input): """ Shows the current map. The blue n's are rooms that have notes. The red x is where you are. If you want to see the notes too, pass a true in. category: mapper """ global mapper withnotes = args["withnotes"] smallview = args["smallview"] temp = mapper.getMap(1, withnotes, smallview)[0] exported.write_message("mapper: showing map\n%s" % temp) exported.write_message("coordinates are x%s, y%s." % (mapper._x, mapper._y)) commands_dict["mshow"] = (mshow_cmd, "smallview:boolean=true withnotes:boolean=false") def mredo_cmd(ses, args, input): """ Redoes the last undone command. category: mapper """ global mapper temp = mapper.redo() if temp: exported.write_message("mapper: redid %s." % temp) else: exported.write_message("mapper: nothing to redo.") commands_dict["mredo"] = (mredo_cmd, "") def mundo_cmd(ses, args, input): """ Removes the last room created. category: mapper """ global mapper temp = mapper.undo() if temp: exported.write_message("mapper: undid %s." % temp) else: exported.write_message("mapper: nothing to undo.") commands_dict["mundo"] = (mundo_cmd, "") def msetexits_cmd(ses, args, input): """ Sets exits on a room. category: mapper """ global mapper, DIRECTIONS if mapper._enabled == 1: exits = args["exits"] exits = exits.replace(",", " ") exits = exits.replace("(", " ") exits = exits.replace(")", " ") exits = exits.replace(".", "") exits = exits.split() nexits = [DIRECTIONS[mem] for mem in exits if DIRECTIONS.has_key(mem)] nonexits = [mem for mem in exits if not DIRECTIONS.has_key(mem)] if nonexits: exported.write_error("mapper: %r aren't valid exits" % nonexits) mapper.setexits(nexits) exported.write_message("mapper: setting exits %r" % exits) commands_dict["msetexits"] = (msetexits_cmd, "exits=") def msetsymbol_cmd(ses, args, input): """ Lets you change the symbol for a room. category: mapper """ global mapper if mapper._enabled == 1: sym = args["sym"] mapper.setsymbol(sym) exported.write_message("mapper: symbol set to '%s'." % sym) else: exported.write_error("mapper: mapper is disabled--you cannot set the symbol.") return if args["default"] == 1: mapper._dsymbol = sym exported.write_message("mapper: default symbol set to '%s'." % sym) commands_dict["msetsymbol"] = (msetsymbol_cmd, "sym default:boolean=no") def msetnotes_cmd(ses, args, input): """ Adds notes to a room. category: mapper """ global mapper if mapper._enabled == 1: notes = args["input"] if not notes: exported.write_error("mapper: requires a note.") return mapper.setnotes(notes) exported.write_message("mapper: adding notes '%s'" % notes) commands_dict["msetnotes"] = (msetnotes_cmd, "input=", "limitparsing=0") def mstats_cmd(ses, args, input): """ Gives some mapper stats. category: mapper """ global mapper if mapper._enabled == 1: exported.write_message("mapper is enabled.") else: exported.write_message("mapper is disabled.") exported.write_message("there are %d room(s) mapped." % len(mapper._map.keys())) exported.write_message("coordinates are x%s, y%s." % (mapper._x, mapper._y)) exported.write_message("default symbol is %s." % mapper._dsymbol) commands_dict["mstats"] = (mstats_cmd, "") def mexport_cmd(ses, args, input): """ Exports a map file into an ascii file. category: mapper """ global mapper filename = args["filename"] mapfile = mapper.exportMap() f = open(DATADIR + filename, "w") f.write(mapfile) f.close() exported.write_message("mapper: map saved as %s." % filename) commands_dict["mexport"] = (mexport_cmd, "filename") def msave_cmd(ses, args, input): """ Saves the mapfile into the pickle file of map files. category: mapper """ global mapper mapname = args["mapname"] try: f = open(DATADIR + "maps.sav", "r") maps = cPickle.load(f) f.close() except: maps = {} maps[mapname] = mapper f = open(DATADIR + "maps.sav", "w") cPickle.dump(maps, f) f.close() exported.write_message("mapper: map saved to %s." % mapname) commands_dict["msave"] = (msave_cmd, "mapname") def mlist_cmd(ses, args, input): """ Lists maps we have in the pickle of map files. category: mapper """ f = open(DATADIR + "maps.sav", "r") maps = cPickle.load(f) f.close() listing = maps.keys() listing.sort() exported.write_message("Maps available:") exported.write_message(utils.columnize(listing, indent=3)) commands_dict["mlist"] = (mlist_cmd, "") def mcompile_cmd(ses, args, input): """ Goes through all the maps and exports them all. category: mapper """ f = open(DATADIR + "maps.sav", "r") maps = cPickle.load(f) f.close() exported.write_message("mapper: Compiling all maps.") for m in maps.keys(): exported.write_message("mapper: exporting %s." % (m + ".amap")) mapfile = maps[m].exportMap(m) f = open(DATADIR + m + ".amap", "w") f.write(mapfile) f.close() exported.write_message("mapper: done.") commands_dict["mcompile"] = (mcompile_cmd, "") def mload_cmd(ses, args, input): """ Loads the mapfile from a pickle of map files. category: mapper """ global mapper mapname = args["mapname"] f = open(DATADIR + "maps.sav", "r") maps = cPickle.load(f) f.close() mapper = maps[mapname] if not hasattr(mapper, "_dsymbol"): mapper._dsymbol = "o" exported.write_message("mapper: map %s loaded." % mapname) commands_dict["mload"] = (mload_cmd, "mapname") def mremovemap_cmd(ses, args, input): """ Removes a map from the map file. There is no undoing this action. category: mapper """ global mapper mapname = args["mapname"] f = open(DATADIR + "maps.sav", "r") maps = cPickle.load(f) f.close() del maps[mapname] f = open(DATADIR + "maps.sav", "w") cPickle.dump(maps, f) f.close() exported.write_message("mapper: map %s removed." % mapname) commands_dict["mremovemap"] = (mremovemap_cmd, "mapname") def load(): """ Initializes the module by binding all the commands.""" global mapper modutils.load_commands(commands_dict) def unload(): """ Unload things.""" global mapper modutils.unload_commands(commands_dict)