Advertisement
Guest User

object.py

a guest
Jul 23rd, 2014
162
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 37.57 KB | None | 0 0
  1. """
  2. MUDtrix - basic objects
  3.  
  4. --------
  5. Objects:
  6. --------
  7. Readable
  8. Talking NPCs
  9. Wand
  10.  
  11. ---------
  12. Commands:
  13. ---------
  14. CmdRead - read readable objects
  15. CmdTalk - talk with NPC's
  16.  
  17. ---------------
  18. List of spells:
  19. ---------------
  20. CmdAvis
  21. CmdArania
  22. """
  23.  
  24. import random, time
  25. from django.conf import settings
  26.  
  27. from ev import Object as DefaultObject
  28. from ev import Exit, Command, CmdSet, Script, default_cmds, search_object, utils
  29. from contrib import menusystem
  30. from game.gamesrc.scripts import script as mudtrix_script
  31.  
  32. BASE_CHARACTER_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
  33.  
  34. #-----------------------------------------------------------------------
  35. # Object class is base class for all other objects
  36. #-----------------------------------------------------------------------
  37. class Object(DefaultObject):
  38. """
  39. The BaseObject class implements several hooks tying into the game
  40. engine. By re-implementing these hooks you can control the
  41. system. You should never need to re-implement special Python
  42. methods, such as __init__ and especially never __getattribute__ and
  43. __setattr__ since these are used heavily by the typeclass system
  44. of Evennia and messing with them might well break things for you.
  45.  
  46.  
  47. * Base properties defined/available on all Objects
  48.  
  49. key (string) - name of object
  50. name (string)- same as key
  51. aliases (list of strings) - aliases to the object. Will be saved to
  52. database as AliasDB entries but returned as strings.
  53. dbref (int, read-only) - unique #id-number. Also "id" can be used.
  54. dbobj (Object, read-only) - link to database model. dbobj.typeclass points
  55. back to this class
  56. typeclass (Object, read-only) - this links back to this class as an
  57. identified only. Use self.swap_typeclass() to switch.
  58. date_created (string) - time stamp of object creation
  59. permissions (list of strings) - list of permission strings
  60.  
  61. player (Player) - controlling player (if any, only set together with
  62. sessid below)
  63. sessid (int, read-only) - session id (if any, only set together with
  64. player above)
  65. location (Object) - current location. Is None if this is a room
  66. home (Object) - safety start-location
  67. sessions (list of Sessions, read-only) - returns all sessions connected
  68. to this object
  69. has_player (bool, read-only)- will only return *connected* players
  70. contents (list of Objects, read-only) - returns all objects inside this
  71. object (including exits)
  72. exits (list of Objects, read-only) - returns all exits from this
  73. object, if any
  74. destination (Object) - only set if this object is an exit.
  75. is_superuser (bool, read-only) - True/False if this user is a superuser
  76.  
  77. * Handlers available
  78.  
  79. locks - lock-handler: use locks.add() to add new lock strings
  80. db - attribute-handler: store/retrieve database attributes on this
  81. self.db.myattr=val, val=self.db.myattr
  82. ndb - non-persistent attribute handler: same as db but does not create
  83. a database entry when storing data
  84. scripts - script-handler. Add new scripts to object with scripts.add()
  85. cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
  86. nicks - nick-handler. New nicks with nicks.add().
  87.  
  88. * Helper methods (see src.objects.objects.py for full headers)
  89.  
  90. search(ostring, global_search=False, attribute_name=None,
  91. use_nicks=False, location=None, ignore_errors=False, player=False)
  92. execute_cmd(raw_string)
  93. msg(text=None, **kwargs)
  94. msg_contents(message, exclude=None, from_obj=None, **kwargs)
  95. move_to(destination, quiet=False, emit_to_obj=None, use_destination=True)
  96. copy(new_key=None)
  97. delete()
  98. is_typeclass(typeclass, exact=False)
  99. swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
  100. access(accessing_obj, access_type='read', default=False)
  101. check_permstring(permstring)
  102.  
  103. * Hooks (these are class methods, so args should start with self):
  104.  
  105. basetype_setup() - only called once, used for behind-the-scenes
  106. setup. Normally not modified.
  107. basetype_posthook_setup() - customization in basetype, after the object
  108. has been created; Normally not modified.
  109.  
  110. at_object_creation() - only called once, when object is first created.
  111. Object customizations go here.
  112. at_object_delete() - called just before deleting an object. If returning
  113. False, deletion is aborted. Note that all objects
  114. inside a deleted object are automatically moved
  115. to their <home>, they don't need to be removed here.
  116.  
  117. at_init() - called whenever typeclass is cached from memory,
  118. at least once every server restart/reload
  119. at_cmdset_get() - this is called just before the command handler
  120. requests a cmdset from this object
  121. at_pre_puppet(player)- (player-controlled objects only) called just
  122. before puppeting
  123. at_post_puppet() - (player-controlled objects only) called just
  124. after completing connection player<->object
  125. at_pre_unpuppet() - (player-controlled objects only) called just
  126. before un-puppeting
  127. at_post_unpuppet(player) - (player-controlled objects only) called just
  128. after disconnecting player<->object link
  129. at_server_reload() - called before server is reloaded
  130. at_server_shutdown() - called just before server is fully shut down
  131.  
  132. at_access(result, accessing_obj, access_type) - called with the result
  133. of a lock access check on this object. Return value
  134. does not affect check result.
  135.  
  136. at_before_move(destination) - called just before moving object
  137. to the destination. If returns False, move is cancelled.
  138. announce_move_from(destination) - called in old location, just
  139. before move, if obj.move_to() has quiet=False
  140. announce_move_to(source_location) - called in new location, just
  141. after move, if obj.move_to() has quiet=False
  142. at_after_move(source_location) - always called after a move has
  143. been successfully performed.
  144. at_object_leave(obj, target_location) - called when an object leaves
  145. this object in any fashion
  146. at_object_receive(obj, source_location) - called when this object receives
  147. another object
  148.  
  149. at_before_traverse(traversing_object) - (exit-objects only)
  150. called just before an object traverses this object
  151. at_after_traverse(traversing_object, source_location) - (exit-objects only)
  152. called just after a traversal has happened.
  153. at_failed_traverse(traversing_object) - (exit-objects only) called if
  154. traversal fails and property err_traverse is not defined.
  155.  
  156. at_msg_receive(self, msg, from_obj=None, **kwargs) - called when a message
  157. (via self.msg()) is sent to this obj.
  158. If returns false, aborts send.
  159. at_msg_send(self, msg, to_obj=None, **kwargs) - called when this objects
  160. sends a message to someone via self.msg().
  161.  
  162. return_appearance(looker) - describes this object. Used by "look"
  163. command by default
  164. at_desc(looker=None) - called by 'look' whenever the
  165. appearance is requested.
  166. at_get(getter) - called after object has been picked up.
  167. Does not stop pickup.
  168. at_drop(dropper) - called when this object has been dropped.
  169. at_say(speaker, message) - by default, called if an object inside this
  170. object speaks
  171.  
  172. """
  173. pass
  174.  
  175. #--------------------------------------------------------------------------------
  176. # Readable objects : The objects one can 'read'
  177. #--------------------------------------------------------------------------------
  178. class CmdRead(Command):
  179. """
  180. Usage:
  181. read [obj]
  182.  
  183. Read some text.
  184. """
  185.  
  186. key = "read"
  187. locks = "cmd:all()"
  188. help_category = "General"
  189.  
  190. def func(self):
  191. "Implement the read command."
  192. if self.args:
  193. obj = self.caller.search(self.args.strip())
  194. else:
  195. obj = self.obj
  196. if not obj:
  197. return
  198. # we want an attribute read_text to be defined.
  199. readtext = obj.db.readable_text
  200. if readtext:
  201. string = "You read {C%s{n:\n %s" % (obj.key, readtext)
  202. else:
  203. string = "There is nothing to read on %s." % obj.key
  204. self.caller.msg(string)
  205.  
  206. class CmdSetReadable(CmdSet):
  207. "CmdSet for readables"
  208. def at_cmdset_creation(self):
  209. "called when object is created."
  210. self.add(CmdRead())
  211.  
  212. class Readable(Object):
  213. """
  214. This object defines some attributes and defines a read method on itself.
  215. """
  216. def at_object_creation(self):
  217. "Called when object is created"
  218. super(Readable, self).at_object_creation()
  219. self.db.readable_text = "There is no text written on %s." % self.key
  220. # define a command on the object.
  221. self.cmdset.add_default(CmdSetReadable, permanent=True)
  222.  
  223. #-----------------------------------------------------------------------------------
  224. # Talking NPC
  225. #-----------------------------------------------------------------------------------
  226. #
  227. # Talk Command
  228. #
  229.  
  230. class CmdTalk(default_cmds.MuxCommand):
  231. """
  232. talks to an npc
  233.  
  234. Usage:
  235. talk
  236.  
  237. This command is only available if a talkative non-player-character (NPC)
  238. is actually present. It will strike up a conversation with that NPC
  239. and give you options on what to talk about.
  240. """
  241. key = "talk"
  242. locks = "cmd:all()"
  243. help_category = "General"
  244.  
  245. def func(self):
  246. "Implements the command."
  247.  
  248. # self.obj is the NPC this is defined on
  249. obj = self.obj
  250.  
  251. self.caller.msg("(You walk up and talk to %s.)" % self.obj.key)
  252.  
  253. # conversation is a dictionary of keys, each pointing to
  254. # a dictionary defining the keyword arguments to the MenuNode
  255. # constructor.
  256. conversation = obj.db.conversation
  257. if not conversation:
  258. self.caller.msg("%s says: 'Sorry, I don't have time to talk right now.'" % (self.obj.key))
  259. return
  260.  
  261. # build all nodes by loading them from the conversation tree.
  262. menu = menusystem.MenuTree(self.caller)
  263. for key, kwargs in conversation.items():
  264. menu.add(menusystem.MenuNode(key, **kwargs))
  265. menu.start()
  266.  
  267.  
  268. class TalkingCmdSet(CmdSet):
  269. "Stores the talk command."
  270. key = "talkingcmdset"
  271.  
  272. def at_cmdset_creation(self):
  273. "populates the cmdset"
  274. self.add(CmdTalk())
  275.  
  276. #
  277. # Discussion tree. See contrib.menusystem.MenuNode for the keywords.
  278. # (This could be in a separate module too)
  279. #
  280.  
  281. CONV = {"START": {"text": "Hello there, how can I help you?",
  282. "links": ["info1", "info2"],
  283. "linktexts": ["Hey, do you know what this 'Evennia' thing is all about?",
  284. "What's your name, little NPC?"],
  285. "keywords": None,
  286. "code": None},
  287. "info1": {"text": "Oh, Evennia is where you are right now! Don't you feel the power?",
  288. "links": ["info3", "info2", "END"],
  289. "linktexts":["Sure, *I* do, not sure how you do though. You are just an NPC.",
  290. "Sure I do. What's yer name, NPC?",
  291. "Ok, bye for now then."],
  292. "keywords": None,
  293. "code": None},
  294. "info2": {"text": "My name is not really important ... I'm just an NPC after all.",
  295. "links": ["info3", "info1"],
  296. "linktexts": ["I didn't really want to know it anyhow.",
  297. "Okay then, so what's this 'Evennia' thing about?"],
  298. "keywords": None,
  299. "code": None},
  300. "info3": {"text": "Well ... I'm sort of busy so, have to go. NPC business. Important stuff. You wouldn't understand.",
  301. "links": ["END", "info2"],
  302. "linktexts": ["Oookay ... I won't keep you. Bye.",
  303. "Wait, why don't you tell me your name first?"],
  304. "keywords": None,
  305. "code": None},
  306. }
  307.  
  308.  
  309. class TalkingNPC(DefaultObject):
  310. """
  311. This implements a simple Object using the talk command and using the
  312. conversation defined above. .
  313. """
  314.  
  315. def at_object_creation(self):
  316. "This is called when object is first created."
  317. # store the conversation.
  318. self.db.conversation = CONV
  319. self.db.desc = "This is a talkative NPC."
  320. # assign the talk command to npc
  321. self.cmdset.add_default(TalkingCmdSet, permanent=True)
  322.  
  323.  
  324. #-----------------------------------------------------------------------------------
  325. # Spells - Wand class, spell commands and cmdset
  326. #-----------------------------------------------------------------------------------
  327. #
  328. # Spells are a set of commands which share common properties like follows
  329. # - Spells work only if the person trying them has a wand,
  330. # - The probability of all spells increase as time passes by,
  331. # - All spells work only after they are 'learnt'.
  332. #
  333. #-----------------------------------------------------------------------------------
  334.  
  335.  
  336. #class Spell(default_cmds.MuxCommand):
  337. # This is the base command for all the spells.
  338. # A thought for later is that this can be used to add to Cmdset all the spells at once.
  339. # pass
  340.  
  341. class CmdSetSpell(CmdSet):
  342. "Holds the attack command."
  343. def at_cmdset_creation(self):
  344. "called at first object creation."
  345. self.add(CmdAvis())
  346. self.add(CmdArania())
  347. self.add(CmdExpelliarmus())
  348.  
  349. class Wand(DefaultObject):
  350. """
  351. This defines the wand which is used for spells.
  352.  
  353. Important attributes - set at creation
  354. hit - chance(or probability) to hit (0-1).
  355. This can be used for changing probability of all spells depending on
  356. score of the player
  357. """
  358. def at_object_creation(self):
  359. "Called at first creation of the object"
  360. super(Wand, self).at_object_creation()
  361. self.db.hit = 0.4 # hit chance
  362. self.db.magic = True
  363. self.cmdset.add_default(CmdSetSpell, permanent=True)
  364.  
  365. def reset(self):
  366. """
  367. When reset, the wand is simply deleted, unless it has a place
  368. to return to.
  369. """
  370. if self.location.has_player and self.home == self.location:
  371. self.location.msg_contents("%s suddenly and magically fades into nothingness, as if it was never there ..." % self.key)
  372. self.delete()
  373. else:
  374. self.location = self.home
  375.  
  376.  
  377. #---------------------------------------------------------------------------------
  378. # List of spells
  379. #---------------------------------------------------------------------------------
  380. # Avis - conjures a flock of birds from the tip of wand.
  381. #---------------------------------------------------------------------------------
  382.  
  383. class CmdAvis(Command):
  384. """
  385. conjures a flock of birds from the tip of wand.
  386.  
  387. Usage:
  388. avis
  389.  
  390. """
  391. key = "avis"
  392. aliases = ["Avis"]
  393. locks = "cmd:holds()"
  394. help_category = "Spells"
  395.  
  396. def func(self):
  397. "Actual function"
  398. hit = float(self.obj.db.hit) * 1.5 # increased the probability of hitting because this is an easy spell.
  399.  
  400. if random.random() <= hit:
  401. self.caller.msg("A flock of birds emerge from your wand. They fly away noisily into nowhere...")
  402. self.caller.location.msg_contents("A heavy cluttering noise distracts you. You see a flock of birds "+
  403. "emerging from {c%s{n's wand. They fly away into nowhere..." %
  404. (self.caller), exclude=[self.caller])
  405. else:
  406. self.caller.msg("You said your spell but nothing happens! Don't worry, say it again with all your heart.")
  407.  
  408. #---------------------------------------------------------------------------------
  409. # Arania Exumai - Kills or attacks Spiders
  410. #---------------------------------------------------------------------------------
  411.  
  412. class CmdArania(Command):
  413. """
  414. attack spiders
  415.  
  416. Usage:
  417. Arania Exumai (aliases: arania)
  418.  
  419. Used to attack and kill huge clusters of spiders
  420. """
  421. key = "arania"
  422. aliases = ["Arania Exumai"]
  423. locks = "cmd:holds()"
  424. help_category = "Spells"
  425.  
  426. def func(self):
  427. "Actual function"
  428. hit = float(self.obj.db.hit)*1.2 # medium difficulty
  429.  
  430. if random.random() <= hit:
  431. self.caller.msg("A {yblast of light{n apears from the tip of the wand.")
  432. self.caller.location.msg_contents("A {yblast of light{n appears from {c%s{n's wand" %
  433. (self.caller), exclude=[self.caller])
  434. # call enemy hook
  435. if self.caller.search("Spider"):
  436. target = self.caller.search("Spider")
  437. else:
  438. self.caller.msg("There are no spiders to attack.")
  439. return
  440. if hasattr(target, "at_hit"):
  441. # should return True if target is defeated, False otherwise.
  442. return target.at_hit(self.obj, self.caller, damage = 10)
  443. elif target.db.health:
  444. target.db.health -= damage
  445. else:
  446. # sorry, impossible to fight this enemy ...
  447. self.caller.msg("The enemy seems unaffacted.")
  448. return False
  449. else:
  450. self.caller.msg("You said your spell but nothing happens! Don't worry, say it with all your heart.")
  451.  
  452. #---------------------------------------------------------------------------------
  453. # Expelliarmus - Throws the opponent's wand
  454. #---------------------------------------------------------------------------------
  455.  
  456. class CmdExpelliarmus(Command):
  457. """
  458. throws away opponent's wand
  459.  
  460. Usage:
  461. Expelliarmus <opponent>
  462. """
  463.  
  464. key = "expelliarmus"
  465. aliases = ["Expelliarmus", "expell", "Expell"]
  466. lock = "cmd:holds()"
  467. help_category = "Spells"
  468.  
  469. def func(self):
  470. "Actual function"
  471. hit = float(self.obj.db.hit)*1.5 # high difficulty
  472.  
  473. if random.random() >= hit:
  474. if self.args:
  475. #target is the opponent whose wand is being targeted
  476. target = self.caller.search(self.args.strip())
  477. else:
  478. return
  479.  
  480. #if an object "Wand" is found with opponent
  481. if target.search(r'Wand'):
  482. wand = target.search(r'Wand')
  483. #wand drops on the room location
  484. #exits = [ex for ex in target.location.exits]
  485. #wand.location = exits[random.randint(0, len(exits) - 1)]
  486. wand.location = target.location
  487. self.caller.msg("A {yflash of light{n comes out of your wand and %s's wand falls down" %(target))
  488. self.caller.location.msg_contents("A {yflash of light{n emerging from {c%s{n's wand" % (self.caller), exclude=[self.caller,target])
  489. target.msg("{c%s{n hits your wand with a {ylightning storm{n and your wand falls off somewhere" %(self.caller))
  490. else:
  491. return
  492.  
  493. else:
  494. self.caller.msg("You said your spell but nothing happens! Don't worry, aim properly and say it with all your heart.")
  495.  
  496.  
  497. #---------------------------------------------------------------------------------
  498. # Arania Exumai - Kills or attacks Spiders
  499. #---------------------------------------------------------------------------------
  500.  
  501. class CmdArania(Command):
  502. """
  503. levitates objects into air
  504.  
  505. Usage:
  506. Wingardium Leviosa <target>
  507.  
  508. (aliases: leviosa)
  509.  
  510. """
  511. key = "Wingardium Leviosa"
  512. aliases = ["leviosa", "wingardium"]
  513. locks = "cmd:holds()"
  514. help_category = "Spells"
  515.  
  516. def func(self):
  517. "Actual function"
  518.  
  519. # If no target is given
  520. if not self.args:
  521. self.caller.msg("Specify the target.")
  522. return
  523.  
  524. # Lower the hit rate
  525. hit = float(self.obj.db.hit)*1.2 # high difficulty
  526.  
  527. if random.random() <= hit:
  528. self.caller.msg("A {yblast of light{n apears from the tip of the wand.")
  529. self.caller.location.msg_contents("A {yblast of light{n appears from {c%s{n's wand" %
  530. (self.caller), exclude=[self.caller])
  531. # call target
  532. if self.caller.search(self.args):
  533. target = self.caller.search(self.args)
  534. return
  535. if hasattr(target, "at_hit"):
  536. # should return True if target is defeated, False otherwise.
  537. return target.at_hit(self.obj, self.caller, damage = 10)
  538. elif target.db.health:
  539. target.db.health -= damage
  540. else:
  541. # sorry, impossible to fight this enemy ...
  542. self.caller.msg("The enemy seems unaffacted.")
  543. return False
  544. else:
  545. self.caller.msg("You said your spell but nothing happens! Don't worry, say it with all your heart.")
  546.  
  547.  
  548. #-----------------------------------------------------------------------------------
  549. # Mob - Mobile Enemy Object
  550. #---------------------------------------------------------------------------------
  551.  
  552. class Mob(Object):
  553. """
  554. This type of mobile will roam from exit to exit at
  555. random intervals. Simply lock exits against the is_mob attribute
  556. to block them from the mob (lockstring = "traverse:not attr(is_mob)").
  557. """
  558. def at_object_creation(self):
  559. "This is called when the object is first created."
  560. #self.db.info = "This is a moving object. It moves randomly from room to room."
  561.  
  562. self.scripts.add(mudtrix_script.IrregularEvent)
  563. # this is a good attribute for exits to look for, to block
  564. # a mob from entering certain exits.
  565. self.db.is_mob = True
  566. self.db.last_location = None
  567. # only when True will the mob move.
  568. self.db.roam_mode = True
  569.  
  570. def announce_move_from(self, destination):
  571. "Called just before moving"
  572. self.location.msg_contents("%s drifts in the direction of %s." % (self.key, destination.key))
  573.  
  574. def announce_move_to(self, source_location):
  575. "Called just after arriving"
  576. self.location.msg_contents("%s appears from the %s." % (self.key, source_location.key))
  577.  
  578. def update_irregular(self):
  579. "Called at irregular intervals. Moves the mob."
  580. if self.roam_mode:
  581. exits = [ex for ex in self.location.exits
  582. if ex.access(self, "traverse")]
  583. if exits:
  584. # Try to make it so the mob doesn't backtrack.
  585. new_exits = [ex for ex in exits
  586. if ex.destination != self.db.last_location]
  587. if new_exits:
  588. exits = new_exits
  589. self.db.last_location = self.location
  590. # execute_cmd() allows the mob to respect exit and
  591. # exit-command locks, but may pose a problem if there is more
  592. # than one exit with the same name.
  593. # - see Enemy example for another way to move
  594. self.execute_cmd("%s" % exits[random.randint(0, len(exits) - 1)].key)
  595.  
  596.  
  597. #------------------------------------------------------------
  598. #
  599. # Enemy - mobile attacking object
  600. #
  601. # An enemy is a mobile that is aggressive against players
  602. # in its vicinity. An enemy will try to attack characters
  603. # in the same location. It will also pursue enemies through
  604. # exits if possible.
  605. #
  606. # An enemy needs to have a Weapon object in order to
  607. # attack.
  608. #
  609. #------------------------------------------------------------
  610.  
  611. class AttackTimer(Script):
  612. """
  613. This script is what makes an eneny "tick".
  614. """
  615. def at_script_creation(self):
  616. "This sets up the script"
  617. self.key = "AttackTimer"
  618. self.desc = "Drives an Enemy's combat."
  619. self.interval = random.randint(2, 3) # how fast the Enemy acts
  620. self.start_delay = True # wait self.interval before first call
  621. self.persistent = True
  622.  
  623. def at_repeat(self):
  624. "Called every self.interval seconds."
  625. if self.obj.db.inactive:
  626. return
  627. #print "attack timer: at_repeat", self.dbobj.id, self.ndb.twisted_task,
  628. # id(self.ndb.twisted_task)
  629. if self.obj.db.roam_mode:
  630. self.obj.roam()
  631. #return
  632. elif self.obj.db.battle_mode:
  633. #print "attack"
  634. self.obj.attack()
  635. return
  636. elif self.obj.db.pursue_mode:
  637. #print "pursue"
  638. self.obj.pursue()
  639. #return
  640.  
  641. else:
  642. #dead mode. Wait for respawn.
  643. if not self.obj.db.dead_at:
  644. self.obj.db.dead_at = time.time()
  645. if (time.time() - self.obj.db.dead_at) > self.obj.db.dead_timer:
  646. self.obj.reset()
  647.  
  648. class Spider(Mob):
  649. """
  650. This is a monster with health (hit points).
  651.  
  652. Spiders can be in four modes:
  653. roam (inherited from Mob) - where it just moves around randomly
  654. battle - where it stands in one place and attacks players
  655. pursue - where it follows a player, trying to enter combat again
  656. dead - passive and invisible until it is respawned
  657.  
  658. Upon creation, the following attributes describe the enemy's actions
  659. desc - description
  660. full_health - integer number > 0
  661. defeat_location - unique name or #dbref to the location the player is
  662. taken when defeated. If not given, will remain in room.
  663. defeat_text - text to show player when they are defeated (just before
  664. being whisped away to defeat_location)
  665. defeat_text_room - text to show other players in room when a player
  666. is defeated
  667. win_text - text to show player when defeating the enemy
  668. win_text_room - text to show room when a player defeates the enemy
  669. respawn_text - text to echo to room when the mob is reset/respawn in
  670. that room.
  671.  
  672. """
  673. def at_object_creation(self):
  674. "Called at object creation."
  675. super(Spider, self).at_object_creation()
  676.  
  677. #self.db.info = "This spider will attack players in the same room."
  678.  
  679. # state machine modes
  680. self.db.roam_mode = True
  681. self.db.battle_mode = False
  682. self.db.pursue_mode = False
  683. self.db.dead_mode = False
  684. # health (change this at creation time)
  685. self.db.full_health = 20
  686. self.db.health = 20
  687. self.db.dead_at = time.time()
  688. self.db.dead_timer = 100 # how long to stay dead
  689. # this is used during creation to make sure the mob doesn't move away
  690. self.db.inactive = True
  691. # store the last player to hit
  692. self.db.last_attacker = None
  693. # where to take defeated enemies
  694. #self.db.defeat_location = "darkcell"
  695. self.scripts.add(AttackTimer)
  696.  
  697. def update_irregular(self):
  698. "the irregular event is inherited from Mob class"
  699. strings = self.db.irregular_echoes
  700. if strings:
  701. self.location.msg_contents(strings[random.randint(0, len(strings) - 1)])
  702.  
  703. def roam(self):
  704. "Called by Attack timer. Will move randomly as long as exits are open."
  705.  
  706. # in this mode, the mob is healed.
  707. self.db.health = self.db.full_health
  708. players = [obj for obj in self.location.contents
  709. if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
  710. if players:
  711. # we found players in the room. Attack.
  712. self.db.roam_mode = False
  713. self.db.pursue_mode = False
  714. self.db.battle_mode = True
  715.  
  716. elif random.random() < 0.2:
  717. # no players to attack, move about randomly.
  718. exits = [ex.destination for ex in self.location.exits
  719. if ex.access(self, "traverse")]
  720. if exits:
  721. # Try to make it so the mob doesn't backtrack.
  722. new_exits = [ex for ex in exits
  723. if ex.destination != self.db.last_location]
  724. if new_exits:
  725. exits = new_exits
  726. self.db.last_location = self.location
  727. # locks should be checked here
  728. self.move_to(exits[random.randint(0, len(exits) - 1)])
  729. else:
  730. # no exits - a dead end room. Respawn back to start.
  731. self.move_to(self.home)
  732.  
  733. def attack(self):
  734. """
  735. This is the main mode of combat. It will try to hit players in
  736. the location. If players are defeated, it will whisp them off
  737. to the defeat location.
  738. """
  739. last_attacker = self.db.last_attacker
  740. players = [obj for obj in self.location.contents
  741. if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
  742. if players:
  743.  
  744. # find a target
  745. if last_attacker in players:
  746. # prefer to attack the player last attacking.
  747. target = last_attacker
  748. else:
  749. # otherwise attack a random player in location
  750. target = players[random.randint(0, len(players) - 1)]
  751.  
  752. # try to use the weapon in hand
  753. attack_cmds = ("pierce", "slash") #attention required
  754. cmd = attack_cmds[random.randint(0, len(attack_cmds) - 1)]
  755. self.execute_cmd("%s %s" % (cmd, target))
  756.  
  757. # analyze result.
  758. if target.db.health <= 0:
  759. # we reduced enemy to 0 health. Whisp them off to
  760. # the prison room.
  761. tloc = search_object(self.db.defeat_location)
  762. tstring = self.db.defeat_text
  763. if not tstring:
  764. tstring = "You feel your conciousness slip away ... you fall to the ground as "
  765. tstring += "the spiders envelops you ...\n"
  766. target.msg(tstring)
  767. ostring = self.db.defeat_text_room
  768. if tloc:
  769. if not ostring:
  770. ostring = "\n%s envelops the fallen ... and then their body is suddenly gone!" % self.key
  771. # silently move the player to defeat location
  772. # (we need to call hook manually)
  773. target.location = tloc[0]
  774. tloc[0].at_object_receive(target, self.location)
  775. elif not ostring:
  776. ostring = "%s falls to the ground!" % target.key
  777. self.location.msg_contents(ostring, exclude=[target])
  778. # Pursue any stragglers after the battle
  779. self.battle_mode = False
  780. self.roam_mode = False
  781. self.pursue_mode = True
  782. else:
  783. # no players found, this could mean they have fled.
  784. # Switch to pursue mode.
  785. self.battle_mode = False
  786. self.roam_mode = False
  787. self.pursue_mode = True
  788.  
  789. def pursue(self):
  790. """
  791. In pursue mode, the enemy tries to find players in adjoining rooms, preferably
  792. those that previously attacked it.
  793. """
  794. last_attacker = self.db.last_attacker
  795. players = [obj for obj in self.location.contents if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
  796. if players:
  797. # we found players in the room. Maybe we caught up with some,
  798. # or some walked in on us before we had time to pursue them.
  799. # Switch to battle mode.
  800. self.battle_mode = True
  801. self.roam_mode = False
  802. self.pursue_mode = False
  803. else:
  804. # find all possible destinations.
  805. destinations = [ex.destination for ex in self.location.exits
  806. if ex.access(self, "traverse")]
  807. # find all players in the possible destinations. OBS-we cannot
  808. # just use the player's current position to move the Enemy; this
  809. # might have changed when the move is performed, causing the enemy
  810. # to teleport out of bounds.
  811. players = {}
  812. for dest in destinations:
  813. for obj in [o for o in dest.contents
  814. if utils.inherits_from(o, BASE_CHARACTER_TYPECLASS)]:
  815. players[obj] = dest
  816. if players:
  817. # we found targets. Move to intercept.
  818. if last_attacker in players:
  819. # preferably the one that last attacked us
  820. self.move_to(players[last_attacker])
  821. else:
  822. # otherwise randomly.
  823. key = players.keys()[random.randint(0, len(players) - 1)]
  824. self.move_to(players[key])
  825. else:
  826. # we found no players nearby. Return to roam mode.
  827. self.battle_mode = False
  828. self.roam_mode = True
  829. self.pursue_mode = False
  830.  
  831. def at_hit(self, weapon, attacker, damage):
  832. """
  833. Called when this object is hit by an enemy's weapon
  834. Should return True if enemy is defeated, False otherwise.
  835.  
  836. In the case of players attacking, we handle all the events
  837. and information from here, so the return value is not used.
  838. """
  839.  
  840. self.db.last_attacker = attacker
  841. if not self.db.battle_mode:
  842. # we were attacked, so switch to battle mode.
  843. self.db.roam_mode = False
  844. self.db.pursue_mode = False
  845. self.db.battle_mode = True
  846. #self.scripts.add(AttackTimer)
  847.  
  848. if not weapon.db.magic:
  849. # In the tutorial, the enemy is a ghostly apparition, so
  850. # only magical weapons can harm it.
  851. string = self.db.weapon_ineffective_text
  852. if not string:
  853. string = "Your weapon just passes through your enemy, causing no effect!"
  854. attacker.msg(string)
  855. return
  856. else:
  857. # an actual hit
  858. health = float(self.db.health)
  859. health -= damage
  860. self.db.health = health
  861. if health <= 0:
  862. string = self.db.win_text
  863. if not string:
  864. string = "After your last hit, %s fold in on itself. " % self.key
  865. string += "In a moment they pause their creepy motion. But you have a "
  866. string += "feeling it is only temporarily weakened. "
  867. string += "You fear it's only a matter of time before it comes into life somewhere again."
  868. attacker.msg(string)
  869. string = self.db.win_text_room
  870. if not string:
  871. string = "After %s's last hit, %s fold in on itself. " % (attacker.name, self.key)
  872. string += "In a moment they pause their creepy motion. But you have a "
  873. string += "feeling it is only temporarily weakened. "
  874. string += "You fear it's only a matter of time before it comes into life somewhere again."
  875. self.location.msg_contents(string, exclude=[attacker])
  876.  
  877. # put mob in dead mode and hide it from view.
  878. # AttackTimer will bring it back later.
  879. self.db.dead_at = time.time()
  880. self.db.roam_mode = False
  881. self.db.pursue_mode = False
  882. self.db.battle_mode = False
  883. self.db.dead_mode = True
  884. self.location = None
  885. else:
  886. self.location.msg_contents("%s wails, shudders and writhes." % self.key)
  887. return False
  888.  
  889. def reset(self):
  890. """
  891. If the mob was 'dead', respawn it to its home position and reset
  892. all modes and damage."""
  893. if self.db.dead_mode:
  894. self.db.health = self.db.full_health
  895. self.db.roam_mode = True
  896. self.db.pursue_mode = False
  897. self.db.battle_mode = False
  898. self.db.dead_mode = False
  899. self.location = self.home
  900. string = self.db.respawn_text
  901. if not string:
  902. string = "%s fades into existence from out of thin air. It's looking pissed." % self.key
  903. self.location.msg_contents(string)
  904.  
  905. #--------------------------------------------------------------------------------------------------------
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement