root/adventure/world.py

Revision 156, 17.9 kB (checked in by thib, 6 months ago)

Quelques modifs ( World, Place et Thing héritent d'object )

Line 
1 # -*- coding: utf-8 -*-
2
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
20 from os.path import dirname, abspath
21 from xml.dom.minidom import parse as xml_parse
22
23 from xmpp import Message, Presence, NS_MUC_USER, JID
24 from xmpp_error import Error
25
26 from place import Place
27 from player import Player
28 from thing import Thing, NonPlayableCharacter
29
30 class World(object):
31     """ World class.
32     Worlds have their own JID and correspond to an xml file """
33     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. """
37         self.muc = muc
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')
51
52         doc = xml_parse(filename)
53         doc_getAttribute = doc.documentElement.getAttribute
54         self.node = doc_getAttribute('node')
55         self.name = doc_getAttribute('name')
56         self.start = doc_getAttribute('start')
57         get_elements = doc.documentElement.getElementsByTagName
58         places = get_elements('place')
59        
60         # Parsing places
61         for place in places:
62             place_obj = Place(self,
63                 place.getAttribute('name'),
64                 place.getElementsByTagName('description')[0].firstChild.data)
65             self.places[place_obj.name] = place_obj
66            
67             # Parsing exits of the place
68             gos = place.getElementsByTagName('go')
69             for go in gos:
70                 place_obj.exits[
71                     go.getAttribute('spec')] = go.getAttribute('place')
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)
185
186
187
188     def send(self, resource, stanza):
189         """ Send stanza through the room """
190         # Avoid sending to things without JID
191         if stanza.getTo():
192             self.muc.snd(self.node, resource, stanza)
193
194     def place(self, placename):
195         """ Return the Place whose name is placename """
196         if self.places.has_key(placename):
197             return self.places[placename]
198         return None
199
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)
203
204     def handle_presence(self, pres):
205         """ Handle presence stanzas coming to the room """
206         # A help for the irritated first:
207         if pres.getType() == 'subscribe':
208             msg = Message(pres.getFrom())
209             msg.setType('normal')
210             msg.setSubject('Adventure component help')
211             msg.setBody('You don\'t need to subscribe to my presence. '
212                         'Simply use your Jabber client to join the MUC or '
213                         'conference at %s' % pres.getTo())
214             self.send(None, msg)
215             return True
216
217         # Look if player is already known
218         player = None
219         for thing in self.players:
220             if pres.getTo().getResource() == thing.name:
221                 player = thing
222
223             # Disallow nick changes
224             if (pres.getFrom() == thing.jid and player != thing):
225                 answer = Error(pres, 'not-acceptable',
226                                'Nickchange not allowed')
227                 self.send(thing.name, answer)
228                 return True
229        
230         # Either nick-collission or empty nick
231         if (player and pres.getFrom() != player.jid or
232             len(pres.getTo().getResource()) < 2):
233             answer = None
234             if len(pres.getTo().getResource()) > 1:
235                 answer = Error(pres, 'conflict', 'Nickname already used')
236             else:
237                 answer = Error(pres, 'not-acceptable', 'Please use a nickname')
238             self.send(None, answer)
239             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
253
254         # Add the valid player
255         if not player:
256             player = Player(self, pres.getTo().getResource(), pres.getFrom())
257             player.presence = pres
258             self.players.append(player)
259             self.move_thing(player, self.start)
260            
261             # Send the bag presence to the player:
262             player.send_presence('Bag', self.bag.presence)
263         # Or broadcast updated presence
264         else:
265             player.presence = pres
266            
267             player.place.broadcast_status(player)
268    
269         # Remove the player instantly
270         if pres.getType() in ('error', 'unavailable'):
271             self.move_thing(player, None, warning=False)
272             self.players.remove(player)
273
274     def handle_message(self, msg):
275         """ Handle message stanzas coming to the MUC """
276         player = None
277        
278         # Search the player by its JID
279         for thing in self.players:
280             if msg.getFrom() == thing.jid:
281                 player = thing
282
283         # If the player were not found, send an answer to the message
284         if not player:
285             answer = Error(msg, 'forbidden')
286             self.send(msg.getTo().getResource(), answer)
287             return True
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             self.bag_command(player, msg.getBody())
299             return True
300        
301         if msg.getTo().getResource():
302             for visitor in player.place.visitors:
303                 if visitor.name == msg.getTo().getResource():
304                     visitor.send_message(player.name, msg.getBody(),
305                                          msg_type='normal')
306                     return True
307             for npc in player.place.npcs:
308                 if npc.name == msg.getTo().getResource():
309                     player.send_message(npc.name,
310                                         'Sorry, I\'m a NPC, you can\'t send private messages to me.',
311                                         msg_type='normal')
312                     return True
313         # Parse the content. If not handled, broadcast it.
314         if not self.command(player, msg.getBody()):
315             player.say(msg.getBody())
316
317     def send_invitation(self, from_, to_):
318         """ Send an invitation to jid to_ from jid from_ """
319         msg = Message(to_)
320         msg.setFrom(JID(node=self.node, domain=self.muc.jid.getDomain()))
321         msg_x = msg.setTag(NS_MUC_USER + ' x')
322         msg_invite = msg_x.setTag(NS_MUC_USER + ' invite')
323         msg_invite.setAttr('from', from_)
324         msg_reason = msg_invite.setTag(NS_MUC_USER + ' reason')
325         msg_reason.setData('Join the super MUDMUC!(World %s)' % self.name)
326         self.send(None, msg)
327
328     def bag_command(self, player, text):
329         """ Try to parse commands or return False if not handled """
330         if text in ('list', 'ls'):
331            
332             temp_items = list()
333             temp_numbers = list()
334             for item in player.inventory:
335                 if item in temp_items:
336                     temp_numbers[temp_items.index(item)] += 1
337                 else:
338                     temp_items.append(item)
339                     temp_numbers.append(1)
340             if not len(temp_items):
341                 player.send_message('Bag', 'Your bag is empty!',
342                                     msg_type='normal')
343             elif len(temp_items) == 1:
344                 player.send_message('Bag',
345                                     'Your bag contains: %s (x%d)'
346                                         % (temp_items[0], temp_numbers[0]),
347                                     msg_type='normal')
348             else:
349                 answer_text = 'Your bag contains: '
350                 for item_i in xrange(len(temp_items)-1):
351                     answer_text += '%s (x%d), ' % (temp_items[item_i],
352                                                    temp_numbers[item_i])
353                 answer_text += 'and %s (x%d)' % (temp_items[len(temp_items)-1], temp_numbers[len(temp_numbers)-1])
354                 player.send_message('Bag', answer_text, msg_type='normal')
355         else:
356             player.send_message('Bag', 'I don\'t want to chat. If you want to use me, type ls or list', msg_type='normal')
357
358     def command(self, player, text):
359         """ Try to parse commands or return False if not handled """
360         if text in ('?', '!help'):
361             player.send_message('Help!', '(Command) who')
362             player.send_message('Help!', '(Command) inventory')
363             place = player.place
364             if place:
365                 for exit_name in place.exits.keys():
366                     player.send_message('Help!', '(Command) go %s' % exit_name)
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:
382             words = text.split(' ')
383             cmd = words.pop(0)
384             if len(words):
385                 what = words.pop(0)
386             else:
387                 what = ''
388             if cmd == 'go':
389                 oldplace = player.place
390                 newplace = None
391
392                 for name, place in oldplace.exits.items():
393                     if name.lower() == what.lower():
394                         newplace = place
395
396                 if newplace:
397                     self.move_thing(player, newplace)
398                 else:
399                     player.send_message(None, 'You cannot go there')
400                 return True
401             elif cmd == 'inventory':
402                 self.bag_command(player, 'ls')
403                 return True
404             elif cmd == 'who':
405                 player.send_message(None, 'Players in "%s":' % self.name)
406                 for thing in self.players:
407                     player.send_message(None,
408                                         ' - %s is at/in %s' % (thing.name,
409                                                               thing.place.name))
410                 return True
411             else:
412                 handled = False
413                 for thing in (player.place.npcs + player.place.things):
414                     if thing.handle_command(text, player):
415                         handled = True
416                 if handled:
417                     return True
418         return False
419
Note: See TracBrowser for help on using the browser.