text: """
  Characters
  
  Characters are (by default) Objects setup to be puppeted by Players.
  They are what you "see" in game. The Character class in this module
  is setup to be the "default" character type created by the default
  creation commands.
  
  """
  from evennia.objects.objects import DefaultCharacter
  from typeclasses.mixins import MsgMixins, ObjectMixins, NameMixins
  from typeclasses.wearable.mixins import UseEquipmentMixins
  from world.msgs.messagehandler import MessageHandler
  from world.msgs.languagehandler import LanguageHandler
  from evennia.utils.utils import lazy_property, variable_from_module
  import time
  from world.stats_and_skills import do_dice_check
  from world.magic.mixins import MagicMixins
  
  
  class Character(UseEquipmentMixins, NameMixins, MsgMixins, ObjectMixins, MagicMixins, DefaultCharacter):
      """
      The Character defaults to reimplementing some of base Object's hook methods with the
      following functionality:
  
      at_basetype_setup - always assigns the DefaultCmdSet to this object type
                      (important!)sets locks so character cannot be picked up
                      and its commands only be called by itself, not anyone else.
                      (to change things, use at_object_creation() instead).
      at_after_move - Launches the "look" command after every move.
      at_post_unpuppet(player) -  when Player disconnects from the Character, we
                      store the current location in the pre_logout_location Attribute and
                      move it to a None-location so the "unpuppeted" character
                      object does not need to stay on grid. Echoes "Player has disconnected"
                      to the room.
      at_pre_puppet - Just before Player re-connects, retrieves the character's
                      pre_logout_location Attribute and move it back on the grid.
      at_post_puppet - Echoes "PlayerName has entered the game" to the room.
  
      """
      def at_object_creation(self):
          """
          Called once, when this object is first created.
          """
          # setting up custom attributes for ArxMUSH
          # BriefMode is for toggling brief descriptions from rooms
          self.db.briefmode = False
          self.db.gender = "Female"
          self.db.age = 20
          self.db.concept = "None"
          self.db.fealty = "None"
          self.db.marital_status = "single"
          self.db.family = "None"
          self.db.dice_string = "Default Dicestring"
          self.db.health_status = "alive"
          self.db.sleep_status = "awake"
          self.db.skills = {}
          self.db.abilities = {}
          self.at_init()
          self.locks.add("delete:perm(Immortals);tell:all()")
  
      @property
      def is_character(self):
          return True
  
      @lazy_property
      def messages(self):
          return MessageHandler(self)
  
      @lazy_property
      def languages(self):
          return LanguageHandler(self)
  
      def at_after_move(self, source_location, **kwargs):
          """
          Hook for after movement. Look around, with brief determining how much detail we get.
          :param source_location: Room
          :return:
          """
          table = self.db.sitting_at_table
          if table and source_location != self.location:
              table.leave(self)
          if self.db.briefmode:
              string = ""
              # handle cases of self.location being None or not a Room object
              try:
                  string = "{c%s{n" % self.location.name
                  string += self.location.return_contents(self, show_places=False)
                  string += self.location.event_string()
              except AttributeError:
                  pass
              self.msg(string)
          else:
              self.msg(self.at_look(self.location))
          if self.ndb.waypoint:
              traversed = self.ndb.traversed or []
              try:
                  traversed.append(source_location.id)
              except AttributeError:
                  pass
              self.ndb.traversed = list(set(traversed))
              if self.location == self.ndb.waypoint:
                  self.msg("You have reached your destination.")
                  self.ndb.waypoint = None
                  self.ndb.traversed = []
                  return
              dirs = self.get_directions(self.ndb.waypoint)
              if dirs:
                  self.msg("You sense your destination lies through the %s." % dirs)
              else:
                  self.msg("You've lost track of how to get to your destination.")
                  self.ndb.waypoint = None
                  self.ndb.traversed = []
          if self.ndb.following and self.ndb.following.location != self.location:
              self.stop_follow()
          if self.db.room_title:
              self.attributes.remove("room_title")
          if self.combat.combat and self in self.combat.combat.ndb.observers:
              self.combat.combat.remove_observer(self)
          if self.location:
              self.location.triggerhandler.check_room_entry_triggers(self)
  
      def return_appearance(self, pobject, detailed=False, format_desc=False, show_contents=False):
          """
          This is a convenient hook for a 'look'
          command to call.
          """
          if not pobject:
              return
          # get and identify all objects
          if pobject is self or pobject.check_permstring("builders"):
              detailed = True
          strip_ansi = pobject.db.stripansinames
          string = "{c%s{n" % self.get_fancy_name()
          # Health appearance will also determine whether we
          # use an alternate appearance if we are dead.
          health_appearance = self.get_health_appearance()
          # desc is our current appearance, can be fake. self.perm_desc is 'true' form
          desc = self.desc
          # to do: check to see through disguises
          if strip_ansi:
              try:
                  from evennia.utils.ansi import parse_ansi
                  desc = parse_ansi(desc, strip_ansi=True)
              except (AttributeError, ValueError, TypeError, UnicodeDecodeError):
                  pass
          script = self.appearance_script
          if desc:
              extras = self.return_extras(pobject)
              if extras:
                  extras += "\n"
              string += "\n\n%s%s" % (extras, desc)
          if script:
              scent = script.db.scent
              if scent:
                  string += "\n\n%s{n" % scent
          if health_appearance:
              string += "\n\n%s" % health_appearance
          string += self.return_contents(pobject, detailed, strip_ansi=strip_ansi)
          return string
  
      @property
      def species(self):
          return self.db.species or "Human"
  
      @property
      def appearance_script(self):
          scripts = self.scripts.get("Appearance")
          if scripts:
              return scripts[0]
  
      def return_extras(self, pobject):
          """
          Return a string from glancing at us
          :param pobject: Character
          :return:
          """
          mask = self.db.mask
          if not mask:
              hair = self.db.haircolor or ""
              eyes = self.db.eyecolor or ""
              skin = self.db.skintone or ""
              height = self.db.height or ""
              species = self.species
              gender = self.db.gender or ""
              age = self.db.age
          else:
              hair = mask.db.haircolor or "--"
              eyes = mask.db.eyecolor or "--"
              skin = mask.db.skintone or "--"
              height = mask.db.height or "--"
              species = mask.db.species or "--"
              gender = mask.db.gender or "--"
              age = mask.db.age or "--"
          hair = hair.capitalize()
          eyes = eyes.capitalize()
          skin = skin.capitalize()
          gender = gender.capitalize()
          if pobject.check_permstring("builders"):
              true_age = self.db.real_age
              if true_age and true_age != age:
                  pobject.msg("{wThis true age is:{n %s" % true_age)
          string = """
  {w.---------------------->Physical Characteristics<---------------------.{n
  {w|                                                                     |{n
  {w| Species:{n %(species)-14s {wGender:{n %(gender)-15s {wAge:{n %(age)-15s{w|{n
  {w| Height:{n %(height)-15s {wEye Color:{n %(eyes)-15s                  {w|{n
  {w| Hair Color:{n %(hair)-11s {wSkin Tone:{n %(skin)-17s                {w|{n
  {w.---------------------------------------------------------------------.{n
  """ % ({'species': species, 'hair': hair, 'eyes': eyes, 'height': height, 'gender': gender, 'age': age, 'skin': skin})
          return string
  
      def death_process(self, *args, **kwargs):
          """
          This object dying. Set its state to dead, send out
          death message to location. Add death commandset.
          """
          if self.db.health_status == "dead":
              return
          self.db.health_status = "dead"
          self.db.container = True
          if self.location:
              self.location.msg_contents("{r%s has died.{n" % self.name)
          try:
              from commands.cmdsets import death
              cmds = death.DeathCmdSet
              if cmds.key not in [ob.key for ob in self.cmdset.all()]:
                  self.cmdset.add(cmds, permanent=True)
          except Exception as err:
              print("<<ERROR>>: Error when importing death cmdset: %s" % err)
          from server.utils.arx_utils import inform_staff
          if not self.db.npc:
              inform_staff("{rDeath{n: Character {c%s{n has died." % self.key)
  
      def resurrect(self, *args, **kwargs):
          """
          Cue 'Bring Me Back to Life' by Evanessence.
          """
          self.db.health_status = "alive"
          self.db.container = False
          if self.location:
              self.location.msg_contents("{w%s has returned to life.{n" % self.name)
          try:
              from commands.cmdsets import death
              self.cmdset.delete(death.DeathCmdSet)
          except Exception as err:
              print("<<ERROR>>: Error when importing mobile cmdset: %s" % err)
          # we'll also be asleep when we're dead, so that we're resurrected unconscious if we're brought back
          self.fall_asleep(uncon=True, quiet=True)
  
      def fall_asleep(self, uncon=False, quiet=False, verb=None, **kwargs):
          """
          Falls asleep. Uncon flag determines if this is regular sleep,
          or unconsciousness.
          """
          reason = " is %s and" % verb if verb else ""
          if uncon:
              self.db.sleep_status = "unconscious"
          else:
              self.db.sleep_status = "asleep"
          if self.location and not quiet:
              self.location.msg_contents("%s%s falls %s." % (self.name, reason, self.db.sleep_status))
          try:
              from commands.cmdsets import sleep
              cmds = sleep.SleepCmdSet
              if cmds.key not in [ob.key for ob in self.cmdset.all()]:
                  self.cmdset.add(cmds, permanent=True)
          except Exception as err:
              print("<<ERROR>>: Error when importing sleep cmdset: %s" % err)
  
      @property
      def conscious(self):
          return ((self.db.sleep_status != "unconscious" and self.db.sleep_status != "asleep")
                  and self.db.health_status != "dead")
  
      def wake_up(self, quiet=False):
          """
          Wakes up.
          """
          if self.db.health_status == "dead":
              return
          if self.location:
              if not quiet and not self.conscious:
                  self.location.msg_contents("%s wakes up." % self.name)
          try:
              from commands.cmdsets import sleep
              self.cmdset.delete(sleep.SleepCmdSet)
          except Exception as err:
              print("<<ERROR>>: Error when importing mobile cmdset: %s" % err)
          self.db.sleep_status = "awake"
          return
  
      def recovery_test(self, diff_mod=0, free=False):
          """
          A mechanism for healing characters. Whenever they get a recovery
          test, they heal the result of a willpower+stamina roll, against
          a base difficulty of 0. diff_mod can change that difficulty value,
          and with a higher difficulty can mean it can heal a negative value,
          resulting in the character getting worse off. We go ahead and change
          the player's health now, but leave the result of the roll in the
          caller's hands to trigger other checks - death checks if we got
          worse, unconsciousness checks, whatever.
          """
          # no helping us if we're dead
          if self.db.health_status == "dead":
              return
          diff = 0 + diff_mod
          roll = do_dice_check(self, stat_list=["willpower", "stamina"], difficulty=diff)
          self.change_health(roll)
          if not free:
              self.db.last_recovery_test = time.time()
          return roll
  
      def change_health(self, amount, quiet=False, affect_real_dmg=True, wake=True):
          """
          Change character's health and maybe tell them about it.
          Positive amount will 'heal'. Negative will 'harm'.
          A character with hp to attempt waking up.
          """
          difference = self.get_health_percentage(abs(amount))
          if not quiet:
              msg = "You feel "
              if difference <= 0:
                  msg += "no "
              elif difference <= 0.1:
                  msg += "a little "
              elif difference <= 0.25:
                  pass
              elif difference <= 0.5:
                  msg += "a lot "
              elif difference <= 0.75:
                  msg += "significantly "
              else:
                  msg += "profoundly "
              msg += "better" if amount > 0 else "worse"
              punctuation = "." if difference < 0.5 else "!"
              self.msg(msg + punctuation)
          if affect_real_dmg:
              self.real_dmg -= amount
          else:
              self.temp_dmg -= amount
          if difference:
              self.triggerhandler.check_health_change_triggers(amount)
          if wake and self.dmg <= self.max_hp and self.db.sleep_status != "awake":
              self.wake_up()
  
      def get_health_percentage(self, damage=None):
          """Returns the float percentage of the health. If damage is not specified, we use self.dmg"""
          if damage is None:
              damage = self.dmg
          return float(damage) / float(self.max_hp)
  
      def get_health_appearance(self):
          """
          Return a string based on our current health.
          """
          wounds = self.get_health_percentage()
          msg = "%s " % self.name
          if self.db.health_status == "dead":
              return msg + "is currently dead."
          elif wounds <= 0:
              msg += "is in perfect health"
          elif 0 < wounds <= self.death_threshold:
              msg += "seems to have %s injuries" % self.get_wound_descriptor(self.dmg)
          else:
              msg += "is in critical condition - possibly dying"
          sleep_status = self.db.sleep_status
          if sleep_status and sleep_status != "awake":
              msg += ", and is %s" % sleep_status
          return msg + "."
  
      def get_wound_descriptor(self, dmg):
          wound = self.get_health_percentage(dmg)
          if wound <= 0:
              wound_desc = "no"
          elif wound <= 0.1:
              wound_desc = "minor"
          elif 0.1 < wound <= 0.25:
              wound_desc = "moderate"
          elif 0.25 < wound <= 0.5:
              wound_desc = "serious"
          elif 0.5 < wound <= 0.75:
              wound_desc = "severe"
          elif 0.75 < wound < 2.0:
              wound_desc = "grievous"
          else:
              wound_desc = "grave"
          return wound_desc
  
      def sensing_check(self, difficulty=15, invis=False, allow_wake=False):
          """
          See if the character detects something that is hiding or invisible.
          The difficulty is supplied by the calling function.
          Target can be included for additional situational
          """
          if not self.conscious and not allow_wake:
              return -100
          roll = do_dice_check(self, stat="perception", stat_keep=True, difficulty=difficulty)
          return roll
  
      def get_fancy_name(self, short=False, display_mask=True):
          """
          Returns either an illusioned name, a long_name with titles, or our key.
          """
          if self.db.false_name and display_mask:
              return self.db.false_name
          if not short and self.db.longname:
              return self.db.longname
          return self.db.colored_name or self.key
  
      @property
      def max_hp(self):
          """Returns our max hp"""
          hp = self.db.stamina or 0
          hp *= 20
          hp += 20
          bonus = self.db.bonus_max_hp or 0
          hp += bonus
          hp += self.boss_rating * 100
          return hp
  
      @property
      def death_threshold(self):
          """
          Multiplier on how much higher our damage must be than our max health for us to
          roll to survive dying.
  
          Returns:
              float: Multiplier before death checks happen
          """
          return 1.25
  
      @property
      def dmg(self):
          """Returns how much damage we've taken."""
          return self.real_dmg + self.temp_dmg
  
      @dmg.setter
      def dmg(self, value):
          self.real_dmg = value
  
      # alias for dmg
      damage = dmg
  
      @property
      def temp_dmg(self):
          if self.ndb.temp_dmg is None:
              self.ndb.temp_dmg = 0
          return self.ndb.temp_dmg
  
      @temp_dmg.setter
      def temp_dmg(self, value):
          self.ndb.temp_dmg = value
  
      @property
      def real_dmg(self):
          return self.db.damage or 0
  
      @real_dmg.setter
      def real_dmg(self, dmg):
          if dmg < 1:
              dmg = 0
          self.db.damage = dmg
          self.start_recovery_script()
  
      def start_recovery_script(self):
          # start the script if we have damage
          start_script = self.dmg > 0
          scripts = [ob for ob in self.scripts.all() if ob.key == "Recovery"]
          if scripts:
              if start_script:
                  scripts[0].start()
              else:
                  scripts[0].stop()
          elif start_script:
              self.scripts.add("typeclasses.scripts.recovery.Recovery")
  
      @property
      def xp(self):
          return self.db.xp or 0
  
      @xp.setter
      def xp(self, value):
          self.db.xp = value
  
      def adjust_xp(self, value):
          """
          Spend or earn xp. Total xp keeps track of all xp we've earned on this
          character, and isn't lowered by spending xp. Checks for having sufficient
          xp should be before this takes place, so we'll raise an exception if they
          can't pay the cost.
          """
          if not self.db.total_xp:
              self.db.total_xp = 0
          if not self.xp:
              self.xp = 0
          if value > 0:
              self.db.total_xp += value
              try:
                  self.roster.adjust_xp(value)
              except (AttributeError, ValueError, TypeError):
                  pass
          else:
              if self.xp < abs(value):
                  raise ValueError("Bad value passed to adjust_xp -" +
                                   " character did not have enough xp to pay for the value.")
          self.xp += value
  
      def follow(self, targ):
          if not targ.ndb.followers:
              targ.ndb.followers = []
          targ.msg("%s starts to follow you. To remove them as a follower, use 'ditch'." % self.name)
          if self not in targ.ndb.followers:
              targ.ndb.followers.append(self)
          self.msg("You start to follow %s. To stop following, use 'follow' with no arguments." % targ.name)
          self.ndb.following = targ
  
      def stop_follow(self):
          f_targ = self.ndb.following
          if not f_targ:
              return
          self.msg("You stop following %s." % f_targ.name)
          if f_targ.ndb.followers:
              try:
                  f_targ.ndb.followers.remove(self)
                  f_targ.msg("%s stops following you." % self.name)
              except (ValueError, TypeError, AttributeError):
                  pass
          self.ndb.following = None
  
      def msg_watchlist(self, msg):
          """
          Sends a message to all players who are watching this character if
          we are not hiding from watch.
          """
          watchers = self.db.watched_by or []
          pc = self.player_ob
          if not pc:
              return
          if not watchers or pc.db.hide_from_watch:
              return
          for watcher in watchers:
              spam = watcher.ndb.journal_spam or []
              if self not in spam:
                  watcher.msg(msg)
                  spam.append(self)
                  watcher.ndb.journal_spam = spam
  
      def _get_max_support(self):
          try:
              dompc = self.player_ob.Dominion
              remaining = 0
              for member in dompc.memberships.filter(deguilded=False):
                  remaining += member.pool_share
              for ren in dompc.renown.all():
                  remaining += ren.level
          except (TypeError, AttributeError, ValueError):
              return 0
          interval = self.social_clout
          multiplier = 1.0
          total = 0
          if interval <= 0:
              return 0
          while multiplier > 0:
              if interval >= remaining:
                  total += remaining * multiplier
                  return int(total)
              total += interval * multiplier
              multiplier -= 0.25
              remaining -= interval
          return int(total)
      max_support = property(_get_max_support)
  
      @property
      def social_clout(self):
          """Another representation of social value of a character"""
          total = 0
          my_skills = self.db.skills or {}
          skills_used = {"diplomacy": 2, "empathy": 2, "seduction": 2, "etiquette": 2, "manipulation": 2, "propaganda": 2,
                         "intimidation": 1, "leadership": 1, "streetwise": 1, "performance": 1, "haggling": 1}
          stats_used = {"charm": 2, "composure": 1, "command": 1}
          for skill, exponent in skills_used.items():
              total += pow(my_skills.get(skill, 0), exponent)
          for stat, exponent in stats_used.items():
              total += pow((self.attributes.get(stat) or 0), exponent)
          return total // 5
  
      @property
      def guards(self):
          if self.db.assigned_guards is None:
              self.db.assigned_guards = []
          return self.db.assigned_guards
  
      def remove_guard(self, guard):
          """
          This discontinues anything we were using the guard for.
          Args:
              guard: Previously a guard, possibly a retainer.
          """
          if guard in self.guards:
              self.guards.remove(guard)
          if self.messages.custom_messenger == guard:
              self.messages.custom_messenger = None
  
      @property
      def num_guards(self):
          return sum(ob.quantity for ob in self.guards)
  
      @property
      def present_guards(self):
          return [ob for ob in self.guards if ob.location == self.location]
  
      @property
      def num_armed_guards(self):
          try:
              return sum([ob.num_armed_guards for ob in self.present_guards])
          except TypeError:
              return 0
  
      @property
      def max_guards(self):
          try:
              return 15 - (self.db.social_rank or 10)
          except TypeError:
              return 5
  
      def get_directions(self, room):
          """
          Uses the ObjectDB manager and repeated related_set calls in order
          to find the exit in the current room that directly points to it.
          """
          loc = self.location
          if not loc:
              return
          x_ori = loc.db.x_coord
          y_ori = loc.db.y_coord
          x_dest = room.db.x_coord
          y_dest = room.db.y_coord
          check_exits = []
          try:
              x = x_dest - x_ori
              y = y_dest - y_ori
              dest = ""
              if y > 0:
                  dest += "north"
              if y < 0:
                  dest += "south"
              check_exits.append(dest)
              if x > 0:
                  dest += "east"
                  check_exits.append("east")
              if x < 0:
                  dest += "west"
                  check_exits.append("west")
              if abs(x) > abs(y):
                  check_exits.reverse()
              # inserts the NE/SE/SW/NW direction at 0 to be highest priority
              check_exits.insert(0, dest)
              for dirname in check_exits:
                  if loc.locations_set.filter(db_key__iexact=dirname).exclude(db_destination__in=self.ndb.traversed or []):
                      return "{c" + dirname + "{n"
              dest = "{c" + dest + "{n roughly. Please use '{w@map{n' to determine an exact route"
          except (AttributeError, TypeError, ValueError):
              print("Error in using directions for rooms: %s, %s" % (loc.id, room.id))
              print("origin is (%s,%s), destination is (%s, %s)" % (x_ori, y_ori, x_dest, y_dest))
              self.msg("Rooms not properly set up for @directions. Logging error.")
              return
          # try to find it through traversal
          base_query = "db_destination_id"
          exit_name = []
          iterations = 0
          # anything beyond 10 squares becomes extremely lengthy
          max_iter = 5
          exit_ids = [ob.id for ob in loc.exits]
          q_add = ""
          from django.db.models import Q
          exclude_ob = Q()
  
          def get_new_exclude_ob():
              """Helper function to build Q() objects to exclude"""
              base_exclude_query = "db_tags__db_key"
              other_exclude_query = {q_add + "db_destination_id": loc.id}
              traversed_query = {q_add + "db_destination_id__in": self.ndb.traversed or []}
              exclude_query = q_add + base_exclude_query
              exclude_dict = {exclude_query: "secret"}
              return Q(**exclude_dict) | Q(**other_exclude_query) | Q(**traversed_query)
  
          while not exit_name and iterations < max_iter:
              q_add = "db_destination__locations_set__" * iterations
              query = q_add + base_query
              filter_dict = {query: room.id}
              exclude_ob |= get_new_exclude_ob()
              q_ob = Q(Q(**filter_dict) & ~exclude_ob)
              exit_name = loc.locations_set.distinct().filter(id__in=exit_ids).exclude(exclude_ob).filter(q_ob)
              iterations += 1
          if not exit_name:
              return "{c" + dest + "{n"
          return "{c" + str(exit_name[0]) + "{n"
  
      def at_post_puppet(self):
          """
          Called just after puppeting has completed.
  
          :type self: Character
          """
  
          super(Character, self).at_post_puppet()
          try:
              self.messages.messenger_notification(2, force=True)
          except (AttributeError, ValueError, TypeError):
              import traceback
              traceback.print_exc()
  
          guards = self.guards
          for guard in guards:
              if guard.discreet:
                  continue
              docked_location = guard.db.docked
              if docked_location and docked_location == self.location:
                  guard.summon()
  
      def at_post_unpuppet(self, player, session=None, **kwargs):
          """
          We stove away the character when the player goes ooc/logs off,
          otherwise the character object will remain in the room also after the
          player logged off ("headless", so to say).
  
          :type self: Character
          :type player: Player
          :type session: Session
          """
          super(Character, self).at_post_unpuppet(player, session)
          if not self.sessions.count():
              table = self.db.sitting_at_table
              if table:
                  table.leave(self)
              guards = self.guards
              for guard in guards:
                  try:
                      if guard.location and 'persistent_guard' not in guard.tags.all():
                          guard.dismiss()
                  except AttributeError:
                      continue
  
      @property
      def portrait(self):
          from web.character.models import Photo
          try:
              return self.roster.profile_picture
          except (AttributeError, Photo.DoesNotExist):
              return None
  
      def get_absolute_url(self):
          from django.core.urlresolvers import reverse
          return reverse('character:sheet', kwargs={'object_id': self.id})
  
      @lazy_property
      def combat(self):
          from typeclasses.scripts.combat.combatant import CombatHandler
          return CombatHandler(self)
  
      def view_stats(self, viewer, combat=False):
          from commands.base_commands.roster import display_stats, display_skills, display_abilities
          display_stats(viewer, self)
          display_skills(viewer, self)
          display_abilities(viewer, self)
          if combat:
              viewer.msg(self.combat.display_stats())
  
      @property
      def posecount(self):
          return self.db.pose_count or 0
  
      @posecount.setter
      def posecount(self, val):
          self.db.pose_count = val
  
      @property
      def previous_posecount(self):
          return self.db.previous_posecount or 0
  
      @previous_posecount.setter
      def previous_posecount(self, val):
          self.db.previous_posecount = val
  
      @property
      def total_posecount(self):
          return self.posecount + self.previous_posecount
  
      def announce_move_from(self, destination, msg=None, mapping=None, **kwargs):
          """
          Called if the move is to be announced. This is
          called while we are still standing in the old
          location.
          Args:
              destination (Object): The place we are going to.
              msg (str, optional): a replacement message.
              mapping (dict, optional): additional mapping objects.
          """
          def format_string(viewer):
              if msg:
                  return msg
              if secret:
                  return "%s is leaving." % self.get_display_name(viewer)
              else:
                  return "%s is leaving, heading for %s." % (self.get_display_name(viewer),
                                                             destination.get_display_name(viewer))
          if not self.location:
              return
          secret = False
          if mapping:
              secret = mapping.get('secret', False)
          for obj in self.location.contents:
              if obj != self:
                  string = format_string(obj)
                  obj.msg(string)
  
      def announce_move_to(self, source_location, msg=None, mapping=None, **kwargs):
          """
          Called after the move if the move was not quiet. At this point
          we are standing in the new location.
  
          Args:
              source_location (Object): The place we came from
              msg (str, optional): the replacement message if location.
              mapping (dict, optional): additional mapping objects.
  
          You can override this method and call its parent with a
          message to simply change the default message.  In the string,
          you can use the following as mappings (between braces):
              object: the object which is moving.
              exit: the exit from which the object is moving (if found).
              origin: the location of the object before the move.
              destination: the location of the object after moving.
  
          """
          if not source_location and self.location.has_player:
              # This was created from nowhere and added to a player's
              # inventory; it's probably the result of a create command.
              string = "You now have %s in your possession." % self.get_display_name(self.location)
              self.location.msg(string)
              return
  
          secret = False
          if mapping:
              secret = mapping.get('secret', False)
  
          def format_string(viewer):
              if msg:
                  return msg
              if secret:
                  return "%s arrives." % self.get_display_name(viewer)
              else:
                  from_str = " from %s" % source_location.get_display_name(viewer) if source_location else ""
                  return "%s arrives%s." % (self.get_display_name(viewer), from_str)
  
          for obj in self.location.contents:
              if obj != self:
                  string = format_string(obj)
                  obj.msg(string)
  
      @property
      def can_crit(self):
          try:
              if self.roster.roster.name == "Active":
                  return True
              else:
                  return False
          except AttributeError:
              return False
  
      @property
      def titles(self):
          full_titles = self.db.titles or []
          return ", ".join(str(ob) for ob in full_titles)
  
      @property
      def is_npc(self):
          if self.tags.get("npc"):
              return True
          try:
              if self.roster.roster.name == "Unavailable":
                  return True
          except AttributeError:
              pass
          return False
  
      @property
      def attackable(self):
          return not bool(self.tags.get("unattackable"))
  
      @property
      def boss_rating(self):
          try:
              return int(self.db.boss_rating)
          except (TypeError, ValueError):
              return 0
  
      @boss_rating.setter
      def boss_rating(self, value):
          self.db.boss_rating = value
  
      @property
      def sleepless(self):
          """Cannot fall unconscious - undead, etc"""
          return bool(self.tags.get("sleepless"))
  
      @property
      def defense_modifier(self):
          return self.db.defense_modifier or 0
  
      @defense_modifier.setter
      def defense_modifier(self, value):
          self.db.defense_modifier = value
  
      @property
      def attack_modifier(self):
          base = self.db.attack_modifier or 0
          return base + (self.boss_rating * 10)
  
      @attack_modifier.setter
      def attack_modifier(self, value):
          self.db.attack_modifier = value
  
      def search(self,  # type: Character
                 searchdata, global_search=False, use_nicks=True, typeclass=None, location=None,
                 attribute_name=None, quiet=False, exact=False, candidates=None, nofound_string=None,
                 multimatch_string=None, use_dbref=True):
          from django.conf import settings
          # if we're staff, we just use the regular search method
          if self.check_permstring("builders"):
              return super(Character, self).search(searchdata, global_search=global_search, use_nicks=use_nicks,
                                                   typeclass=typeclass, location=location,
                                                   attribute_name=attribute_name, quiet=quiet, exact=exact,
                                                   candidates=candidates, nofound_string=nofound_string,
                                                   multimatch_string=multimatch_string, use_dbref=use_dbref)
          # we're not staff. We get search results, then throw out matches of people wearing masks that were by their key
          results = super(Character, self).search(searchdata, global_search=global_search, use_nicks=use_nicks,
                                                  typeclass=typeclass, location=location, attribute_name=attribute_name,
                                                  quiet=True, exact=exact, candidates=candidates,
                                                  nofound_string=nofound_string, multimatch_string=multimatch_string,
                                                  use_dbref=use_dbref)
          # we prune results of keys for masked (false_name) objects in results
          results = [ob for ob in results if not ob.db.false_name or searchdata.lower() != ob.key.lower()]
          # quiet means that messaging is handled elsewhere
          if quiet:
              return results
          if location == self:
              nofound_string = nofound_string or "You don't carry '%s'." % searchdata
              multimatch_string = multimatch_string or "You carry more than one '%s':" % searchdata
          # call the _AT_SEARCH_RESULT func to transform our results and send messages
          _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
          return _AT_SEARCH_RESULT(results, self, query=searchdata,  nofound_string=nofound_string,
                                   multimatch_string=multimatch_string)
  
      def can_be_trained_by(self, trainer):
          """
          Checks if we can be trained by trainer. If False, send a message to trainer and let them know why. The default
          implementation will just return True, but this is overridden in Retainers, for example.
  
          Args:
              trainer: Character to check training
  
          Returns:
              True if we can be trained, False otherwise.
          """
          if self.db.trainer:
              trainer.msg("They have already been trained.")
              return False
          return True
  
      def post_training(self, trainer, trainer_msg="", targ_msg="", **kwargs):
          """
          Handles bookkeeping after this character is trained.
  
          Args:
              trainer: Character that trained us.
              trainer_msg (str): Message to send to trainer
              targ_msg (str): Message to send to this Character
  
          Returns:
              True if everything went off. Used for trying to catch extremely elusive caching errors.
          """
          from server.utils.arx_utils import trainer_diagnostics
          currently_training = trainer.db.currently_training or []
          # num_trained is redundancy to attempt to prevent cache errors.
          num_trained = trainer.db.num_trained or len(currently_training)
          if num_trained < len(currently_training):
              num_trained = len(currently_training)
          num_trained += 1
          self.db.trainer = trainer
          currently_training.append(self)
          trainer.db.currently_training = currently_training
          trainer.db.num_trained = num_trained
          if trainer_msg:
              trainer.msg(trainer_msg)
          if targ_msg:
              self.msg(targ_msg)
          print "Character.post_training call: %s" % trainer_diagnostics(trainer)
          return True
  
      def show_online(self, caller, check_puppet=True):
          """
  
          Args:
              caller: Player who is checking to see if they can see us online
              check_puppet: Whether the Character needs an active puppet to show as online
  
          Returns:
              True if we're online and the player has privileges to see us. False otherwise
          """
          if check_puppet:
              if not self.sessions.all():
                  return False
              player = self.player
          else:
              player = self.player_ob
          if not player:
              return False
          if not player.db.hide_from_watch:
              return True
          if caller.check_permstring("builders"):
              return True
          # we're hiding from watch and caller is not staff, so they don't see us online
          return False
  
      @property
      def glass_jaw(self):
          return False
  
      @property
      def valid_actions(self):
          from world.dominion.models import PlotAction
          from django.db.models import Q
          return PlotAction.objects.filter(Q(dompc=self.dompc) | Q(assistants=self.dompc)).exclude(
              status=PlotAction.CANCELLED).distinct()
  
      @property
      def past_actions(self):
          return self.player_ob.past_actions
  
      @property
      def past_participated_actions(self):
          return self.player_ob.past_participated_actions
  
      @property
      def recent_actions(self):
          return self.player_ob.recent_actions
  
      @property
      def recent_assists(self):
          return self.player_ob.recent_assists
  
      @property
      def skills(self):
          if self.db.skills is None:
              self.db.skills = {}
          return self.db.skills
  
      @property
      def truesight(self):
          return self.check_permstring("builders") or self.tags.get("story_npc")
  
      def get_display_name(self, looker, **kwargs):
          if not self.is_disguised:
              return super(Character, self).get_display_name(looker, **kwargs)
          name = self.name
          try:
              if looker.truesight:
                  name = "%s (%s)" % (self.name, self.key)
                  if looker.check_permstring("builders"):
                      name += "(%s)" % self.id
          except AttributeError:
              pass
          return name
  
      @property
      def dompc(self):
          """Returns our Dominion object"""
          return self.player_ob.Dominion
  
      @property
      def secrets(self):
          from web.character.models import Clue
          return self.roster.clue_discoveries.filter(clue__clue_type=Clue.CHARACTER_SECRET,
                                                     clue__tangible_object=self).exclude(clue__desc="").distinct()
  
      def at_magic_exposure(self, alignment=None, affinity=None, strength=10):
          if not self.practitioner:
              return
  
          if not alignment:
              from world.magic.models import Alignment
              alignment = Alignment.PRIMAL
  
          self.practitioner.at_magic_exposure(alignment=alignment, affinity=affinity, strength=strength)
  