Changeset 131

Show
Ignore:
Timestamp:
04/26/08 13:53:53 (2 years ago)
Author:
elghinn
Message:

* [ThibG] many changes + images
* reviewed ThibG's patch

Location:
adventure
Files:
10 added
2 removed
7 modified

Legend:

Unmodified
Added
Removed
  • adventure/README

    r94 r131  
    1 What is this? 
     1Notes: 
     2------ 
    23 
    3 This is an example of what you can do with XMPP4R. It is a conferencing 
    4 component in which you can walk around, travel to various places, look 
    5 at things and talk to other visitors on the same places. If you like 
    6 Multi-User Dungeons (MUDs) this is for you! 
     4tower/{bad,cat,spider}.svg are from Pierre-Marie de Rodat <pmdomine@orange.fr> (JID: pmdomine@im.apinc.org). His images are under GPLv3 (as the rest of this project). 
    75 
    8 --- 
     6tower/rana01_architetto_france_01.svg is from openclipart.org and under Public Domain. 
    97 
    10 How does it work? 
    11  
    12 The component loads a few worlds from a few XML files. Each world is a 
    13 component. Once joined the chat will tell you what you can do. Remember 
    14 that you can get a command listing anytime by saying '?'. 
    15  
    16 Reading 'You can go north, west' you may say 'go north' to go in the 
    17 northern direction and 'go west' to go in the western direction. You'll 
    18 then find yourself at some other place but still in the same MUC 
    19 conference. Your groupchat roster will change as you'll notice different 
    20 people and things that are just in the same place, not other places. 
    21  
    22 Before starting to hack the scripts you may want to take a look at 
    23 tower.xml. Note that users are <thing/>s, too and the whole world could 
    24 be serialized back to XML by just issuing "world.to_s". 
    25  
    26 Please note that the code, especially the error handling, is of extreme 
    27 poor quality and was mostly written in one afternoon. If I'm going to 
    28 develop this further everything should be rewritten... 
    29  
    30 --- 
    31  
    32 How to try? 
    33  
    34 Because this is a component you are going to need your own Jabber 
    35 Daemon - which you'll need anyways if you're going to experiment with 
    36 XMPP4R. ;-) 
    37  
    38 Syntax: 
    39 ./adventure.rb <JID> <Password> <Host> 
    40  
    41 Example: 
    42 ./adventure.rb mud.example.com geheimnis localhost 
    43  
    44 --- 
    45  
    46 Messages seem to have random order? 
    47  
    48 I don't know any solution for this. One may add short delays between 
    49 messages, but that would only be a very dirty hack. 
    50  
    51 RFC3920: 
    52  
    53 "10.  Server Rules for Handling XML Stanzas 
    54  
    55 Compliant server implementations MUST ensure in-order processing of 
    56 XML stanzas between any two entities." 
    57  
  • adventure/adventuremuc.py

    r121 r131  
    44from sys import argv 
    55from world import World 
    6 from xmpp import Component, JID, NS_COMPONENT_ACCEPT 
     6from xmpp import (Component, JID, NS_COMPONENT_ACCEPT, NS_DATA, NS_VCARD, Node, 
     7                  NodeProcessed) 
    78from xmpp_error import Error 
    89 
     
    4344        self.send(stanza) 
    4445 
    45     def handle_iq(self, _, iq_): 
     46    def handle_iq(self, _, iq): 
    4647        """ 
    4748        Handle iq stanza 
    4849        """ 
    49         #print "iq: from #{iq.from} type #{iq.type} to #{iq.to}: #{iq.queryns}" 
     50        if not iq.getQueryNS(): 
     51            # If not a query, is it a vCard? 
     52            vcard = iq.getTag('vCard') 
    5053 
    51         #if iq.query.kind_of?(Jabber::Discovery::IqQueryDiscoInfo): 
    52         #    handle_disco_info(iq) 
    53         #    return True 
    54         #elif iq.query.kind_of?(Jabber::Discovery::IqQueryDiscoItems): 
    55         #    handle_disco_items(iq) 
    56         #    return True 
    57         #else: 
    58         #    return False 
    59         pass 
     54            if vcard: 
     55                # Resolve world and player involved 
     56                world_name = iq.getTo().getNode() 
     57                player_name = iq.getTo().getResource() 
     58                world = self.worlds[world_name] 
     59                player = None 
     60                for thing in (world.players + world.npcs): 
     61                    if thing.name == player_name: 
     62                        player = thing 
     63                if player_name == 'Bag': 
     64                    player = world.bag 
     65                # Reply 
     66                answer = iq.buildReply('result') 
     67                if iq.getType() == 'get' and player is not None: 
     68                    if player.vcard is not None: 
     69                        answer.addChild(node=player.vcard) 
     70                    else: 
     71                        answer.setTag(NS_VCARD + ' vCard') 
     72                self.send(answer) 
     73                raise NodeProcessed 
     74                     
     75        elif (iq.getQueryNS().endswith('#info') or 
     76              iq.getQueryNS().endswith('#items')): 
     77            self.handle_disco_info(iq) 
     78            raise NodeProcessed 
     79 
     80    def handle_disco_info(self, iq): 
     81        if iq.getType() != 'get' : 
     82            #answer = iq.buildReply('error') 
     83            answer = Error(iq, 'bad-request') 
     84            self.send(answer) 
     85            return 
     86        answer = iq.buildReply('result') 
     87        if not iq.getTo().getNode(): 
     88            query = answer.getTag('query') 
     89            if not query: 
     90                query = answer.setTag('query') 
     91            query.setTag('identity', attrs = {'category': 'conference', 
     92                                              'type': 'text', 
     93                                              'name': 'MUD-MUC'}) 
     94            query.setTag('feature', 
     95                         attrs = {'var': 'http://jabber.org/protocol/muc' }) 
     96        elif self.worlds.has_key(iq.getTo().getNode()): 
     97            world_name = iq.getTo().getNode() 
     98            query = answer.getTag('query') 
     99            if not query: 
     100                query = answer.setTag('query') 
     101            query.setTag('identity', attrs = {'category': 'conference', 
     102                                              'type': 'text', 
     103                                              'name': world_name}) 
     104            query.setTag('feature', attrs = 
     105                         {'var': 'http://jabber.org/protocol/muc' }) 
     106            query.setTag('feature', attrs = {'var': 'muc_public' }) 
     107            query.setTag('feature', attrs = {'var': 'muc_persistent' }) 
     108            query.setTag('feature', attrs = {'var': 'muc_open' }) 
     109            query.setTag('feature', attrs = {'var': 'muc_semianonymous' }) 
     110            query.setTag('feature', attrs = {'var': 'muc_unmoderated' }) 
     111            query.setTag('feature', attrs = {'var': 'muc_unsecured' }) 
     112            data = query.setTag(NS_DATA + ' x', attrs = {'type': 'result'}) 
     113            field = data.setTag('field', attrs = {'type': 'hidden', 
     114                                                  'var': 'FORM_TYPE'}) 
     115            field.addChild(node=Node('value', {}, 
     116                                     'http://jabber.org/protocol/muc#roominfo')) 
     117             
     118            field = data.setTag('field', 
     119                                attrs = {'label': 'Description', 
     120                                         'var': 'muc#roominfo_description'}) 
     121            field.addChild(node=Node('value', {}, self.worlds[world_name].name)) 
     122             
     123            field = data.setTag('field', 
     124                                attrs = {'label': 'Number of occupants', 
     125                                         'var': 'muc#roominfo_occupants'}) 
     126            field.addChild(node=Node('value', {}, 
     127                                     str(len(self.worlds[world_name].players)))) 
     128        self.send(answer) 
    60129   
    61     #def handle_disco_info(iq): 
    62     #    if iq.type != :get : 
    63     #        answer = iq.answer 
    64     #        answer.type = :error 
    65     #        answer.add(Jabber::Error.new('bad-request')) 
    66     #        self.send(answer) if iq.type != :error 
    67     #        return 
    68     #    answer = iq.answer 
    69     #    answer.type = :result 
    70     #    if iq.to.node == nil: 
    71     #        answer.query.add(Jabber::Discovery::Identity.new('conference', 'Adventure component', 'text')) 
    72     #        answer.query.add(Jabber::Discovery::Feature.new(Jabber::Discovery::IqQueryDiscoInfo.new.namespace)) 
    73     #        answer.query.add(Jabber::Discovery::Feature.new(Jabber::Discovery::IqQueryDiscoItems.new.namespace)) 
    74     #    else: 
    75     #        world = self.worlds[iq.to.node] 
    76     #        if world.nil? : 
    77     #            answer.type = :error 
    78     #            answer.query.add(Jabber::Error.new('item-not-found', 'The world you are trying to reach is currently unavailable.')) 
    79     #        else: 
    80     #            answer.query.add(Jabber::Discovery::Identity.new('conference', world.iname, 'text')) 
    81     #            answer.query.add(Jabber::Discovery::Feature.new(Jabber::Discovery::IqQueryDiscoInfo.new.namespace)) 
    82     #            answer.query.add(Jabber::Discovery::Feature.new(Jabber::Discovery::IqQueryDiscoItems.new.namespace)) 
    83     #            answer.query.add(Jabber::Discovery::Feature.new(Jabber::MUC::XMUC.new.namespace)) 
    84     #            answer.query.add(Jabber::Discovery::Feature.new(Jabber::MUC::XMUCUser.new.namespace)) 
    85     #    self.send(answer) 
    86    
    87     #def handle_disco_items(iq): 
    88     #    if iq.type != :get : 
    89     #        answer = iq.answer 
    90     #        answer.add(Jabber::Error.new('bad-request')) 
    91     #        self.send(answer) 
    92     #        return 
    93     #    answer = iq.answer 
    94     #    answer.type = :result 
    95     #    if iq.to.node == nil: 
    96     #        self.worlds.each { |node,world| 
    97     #                      answer.query.add(Jabber::Discovery::Item.new(Jabber::JID::new(String::new(node), self.component.jid.domain), world.iname)) 
    98     #                      } 
    99     #    self.send(answer) 
     130    def handle_disco_items(self, iq): 
     131        if iq.getType() != 'get': 
     132            self.send(answer) 
     133            return 
     134        answer = iq.buildReply('result') 
     135        if not iq.getTo().getNode(): 
     136            query = answer.getTag('query') 
     137            if not query: 
     138                query = answer.setTag('query') 
     139            for name, i in self.worlds.items(): 
     140                query.setTag('item', 
     141                             attrs = {'jid': JID(node=i.node, 
     142                                                 domain=self.jid.getDomain()), 
     143                                      'name': name}) 
     144        self.send(answer) 
    100145 
    101146    def handle_presence(self, _, pres): 
     
    103148        Handle presence stanza 
    104149        """ 
    105         print 'presence: from %s type %s to %s' % (pres.getFrom(), 
    106                                                    pres.getType(), 
    107                                                    pres.getTo()) 
     150        print 'presence: from %s type %s to %s' % (unicode(pres.getFrom()), 
     151                                                   unicode(pres.getType()), 
     152                                                   unicode(pres.getTo())) 
    108153 
    109154        world = pres.getTo().getNode() 
     
    144189    MUD = AdventureMUC(argv[1], argv[2], argv[3], int(argv[4])) 
    145190    MUD.add_world('cave.xml') 
     191    MUD.add_world('tower/tower.xml') 
    146192    while 1: 
    147193        MUD.Process(10) 
  • adventure/cave.xml

    r121 r131  
    1 <world node='cave' name='A mysterious dark cave' start='cave_1'> 
     1<world node='cave' name='A mysterious dark cave' start='cave (1)'> 
    22 
    3     <place name='cave_1'> 
     3    <place name='cave (1)'> 
    44        <description>You are in the dark, but you can hear a little sound from the East, and the wind from the West.</description> 
    5         <go spec="east" place="cave_2" /> 
    6         <go spec="west" place="cave_3" /> 
     5        <go spec="east" place="cave (2)" /> 
     6        <go spec="west" place="cave (3)" /> 
     7         
     8        <npc name="Wizard"> 
     9            <on-enter> 
     10                <narration>%self% looks at %actor%...</narration> 
     11                <say>Hello %actor%! I'm the greatest Wizard in the World! 
     12                Welcome to this strange place! 
     13                If you need help, talk to me!</say> 
     14            </on-enter> 
     15             
     16            <presence> 
     17                <show>available</show> 
     18                <status>A strange man</status> 
     19            </presence> 
     20             
     21            <commands> 
     22                <command name="talk to %self%" aliases="I need help"><say>So, you want some help? Type "?"! 
     23                It will list all the available commands!</say></command> 
     24            </commands> 
     25        </npc> 
    726    </place> 
    827     
    9     <place name="cave_2"> 
     28    <place name="cave (2)"> 
    1029        <description>There is a strange man, lying on the floor, with a happy face.</description> 
    11         <go spec="west" place="cave_1" /> 
     30        <go spec="west" place="cave (1)" /> 
     31         
     32        <npc name="Drunk man" aliases='the man,man'> 
     33            <on-enter> 
     34                <say>...!</say> 
     35            </on-enter> 
     36         
     37            <commands> 
     38                <command name="look at %self%"><narration> A drunk man with a bottle </narration></command> 
     39                <command name="talk to %self%" signal="Bottle_taken"><say> ...! </say></command> 
     40                <command name="hit %self%"><narration> He didn't move... Don't drink as much as him...</narration></command> 
     41            </commands> 
     42         
     43            <presence> 
     44                <show>dnd</show> 
     45                <status>A man lying on the floor</status> 
     46            </presence> 
     47        </npc> 
     48         
     49        <thing name="bottle" aliases='the bottle'> 
     50                <commands> 
     51                    <command name="pick up %self%"> 
     52                        <narration> %actor% take the bottle </narration> 
     53                         <signal>Bottle_taken</signal> 
     54                         <give>bottle</give> 
     55                    </command> 
     56                </commands> 
     57        </thing> 
    1258    </place> 
    1359     
    14     <place name="cave_3"> 
     60    <place name="cave (3)"> 
    1561        <description>You can see a light ray from the North.</description> 
    16         <go spec="east" place="cave_1" /> 
    17         <go spec="north" place="cave_4" /> 
     62        <go spec="east" place="cave (1)" /> 
     63        <go spec="north" place="cave (4)" /> 
    1864    </place> 
    1965     
    20     <place name="cave_4"> 
     66    <place name="cave (4)"> 
    2167        <description>There is a fire, and a stupid rat... 
    2268        No exit, make what you want, but please don't hurt any animal.</description> 
    23         <go spec="south" place="cave_3" /> 
    24     </place> 
    25      
    26     <thing name="fire" place="cave_4"> 
    27         <commands> 
    28             <command name="look at"><narration>A wood fire...</narration></command> 
    29         </commands> 
    30     </thing> 
    31      
    32     <npc name="Rat" aliases="stupid rat, ugly rat" place="cave_4" respawn="60"> 
    33      
    34         <presence> 
    35             <show>chat</show> 
    36             <status>Happy Rat</status> 
    37         </presence> 
     69        <go spec="south" place="cave (3)" /> 
    3870         
    39         <commands> 
    40             <command name="look at"><narration>An ugly rat</narration></command> 
    41             <command name="use %s on" objects="bottle"> 
    42                 <use target="bottle" /> 
    43                 <narration>%actor% use alcool bottle on %self%</narration> 
    44                 <say>Gniii!</say> 
    45                 <narration>%self% run away, and go through the fire. 
    46                 The rat whas ugly, its uglier now, don't watch.</narration> 
    47                 <destroy /> 
    48             </command> 
    49         </commands> 
    50     </npc> 
    51  
    52     <npc name="Drunk man" aliases='the man, man' place='cave_2'> 
    53         <on-enter> 
    54             <say>...!</say> 
    55         </on-enter> 
    56      
    57         <commands> 
    58             <command name="look at"><narration> A drunk man with a bottle </narration></command> 
    59             <command name="talk to"><say> ...! </say></command> 
    60             <command name="hit"><narration> He didn't move... Don't drink as much as him...</narration></command> 
    61         </commands> 
    62      
    63         <presence> 
    64             <show>dnd</show> 
    65             <status>A man lying on the floor</status> 
    66         </presence> 
    67          
    68         <thing inherit="bottle" respawn='20'> 
     71        <thing name="fire"> 
    6972            <commands> 
    70                 <command name="take"> 
    71                     <narration> %actor% take the bottle </narration> 
    72                      <say target="Drunk man"> ...! </say> 
    73                      <give /> 
    74                 </command> 
    75                 <inherit command="look at"/> 
     73                <command name="look at %self%"><narration>A wood fire...</narration></command> 
    7674            </commands> 
    7775        </thing> 
    78      
    79     </npc> 
    80  
    81     <thing name='bottle'/> 
     76         
     77        <npc name="Rat" aliases="stupid rat,ugly rat" respawn="60"> 
     78         
     79            <presence> 
     80                <show>chat</show> 
     81                <status>Happy Rat</status> 
     82            </presence> 
     83             
     84            <commands> 
     85                <command name="look at %self%"><narration>An ugly rat</narration></command> 
     86                <command name="use %s on %self%" objects="bottle"> 
     87                    <use target="actor">bottle</use> 
     88                    <narration>%actor% use alcool bottle on %self%</narration> 
     89                    <say>Gniii!</say> 
     90                    <narration>%self% run away, and go through the fire. 
     91                    The rat was ugly, its uglier now, don't watch.</narration> 
     92                    <destroy /> 
     93                </command> 
     94            </commands> 
     95        </npc> 
     96    </place> 
    8297 
    8398</world> 
  • adventure/place.py

    r118 r131  
    11# -*- coding: utf-8 -*- 
    22 
     3#  MUDMUC 
     4#  place.py 
     5#  Copyright (c) 2008 Thibaut Girka, Anaël Verrier 
     6 
     7#  This program is free software; you can redistribute it and/or modify 
     8#  it under the terms of the GNU General Public License as published by 
     9#  the Free Software Foundation; version 3 only. 
     10 
     11#  This program is distributed in the hope that it will be useful, 
     12#  but WITHOUT ANY WARRANTY; without even the implied warranty of 
     13#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     14#  GNU General Public License for more details. 
     15 
     16#  You should have received a copy of the GNU General Public License 
     17#  along with this program; if not, write to the Free Software 
     18#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
     19 
     20from xmpp import Message, Presence, NS_MUC_USER 
     21 
    322class Place: 
    4     def __init__(self, name, description): 
     23    def __init__(self, world, name, description): 
     24        self.world = world 
    525        self.name = name 
    626        self.description = description 
    7         self.visitors = list() 
    8         self.participants = list() 
    9         self.objects = list() 
    10         self.respawn = dict() # {'object name': timestamp_to_respawn, ...} 
    11         self.exits = dict() # {'name': 'place_name', ...} 
     27         
     28        self.visitors = list() # Players ; Role: visitors 
     29        self.npcs = list() # Role: Participants 
     30         
     31        self.things = list() # List of Things 
     32        self.exits = dict() # { spec: place_name } 
     33     
     34    def player_leaved(self, player): 
     35        """ 
     36        Broadcast unavaibility of Player or NonPlayableCharacter, 
     37        Trigger on-leave events and remove 
     38        Player/NonPlayableCharacter from the lists. 
     39        """ 
     40         
     41        if player.place: 
     42            message = '/me leaves %s going to %s' % (self.name, 
     43                                                     player.place.name) 
     44        else: 
     45            message = '/me disintegrates' 
     46         
     47        if player.is_npc: 
     48            player.trigger_event('on-self-leave') 
     49            self.npcs.remove(player) 
     50        else: 
     51            self.visitors.remove(player) 
     52             
     53            # Send players and npcs unaivaibility to player 
     54            for npc in self.npcs: 
     55                pres = Presence(node=npc.presence) 
     56                pres.setType('unavailable') 
     57                player.send_presence(npc.name, pres) 
     58            for visitor in self.visitors: 
     59                if visitor is not player: 
     60                    pres = visitor.fix_presence() 
     61                    pres.setType('unavailable') 
     62                    player.send_presence(visitor.name, pres) 
     63         
     64        # Broadcast player's unavailibility to all players in the room 
     65        self.broadcast_message(player.name, message) 
     66        self.broadcast_status(player, 'unavailable') 
     67         
     68        # Trigger on-leave events 
     69        self.trigger_event('on-leave', player) 
     70     
     71    def player_entered(self, player, old_place=None): 
     72        """ 
     73        Broadcast avaibility of Player or NonPlayableCharacter, 
     74        Trigger on-enter events, append 
     75        Player/NonPlayableCharacter to the lists and send description 
     76        of the room to the Player. 
     77        """ 
     78         
     79        if old_place: 
     80            message = '/me enters in %s coming from %s' % (self.name, 
     81                                                      old_place.name) 
     82        else: 
     83            message = '/me spawns' 
     84         
     85        # Broadcast player's availibility to all players in the room 
     86        self.broadcast_status(player) 
     87        self.broadcast_message(player.name, message) 
     88         
     89        if player.is_npc: 
     90            self.npcs.append(player) 
     91            player.trigger_event('on-self-enter') 
     92        else: 
     93            self.visitors.append(player) 
     94            # Send description of the room 
     95            player.send_message(None, 
     96                            '\n******\n\n\n\n**********\n' 
     97                            'Entering %s\n**********' 
     98                            '\n\n%s\n' % (self.name.capitalize(), 
     99                                          self.description), 
     100                            '%s: %s' % (self.world.name, 
     101                                        self.name.capitalize())) 
     102             
     103            # Send players and npcs avaibility to player 
     104            for npc in self.npcs: 
     105                player.send_presence(npc.name, npc.presence) 
     106            for visitor in self.visitors: 
     107                pres = visitor.fix_presence() 
     108                player.send_presence(visitor.name, pres) 
     109         
     110        # Trigger on-enter events 
     111        self.trigger_event('on-enter', player) 
     112     
     113    def trigger_event(self, event_type, actor): 
     114        """ Trigger event of type event_type for all things and npcs """ 
     115        for npc in self.npcs: 
     116            if actor is not npc: 
     117                npc.trigger_event(event_type, actor) 
     118        for thing in self.things: 
     119            thing.trigger_event(event_type, actor) 
     120             
     121    def broadcast_status(self, player, status=None): 
     122        """ Broadcast a presence stanza in the room """ 
     123        presence = player.fix_presence() 
     124        if status is not None: 
     125            presence.setType(status) 
     126        for visitor in self.visitors: 
     127            visitor.send_presence(player.name, presence) 
     128     
     129    def broadcast_message(self, from_, message): 
     130        """ Broadcast a message in all visitors in the room """ 
     131        for visitor in self.visitors: 
     132            visitor.send_message(from_, message) 
     133 
  • adventure/player.py

    r121 r131  
    11# -*- coding: utf-8 -*- 
    22 
     3#  MUDMUC 
     4#  player.py 
     5#  Copyright (c) 2008 Thibaut GIRKA, Anaël Verrier 
     6 
     7#  This program is free software; you can redistribute it and/or modify 
     8#  it under the terms of the GNU General Public License as published by 
     9#  the Free Software Foundation; version 3 only. 
     10 
     11#  This program is distributed in the hope that it will be useful, 
     12#  but WITHOUT ANY WARRANTY; without even the implied warranty of 
     13#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     14#  GNU General Public License for more details. 
     15 
     16#  You should have received a copy of the GNU General Public License 
     17#  along with this program; if not, write to the Free Software 
     18#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
     19 
     20from os.path import isfile, isabs, join as path_join 
     21from base64 import encodestring 
     22from mimetypes import guess_type as guess_mimetype 
     23from sha import sha 
     24 
    325from thing import Thing 
    4 from xmpp import Message 
     26from xmpp import Message, Presence, NS_MUC_USER, NS_VCARD, Node 
    527 
    6 class Player(Thing): 
     28class Player(object): 
    729    def __init__(self, world, name, jid): 
    8         Thing.__init__(self, world, name, None) 
     30        self.world = world 
     31        self.name = name 
    932        self.jid = unicode(jid) 
    1033        self.place = None 
    11    
    12     def see(self, place): 
    13         if not place: 
    14             return 
     34        self.presence = None 
     35         
     36        self.vcard = None 
     37         
     38        self.is_npc = False 
     39         
     40        self.inventory = list() 
     41     
     42    def set_vcard(self, image=None, birthday=None): 
     43        vcard = Node(NS_VCARD + ' vCard') 
     44        vcard.addChild(node=Node('NICKNAME', {}, self.name)) 
     45        if image: 
     46            if not isabs(image): 
     47                image = path_join(self.world.data_dir, image) 
     48            if isfile(image): 
     49                file = open(image, 'rb') 
     50                data = file.read() 
     51                encoded_data = encodestring(data) 
     52                mime = guess_mimetype(image)[0] 
     53                file.close() 
     54                photo = vcard.setTag('PHOTO') 
     55                photo.addChild(node=Node('TYPE', {}, mime)) 
     56                photo.addChild(node=Node('BINVAL', {}, encoded_data)) 
     57                 
     58                vcard_pres = self.presence.getTag(NS_VCARD + ':x:update x') 
     59                if not vcard_pres: 
     60                    vcard_pres = self.presence.setTag(NS_VCARD + ':x:update x') 
     61                vcard_pres.addChild(node=Node('photo', {}, 
     62                                              sha(data).hexdigest())) 
     63             
     64        if birthday: 
     65            vcard.addChild(node=Node('BDAY', {}, birthday)) 
     66        #TODO: DESC 
     67        self.vcard = vcard 
    1568 
    16         for line in place.description.split('\n'): 
    17             self.send_message(None, line.strip()) 
    18  
    19         self.send_message(None, ' ') 
    20         self.send_message(None, 'You can go %s' % ', '.join(place.exits.keys())) 
    21  
    22     def send_message(self, fromresource, text, subject=None): 
     69    def send_message(self, fromresource, text, subject=None, 
     70                     msg_type='groupchat'): 
     71        """ Send message to the player """ 
     72        # New message stanza to the player 
    2373        msg = Message(self.jid, text) 
    24         msg.setType('groupchat') 
     74        msg.setType(msg_type) 
    2575        if subject: 
    2676            msg.setSubject(subject) 
    2777        self.world.send(fromresource, msg) 
     78     
     79    def send_presence(self, fromresource, presence): 
     80        """ Send presence stanza to the player """ 
     81        pres = Presence(node=presence) 
     82        pres.setTo(self.jid) 
     83        self.world.send(fromresource, pres) 
     84     
     85    def say(self, text, target=None): 
     86        """ Make the player say something. If target is None, broadcast """ 
     87        if target is None: 
     88            self.place.broadcast_message(self.name, text) 
     89        else: 
     90            target.send_message(self.name, text) 
     91     
     92    def move_to(self, to_, warning=True): 
     93        """ Move the player to its new place """ 
     94        oldplace = self.place 
     95        self.place = to_ 
     96        if self.place: 
     97            self.place.player_entered(self, oldplace) 
     98        elif warning: 
     99            self.send_message(None, 
     100                              'Sorry! The room isn\'t available!\n' 
     101                              'It\'s a bug for sure ' 
     102                              '(either in the data ' 
     103                              'either in the engine) :/') 
     104        if oldplace is not None: 
     105            oldplace.player_leaved(self) 
     106                               
     107    def fix_presence(self, role=None, affiliation=None): 
     108        """ 
     109        Fix presence: Remove double presence tag and set 
     110        the specified role and affiliation 
     111        """ 
     112        if role: 
     113            if self.is_npc: 
     114                role = 'participant' 
     115                affiliation = 'member' 
     116            else: 
     117                role = 'visitor' 
     118                affiliation = 'none' 
     119        pres = Presence(node=self.presence) 
     120        tag_x = pres.getTag(NS_MUC_USER + ' x') 
     121        if not tag_x: 
     122            tag_x = pres.setTag(NS_MUC_USER + ' x') 
     123        tag_item = tag_x.getTag(NS_MUC_USER + ' item') 
     124        if not tag_item: 
     125            tag_item = tag_x.setTag(NS_MUC_USER + ' item') 
     126        tag_item.setAttr('affiliation', affiliation) 
     127        tag_item.setAttr('role', role) 
     128        return pres 
    28129 
    29     def on_enter(self, thing, from_): 
    30         if thing != self: 
    31             if from_: 
    32                 self.send_message(None, '%s enters %s coming from %s' % 
    33                                   (thing.name, self.place, from_)) 
    34             else: 
    35                 self.send_message(None, '%s spawns' % thing.name) 
    36130 
    37     def on_leave(self, thing, to_): 
    38         if thing != self: 
    39             if to_: 
    40                 self.send_message(None, '%s leaves %s going to %s' % 
    41                                   (thing.name, self.place, to_)) 
    42             else: 
    43                 self.send_message(None, '%s disintegrates' % thing.name) 
     131class NonPlayableCharacter(Player, Thing): 
     132    def __init__(self, world, name, jid, place): 
     133        Player.__init__(self, world, name, jid) 
     134        Thing.__init__(self, world, name, place) 
     135         
     136        self.on_self_enter = list() 
     137        self.on_self_leave = list() 
     138         
     139        self.is_npc = True 
     140     
    44141 
  • adventure/thing.py

    r121 r131  
    11# -*- coding: utf-8 -*- 
    22 
    3 #from player import Player 
    4 from xmpp import Presence 
     3#  MUDMUC 
     4#  thing.py 
     5#  Copyright (c) 2008 Thibaut Girka, Anaël Verrier 
     6 
     7#  This program is free software; you can redistribute it and/or modify 
     8#  it under the terms of the GNU General Public License as published by 
     9#  the Free Software Foundation; version 3 only. 
     10 
     11#  This program is distributed in the hope that it will be useful, 
     12#  but WITHOUT ANY WARRANTY; without even the implied warranty of 
     13#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     14#  GNU General Public License for more details. 
     15 
     16#  You should have received a copy of the GNU General Public License 
     17#  along with this program; if not, write to the Free Software 
     18#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
    519 
    620class Thing: 
    7     def __init__(self, world, name, parent): 
     21    def __init__(self, world, name, place): 
    822        self.world = world 
    923        self.name = name 
    10         self.parent = parent 
    11         self.aliases = (name,) # Noms alternatifs 
    12         self.actions = list() 
    13         self.presences = None 
    14         self.uid = 0 
    15         self.place = None 
    16         self.enters = list() 
    17         self.leaves = list() 
    18         self.jid = None # useful ? 
    19  
    20     def presence(self): 
    21         pres = None 
    22         #for pres in each_element('presence'): 
    23         #    pres = Presence(node=pres) 
    24         #if isinstance(self, Player): 
    25         #    pres.add(Jabber::MUC::XMUCUser.new).add(Jabber::MUC::XMUCUserItem.new('none', 'participant')) 
    26         #else: 
    27         #    pres.add(Jabber::MUC::XMUCUser.new).add(Jabber::MUC::XMUCUserItem.new('owner', 'moderator')) 
    28         return pres 
    29  
    30     def see(self, place): 
    31         pass 
    32  
    33     def send_message(self, fromresource, text, subject=None): 
    34         pass 
    35  
    36     def send_message_to_place(self, fromresource, text): 
    37         for thing in self.world.each_element('thing'): 
    38             if thing.place == self.place: 
    39                 thing.send_message(fromresource, text) 
    40    
    41     def on_enter(self, thing, from_): 
    42         for command in self.enters: 
    43             self.command(thing, command, [from_]) 
    44  
    45     def on_leave(self, thing, to_): 
    46         for command in self.leaves: 
    47             self.command(thing, command, [to_]) 
    48  
    49     def command(self, source, command, arguments): 
    50         if command.action[1]: 
    51             text = command.action[1] 
    52         else: 
    53             text = '' 
    54         target = None 
    55         if command.action[2]: 
    56             for thing in self.world.each_thing_by_place(self.place): 
    57                 if thing.name == command.action[2]: 
    58                     target = thing 
     24        self.place = place 
     25        self.aliases = [name,] # Alternative names 
     26        self.commands = list() # List of dicts : name, actions : list() 
     27        self.on_enters = list() 
     28        self.on_leaves = list() 
     29        self.uid = 0 # Useful? 
     30     
     31    def add_aliases(self, aliases_obj, alias_str): 
     32        """ Safely add aliases """ 
     33        if not (isinstance(alias_str, str) or isinstance(alias_str, unicode)): 
     34            return False 
     35        if alias_str: 
     36            aliases = alias_str.split(',') 
     37            for alias in aliases: 
     38                if alias: 
     39                    aliases_obj.append(alias.strip()) 
     40     
     41    def parse_commands(self, command_elements): 
     42        """ Parse commands and store them in self.commands """ 
     43        for command in command_elements: 
     44            if command.getAttribute('objects'): 
     45                objects = command.getAttribute('objects').split(',') 
     46                required_objects = tuple(objects) 
     47            else: 
     48                required_objects = tuple() 
     49            command_obj = dict() 
     50            command_obj['name'] = command.getAttribute('name').replace('%self%', '<<self>>') 
     51            command_obj['name'] = command_obj['name'] % required_objects 
     52            command_obj['aliases'] = list() 
     53            command_obj['aliases'].append(command_obj['name']) 
     54            command_obj['actions'] = list() 
     55            command_obj['signal'] = command.getAttribute('signal').strip() 
     56            command_obj['objects'] = list(required_objects) 
     57             
     58            aliases = command.getAttribute('aliases') 
     59            self.add_aliases(command_obj['aliases'], aliases) 
     60             
     61            action_elements = command.getElementsByTagName('*') 
     62            for i in action_elements: 
     63                self.parse_action(command_obj['actions'], i) 
     64            self.commands.append(command_obj) 
     65         
     66    def parse_action(self, action_list, action_element): 
     67        """ 
     68        Parse action_element (xml.dom.minidom.Element) 
     69        and append each action in action_list 
     70        """ 
     71        action_obj = dict() 
     72        action_obj['name'] = action_element.localName 
     73        action_obj['target'] = action_element.getAttribute('target') 
     74        if action_element.firstChild and action_element.firstChild.data: 
     75            action_obj['params'] = action_element.firstChild.data.strip() 
     76        action_list.append(action_obj) 
     77 
     78    def do_action(self, action, player=None): 
     79        """ Execute a stored action """ 
     80        # Resolve target: None, self, player, or a Player instance 
     81        target = False 
     82        if action['target'] == 'actor': 
     83            target = player 
     84        elif action['target'] == 'self': 
     85            target = self 
     86        elif action['target']: 
     87            target = None 
     88            for i in (self.place.visitors + self.place.npcs): 
     89                if action['target'] == i.name: 
     90                    target = i 
    5991                    break 
    60         #else: 
    61         if not target: 
    62             target = self 
    63         text.replace('%self%', target.name) 
    64         text.replace('%actor%', source.name) 
    65         text.replace('%place%', self.place) 
    66         if command.action[0] == 'say' or command.action[0] == 'narration': 
    67             sender = None 
    68             if command.action[0] == 'say': 
    69                 sender = self.name 
    70             if command.action[3] == 'all': 
    71                 self.send_message_to_place(sender, text) 
     92         
     93        # Parse parameters, replace %self% and %actor% 
     94        params = '' 
     95        if action.has_key('params'): 
     96            params = action['params'] 
     97            params = params.replace('%self%', self.name) 
     98            if player: 
     99                params = params.replace('%actor%', player.name) 
     100         
     101        # Execute actions 
     102        #   <say [target="actor|self|name"]>text</say> 
     103        if action['name'] == 'say': 
     104            if target: 
     105                self.say(params, target) 
    72106            else: 
    73                 source.send_message(sender, text) 
    74  
    75  
    76  
    77  
    78 class Action: 
    79     def __init__(self, name, parent): 
    80         self.name = name 
    81         self.parent = parent 
    82         self.objects = list() # objets requis 
    83         # Expressions... Un élément si pas de "%" dans la chaîne "name", sinon : 
    84         # un élément par alias ( ex "use bottle on", "use whiskey on" ) 
    85         self.expressions = list() 
    86  
    87         # Élément XML à traiter si les objets sont dans l'inventaire 
    88         #self.xmlelement = None 
    89  
    90         # (("say","coucou",None,None),("destroy",None)) par exemple 
    91         # à respecter : premier élément : nom de la commande, 
    92         #               deuxième : texte, troisième : target 
    93         self.action = list() 
     107                self.say(params) 
     108         
     109        #   <narration>text</narration> 
     110        elif action['name'] == 'narration': 
     111            self.place.broadcast_message(None, params) 
     112         
     113        #   <follow [target="actor|self|name"] /> 
     114        elif action['name'] == 'follow': 
     115            if target.place is not None and not target.is_npc: 
     116                forbidden_places = params.split(',') 
     117                for i in xrange(len(forbidden_places)): 
     118                    forbidden_places[i] = forbidden_places[i].strip() 
     119                if not target.place.name in forbidden_places: 
     120                    self.say('/me follows ' + target.name) 
     121                    self.move_to(target.place) 
     122         
     123        #   <signal [target="name"]>signal</signal> 
     124        elif action['name'] == 'signal': 
     125            if target is False: 
     126                for i in (self.place.npcs + self.place.things): 
     127                    i.trigger_signal(params, player) 
     128            elif target: 
     129                target.trigger_signal(params, player) 
     130            elif ':Failed' not in params: 
     131                for i in (self.place.npcs + self.place.things): 
     132                    i.trigger_signal(params+':Failed', player) 
     133         
     134        #   <move [target="actor|self|name"]>place</move> 
     135        elif action['name'] == 'move': 
     136            if not target: 
     137                target = self 
     138            if not params or params == target.place.name: 
     139                for name, place in target.place.exits.items(): 
     140                    target.move_to(self.world.place(place)) 
     141                    break 
     142            else: 
     143                target.move_to(target.world.place(params)) 
     144         
     145        #   <add-exit>spec: place</add-exit> 
     146        elif action['name'] == 'add-exit': 
     147            (spec, place) = params.split(':') 
     148            spec = spec.strip() 
     149            place = place.strip() 
     150            self.place.exits[spec] = place 
     151         
     152        #   <del-exit>spec</del-exit> 
     153        elif action['name'] == 'del-exit': 
     154            if self.place.exits.has_key(params): 
     155                del self.place.exits[params] 
     156        #   <give target="target">object</give> 
     157        elif action['name'] == 'give': 
     158            if not target: 
     159                target = player 
     160            if target: 
     161                target.inventory.append(params) 
     162        #   <use target="target">object</give> 
     163        elif action['name'] == 'use': 
     164            if not target: 
     165                target = player 
     166            if target: 
     167                target.inventory.remove(params) 
     168     
     169    def trigger_event(self, event, actor=None): 
     170        """ Trigger events if handled by the NPC""" 
     171        if event == 'on-enter': 
     172            for on_enter in self.on_enters: 
     173                if (not on_enter['target'] or 
     174                    on_enter['target'].lower() == actor.name.lower()): 
     175                    for action in on_enter['actions']: 
     176                        self.do_action(action, actor) 
     177         
     178        if event == 'on-leave': 
     179            for on_leave in self.on_leaves: 
     180                if (not on_leave['target'] or 
     181                    on_leave['target'].lower() == actor.name.lower()): 
     182                    for action in on_leave['actions']: 
     183                        self.do_action(action, actor) 
     184         
     185        if event == 'on-self-enter': 
     186            for i in self.on_self_enter: 
     187                self.do_action(i, actor) 
     188         
     189        if event == 'on-self-leave': 
     190            for i in self.on_self_leave: 
     191                self.do_action(i, actor) 
     192     
     193    def trigger_signal(self, signal, actor=None): 
     194        """ Trigger a signal if handled by the NPC """ 
     195        for command in self.commands: 
     196            if signal.strip() == command['signal'].strip(): 
     197                for action in command['actions']: 
     198                    self.do_action(action, actor) 
     199     
     200    def handle_command(self, text, player): 
     201        """ 
     202        Test all combinations with all aliasies. 
     203        If command is handled, execute actions and return True. 
     204        Else, return False. 
     205        """ 
     206        for alias in self.aliases: 
     207            for command_obj in self.commands: 
     208                for command in command_obj['aliases']: 
     209                    command_txt = command 
     210                    command_txt = command_txt.replace('<<self>>', alias) 
     211                    command_txt = command_txt.lower() 
     212                    if command_txt and command_txt in text.lower().strip(): 
     213                        has_required = True 
     214                        for i in command_obj['objects']: 
     215                            if i not in player.inventory: 
     216                                player.send_message('Help!', 
     217                                                    'You don\'t have a %s' % i) 
     218                                has_required = False 
     219                                break 
     220                        if has_required: 
     221                            player.say('/me %s' % command_obj['name'].replace( 
     222                                '<<self>>', self.name)) 
     223                            for action in command_obj['actions']: 
     224                                self.do_action(action, player) 
     225                        return True 
     226        return False 
  • adventure/world.py

    r121 r131  
    11# -*- coding: utf-8 -*- 
    22 
     3#  MUDMUC 
     4#  world.py 
     5#  Copyright (c) 2008 Thibaut Girka, Anaël Verrier 
     6 
     7#  This program is free software; you can redistribute it and/or modify 
     8#  it under the terms of the GNU General Public License as published by 
     9#  the Free Software Foundation; version 3 only. 
     10 
     11#  This program is distributed in the hope that it will be useful, 
     12#  but WITHOUT ANY WARRANTY; without even the implied warranty of 
     13#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     14#  GNU General Public License for more details. 
     15 
     16#  You should have received a copy of the GNU General Public License 
     17#  along with this program; if not, write to the Free Software 
     18#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
     19 
     20from os.path import dirname, abspath 
     21from xml.dom.minidom import parse as xml_parse 
     22 
     23from xmpp import Message, Presence, NS_MUC_USER, JID 
     24from xmpp_error import Error 
     25 
    326from place import Place 
    4 from player import Player 
    5 from xml.dom.minidom import parse as xml_parse 
    6 from xmpp import Message, Presence 
    7 from xmpp_error import Error 
     27from player import Player, NonPlayableCharacter 
     28from thing import Thing 
    829 
    930class World: 
     31    """ World class. 
     32    Worlds have their own JID and correspond to an xml file """ 
    1033    def __init__(self, muc, filename): 
     34        """ Init a world. 
     35        muc is the MUC component handling messages and presences. 
     36        filename is the world's filename. """ 
    1137        self.muc = muc 
    12         self.places = dict() # {'place_name': place, ...} 
    13         self.things = dict() 
     38        self.places = dict() # { place_name: Place() } 
     39        self.players = list() 
     40        self.things = list() 
     41        self.npcs = list() 
     42         
     43        self.data_dir = dirname(abspath(filename)) 
     44 
     45        self.bag = Player(self, 'Bag', 'Bag') 
     46        self.bag.presence = Presence() 
     47        self.bag.presence.setShow('available') 
     48        self.bag.presence.setStatus('A bag (your inventory)') 
     49        self.bag.presence = self.bag.fix_presence('moderator', 'owner') 
     50        self.bag.set_vcard('bag.png') 
    1451 
    1552        doc = xml_parse(filename) 
     
    2057        get_elements = doc.documentElement.getElementsByTagName 
    2158        places = get_elements('place') 
     59         
     60        # Parsing places 
    2261        for place in places: 
    23             place_obj = Place( 
     62            place_obj = Place(self, 
    2463                place.getAttribute('name'), 
    2564                place.getElementsByTagName('description')[0].firstChild.data) 
    2665            self.places[place_obj.name] = place_obj 
     66             
     67            # Parsing exits of the place 
    2768            gos = place.getElementsByTagName('go') 
    2869            for go in gos: 
    2970                place_obj.exits[ 
    3071                    go.getAttribute('spec')] = go.getAttribute('place') 
    31         #things = get_elements('thing') 
    32         #for thing in things: 
     72             
     73            # Parsing NPCs in the place 
     74            npcs = place.getElementsByTagName('npc') 
     75            for npc in npcs: 
     76                npc_obj = NonPlayableCharacter(self, 
     77                                            npc.getAttribute('name'), 
     78                                            npc.getAttribute('name'), 
     79                                            place_obj) 
     80                npc_obj.add_aliases(npc_obj.aliases, 
     81                                    npc.getAttribute('aliases')) 
     82                 
     83                # Parsing NPC presence 
     84                pres = Presence() 
     85                presences = npc.getElementsByTagName('presence') 
     86                image = None 
     87                if len(presences): 
     88                    # Set up NPC status 
     89                    shows = presences[0].getElementsByTagName('show') 
     90                    if len(shows): 
     91                        pres.setShow(shows[0].firstChild.data) 
     92                    status = presences[0].getElementsByTagName('status') 
     93                    if len(status): 
     94                        pres.setStatus(status[0].firstChild.data) 
     95                    images = presences[0].getElementsByTagName('image') 
     96                    if len(images): 
     97                        image = images[0].firstChild.data 
     98                npc_obj.presence = pres 
     99                npc_obj.presence = npc_obj.fix_presence() 
     100                npc_obj.set_vcard(image) 
     101                 
     102                # Add NPC to the World and to the Place 
     103                place_obj.npcs.append(npc_obj) 
     104                self.npcs.append(npc_obj) 
     105                 
     106                # Parse NPC's commands 
     107                commands = npc.getElementsByTagName('commands') 
     108                if len(commands): 
     109                    commands = commands[0].getElementsByTagName('command') 
     110                    npc_obj.parse_commands(commands) 
     111                 
     112                # Set up NPC's on-enter event 
     113                on_enters = npc.getElementsByTagName('on-enter') 
     114                for on_enter in on_enters: 
     115                    actions = on_enter.getElementsByTagName('*') 
     116                    on_enter_obj = dict() 
     117                    on_enter_obj['target'] = on_enter.getAttribute('target').strip() 
     118                    on_enter_obj['actions'] = list() 
     119                    npc_obj.on_enters.append(on_enter_obj) 
     120                    for action in actions: 
     121                        npc_obj.parse_action(on_enter_obj['actions'], action) 
     122                 
     123                # Set up NPC's on-enter event 
     124                on_leaves = npc.getElementsByTagName('on-leave') 
     125                for on_leave in on_leaves: 
     126                    actions = on_leave.getElementsByTagName('*') 
     127                    on_leave_obj = dict() 
     128                    on_leave_obj['target'] = on_leave.getAttribute('target').strip() 
     129                    on_leave_obj['actions'] = list() 
     130                    npc_obj.on_leaves.append(on_leave_obj) 
     131                    for action in actions: 
     132                        npc_obj.parse_action(on_leave_obj['actions'], action) 
     133                 
     134                # Set up NPC's on-self-enter event 
     135                on_self_enters = npc.getElementsByTagName('on-self-enter') 
     136                if len(on_self_enters): 
     137                    actions = on_self_enters[0].getElementsByTagName('*') 
     138                    for action in actions: 
     139                        npc_obj.parse_action(npc_obj.on_self_enter, action) 
     140                 
     141                # Set up NPC's on-self-leave event 
     142                on_self_leave = npc.getElementsByTagName('on-self-leave') 
     143                if len(on_self_leave): 
     144                    actions = on_self_leave[0].getElementsByTagName('*') 
     145                    for action in actions: 
     146                        npc_obj.parse_action(npc_obj.on_self_leave, action) 
     147         
     148            # Parsing Things in the place 
     149            things = place.getElementsByTagName('thing') 
     150            for thing in things: 
     151                thing_obj = Thing(self, thing.getAttribute('name'), place_obj) 
     152                thing_obj.add_aliases(thing_obj.aliases, thing.getAttribute('aliases')) 
     153                 
     154                # Add Thing to the World and to the Place 
     155                place_obj.things.append(thing_obj) 
     156                self.things.append(thing_obj) 
     157                 
     158                # Parse Thing's commands 
     159                commands = thing.getElementsByTagName('commands') 
     160                if len(commands): 
     161                    commands = commands[0].getElementsByTagName('command') 
     162                    thing_obj.parse_commands(commands) 
     163                 
     164                # Set up Thing's on-enter event 
     165                on_enters = thing.getElementsByTagName('on-enter') 
     166                for on_enter in on_enters: 
     167                    actions = on_enter.getElementsByTagName('*') 
     168                    on_enter_obj = dict() 
     169                    on_enter_obj['target'] = on_enter.getAttribute('target').strip() 
     170                    on_enter_obj['actions'] = list() 
     171                    thing_obj.on_enters.append(on_enter_obj) 
     172                    for action in actions: 
     173                        thing_obj.parse_action(on_enter_obj['actions'], action) 
     174                 
     175                # Set up Thing's on-enter event 
     176                on_leaves = thing.getElementsByTagName('on-leave') 
     177                for on_leave in on_leaves: 
     178                    actions = on_leave.getElementsByTagName('*') 
     179                    on_leave_obj = dict() 
     180                    on_leave_obj['target'] = on_leave.getAttribute('target').strip() 
     181                    on_leave_obj['actions'] = list() 
     182                    thing_obj.on_leaves.append(on_leave_obj) 
     183                    for action in actions: 
     184                        thing_obj.parse_action(on_leave_obj['actions'], action) 
    33185 
    34186 
    35187 
    36188    def send(self, resource, stanza): 
     189        """ Send stanza through the room """ 
    37190        # Avoid sending to things without JID 
    38191        if stanza.getTo(): 
     
    40193 
    41194    def place(self, placename): 
     195        """ Return the Place whose name is placename """ 
    42196        if self.places.has_key(placename): 
    43197            return self.places[placename] 
    44198        return None 
    45199 
    46     def each_thing_by_place(self, placename): 
    47         if placename: 
    48             for thing in self.things.values(): 
    49                 if thing.place == placename: 
    50                     yield(thing) 
    51  
    52     def move_thing(self, thing, newplace): 
    53         for t in self.each_thing_by_place(thing.place): 
    54             # Call leave hooks 
    55             t.on_leave(thing, newplace) 
    56  
    57             # Broadcast unavailability presence to leaver 
    58             if t.presence: 
    59                 pres = Presence(node=t.presence) 
    60                 pres.setType('unavailable') 
    61                 pres.setTo(thing.jid) 
    62                 if t.jid != thing.jid: 
    63                     self.send(t.name, pres) 
    64  
    65             # Broadcast unavailability presence to all who are here 
    66             if thing.presence: 
    67                 pres = Presence(node=thing.presence) 
    68                 pres.setType('unavailable') 
    69                 pres.setTo(t.jid) 
    70                 if thing.jid != t.jid: 
    71                     self.send(thing.name, pres) 
    72  
    73         # Enter new place 
    74         oldplace = thing.place 
    75         thing.place = newplace 
    76  
    77         for t in self.each_thing_by_place(thing.place): 
    78             # Broadcast availability presence to enterer 
    79             if t.presence: 
    80                 pres = Presence(node=t.presence) 
    81                 pres.setTo(thing.jid) 
    82                 self.send(t.name, pres) 
    83  
    84             # Broadcast availability presence to all who are here 
    85             if thing.presence: 
    86                 pres = Presence(node=thing.presence) 
    87                 pres.setTo(t.jid) 
    88                 self.send(thing.name, pres) 
    89  
    90         thing.send_message(None, ' ') 
    91         if newplace: 
    92             subject = newplace.capitalize() 
    93         else: 
    94             subject = ' ' 
    95         thing.send_message(None, 'Entering %s' % newplace, subject) 
    96         thing.send_message(None, ' ') 
    97         thing.see(self.place(newplace)) 
    98  
    99         for t in self.each_thing_by_place(thing.place): 
    100             # Call enter hooks 
    101             t.on_enter(thing, oldplace) 
     200    def move_thing(self, thing, newplace, warning=True): 
     201        """ Move thing thing to its new place """ 
     202        thing.move_to(self.place(newplace), warning) 
    102203 
    103204    def handle_presence(self, pres): 
     205        """ Handle presence stanzas coming to the room """ 
    104206        # A help for the irritated first: 
    105207        if pres.getType() == 'subscribe': 
     
    115217        # Look if player is already known 
    116218        player = None 
    117         for thing in self.things.values(): 
    118             if (isinstance(thing, Player) and 
    119                 pres.getTo().getResource() == thing.name): 
     219        for thing in self.players: 
     220            if pres.getTo().getResource() == thing.name: 
    120221                player = thing 
    121222 
    122223            # Disallow nick changes 
    123             if (isinstance(thing, Player) and pres.getFrom() == thing.jid and 
    124                 player != thing): 
     224            if (pres.getFrom() == thing.jid and player != thing): 
    125225                answer = Error(pres, 'not-acceptable', 
    126                                'Nickchange not allowed') #False 
     226                               'Nickchange not allowed') 
    127227                self.send(thing.name, answer) 
    128228                return True 
    129  
     229         
    130230        # Either nick-collission or empty nick 
    131231        if (player and pres.getFrom() != player.jid or 
     
    138238            self.send(None, answer) 
    139239            return True 
     240         
     241        # Disallow nick "Bag", since it's used internally by the MUD 
     242        if pres.getTo().getResource() == 'Bag': 
     243            answer = Error(pres, 'not-acceptable', 'Nickname already used by a non-playbable character') 
     244            self.send(None, answer) 
     245            return True 
     246         
     247        # Nick-collision with a NPC 
     248        for npc in self.npcs: 
     249            if npc.name == pres.getTo().getResource(): 
     250                answer = Error(pres, 'not-acceptable', 'Nickname already used by a non-playbable character') 
     251                self.send(None, answer) 
     252                return True 
    140253 
    141254        # Add the valid player 
     
    143256            player = Player(self, pres.getTo().getResource(), pres.getFrom()) 
    144257            player.presence = pres 
    145             self.things[player.name] = player 
     258            self.players.append(player) 
    146259            self.move_thing(player, self.start) 
    147             player.send_message('Help!', 'Send "?" to get a list of available ' 
    148                                 'commands any time.') 
     260             
     261            # Send the bag presence to the player: 
     262            player.send_presence('Bag', self.bag.presence) 
    149263        # Or broadcast updated presence 
    150264        else: 
    151265            player.presence = pres 
    152  
    153             for t in self.each_thing_by_place(player.place): 
    154                 # Broadcast presence to all who are here 
    155                 if isinstance(t, Player): 
    156                     pres = Presence(node=player.presence) 
    157                     pres.setTo(t.jid) 
    158                     self.send(player.name, pres) 
     266             
     267            player.place.broadcast_status(player) 
    159268     
    160269        # Remove the player instantly 
    161270        if pres.getType() in ('error', 'unavailable'): 
    162             self.move_thing(player, None) 
    163             del self.things[player.name] 
     271            self.move_thing(player, None, warning=False) 
     272            self.players.remove(player) 
    164273 
    165274    def handle_message(self, msg): 
     275        """ Handle message stanzas coming to the room """ 
    166276        player = None 
    167         for thing in self.things.values(): 
    168             if (isinstance(thing, Player) and not msg.getTo().getResource() and 
    169                 msg.getFrom() == thing.jid): 
     277         
     278        # Search the player by its JID 
     279        for thing in self.players: 
     280            if msg.getFrom() == thing.jid: 
    170281                player = thing 
    171282 
     283        # If the player were not found, send an answer to the message 
    172284        if not player: 
    173285            answer = Error(msg, 'forbidden') 
    174286            self.send(msg.getTo().getResource(), answer) 
    175287            return True 
    176  
     288        elif msg.getTo() == JID(node=self.node, 
     289                                domain=self.muc.jid.getDomain()): 
     290            tag_x = msg.getTag('x') 
     291            if tag_x: 
     292                tag_invite = tag_x.getTag('invite') 
     293                # Is the message an invitation? 
     294                if tag_invite: 
     295                    self.send_invitation(msg.getTo(), 
     296                                         tag_invite.getAttr('to')) 
     297        elif msg.getTo().getResource() == 'Bag': 
     298            #TODO: Invetory handling 
     299            self.bag_command(player, msg.getBody()) 
     300            return True 
     301         
     302        if msg.getTo().getResource(): 
     303            for visitor in player.place.visitors: 
     304                if visitor.name == msg.getTo().getResource(): 
     305                    visitor.send_message(player.name, msg.getBody(), 
     306                                         msg_type='normal') 
     307                    return True 
     308            for npc in player.place.npcs: 
     309                if npc.name == msg.getTo().getResource(): 
     310                    player.send_message(npc.name, 
     311                                        'Sorry, I\'m a NPC, you can\'t send private messages to me.', 
     312                                        msg_type='normal') 
     313                    return True 
     314        # Parse the content. If not handled, broadcast it. 
    177315        if not self.command(player, msg.getBody()): 
    178             for thing in self.each_thing_by_place(player.place): 
    179                 thing.send_message(player.name, msg.getBody()) 
     316            player.say(msg.getBody()) 
     317 
     318    def send_invitation(self, from_, to_): 
     319        """ Send an invitation to jid to_ from jid from_ """ 
     320        msg = Message(to_) 
     321        msg.setFrom(JID(node=self.node, domain=self.muc.jid.getDomain())) 
     322        msg_x = msg.setTag(NS_MUC_USER + ' x') 
     323        msg_invite = msg_x.setTag(NS_MUC_USER + ' invite') 
     324        msg_invite.setAttr('from', from_) 
     325        msg_reason = msg_invite.setTag(NS_MUC_USER + ' reason') 
     326        msg_reason.setData('Join the super MUDMUC!(World %s)' % self.name) 
     327        self.send(None, msg) 
     328 
     329    def bag_command(self, player, text): 
     330        """ Try to parse commands or return False if not handled """ 
     331        if text in ('list', 'ls'): 
     332             
     333            temp_items = list() 
     334            temp_numbers = list() 
     335            for item in player.inventory: 
     336                if item in temp_items: 
     337                    temp_numbers[temp_items.index(item)] += 1 
     338                else: 
     339                    temp_items.append(item) 
     340                    temp_numbers.append(1) 
     341            if not len(temp_items): 
     342                player.send_message('Bag', 'Your bag is empty!', 
     343                                    msg_type='normal') 
     344            elif len(temp_items) == 1: 
     345                player.send_message('Bag', 
     346                                    'Your bag contains: %s (x%d)' 
     347                                        % (temp_items[0], temp_numbers[0]), 
     348                                    msg_type='normal') 
     349            else: 
     350                answer_text = 'Your bag contains: ' 
     351                for item_i in xrange(len(temp_items)-1): 
     352                    answer_text += '%s (x%d), ' % (temp_items[item_i], 
     353                                                   temp_numbers[item_i]) 
     354                answer_text += 'and %s (x%d)' % (temp_items[len(temp_items)-1], temp_numbers[len(temp_numbers)-1]) 
     355                player.send_message('Bag', answer_text, msg_type='normal') 
     356        else: 
     357            player.send_message('Bag', 'I don\'t want to chat. If you want to use me, type ls or list', msg_type='normal') 
    180358 
    181359    def command(self, player, text): 
    182         if text == '?': 
     360        """ Try to parse commands or return False if not handled """ 
     361        if text in ('?', '!help'): 
    183362            player.send_message('Help!', '(Command) who') 
    184             place = self.place(player.place) 
     363            place = player.place 
    185364            if place: 
    186365                for exit_name in place.exits.keys(): 
    187366                    player.send_message('Help!', '(Command) go %s' % exit_name) 
    188             for thing in self.each_thing_by_place(player.place): 
    189                 for c in thing.actions: 
    190                     player.send_message('Help!', 
    191                                         '(Command) %s %s' % (c.expressions[0], 
    192                                                              thing.name)) 
    193             return True 
    194         else: 
     367            for thing in (player.place.npcs + player.place.things): 
     368                for c in thing.commands: 
     369                    if len(c['name']) > 3: 
     370                        has_required = True 
     371                        for i in c['objects']: 
     372                            if i not in player.inventory: 
     373                                has_required = False 
     374                                break 
     375                        if has_required: 
     376                            player.send_message('Help!', 
     377                                                '(Command) %s' 
     378                                                  % (c['name'].replace('<<self>>', 
     379                                                     thing.name))) 
     380            return True 
     381        elif text: 
    195382            words = text.split(' ') 
    196383            cmd = words.pop(0) 
     
    200387                what = '' 
    201388            if cmd == 'go': 
    202                 print what 
    203                 oldplace = self.place(player.place) 
     389                oldplace = player.place 
    204390                newplace = None 
    205391 
    206392                for name, place in oldplace.exits.items(): 
    207                     if name == what: 
     393                    if name.lower() == what.lower(): 
    208394                        newplace = place 
    209395 
     
    213399                    player.send_message(None, 'You cannot go there') 
    214400                return True 
     401            elif cmd == 'inventory': 
     402                self.bag_command(player, 'ls') 
     403                return True 
    215404            elif cmd == 'who': 
    216405                player.send_message(None, 'Players in "%s":' % self.name) 
    217                 for thing in self.things.values(): 
    218                     if isinstance(thing, Player): 
    219                         player.send_message(None, 
    220                                             '%s is at/in %s' % (thing.name, 
    221                                                                 thing.place)) 
     406                for thing in self.players: 
     407                    player.send_message(None, 
     408                                        ' - %s is at/in %s' % (thing.name, 
     409                                                              thing.place.name)) 
    222410                return True 
    223411            else: 
    224412                handled = False 
    225                 for thing in self.each_thing_by_place(player.place): 
    226                     if what.lower() in thing.aliases: 
    227                         for action in thing.actions: 
    228                             for c in action.expressions: 
    229                                 if c == cmd: 
    230                                     thing.command(player, action, words) 
    231                                     handled = True 
     413                for thing in (player.place.npcs + player.place.things): 
     414                    if thing.handle_command(text, player): 
     415                        handled = True 
    232416                if handled: 
    233417                    return True 
    234418        return False 
    235