root/projects/2008/pyogp/pyogp.lib.base/trunk/pyogp/lib/base/agent.py

Revision 2491, 39.4 kB (checked in by kotler.linden, 5 months ago)

remerge of kotler_tests

Line 
1 # standard python libs
2 from logging import getLogger, CRITICAL, ERROR, WARNING, INFO, DEBUG
3 import re
4 import sys
5 import signal
6 import uuid
7 import sets
8
9 #related
10 from eventlet import api
11
12 # pyogp
13 from pyogp.lib.base.login import Login, LegacyLoginParams, OGPLoginParams
14 from pyogp.lib.base.datatypes import *
15 from pyogp.lib.base.exc import LoginError
16 from pyogp.lib.base.region import Region
17 from pyogp.lib.base.inventory import *
18 from pyogp.lib.base.groups import *
19 from pyogp.lib.base.event_system import *
20 from pyogp.lib.base.appearance import *
21
22 # pyogp messaging
23 from pyogp.lib.base.message.message_handler import MessageHandler
24
25 from pyogp.lib.base.message.packets import *
26
27 # pyogp utilities
28 from pyogp.lib.base.utilities.helpers import Helpers
29 from pyogp.lib.base.utilities.enums import ImprovedIMDialogue, MoneyTransactionType, TransactionFlags
30
31 # initialize logging
32 logger = getLogger('pyogp.lib.base.agent')
33 log = logger.log
34
35 class Agent(object):
36     """ The Agent class is a container for agent specific data.
37
38     Example, of login via the agent class:
39     Initialize the login class
40
41     >>> client = Agent()
42     >>> client.login('https://login.agni.lindenlab.com/cgi-bin/login.cgi', 'firstname', 'lastname', 'secret', start_location = 'last')
43
44     Sample implementations: examples/sample_agent_login.py
45     Tests: tests/login.txt, tests/test_agent.py
46
47     """
48
49     def __init__(self, settings = None, firstname = '', lastname = '', password = '', agent_id = None, events_handler = None, handle_signals=True):
50         """ initialize this agent """
51
52         # allow the settings to be passed in
53         # otherwise, grab the defaults
54         if settings != None:
55             self.settings = settings
56         else:
57             from pyogp.lib.base.settings import Settings
58             self.settings = Settings()
59
60         # allow the eventhandler to be passed in
61         # so that applications running multiple avatars
62         # may use the same eventhandler
63
64         # otherwise, let's just use our own
65         if events_handler != None:
66             self.events_handler = events_handler
67         else:
68             self.events_handler = AppEventsHandler()
69
70         # signal handler to capture erm signals
71         if handle_signals:
72             self.signal_handler = signal.signal(signal.SIGINT, self.sigint_handler)
73
74         # storage containers for agent attributes
75         # we overwrite with what the grid tells us, rather than what
76         # is passed in and stored in Login()
77         self.firstname = firstname
78         self.lastname = lastname
79         self.password = password
80         self.agent_id = None
81         self.session_id = None
82         self.secure_session_id = None
83         self.name = self.Name()
84
85         # other storage containers
86         self.inventory_host = None
87         self.agent_access = None
88         self.udp_blacklist = None
89         self.home = None
90         self.inventory = None
91         self.start_location = None
92         self.group_manager = GroupManager(self, self.settings)
93
94
95         # additional attributes
96         self.login_response = None
97         self.connected = False
98         self.grid_type = None
99         self.running = True
100         self.helpers = Helpers()
101
102         # data we store as it comes in from the grid
103         self.Position = Vector3()     # this will get updated later, but seed it with 000
104         self.ActiveGroupID = UUID()
105
106         # should we include these here?
107         self.agentdomain = None     # the agent domain the agent is connected to if an OGP context
108         self.child_regions = []     # all neighboring regions
109         self._pending_child_regions = []    # neighbor regions an agent may connect to
110         self.region = None          # the host simulation for the agent
111
112         # init AppearanceManager()
113         self.appearance = AppearanceManager(self.settings, self)
114
115         # Cache of region name->handle; per-agent to prevent information leaks
116         self.region_name_map = {}
117
118         # Cache of agent_id->(first_name, last_name); per agent to prevent info leaks
119         self.agent_id_map = {}
120
121         if self.settings.LOG_VERBOSE: log(DEBUG, 'Initializing agent: %s' % (self))
122
123     def Name(self):
124         """ returns a concatenated firstname + ' ' + lastname"""
125
126         return self.firstname + ' ' + self.lastname
127
128     def login(self, loginuri, firstname=None, lastname=None, password=None, login_params = None, start_location=None, handler=None, connect_region = True):
129         """ login to a login endpoint using the Login() class """
130
131         if (re.search('auth.cgi$', loginuri)):
132
133             self.grid_type = 'OGP'
134
135         elif (re.search('login.cgi$', loginuri)):
136
137             self.grid_type = 'Legacy'
138
139         else:
140             log(WARNING, 'Unable to identify the loginuri schema. Stopping')
141             sys.exit(-1)
142
143         if firstname != None:
144             self.firstname = firstname
145         if lastname != None:
146             self.lastname = lastname
147         if password != None:
148             self.password = password
149
150         # handle either login params passed in, or, account info
151         if login_params == None:
152
153             if (self.firstname == '') or (self.lastname == '') or (self.password == ''):
154
155                 raise LoginError('Unable to login an unknown agent.')
156
157             else:
158
159                 self._login_params = self._get_login_params(loginuri, self.firstname, self.lastname, self.password)
160
161         else:
162
163             self._login_params = login_params
164
165         # login and parse the response
166         login = Login(settings = self.settings)
167
168         self.login_response = login.login(loginuri, self._login_params, start_location, handler = handler)
169         self._parse_login_response()
170
171         # ToDo: what to do with self.login_response['look_at']?
172
173         if self.settings.MULTIPLE_SIM_CONNECTIONS:
174             api.spawn(self._monitor_for_new_regions)
175
176         if connect_region:
177             self._enable_current_region()
178                              
179
180     def logout(self):
181         """ logs an agent out of the current region. calls Region()._kill_coroutines() for all child regions, and Region().logout() for the host region """
182
183         if not self.connected:
184             log(INFO, 'Agent is not logged into the grid. Stopping.')
185             sys.exit()
186
187         self.running = False
188
189         if self.region == None:
190             return
191         else:
192
193             # kill udp and or event queue for child regions
194             [region._kill_coroutines() for region in self.child_regions]
195
196             if self.region.logout():
197                 self.connected = False
198
199         # zero out the password in case we dump it somewhere
200         self.password = ''
201
202     def _get_login_params(self, loginuri, firstname, lastname, password):
203         """ get the proper login parameters of the legacy or ogp enabled grid """
204
205         if self.grid_type == 'OGP':
206
207             login_params = OGPLoginParams(firstname, lastname, password)
208
209         elif self.grid_type == 'Legacy':
210
211             login_params = LegacyLoginParams(firstname, lastname, password)
212
213         return login_params
214
215     def _parse_login_response(self):
216         """ evaluates the login response and propagates data to the Agent() attributes. enables InventoryManager() if settings dictate """
217
218         if self.grid_type == 'Legacy':
219
220             self.firstname = re.sub(r'\"', '', self.login_response['first_name'])
221             self.lastname = self.login_response['last_name']
222             self.agent_id = UUID(self.login_response['agent_id'])
223             self.session_id = UUID(self.login_response['session_id'])
224             self.secure_session_id = UUID(self.login_response['secure_session_id'])
225
226             self.connected = bool(self.login_response['login'])
227             self.inventory_host = self.login_response['inventory_host']
228             self.agent_access = self.login_response['agent_access']
229             self.udp_blacklist = self.login_response['udp_blacklist']
230             self.start_location = self.login_response['start_location']
231
232             if self.login_response.has_key('home'): self.home = Home(self.login_response['home'])
233
234         elif self.grid_type == 'OGP':
235
236             pass
237
238     def _enable_current_region(self, region_x = None, region_y = None, seed_capability = None, udp_blacklist = None, sim_ip = None, sim_port = None, circuit_code = None):
239         """ enables and connects udp and event queue for an agent's current region """
240
241         if self.login_response.has_key('circuit_code'):
242             self.circuit_code = self.login_response['circuit_code']
243
244         region_x = region_x or self.login_response['region_x']
245         region_y = region_y or self.login_response['region_y']
246         seed_capability = seed_capability or self.login_response['seed_capability']
247         udp_blacklist = udp_blacklist or self.login_response['udp_blacklist']
248         sim_ip = sim_ip or self.login_response['sim_ip']
249         sim_port = sim_port or self.login_response['sim_port']
250         circuit_code = circuit_code or self.login_response['circuit_code']
251
252         # enable the current region, setting connect = True
253         self.region = Region(region_x, region_y, seed_capability, udp_blacklist, sim_ip, sim_port, circuit_code, self, settings = self.settings, events_handler = self.events_handler)
254
255         self.region.is_host_region = True       
256
257         # start the simulator udp and event queue connections
258         if self.settings.LOG_COROUTINE_SPAWNS: log(INFO, "Spawning a coroutine for connecting to the agent's host region.")
259
260         api.spawn(self.region.connect)
261        
262         self.enable_callbacks()
263
264        
265
266     def _enable_child_region(self, region_params):
267         """ enables a child region. eligible simulators are sent in EnableSimulator over the event queue, and routed through the packet handler """
268
269         # if this is the sim we are already connected to, skip it
270         if self.region.sim_ip == region_params['IP'] and self.region.sim_port == region_params['Port']:
271             #self.region.sendCompleteAgentMovement()
272             log(DEBUG, "Not enabling a region we are already connected to: %s" % (str(region_params['IP']) + ":" + str(region_params['Port'])))
273             return
274
275         child_region = Region(circuit_code = self.circuit_code, sim_ip = region_params['IP'], sim_port = region_params['Port'], handle = region_params['Handle'], agent = self, settings = self.settings, events_handler = self.events_handler)
276
277         self.child_regions.append(child_region)
278
279         log(INFO, "Enabling a child region with ip:port of %s" % (str(region_params['IP']) + ":" + str(region_params['Port'])))
280
281         if self.settings.LOG_COROUTINE_SPAWNS: log(INFO, "Spawning a coroutine for connecting to a neighboring region.")
282
283         api.spawn(child_region.connect_child)
284
285     def _monitor_for_new_regions(self):
286         """ enable connections to neighboring regions found in the pending queue """
287
288         while self.running:
289
290             if len(self._pending_child_regions) > 0:
291
292                 for region_params in self._pending_child_regions:
293                    
294                     self._enable_child_region(region_params)
295                     self._pending_child_regions.remove(region_params)
296
297             api.sleep(0)
298
299     def _start_EQ_on_neighboring_region(self, message):
300         """ enables the event queue on an agent's neighboring region """
301
302         region = [region for region in self.child_regions if message.blocks['Message_Data'][0].get_variable('sim-ip-and-port').data == str(region.sim_ip) + ":" + str(region.sim_port)]
303
304         if region != []:
305
306             region[0]._set_seed_capability(message.blocks['Message_Data'][0].get_variable('seed-capability').data)
307
308             region[0]._get_region_capabilities()
309
310             log(DEBUG, 'Spawning neighboring region event queue connection')
311             region[0]._startEventQueue()
312
313     def enable_callbacks(self):
314         """ enable the Agents() callback handlers for packet received events """
315         # TODO oopify
316        
317         if self.settings.ENABLE_INVENTORY_MANAGEMENT:
318             while self.region.capabilities == {}:
319
320                 api.sleep(0)
321            
322             inventory_caps = ['FetchInventory', 'WebFetchInventoryDescendents', 'FetchLib', 'FetchLibDescendents']
323    
324             if sets.Set(self.region.capabilities.keys()).intersection(inventory_caps):
325    
326                 caps = dict([(capname, self.region.capabilities[capname]) for capname in inventory_caps])
327    
328                 log(INFO, "Using the capability based inventory management mechanism")
329    
330                 self.inventory = AIS(self, caps)
331    
332             else:
333    
334                 log(INFO, "Using the UDP based inventory management mechanism")
335    
336                 self.inventory = UDP_Inventory(self)
337    
338             self.inventory._parse_folders_from_login_response()   
339             self.inventory.enable_callbacks()
340
341         if self.settings.ENABLE_APPEARANCE_MANAGEMENT:
342             self.appearance.enable_callbacks()
343         if self.settings.ENABLE_GROUP_CHAT:
344             self.group_manager.enable_callbacks()
345         if self.settings.MULTIPLE_SIM_CONNECTIONS:
346
347             onEnableSimulator_received = self.region.message_handler.register('EnableSimulator')
348             onEnableSimulator_received.subscribe(self.onEnableSimulator)
349
350             onEstablishAgentCommunication_received = self.region.message_handler.register('EstablishAgentCommunication')
351             onEstablishAgentCommunication_received.subscribe(self.onEstablishAgentCommunication)
352
353         if self.settings.HANDLE_PACKETS:
354
355             onAlertMessage_received = self.region.message_handler.register('AlertMessage')
356             onAlertMessage_received.subscribe(self.onAlertMessage)
357
358             onAgentDataUpdate_received = self.region.message_handler.register('AgentDataUpdate')
359             onAgentDataUpdate_received.subscribe(self.onAgentDataUpdate)
360
361             onAgentMovementComplete_received = self.region.message_handler.register('AgentMovementComplete')
362             onAgentMovementComplete_received.subscribe(self.onAgentMovementComplete)
363
364             onHealthMessage_received = self.region.message_handler.register('HealthMessage')
365             onHealthMessage_received.subscribe(self.onHealthMessage)
366
367             onImprovedInstantMessage_received = self.region.message_handler.register('ImprovedInstantMessage')
368             onImprovedInstantMessage_received.subscribe(self.onImprovedInstantMessage)
369
370             self.region.message_handler.register('TeleportStart').subscribe(self.simple_callback('Info'))
371             self.region.message_handler.register('TeleportProgress').subscribe(self.simple_callback('Info'))
372             self.region.message_handler.register('TeleportFailed').subscribe(self.simple_callback('Info'))
373             self.region.message_handler.register('TeleportFinish').subscribe(self.onTeleportFinish)
374
375             self.region.message_handler.register('OfflineNotification').subscribe(self.simple_callback('AgentBlock'))
376             self.region.message_handler.register('OnlineNotification').subscribe(self.simple_callback('AgentBlock'))
377
378             self.region.message_handler.register('MoneyBalanceReply').subscribe(self.simple_callback('MoneyData'))
379             self.region.message_handler.register('RoutedMoneyBalanceReply').subscribe(self.simple_callback('MoneyData'))
380
381             if self.settings.ENABLE_COMMUNICATIONS_TRACKING:
382                 onChatFromSimulator_received = self.region.message_handler.register('ChatFromSimulator')
383                 onChatFromSimulator_received.subscribe(self.onChatFromSimulator)
384    
385
386     def simple_callback(self, blockname):
387         """Generic callback creator for single-block packets."""
388
389         def repack(packet, blockname):
390             """Repack a single block packet into an AppEvent"""
391             payload = {}
392             block = packet.blocks[blockname][0]
393             for var in block.var_list:
394                 payload[var] = block.get_variable(var).data
395
396             return AppEvent(packet.name, payload=payload)
397
398         return lambda p: self.events_handler._handle(repack(p, blockname))
399
400
401     def send_AgentDataUpdateRequest(self):
402         """ queues a packet requesting an agent data update """
403
404         packet = AgentDataUpdateRequestPacket()
405
406         packet.AgentData['AgentID'] = self.agent_id
407         packet.AgentData['SessionID'] = self.session_id
408
409         self.region.enqueue_message(packet())
410
411     # ~~~~~~~~~~~~~~
412     # Communications
413     # ~~~~~~~~~~~~~~
414
415     # Chat
416
417     def say(self, Message, Type = 1, Channel = 0):
418         """ queues a packet to send open chat via ChatFromViewer
419
420         Channel: 0 is open chat
421         Type: 0 = Whisper
422               1 = Say
423               2 = Shout
424         """
425
426         packet = ChatFromViewerPacket()
427
428         packet.AgentData['AgentID'] = self.agent_id
429         packet.AgentData['SessionID'] = self.session_id
430
431         packet.ChatData['Message'] = Message
432         packet.ChatData['Type'] = Type
433         packet.ChatData['Channel'] = Channel
434
435         self.region.enqueue_message(packet())
436
437     # Instant Message (im, group chat)
438
439     def instant_message(self, ToAgentID = None, Message = None, _ID = None):
440         """ sends an instant message to another avatar, wrapping Agent().send_ImprovedInstantMessage() with some handy defaults """
441
442         if ToAgentID != None and Message != None:
443
444             if _ID == None: _ID = self.agent_id
445
446             _AgentID = self.agent_id
447             _SessionID = self.session_id
448             _FromGroup = False
449             _ToAgentID = UUID(str(ToAgentID))
450             _ParentEstateID = 0
451             _RegionID = UUID()
452             _Position = self.Position
453             _Offline = 0
454             _Dialog = ImprovedIMDialogue.FromAgent
455             _ID = _ID
456             _Timestamp = 0
457             _FromAgentName = self.firstname + ' ' + self.lastname
458             _Message = Message
459             _BinaryBucket = ''
460
461             self.send_ImprovedInstantMessage(_AgentID, _SessionID, _FromGroup, _ToAgentID, _ParentEstateID, _RegionID, _Position, _Offline, _Dialog, _ID, _Timestamp, _FromAgentName, _Message, _BinaryBucket)
462
463         else:
464
465             log(INFO, "Please specify an agentid and message to send in agent.instant_message")
466
467     def send_ImprovedInstantMessage(self, AgentID = None, SessionID = None, FromGroup = None, ToAgentID = None, ParentEstateID = None, RegionID = None, Position = None, Offline = None, Dialog = None, _ID = None, Timestamp = None, FromAgentName = None, Message = None, BinaryBucket = None, AgentDataBlock = {}, MessageBlockBlock = {}):
468         """ sends an instant message packet to ToAgentID. this is a multi-purpose message for inventory offer handling, im, group chat, and more """
469
470         packet = ImprovedInstantMessagePacket()
471
472         if AgentDataBlock == {}:
473             packet.AgentData['AgentID'] = AgentID
474             packet.AgentData['SessionID'] = SessionID           
475         else:
476             packet.AgentData = AgentDataBlock
477
478         if FromAgentName == None:
479             FromAgentName = self.Name()
480
481         # ha! when scripting out packets.py, never considered a block named *block
482         if MessageBlockBlock == {}:
483
484             packet.MessageBlock['FromGroup'] = FromGroup                   # Bool
485             packet.MessageBlock['ToAgentID'] = UUID(str(ToAgentID))   # LLUUID
486             packet.MessageBlock['ParentEstateID'] = ParentEstateID         # U32
487             packet.MessageBlock['RegionID'] = UUID(str(RegionID))     # LLUUID
488             packet.MessageBlock['Position'] = Position()                     # LLVector3
489             packet.MessageBlock['Offline'] = Offline                       # U8
490             packet.MessageBlock['Dialog'] = Dialog                         # U8 IM Type
491             packet.MessageBlock['ID'] = UUID(str(_ID))                # LLUUID
492             packet.MessageBlock['Timestamp'] = Timestamp                   # U32
493             packet.MessageBlock['FromAgentName'] = FromAgentName           # Variable 1
494             packet.MessageBlock['Message'] = Message                       # Variable 2
495             packet.MessageBlock['BinaryBucket'] = BinaryBucket             # Variable 2
496
497         self.region.enqueue_message(packet(), True)
498  
499     def send_RetrieveInstantMessages(self):
500         """ asks simulator for instant messages stored while agent was offline """
501        
502         packet = RetrieveInstantMessagesPackets()
503        
504         packet.AgentDataBlock['AgentID'] = self.agent_id
505         packet.AgentDataBlock['SessionID'] = self.session_id
506        
507         self.region.enqueue_message(packet())
508
509
510     def sigint_handler(self, signal, frame):
511         """ catches terminal signals (Ctrl-C) to kill running client instances """
512
513         log(INFO, "Caught signal... %d. Stopping" % signal)
514         #self.running = False
515         self.logout()
516         #sys.exit(0)
517
518     def __repr__(self):
519         """ returns a representation of the agent """
520
521         if self.firstname == None:
522             return 'A new agent instance'
523         else:
524             return self.Name()
525
526     def onAgentDataUpdate(self, packet):
527         """ callback handler for received AgentDataUpdate messages which populates various Agent() attributes """
528
529         if self.agent_id == None:
530             self.agent_id = packet.blocks['AgentData'][0].get_variable('AgentID').data
531
532         if self.firstname == None:
533             self.firstname = packet.blocks['AgentData'][0].get_variable('FirstName').data
534
535         if self.lastname == None:
536             self.firstname = packet.blocks['AgentData'][0].get_variable('LastName').data
537
538         self.GroupTitle = packet.blocks['AgentData'][0].get_variable('GroupTitle').data
539
540         self.ActiveGroupID = packet.blocks['AgentData'][0].get_variable('ActiveGroupID').data
541
542         self.GroupPowers = packet.blocks['AgentData'][0].get_variable('GroupPowers').data
543
544         self.GroupName = packet.blocks['AgentData'][0].get_variable('GroupName').data
545
546     def onAgentMovementComplete(self, packet):
547         """ callback handler for received AgentMovementComplete messages which populates various Agent() and Region() attributes """
548
549         self.Position = packet.blocks['Data'][0].get_variable('Position').data
550         if self.Position == None:
551             log(WARNING, "agent.position is None agent.py")
552         self.LookAt = packet.blocks['Data'][0].get_variable('LookAt').data
553
554         self.region.RegionHandle = packet.blocks['Data'][0].get_variable('RegionHandle').data
555
556         #agent.Timestamp = packet.blocks['Data'][0].get_variable('Timestamp')
557
558         self.region.ChannelVersion = packet.blocks['SimData'][0].get_variable('ChannelVersion').data
559
560         # Raise a plain-vanilla AppEvent
561         self.simple_callback('Data')(packet)
562
563     def onHealthMessage(self, packet):
564         """ callback handler for received HealthMessage messages which populates Agent().health """
565
566         self.health = packet.blocks['HealthData'][0].get_variable('Health').data
567
568     def onAgentGroupDataUpdate(self, packet):
569         """ callback handler for received AgentGroupDataUpdate messages which updates stored group instances in the group_manager """
570
571         # AgentData block
572         AgentID = packet.blocks['AgentData'][0].get_variable('AgentID').data
573
574         # GroupData block
575         for GroupData_block in packet.blocks['GroupData']:
576
577             AcceptNotices = GroupData_block.get_variable('AcceptNotices').data
578             GroupPowers = GroupData_block.get_variable('GroupPowers').data
579             GroupID = GroupData_block.get_variable('GroupID').data
580             GroupName = GroupData_block.get_variable('GroupName').data
581             ListInProfile = GroupData_block.get_variable('ListInProfile').data
582             Contribution = GroupData_block.get_variable('Contribution').data
583             GroupInsigniaID = GroupData_block.get_variable('GroupInsigniaID').data
584
585             # make sense of group powers
586             GroupPowers = [ord(x) for x in GroupPowers]
587             GroupPowers = ''.join([str(x) for x in GroupPowers])
588
589             group = Group(AcceptNotices, GroupPowers, GroupID, GroupName, ListInProfile, Contribution,GroupInsigniaID )
590
591             self.group_manager.store_group(group)
592
593     def onChatFromSimulator(self, packet):
594         """ callback handler for received ChatFromSimulator messages which parses and fires a ChatReceived event. """
595
596         log(INFO, "Working on parsing chat messages....")
597
598         FromName = packet.blocks['ChatData'][0].get_variable('FromName').data
599         SourceID = packet.blocks['ChatData'][0].get_variable('SourceID').data
600         OwnerID = packet.blocks['ChatData'][0].get_variable('OwnerID').data
601         SourceType = packet.blocks['ChatData'][0].get_variable('SourceType').data
602         ChatType = packet.blocks['ChatData'][0].get_variable('ChatType').data
603         Audible = packet.blocks['ChatData'][0].get_variable('Audible').data
604         Position = packet.blocks['ChatData'][0].get_variable('Position').data
605         Message = packet.blocks['ChatData'][0].get_variable('Message').data
606
607         message = AppEvent('ChatReceived', FromName = FromName, SourceID = SourceID, OwnerID = OwnerID, SourceType = SourceType, ChatType = ChatType, Audible = Audible, Position = Position, Message = Message)
608
609         log(INFO, "Received chat from %s: %s" % (FromName, Message))
610
611         self.events_handler._handle(message)
612
613     def onImprovedInstantMessage(self, packet):
614         """ callback handler for received ImprovedInstantMessage messages. much is passed in this message, and handling the data is only partially implemented """
615
616         log(INFO, "Working on parsing ImprovedInstantMessage messages....")
617
618         Dialog = packet.blocks['MessageBlock'][0].get_variable('Dialog').data
619         FromAgentID = packet.blocks['AgentData'][0].get_variable('AgentID').data
620
621         if Dialog == ImprovedIMDialogue.InventoryOffered:
622
623             self.inventory.handle_inventory_offer(packet)
624
625         elif Dialog == ImprovedIMDialogue.InventoryAccepted:
626
627             if str(FromAgentID) != str(self.agent_id):
628
629                 FromAgentName = packet.blocks['MessageBlock'][0].get_variable('FromAgentName').data
630                 InventoryName = packet.blocks['MessageBlock'][0].get_variable('Message').data
631
632                 log(INFO, "Agent %s accepted the inventory offer." % (FromAgentName))
633
634         elif Dialog == ImprovedIMDialogue.InventoryDeclined:
635
636             if str(FromAgentID) != str(self.agent_id):
637
638                 FromAgentName = packet.blocks['MessageBlock'][0].get_variable('FromAgentName').data
639                 InventoryName = packet.blocks['MessageBlock'][0].get_variable('Message').data
640
641                 log(INFO, "Agent %s declined the inventory offer." % (FromAgentName))
642
643         elif Dialog == ImprovedIMDialogue.FromAgent:
644
645             RegionID = packet.blocks['MessageBlock'][0].get_variable('RegionID').data
646             Position = packet.blocks['MessageBlock'][0].get_variable('Position').data
647             ID = packet.blocks['MessageBlock'][0].get_variable('ID').data
648             FromAgentName = packet.blocks['MessageBlock'][0].get_variable('FromAgentName').data
649             Message = packet.blocks['MessageBlock'][0].get_variable('Message').data
650
651             message = AppEvent('InstantMessageReceived', FromAgentID = FromAgentID, RegionID = RegionID, Position = Position, ID = ID, FromAgentName = FromAgentName, Message = Message)
652
653             log(INFO, "Received instant message from %s: %s" % (FromAgentName, Message))
654
655             self.events_handler._handle(message)
656
657         else:
658
659             self.helpers.log_packet(packet, self)
660
661     def onAlertMessage(self, packet):
662         """ callback handler for received AlertMessage messages. logs and raises an event """
663
664         # ToDo: raise an event when this is received
665
666         AlertMessage = packet.blocks['AlertData'][0].get_variable('Message').data
667
668         log(WARNING, "AlertMessage from simulator: %s" % (AlertMessage))
669
670     def onEnableSimulator(self, packet):
671         """ callback handler for received EnableSimulator messages. stores the region data for later connections """
672
673         IP = [ord(x) for x in packet.blocks['SimulatorInfo'][0].get_variable('IP').data]
674         IP = '.'.join([str(x) for x in IP])
675
676         Port = packet.blocks['SimulatorInfo'][0].get_variable('Port').data
677
678         # not sure what this is, but pass it up
679         Handle = [ord(x) for x in packet.blocks['SimulatorInfo'][0].get_variable('Handle').data]
680
681         region_params = {'IP': IP, 'Port': Port, 'Handle': Handle}
682
683         log(INFO, 'Received EnableSimulator for %s' % (str(IP) + ":" + str(Port)))
684
685         # are we already prepping to connect to the sim?
686         if region_params not in self._pending_child_regions:
687
688             # are we already connected to the sim?
689             known_region = False
690
691             # don't append to the list if we already know about this region
692             for region in self.child_regions:
693                 if region.sim_ip == region_params['IP'] and region.sim_port == region_params['Port']:
694                     known_region = True
695
696             #agent._enable_child_region(IP, Port, Handle)
697             if not known_region:
698                 self._pending_child_regions.append(region_params)
699
700     def onEstablishAgentCommunication(self, message):
701         """ callback handler for received EstablishAgentCommunication messages. try to enable the event queue for a neighboring region based on the data received """
702
703         log(INFO, 'Received EstablishAgentCommunication for %s' % (message.blocks['Message_Data'][0].get_variable('sim-ip-and-port').data))
704
705         is_running = False
706
707         # don't enable the event queue when we already have it running
708         for region in self.child_regions:
709             if (str(region.sim_ip) + ":" + str(region.sim_port) == message.blocks['Message_Data'][0].get_variable('sim-ip-and-port').data) and region.event_queue != None:
710                 if region.event_queue._running:
711                     is_running = True
712
713         # start the event queue
714         if not is_running:
715             self._start_EQ_on_neighboring_region(message)
716
717
718     def teleport(self,
719                  region_name=None,
720                  region_handle=None,
721                  region_id=None,
722                  position=Vector3(X=128, Y=128, Z=128),
723                  look_at=Vector3(X=128, Y=128, Z=128)):
724         """Initiate a teleport to the specified location. When passing a region name
725         it may be necessary to request the destination region handle from the current sim
726         before the teleport can start."""
727        
728         log(INFO, 'teleport name=%s handle=%s id=%s', str(region_name), str(region_handle), str(region_id))
729        
730         # Handle intra-region teleports even by name
731         if not region_id and region_name and region_name.lower() == self.region.SimName.lower():
732             region_id = self.region.RegionID
733
734         if not region_id and not region_handle and region_name.lower() in self.region_name_map:
735             region_handle = self.region_name_map[region_name.lower()]
736
737         if region_id:
738             log(INFO, 'sending TP request packet')
739             packet = TeleportRequestPacket()
740
741             packet.AgentData['AgentID'] = self.agent_id
742             packet.AgentData['SessionID'] = self.session_id
743            
744             packet.Info['RegionID'] = region_id
745             packet.Info['Position'] = position
746             packet.Info['LookAt'] = look_at
747            
748             self.region.enqueue_message(packet())
749
750         elif region_handle:
751             log(INFO, 'sending TP location request packet')
752             packet = TeleportLocationRequestPacket()
753
754             packet.AgentData['AgentID'] = self.agent_id
755             packet.AgentData['SessionID'] = self.session_id
756            
757             packet.Info['RegionHandle'] = region_handle
758             packet.Info['Position'] = position
759             packet.Info['LookAt'] = look_at
760            
761             self.region.enqueue_message(packet())
762
763         else:
764             log(INFO, "Target region's handle not known, sending map name request")
765             # do a region_name to region_id lookup and then request the teleport
766             self.send_MapNameRequest(
767                 region_name,
768                 lambda handle: self.teleport(region_handle=handle, position=position, look_at=look_at))
769                
770    
771     def send_MapNameRequest(self, region_name, callback):
772
773         handler = self.region.message_handler.register('MapBlockReply')
774        
775         def onMapBlockReplyPacket(packet):
776             log(INFO, 'MapBlockReplyPacket received')
777             for block in packet.blocks['Data']:
778                 if block.get_variable('Name').data.lower() == region_name.lower():
779                     handler.unsubscribe(onMapBlockReplyPacket)
780
781                     x = block.get_variable('X').data
782                     y = block.get_variable('Y').data
783                     region_handle = Region.xy_to_handle(x,y)
784
785                     self.region_name_map[region_name.lower()] = region_handle
786                    
787                     callback(region_handle)                   
788                     return
789             # Leave it registered, as the event may come later
790            
791         # Register a handler for the response       
792         handler.subscribe(onMapBlockReplyPacket)
793
794         # ...and make the request
795         log(INFO, 'sending MapNameRequestPacket')
796         packet = MapNameRequestPacket()
797         packet.AgentData['AgentID'] = self.agent_id
798         packet.AgentData['SessionID'] = self.session_id
799         packet.AgentData['Flags'] = 0
800         packet.AgentData['EstateID'] = 0
801         packet.AgentData['Godlike'] = False
802         packet.NameData['Name'] = region_name.lower()
803         self.region.enqueue_message(packet())
804
805     def onTeleportFinish(self, packet):
806         """Handle the end of a successful teleport"""
807
808         log(INFO, "Teleport finished, taking care of details...")
809
810         # Raise a plain-vanilla AppEvent for the Info block
811         self.simple_callback('Info')(packet)
812
813         # packed binary U64 to integral x, y
814         region_handle = packet.blocks['Info'][0].get_variable('RegionHandle').data       
815         region_x, region_y = Region.handle_to_xy(region_handle)
816
817         # packed binary to dotted-octet
818         sim_ip = packet.blocks['Info'][0].get_variable('SimIP').data
819         sim_ip = '.'.join(map(str,struct.unpack('BBBB', sim_ip)))
820
821         # *TODO: Make this more graceful
822         log(INFO, "Disconnecting from old region")
823         [region._kill_coroutines() for region in self.child_regions]
824         self.region._kill_coroutines()
825
826         self.region = None
827         self.child_regions = []
828         self._pending_child_regions = []
829
830         log(INFO, "Enabling new region")
831         self._enable_current_region(
832             region_x = region_x,
833             region_y = region_y,
834             seed_capability = packet.blocks['Info'][0].get_variable('SeedCapability').data,
835             sim_ip = sim_ip,
836             sim_port = packet.blocks['Info'][0].get_variable('SimPort').data
837             )
838
839
840     def request_agent_names(self, agent_ids, callback):
841         """Request agent names. When all names are known, callback
842         will be called with a list of tuples (agent_id, first_name,
843         last_name). If all names are known, callback will be called
844         immediately."""
845        
846         def _fire_callback(_):
847             cbdata = [(agent_id,
848                        self.agent_id_map[agent_id][0],
849                        self.agent_id_map[agent_id][1])
850                       for agent_id in agent_ids]
851             callback(cbdata)
852        
853         names_to_request = [ agent_id
854                              for agent_id in agent_ids
855                              if agent_id not in self.agent_id_map ]
856         if names_to_request:
857             self.send_UUIDNameRequest(names_to_request, _fire_callback)
858         else:
859             _fire_callback([])           
860
861
862     def send_UUIDNameRequest(self, agent_ids, callback):
863         handler = self.region.message_handler.register('UUIDNameReply')
864
865         def onUUIDNameReply(packet):
866             log(INFO, 'UUIDNameReplyPacket received')
867            
868             cbdata = []
869             for block in packet.blocks['UUIDNameBlock']:
870                 agent_id = str(block.get_variable('ID').data)
871                 first_name = block.get_variable('FirstName').data
872                 last_name = block.get_variable('LastName').data
873                 self.agent_id_map[agent_id] = (first_name, last_name)
874                 cbdata.append((agent_id, first_name, last_name))
875
876             # Fire the callback only when all names are received
877             missing = [ agent_id
878                         for agent_id in agent_ids
879                         if agent_id not in self.agent_id_map ]
880             if len(missing) == 0:
881                 handler.unsubscribe(onUUIDNameReply)
882                 callback(cbdata)
883             else:
884                 log(INFO, 'Still waiting on %d names', len(missing))
885            
886         handler.subscribe(onUUIDNameReply)
887         log(INFO, 'sending UUIDNameRequest')
888         packet = UUIDNameRequestPacket()
889         packet.UUIDNameBlockBlocks = [ {'ID':UUID(agent_id) } for agent_id in agent_ids ]
890         self.region.enqueue_message(packet())
891
892
893     def request_balance(self, callback):
894         """Request the current agent balance."""
895         handler = self.region.message_handler.register('MoneyBalanceReply')
896
897         def onMoneyBalanceReply(packet):
898             log(INFO, 'MoneyBalanceReply received')
899             handler.unsubscribe(onMoneyBalanceReply) # One-shot handler
900             balance = packet.blocks['MoneyData'][0].get_variable('MoneyBalance').data
901             callback(balance)
902
903         handler.subscribe(onMoneyBalanceReply)
904         log(INFO, 'sending MoneyBalanceRequest')
905         packet = MoneyBalanceRequestPacket()
906         packet.AgentData['AgentID'] = self.agent_id
907         packet.AgentData['SessionID'] = self.session_id
908         packet.MoneyData['TransactionID'] = UUID()
909         self.region.enqueue_message(packet())
910        
911
912     def give_money(self, target_id, amount,
913                    description='',
914                    transaction_type=MoneyTransactionType.Gift,
915                    flags=TransactionFlags.Null):
916         """Give money to another agent"""
917         log(INFO, 'sending MoneyTransferRequest')
918         packet = MoneyTransferRequestPacket()
919         packet.AgentData['AgentID'] = self.agent_id
920         packet.AgentData['SessionID'] = self.session_id
921         packet.MoneyData['SourceID'] = self.agent_id
922         packet.MoneyData['DestID'] = UUID(target_id)
923         packet.MoneyData['Flags'] = flags
924         packet.MoneyData['Amount'] = amount
925         packet.MoneyData['AggregatePermNextOwner'] = 0
926         packet.MoneyData['AggregatePermInventory'] = 0
927         packet.MoneyData['TransactionType'] = transaction_type
928         packet.MoneyData['Description'] = description
929         self.region.enqueue_message(packet())
930
931
932 class Home(object):
933     """ contains the parameters describing an agent's home location as returned in login_response['home'] """
934
935     def __init__(self, params):
936         """ initialize the Home object by parsing the data passed in """
937
938         # eval(params) would be nice, but fails to parse the string the way one thinks it might
939         items =  params.split(', \'')
940
941         # this creates:
942         #   self.region_handle
943         #   self.look_at
944         #   self.position
945         for i in items:
946             i = re.sub(r'[\"\{}\'"]', '', i)
947             i = i.split(':')
948             setattr(self, i[0], eval(re.sub('r', '', i[1])))
949
950         self.global_x = self.region_handle[0]
951         self.global_y = self.region_handle[1]
952
953         self.local_x = self.position[0]
954         self.local_y = self.position[1]
955         self.local_z = self.position[2]
956
957 """
958 Contributors can be viewed at:
959 http://svn.secondlife.com/svn/linden/projects/2008/pyogp/CONTRIBUTORS.txt
960
961 $LicenseInfo:firstyear=2008&license=apachev2$
962
963 Copyright 2009, Linden Research, Inc.
964
965 Licensed under the Apache License, Version 2.0 (the "License").
966 You may obtain a copy of the License at:
967     http://www.apache.org/licenses/LICENSE-2.0
968 or in
969     http://svn.secondlife.com/svn/linden/projects/2008/pyogp/LICENSE.txt
970
971 $/LicenseInfo$
972 """
Note: See TracBrowser for help on using the browser.