"""The base module for L{th.Monster.Monster} species. This module should only be imported by C{th.data.Species}. """ from .. import Monster, Utl, g class Spe(object): """The base class representing a species of L{th.Monster.Monster}. Each Spe instance is a singleton of a Spe subclass. Spe subclasses define a species's attributes and methods. This class should not be instantiated directly. @ivar smart: True if this Spe is intelligent. @ivar human: True if this Spe is a human. @ivar peaceful: True if Monsters of this Spe will only attack if they were attacked first. @ivar chase: True if Monsters of this Spe will chase Monsters that they were attacking. @ivar no_wander: True if Monsters of this Spe do not wander randomly. @ivar gc_immune: True if this Spe is implicitly immune to the Garbage Collector's special one-hit-kill attack. @ivar scavenger: True if Monsters of this Spe will only pick up corpses as food. @ivar teleports: True if this Spe has teleportitis. @ivar headless: True if this Spe has no head or no organic brain. (e.g. a golem or a robot.) @ivar boss: True if this Spe is a unique opponent of the player. @ivar uses: True if this Spe can use items. @ivar wears: True if this Spe can wear armor. @ivar wields: True if this Spe can wield weapons. @ivar eats: True if this Spe can eat. @ivar usable_special: True if the player can activate this Spe's special attack. @ivar auto_special: True if this Spe has a passive special attack. @ivar xenophobic: True if this Spe attacks everything not of its species. @ivar species_id: If a Spe is L{xenophobic}, Monsters of that species will not attack other Monsters with different Spes but the same species_id. @ivar size: An integer representing the size of the monster. See L{th.Monster} for possible values. @ivar die_verb: The action of killing a Monster of this Spe, like "you C{die_verb} the monster." L{th.Ifc} markup is allowed. @ivar attack_verb: The action a Monster of this Spe makes when attacking without a wielded weapon, like "the monster C{attack_verb} the other monster." L{th.Ifc} markup is allowed. @ivar default_ai: Monsters of this Spe will default to this AI state. See L{th.Monster} for possible values. @see: The instance variables of L{th.Monster.Monster}. """ _obj = None def __new__(cls): if not cls._obj: obj = object.__new__(cls) obj.set_default() cls._obj = obj return cls._obj def make(self, **options): """Create a new L{th.Monster.Monster} with this Spe as its species. @param options: Additional attributes to set on the new Monster. These will override the defaults. @returns: L{th.Monster.Monster} """ tmp = Monster.Monster() tmp.species = self tmp.pc = False self.created += 1 if self.gender == 3: tmp.gender = Utl.d(1, 2) else: tmp.gender = self.gender tmp.int = tmp.min = self.int + Utl.rn(-self.vin) tmp.str = tmp.mst = self.str + Utl.rn(-self.vst) tmp.dxcache = tmp.dex = tmp.mdx = self.dex + Utl.rn(-self.vdx) tmp.hp = tmp.mhp = self.hp + Utl.rn(-self.vhp) tmp.resist_chemical = self.resist_chemical tmp.resist_bullets = self.resist_bullets tmp.resist_polymorph = self.resist_polymorph tmp.resist_gas = self.resist_gas tmp.xenophobic = self.xenophobic tmp.target_player_first = self.target_player_first tmp.alignment = self.alignment tmp.ai = self.default_ai tmp.apcache = tmp.ap = self.ap if 'inventory' in options: inventory = options.pop('inventory') else: inventory = [] if self.inventory: for x in xrange(self.nitems): inventory.append((Utl.rn_seq(self.inventory).make())) tmp.init_inventory(inventory) tmp.setup_inventory() if self.rabid_chance > Utl.rn(100): tmp.rabid = True if self.cyborg_chance > Utl.rn(100): tmp.cyborg = True self.xmake(tmp) for key, value in options.items(): setattr(tmp, key, value) if tmp.rabid: tmp.hp = (tmp.hp * 2) / 3 + 1 if tmp.cyborg: tmp.mhp *= 2 tmp.mst += 5 tmp.hp, tmp.str = tmp.mhp, tmp.mst if tmp.gender == 1 and self.male_names: tmp.name = Utl.rn_seq(self.male_names) elif tmp.gender == 2 and self.female_names: tmp.name = Utl.rn_seq(self.female_names) elif self.names: tmp.name = Utl.rn_seq(self.names) tmp.fmt = self.fmt.copy() tmp.fmt.update(Monster.GENDER_FMT[tmp.gender]) g.insertInPlace(tmp) return tmp def set_default(self): """Sets the default attributes on the Spe. This method is used as __init__ might be called multiple times, which would override a Spe's with the defaults if its attributes were changed at runtime. """ self.size = Monster.NORMAL self.boss = False self.uses = self.wears = self.wields = False self.eats = True self.special_talk = False self.resist_chemical = False self.resist_polymorph = False self.resist_bullets = False self.resist_gas = False self.usable_special = False self.die_verb = 'kill' self.created = 0 self.dead = 0 self.name = '' self.adesc = '' self.last_words = '' self.attack_verb = 'attack|s|' self.human = False self.chase = True self.gc_immune = False self.no_wander = False self.gender = 0 self.names = self.male_names = self.female_names = [] self.photos = [] self.level = None self.regeneration = 100 self.auto_special = False self.alignment = 0 self.peaceful = False self.flees = False self.smart = False self.talk = [] self.chatty = False self.default_ai = 0 self.sleep_timeout = 20 self.inventory = [] self.nitems = 0 self.corpse = None self.wake_text = '' self.scavenger = False self.teleports = False self.xenophobic = False self.polymorphable = True self.headless = False self.target_player_first = False self.rabid_chance = 10 self.cyborg_chance = 10 self.int = self.str = self.dex = self.hp = 1 self.vin = self.vst = self.vdx = self.vhp = 0 self.ap = 2 self.difficulty = 0 self.number = -1 self.species_id = None self.fmt = {'s': 's', 'es': 'es', 'do': 'does', 'are': 'is'} self.xinit() if not self.adesc: self.adesc = 'a %s' % self.name def xinit(self): """Called to initialize a Spe to the correct attributes. This will only ever be called once when tunnelhack is first started. Definition blocks in Species.dat are translated into xinit functions. It's simpler to provide this interface than overwrite L{set_default} and require a C{super} call. """ def xmake(self, mon): """Called whenever a new L{th.Monster.Monster} is being initialized with this Spe as its species. @param mon: The Monster being initialized. @type mon: L{th.Monster.Monster} """ def useSpecial(self, mon, target): """Check if a special attack should be used. This method is called to check if a L{th.Monster.Monster} is able to use its special attack. It is always called when a monster attacks. It is also called sometimes for passive special attacks if L{auto_special} is set to True. Calling this method must be non-destructive. @param mon: The Monster which will be attacking. @type mon: L{th.Monster.Monster} @param target: The Monster this Monster is attacking, or L{mon} if L{auto_special} is True and this is a passive special check. @type target: L{th.Monster.Monster} """ return False def doSpecial(self, mon, target): """Do the actual special attack. @see: The parameters of L{useSpecial}. """ def attacked(self, mon, attacker, loc): """Called whenever a L{th.Monster.Monster} of this Spe is attacked. This method is called at the very start of an attack, before anything else happens. @param mon: The Monster being attacked. @type mon: L{th.Monster.Monster} @param attacker: The Monster attacking L{mon}. @type attacker: L{th.Monster.Monster} @param loc: The location where the attack happened. @type loc: L{th.Node.Node} """ def die(self, mon, killer, loc): """Called whenever a L{th.Monster.Monster} of this Spe is killed. This method is called at the very beginning of L{th.Monster.Monster.die}, before anything else happens. @param mon: The Monster being killed. @type mon: L{th.Monster.Monster} @param killer: The Monster which killed L{mon}. @type killer: L{th.Monster.Monster} @param loc: The location where the Monster died. @type loc: L{th.Node.Node} """ def after_die(self, mon, killer, loc): """Called whenever a L{th.Monster.Monster} of this Spe is killed. This method is called at the very end of L{th.Monster.Monster.die}, after everything else has happened. @see: The parameters of L{die}. """ def damaged(self, mon, attacker, loc): """Called whenever a L{th.Monster.Monster} of this Spe takes damage. This method is called at the end of L{th.Monster.Monster.damage}, but before L{th.Monster.Monster.die} is called. @param mon: The Monster being attacked. @type mon: L{th.Monster.Monster} @param attacker: The Monster attacking L{mon}, or C{None} if it was not a Monster attacking. @type attacker: L{th.Monster.Monster} or C{None} @param loc: The location where the attack happened. @type loc: L{th.Node.Node} """ def __and__(self, other): if self.species_id is not None and other.species_id is not None: return self.species_id == other.species_id else: return self is other