class Follower(Object): following = None messages = Messages({ 'follow_ask': { 'actor': 'You ask to follow {m$char{n.', 'char': '{c$actor{n wants to follow you. Type "{clead $actor{n" ' 'to let ${actor.him_her} follow you.', '*': '{c$actor{n wants to follow {m%char{n.' }, 'follow': { 'actor': 'You start following {m$char{n.', '*': '{c$actor{n starts following {m$char{n.' }, 'unfollow': { 'actor': 'You stop following {m$char{n.', '*': '{c$actor{n stops following {m$char{n.' } }) @property def is_following(self): self._prune_following() return self.following is not None def _prune_following(self): before = self.following if self.following is not None: if not self.following.isa(FollowableObject): self.following = None elif self.location != self.following.location: self.stop_following() return before, self.following def start_following(self, char): if self.following == char: return if (not self.db.is_valid(char, FollowableObject) or self.location != char.location): raise InvalidObject(obj=char, msg="You can't follow that.") if self.is_following: self.stop_following() if not char.allows_following_by(self.ref()): self.emit_message('follow_ask', actor=self.ref(), char=char) else: self.following = char char.gain_follower(self.ref()) self.emit_message('follow', actor=self.ref(), char=char) def stop_following(self, following=None): if following is None or self.following == following: following = self.following if self.db.is_valid(following, FollowableObject): following.lose_follower(self.ref()) self.following = None self.emit_message('unfollow', actor=self.ref(), char=following) def do_follow(self, exit): if exit in self.context: exit.invoke(self.ref())
def area_import(self, data, sandbox): super(AreaExportableBaseObject, self).area_import(data, sandbox) if 'id' in data: self.area_import_id = data['id'] if self.obj_id is None: mudsling.game.db.register_object(self) if 'objmap' not in sandbox: sandbox['objmap'] = {} sandbox['objmap'][self.area_import_id] = self.ref() if sandbox.get('top', None) != self: # If this is a "top" (existing) object, don't rename it. if 'name' in data: self.set_name(data['name']) if 'aliases' in data: self.set_aliases(data['aliases']) if 'messages' in data: self.messages = Messages(messages=data['messages'])
class Thing(DescribableObject, ScriptableObject): """ The basic object in the MUDSling core game world. Can be picked up, dropped, and given. """ create_lock = locks.Lock('perm(create things)') messages = Messages({ 'drop': { 'actor': "You drop $this.", '*': "$actor drops $this." }, 'drop_fail': { 'actor': "You can't seem to drop $this.", '*': "$actor tries to drop $this, but fails." }, 'take': { 'actor': "You take $this.", '*': "$actor takes $this." }, 'take_fail': { 'actor': "You try to pick up $this, but fail.", '*': "$actor tires to pick up $this, but fails." }, 'give': { 'actor': "You hand $this to $recipient.", 'recipient': "$actor hands you $this.", '*': "$actor hands $this to $recipient." }, 'teleport_out': { 'actor': "{bYou dematerialize.", '*': "{c$actor {bvanishes." }, 'teleport_in': { 'actor': "{bYou materialize in {c$dest{b.", '*': "{c$actor {bmaterializes." }, })
class Container(Thing): """ The basic container-like thing. Can be opened or closed, and provides commands for putting things in it. """ import commands.container public_commands = all_commands(commands.container) messages = Messages({ 'close': { 'actor': 'You close $this.', '*': '$actor closes $this.' }, 'open': { 'actor': 'You open $this.', '*': '$actor opens $this.' }, 'add': { 'actor': 'You put $thing in $this.', '*': '$actor puts $thing in $this.' }, 'add_fail': { 'actor': 'You cannot put $thing in $this.' }, 'remove': { 'actor': 'You remove $thing from $this.', '*': '$actor removes $thing from $this.' }, 'remove_fail': { 'actor': 'You cannot remove $thing from $this.' } }) _opened = False can_close = True @property def opened(self): return self._opened if self.can_close else True @property def closed(self): return not self._opened def propagate_sensation_up(self, sensation): return self.opened def open(self, opened_by=None): """ Opens the container. Will emit the 'open' message with opened_by as the actor if specified. :param opened_by: The object responsible for opening the container. :type opened_by: mudsling.objects.Object """ if not self._opened: self._opened = True if opened_by is not None and opened_by.location == self.location: self.emit_message('open', actor=opened_by) else: self.emit([self.ref(), ' opens.']) def close(self, closed_by=None): """ Closes the container. Emits message if `closed_by` is specified. :param closed_by: The object responsible for closing the container. :type closed_by: mudsling.objects.Object """ if self._opened: self._opened = False if closed_by is not None and closed_by.location == self.location: self.emit_message('close', actor=closed_by) else: self.emit([self.ref(), ' closes.']) def desc_title(self, viewer): name = super(Container, self).desc_title(viewer) if self.can_close: if self._opened: name += ' {g(open)' else: name += ' {y(closed)' return name def contents_visible_to(self, obj): if self._opened or obj in self.contents: return super(Container, self).contents_visible_to(obj) else: return [] def describe_to(self, viewer): desc = super(Container, self).describe_to(viewer) if self._opened: desc['open container contents'] = \ 'Contents:\n' + self.contents_as_seen_by(viewer) return desc
class Character(BaseCharacter, DescribableObject, SensingObject, HasGender, FollowableObject, Follower): """Core character class.""" # Do not export characters to area files. area_exportable = False import commands.admin.building as building_commands import commands.character as character_commands private_commands = all_commands( character_commands, # Building commands are administrative, but they apply in a "physical" # manner to the game world, so they are attached to the Character # instead of the Player. building_commands, ) _say_cmd = character_commands.SayCmd _emote_cmd = character_commands.EmoteCmd _look_cmd = character_commands.LookCmd del character_commands, building_commands from rooms import Room, Exit object_settings = { # The classes to use when creating rooms and exits with @dig. ObjSetting(name='building.room_class', type=type, attr='building_room_class', default=lambda o: config.getclass('Classes', 'room class'), parser=parsers.ObjClassStaticParser), ObjSetting(name='building.exit_class', type=type, attr='building_exit_class', default=lambda o: config.getclass('Classes', 'exit class'), parser=parsers.ObjClassStaticParser), } del Room, Exit messages = Messages({ 'say': { 'actor': 'You say, "{g$speech{n".', '*': '{c$actor{n says, "{c$speech{n".' }, 'teleport_out': { 'actor': "{bYou dematerialize.", '*': "{c$actor {bvanishes." }, 'teleport_in': { 'actor': "{bYou materialize in {c$dest{b.", '*': "{c$actor {bmaterializes." }, 'take': { 'actor': 'You take $obj.', '*': '$actor takes $obj.' }, 'take_fail': { 'actor': 'You fail to take $obj.', }, 'drop': { 'actor': 'You drop $obj.', '*': '$actor drops $obj.' }, 'drop_fail': { 'actor': "You can't seem to drop $obj.", }, 'give': { 'actor': 'You give $obj to $recipient.', 'recipient': "$actor hands you $obj.", '*': '$actor gives $obj to $recipient.' }, 'give_fail': { 'actor': 'You fail to give $obj to $recipient.' } }) def is_possessable_by(self, player): """ Core characters are ONLY possessable by players! :rtype: bool """ return player.is_valid(Player) @property def player(self): """ :rtype: Player or None """ player = super(Character, self).player return player if player.is_valid(Player) else None def room_group(self, cls=None): from mudslingcore.rooms import RoomGroup cls = cls or RoomGroup for loc in self._location_walker(): if loc.isa(RoomGroup) and loc.isa(cls): return loc return None def preemptive_command_match(self, raw): if raw.startswith('"'): return self._say_cmd(raw, '"', raw[1:], self.game, self.ref(), self.ref(), True) elif raw.startswith(':'): return self._emote_cmd(raw, ':', raw[1:], self.game, self.ref(), self.ref(), True) return None def match_literals(self, search, cls=None, err=False): if '->' in search and self.has_perm('use nested matching'): parts = filter(len, map(string.strip, search.split('->'))) if len(parts) > 1: matches = self.match_object(parts[0], err=False) if len(matches) == 1: for part in parts[1:]: try: matches = matches[0].match_contents(part) except AttributeError: matches = [] break matches = obj_utils.filter_by_class(matches, cls=cls) if err and len(matches) > 1: raise AmbiguousMatch(query=search, matches=matches) if matches: return matches return super(Character, self).match_literals(search, cls=cls, err=err) @hook('after_moved') def __after_moved(self, moved_from, moved_to, by=None, via=None): if self.game.db.is_valid(moved_to, DescribableObject): cmd = self._look_cmd('look', 'look', '', self.game, self.ref(), self.ref()) cmd.execute() def vision_sense(self, sensation): self.msg(sensation.content_for(self)) def hearing_sense(self, sensation): content = self._format_msg(sensation.content_for(self)) if isinstance(sensation, Speech): msg = [sensation.origin, ' says, "{c', content, '{n".'] elif 'bare' in sensation.traits: msg = content else: msg = ["{mYou hear: {n", content] self.msg(msg) def say(self, speech): if not isinstance(speech, Speech): speech = Speech(str(speech), origin=self.ref()) self.emit(speech, exclude=(self.ref(), )) self.tell('You say, "{g', speech.content, '{n".') def _prepare_emote(self, pose, sep=' ', prefix='', suffix='', show_name=True): msg = [prefix, self.ref() if show_name else '', sep] if isinstance(pose, list): msg.extend(pose) else: msg.append(pose) if suffix: msg.extend((sep, suffix)) return msg def emote(self, pose, sep=' ', prefix='', suffix='', show_name=True): self.emit(self._prepare_emote(pose, sep, prefix, suffix, show_name))
class Player(BasePlayer, ConfigurableObject, ChannelUser, EditorSessionHost, MailRecipient): """ Core player class. """ import commands.player as player_commands import commands.admin.system import commands.admin.perms import commands.admin.tasks import commands.admin.objects import commands.admin.players import commands.admin.areas import commands.scripting private_commands = all_commands(commands.admin.system, commands.admin.perms, commands.admin.tasks, commands.admin.objects, commands.admin.players, commands.admin.areas, commands.scripting, player_commands) del player_commands channels = {} messages = Messages({ 'teleport': { 'actor': "You teleported {c$obj{n to {g$where{n.", 'obj': "{c$actor{n teleported you to {g$where{n." }, 'teleport_failed': { 'actor': "You fail to teleport {c$obj{n to {y$where{n." } }) def __init__(self, **kwargs): super(Player, self).__init__(**kwargs) self.channels = {} def authenticate(self, password, session=None): applicable = bans.check_bans(session, self) if applicable: raise Exception("Connection banned: %s" % applicable[0]) return super(Player, self).authenticate(password, session) def preemptive_command_match(self, raw): """ :type raw: str """ if raw.startswith(';') and self.has_perm("eval code"): import commands.admin.system return commands.admin.system.EvalCmd(raw, ';', raw[1:], self.game, self.ref(), self.ref()) if raw.startswith('?'): import commands.player cmd = commands.player.HelpCmd(raw, '?', raw[1:], self.game, self.ref(), self.ref()) cmd.match_syntax(cmd.argstr) return cmd return None def find_help_topic(self, search): # fltr = lambda e: e.lock.eval(e, self.ref()) def fltr(e): return e.lock.eval(e, self.ref()) try: topic = help.help_db.find_topic(search, entryFilter=fltr) except FailedMatch: topic = self.find_command_help_topic(search) return topic def find_command_help_topic(self, search): #: :type: list of BaseObject totry = [self] if self.is_possessing: totry.append(self.possessing) #: :type: list of mudsling.commands.Command cmds = [] for obj in totry: cmds = obj.match_command(search) if len(cmds): break if len(cmds) == 1: _, cmd = cmds[0] elif len(cmds) > 1: raise AmbiguousMatch(query=search, matches=cmds) else: raise FailedMatch(query=search) topic = help.CommandHelpEntry(cmd) return topic def session_attached(self, session): super(Player, self).session_attached(session) try: player_role = self.game.db.match_role('Core Player') except MatchError as e: logging.warning("Cannot confirm %s (%d) has Player role: %s", self.name, self.obj_id, e.message) else: if not self.has_role(player_role): self.add_role(player_role)
class Wearable(mudslingcore.objects.Thing): """ A generic wearable object. """ worn_by = None public_commands = [WearCmd, UnwearCmd] messages = Messages({ 'wear': { 'actor': 'You put on $this.', '*': '$actor puts on $this.' }, 'unwear': { 'actor': 'You take $this off.', '*': '$actor takes $this off.' } }) @property def is_worn(self): return self.worn_by is not None def check_wear_status(self): this = self.ref() try: if this not in self.worn_by.wearing: raise ValueError if self.worn_by is not None and self.location != self.worn_by: raise ValueError except (AttributeError, ValueError): wearer = self.worn_by self.worn_by = None if self.db.is_valid(wearer, Wearer) and this in wearer.wearing: wearer.wearing.remove(this) def is_worn_by(self, wearer): return wearer.ref() == self.worn_by.ref() def before_wear(self, wearer): wearer = wearer.ref() if self.worn_by == wearer: raise AlreadyWearingError() elif self.is_worn: raise CannotWearError() def on_wear(self, wearer): """ Called after a wearable has been worn by a wearer. """ self.worn_by = wearer.ref() if wearer.isa(Object) and wearer.has_location: self.emit_message('wear', location=wearer.location, actor=wearer) def before_unwear(self, wearer): if self.worn_by != wearer.ref(): raise NotWearingError() def on_unwear(self): prev = self.worn_by self.worn_by = None if self.db.is_valid(prev, Object) and prev.has_location: self.emit_message('unwear', location=prev.location, actor=prev) @hook('before_moved') def __before_moved(self, moving_from, moving_to, by=None, via=None): self.check_wear_status() if self.is_worn: if by == self.worn_by: msg = "You must unwear %s before discarding it." msg = msg % by.name_for(self) else: msg = "%s is currently worn." % self.name raise errors.MoveDenied(self.ref(), moving_from, moving_to, self.ref(), msg=msg)
class FollowableObject(Object): messages = Messages({ 'follow_invite': { 'actor': 'You invite $charlist to follow you.', '*': '{c$actor{n invites $charlist to follow ${actor.him_her}.' }, 'follow_uninvite': { 'actor': 'You stop leading $charlist.', '*': '{c$actor{n stops leading $charlist.' } }) allow_follow = {} followers = [] @hook('after_moved') def __after_moved(self, moved_from, moved_to, by=None, via=None): from mudslingcore.rooms import Exit if self.db.is_valid(via, Exit): self._beckon_followers(via) def _beckon_followers(self, exit): self._prune_followers(ignore_location=True) for follower in self.followers: follower.do_follow(exit) def _prune_allow_follow(self): if 'allow_follow' not in self.__dict__: self.allow_follow = {} remove = [] now = time_utils.unixtime() for char, timestamp in self.allow_follow.iteritems(): if now - timestamp > 120: remove.append(char) for char in remove: del self.allow_follow[char] return remove def _prune_followers(self, ignore_location=False): if 'followers' not in self.__dict__: self.followers = [] prune = set() for f in self.followers: if not f.isa(Follower): prune.add(f) if not ignore_location and f.location != self.location: prune.add(f) for f in prune: self._terminate_follower(f) return prune def allows_following_by(self, char): self._prune_allow_follow() return char in self.allow_follow def follow_invite(self, charlist): self._prune_allow_follow() newly_allowed = [] for char in charlist: if char not in self.allow_follow: newly_allowed.append(char) self.allow_follow[char] = time_utils.unixtime() if newly_allowed: names = RenderList(newly_allowed, format='{g%s{n') self.emit_message('follow_invite', actor=self.ref(), charlist=names) return newly_allowed def follow_uninvite(self, charlist): self._prune_allow_follow() uninvite = [c for c in charlist if c in self.allow_follow or c in self.followers] if uninvite: names = RenderList(uninvite, format='{g%s{n') self.emit_message('follow_uninvite', actor=self.ref(), charlist=names) for char in uninvite: if char in self.allow_follow: del self.allow_follow[char] if char in self.followers: self._terminate_follower(char) return uninvite def gain_follower(self, char): self._prune_followers() if char not in self.followers: self.followers.append(char) def lose_follower(self, char): self._prune_followers() if char in self.followers: self.followers.remove(char) def _terminate_follower(self, follower): """Make something stop following this from this side""" if follower.isa(Follower): follower.stop_following(self.ref()) else: self.lose_follower(follower)
class Exit(areas.AreaExportableBaseObject): """ Core exit class. Transisions an object between two rooms. :ivar source: The room where this exit exists. :ivar dest: The room to which this exit leads. """ #: :type: Room source = None #: :type: Room dest = None _exit_cmd = None messages = Messages({ 'leave': { 'actor': None, '*': "$actor leaves for $exit.", }, 'leave_failed': { 'actor': "You can't go that way." }, 'arrive': { # Shown in destination when transiting this exit. 'actor': None, '*': "$actor has arrived." }, }) def area_export(self, sandbox): export = super(Exit, self).area_export(sandbox) if self.game.db.is_valid(self.source, areas.AreaExportableBaseObject): export['source'] = areas.export_weak_ref(self.source) if self.game.db.is_valid(self.dest, areas.AreaExportableBaseObject): export['dest'] = areas.export_weak_ref(self.dest) return export def area_import(self, data, sandbox): super(Exit, self).area_import(data, sandbox) if 'source' in data: areas.import_weak_ref(self.ref(), 'source', data['source'], sandbox) if 'dest' in data: areas.import_weak_ref(self.ref(), 'dest', data['dest'], sandbox) def emit_message(self, key, exclude=None, **keywords): """ Emit a messate template to the exit's room. Will try to emit the same message key with the suffix '_other_side' on the exit's counterpart on the other side. :param key: The message key to emit. :param exclude: Any objects to exclude from receipt of the message. :param keywords: Keywords for use in the template. :return: List of those objects that received the message. :rtype: list """ suffix = '_other_side' original = not key.endswith(suffix) room = self.source keywords['this'] = self.ref() keywords['room'] = room if original: keywords['origin_exit'] = keywords['this'] keywords['origin_room'] = room msg = self.get_message(key, **keywords) receivers = room.msg_contents(msg, exclude=exclude) counterpart = self.counterpart if original and counterpart is not None: receivers.extend( self.counterpart.emit_message(key + suffix, exclude=exclude, **keywords)) return receivers @property def counterpart(self): """ Find an exit's counterpart in its destination. :return: The corresponding exit back to this exit's source from this exit's destination. :rtype: Exit or None """ v = self.game.db.is_valid if v(self.source, Room) and v(self.dest, Room): counterparts = self.dest.exits_to(self.source) if len(counterparts) > 0: return counterparts[0] return None def invoke(self, obj): """ Check if obj may pass through this exit, and if so, pass obj through this exit. :param obj: The object attempting to pass through the exit. :type obj: mudsling.objects.Object """ if self.transit_allowed(obj): self._move(obj) else: obj.emit( self.get_message('leave_failed', actor=obj, exit=self.ref(), dest=self.dest, room=self.source, source=self.source)) def _invalid_enter_allowed(self, what, exit=None): """ This is used as the enter_allowed call in the event that the destination is not a valid Room. """ what.msg("You cannot walk through an exit to nowhere!") return False def transit_allowed(self, obj): """ Returns True if obj has been allowed to continue on an actual attempt to transit the exit. This is not a simple permission check, it is a permission check AND resulting in-game effects of the actual attempt to transit the exit. :see: Room.leave_allowed and Room.enter_allowed :param obj: The object under consideration. :type obj: mudsling.objects.Object """ # Acquire references to the transition attempt result methods from the # involved rooms, else placeholder functions. leave_allowed = (obj.location.leave_allowed if obj.has_location and obj.location.is_valid(Room) else lambda w, e: True) enter_allowed = (self.dest.enter_allowed if self.dest is not None and self.dest.is_valid(Room) else self._invalid_enter_allowed) me = self.ref() return leave_allowed(obj, me) and enter_allowed(obj, me) def _move(self, obj): """ Pass the object through this exit to its destination. No permission checks -- just do it! :param obj: The object to pass through the exit. :type obj: mudsling.objects.Object """ msg_keys = { 'actor': obj.ref(), 'exit': self.ref(), 'dest': self.dest, 'room': self.source, 'source': self.source, } obj.emit(self.get_message('leave', **msg_keys)) obj.move_to(self.dest, via=self.ref()) if obj.has_location and obj.location == self.dest: obj.emit(self.get_message('arrive', **msg_keys)) def exit_list_name(self): """ Return the string to show when the exit is listed. """ aliases = self.aliases return "%s {c<{n%s{c>{n" % (self.name, aliases[0] if aliases else self.name) def _call_counterpart(self, fname, *a, **kw): c = self.counterpart if c is not None: return getattr(c, fname)(*a, **kw)