aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ScriptEngine/XMREngine/XMRInstBackend.cs
blob: a24036a546ea73d1221c156752f58ff069455f6e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Threading;
using System.Collections.Generic;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Region.ScriptEngine.Shared;
using OpenSim.Region.ScriptEngine.Shared.Api;
using log4net;

using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;

namespace OpenSim.Region.ScriptEngine.XMREngine
{
    /****************************************************\
     *  This file contains routines called by scripts.  *
    \****************************************************/

    public class XMRLSL_Api : LSL_Api
    {
        public AsyncCommandManager acm;
        private XMRInstance inst;

        public void InitXMRLSLApi(XMRInstance i)
        {
            acm = AsyncCommands;
            inst = i;
        }

        protected override void ScriptSleep(int ms)
        {
            inst.Sleep(ms);
        }

        public override void llSleep(double sec)
        {
            inst.Sleep((int)(sec * 1000.0));
        }

        public override void llDie()
        {
            inst.Die();
        }

        /**
         * @brief Seat avatar on prim.
         * @param owner = true: owner of prim script is running in
         *               false: avatar that has given ANIMATION permission on the prim
         * @returns 0: successful
         *         -1: no permission to animate
         *         -2: no av granted perms
         *         -3: av not in region
         */
/* engines should not have own API
        public int xmrSeatAvatar (bool owner)
        {
            // Get avatar to be seated and make sure they have given us ANIMATION permission

            UUID avuuid;
            if (owner) {
                avuuid = inst.m_Part.OwnerID;
            } else {
                if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TRIGGER_ANIMATION) == 0) {
                    return -1;
                }
                avuuid = m_item.PermsGranter;
            }
            if (avuuid == UUID.Zero) {
                return -2;
            }

            ScenePresence presence = World.GetScenePresence (avuuid);
            if (presence == null) {
                return -3;
            }

            // remoteClient = not used by ScenePresence.HandleAgentRequestSit()
            //      agentID = not used by ScenePresence.HandleAgentRequestSit()
            //     targetID = UUID of prim to sit on
            //       offset = offset of sitting position

            presence.HandleAgentRequestSit (null, UUID.Zero, m_host.UUID, OpenMetaverse.Vector3.Zero);
            return 0;
        }
*/
        /**
         * @brief llTeleportAgent() is broken in that if you pass it a landmark,
         *        it still subjects the position to spawn points, as it always
         *        calls RequestTeleportLocation() with TeleportFlags.ViaLocation.
         *        See llTeleportAgent() and CheckAndAdjustTelehub().
         *
         * @param agent    = what agent to teleport
         * @param landmark = inventory name or UUID of a landmark object
         * @param lookat   = looking direction after teleport
         */
/* engines should not have own API
        public void xmrTeleportAgent2Landmark (string agent, string landmark, LSL_Vector lookat)
        {
            // find out about agent to be teleported
            UUID agentId;
            if (!UUID.TryParse (agent, out agentId)) throw new ApplicationException ("bad agent uuid");

            ScenePresence presence = World.GetScenePresence (agentId);
            if (presence == null) throw new ApplicationException ("agent not present in scene");
            if (presence.IsNPC) throw new ApplicationException ("agent is an NPC");
            if (presence.IsGod) throw new ApplicationException ("agent is a god");

            // prim must be owned by land owner or prim must be attached to agent
            if (m_host.ParentGroup.AttachmentPoint == 0) {
                if (m_host.OwnerID != World.LandChannel.GetLandObject (presence.AbsolutePosition).LandData.OwnerID) {
                    throw new ApplicationException ("prim not owned by land's owner");
                }
            } else {
                if (m_host.OwnerID != presence.UUID) throw new ApplicationException ("prim not attached to agent");
            }

            // find landmark in inventory or by UUID
            UUID assetID = ScriptUtils.GetAssetIdFromKeyOrItemName (m_host, landmark);
            if (assetID == UUID.Zero) throw new ApplicationException ("no such landmark");

            // read it in and make sure it is a landmark
            AssetBase lma = World.AssetService.Get (assetID.ToString ());
            if ((lma == null) || (lma.Type != (sbyte)AssetType.Landmark)) throw new ApplicationException ("not a landmark");

            // parse the record
            AssetLandmark lm = new AssetLandmark (lma);

            // the regionhandle (based on region's world X,Y) might be out of date
            // re-read the handle so we can pass it to RequestTeleportLocation()
            var region = World.GridService.GetRegionByUUID (World.RegionInfo.ScopeID, lm.RegionID);
            if (region == null) throw new ApplicationException ("no such region");

            // finally ready to teleport
            World.RequestTeleportLocation (presence.ControllingClient,
                                           region.RegionHandle,
                                           lm.Position,
                                           lookat,
                                           (uint)TeleportFlags.ViaLandmark);
        }
*/
        /**
         * @brief Allow any member of group given by config SetParcelMusicURLGroup to set music URL.
         *        Code modelled after llSetParcelMusicURL().
         * @param newurl = new URL to set (or "" to leave it alone)
         * @returns previous URL string
         */
/* engines should not have own API
        public string xmrSetParcelMusicURLGroup (string newurl)
        {
            string groupname = m_ScriptEngine.Config.GetString ("SetParcelMusicURLGroup", "");
            if (groupname == "") throw new ApplicationException ("no SetParcelMusicURLGroup config param set");

            IGroupsModule igm = World.RequestModuleInterface<IGroupsModule> ();
            if (igm == null) throw new ApplicationException ("no GroupsModule loaded");

            GroupRecord grouprec = igm.GetGroupRecord (groupname);
            if (grouprec == null) throw new ApplicationException ("no such group " + groupname);

            GroupMembershipData gmd = igm.GetMembershipData (grouprec.GroupID, m_host.OwnerID);
            if (gmd == null) throw new ApplicationException ("not a member of group " + groupname);

            ILandObject land = World.LandChannel.GetLandObject (m_host.AbsolutePosition);
            if (land == null) throw new ApplicationException ("no land at " + m_host.AbsolutePosition.ToString ());
            string oldurl = land.GetMusicUrl ();
            if (oldurl == null) oldurl = "";
            if ((newurl != null) && (newurl != "")) land.SetMusicUrl (newurl);
            return oldurl;
        }
*/
    }

    public partial class XMRInstance
    {
        /**
         * @brief The script is calling llReset().
         *        We throw an exception to unwind the script out to its main
         *        causing all the finally's to execute and it will also set
         *        eventCode = None to indicate event handler has completed.
         */
        public void ApiReset()
        {
            ClearQueueExceptLinkMessages();
            throw new ScriptResetException();
        }

        /**
         * @brief The script is calling one of the llDetected...(int number)
         *        functions.  Return corresponding DetectParams pointer.
         */
        public DetectParams GetDetectParams(int number)
        {
            DetectParams dp = null;
            if ((number >= 0) && (m_DetectParams != null) && (number < m_DetectParams.Length)) {
                dp = m_DetectParams[number];
            }
            return dp;
        }

        /**
         * @brief Script is calling llDie, so flag the run loop to delete script
         *        once we are off the microthread stack, and throw an exception
         *        to unwind the stack asap.
         */
        public void Die()
        {
            // llDie doesn't work in attachments!
            if (m_Part.ParentGroup.IsAttachment || m_DetachQuantum > 0)
                return;

            throw new ScriptDieException();
        }

        /**
         * @brief Called by script to sleep for the given number of milliseconds.
         */
        public void Sleep(int ms)
        {
            lock (m_QueueLock) {

                /*
                 * Say how long to sleep.
                 */
                m_SleepUntil = DateTime.UtcNow + TimeSpan.FromMilliseconds(ms);

                /*
                 * Don't wake on any events.
                 */
                m_SleepEventMask1 = 0;
                m_SleepEventMask2 = 0;
            }

            /*
             * The compiler follows all calls to llSleep() with a call to CheckRun().
             * So tell CheckRun() to suspend the microthread.
             */
            suspendOnCheckRunTemp = true;
        }

        /**
         * Block script execution until an event is queued or a timeout is reached.
         * @param timeout = maximum number of seconds to wait
         * @param returnMask = if event is queued that matches these mask bits,
         *                     the script is woken, that event is dequeued and
         *                     returned to the caller.  The event handler is not
         *                     executed.
         * @param backgroundMask = if any of these events are queued while waiting,
         *                         execute their event handlers.  When any such event
         *                         handler exits, continue waiting for events or the
         *                         timeout.
         * @returns empty list: no event was queued that matched returnMask and the timeout was reached
         *                      or a background event handler changed state (eg, via 'state' statement)
         *                else: list giving parameters of the event:
         *                      [0] = event code (integer)
         *                   [1..n] = call parameters to the event, if any
         * Notes:
         *   1) Scrips should use XMREVENTMASKn_<eventname> symbols for the mask arguments,
         *      where n is 1 or 2 for mask1 or mask2 arguments.
         *      The list[0] return argument can be decoded by using XMREVENTCODE_<eventname> symbols.
         *   2) If all masks are zero, the call ends up acting like llSleep.
         *   3) If an event is enabled in both returnMask and backgroundMask, the returnMask bit
         *      action takes precedence, ie, the event is returned.  This allows a simple specification
         *      of -1 for both backgroundMask arguments to indicate that all events not listed in
         *      the returnMask argumetns should be handled in the background.
         *   4) Any events not listed in either returnMask or backgroundMask arguments will be
         *      queued for later processing (subject to normal queue limits).
         *   5) Background event handlers execute as calls from within xmrEventDequeue, they do
         *      not execute as separate threads.  Thus any background event handlers must return
         *      before the call to xmrEventDequeue will return.
         *   6) If a background event handler changes state (eg, via 'state' statement), the state
         *      is immediately changed and the script-level xmrEventDequeue call does not return.
         *   7) For returned events, the detect parameters are overwritten by the returned event.
         *      For background events, the detect parameters are saved and restored.
         *   8) Scripts must contain dummy event handler definitions for any event types that may
         *      be returned by xmrEventDequeue, to let the runtime know that the script is capable
         *      of processing that event type.  Otherwise, the event may not be queued to the script.
         */
        private static LSL_List emptyList = new LSL_List (new object[0]);

        public override LSL_List xmrEventDequeue (double timeout, int returnMask1, int returnMask2,
                                                  int backgroundMask1, int backgroundMask2)
        {
            DateTime sleepUntil = DateTime.UtcNow + TimeSpan.FromMilliseconds (timeout * 1000.0);
            EventParams evt = null;
            int callNo, evc2;
            int evc1 = 0;
            int mask1 = returnMask1 | backgroundMask1;  // codes 00..31
            int mask2 = returnMask2 | backgroundMask2;  // codes 32..63
            LinkedListNode<EventParams> lln = null;
            object[] sv;
            ScriptEventCode evc = ScriptEventCode.None;

            callNo = -1;
            try
            {
                if (callMode == CallMode_NORMAL) goto findevent;

                /*
                 * Stack frame is being restored as saved via CheckRun...().
                 * Restore necessary values then jump to __call<n> label to resume processing.
                 */
                sv          = RestoreStackFrame ("xmrEventDequeue", out callNo);
                sleepUntil  = DateTime.Parse ((string)sv[0]);
                returnMask1 = (int)sv[1];
                returnMask2 = (int)sv[2];
                mask1       = (int)sv[3];
                mask2       = (int)sv[4];
                switch (callNo)
                {
                    case 0: goto __call0;
                    case 1:
                    {
                        evc1 = (int)sv[5];
                        evc  = (ScriptEventCode)(int)sv[6];
                        DetectParams[] detprms = ObjArrToDetPrms ((object[])sv[7]);
                        object[]       ehargs  = (object[])sv[8];
                        evt = new EventParams (evc.ToString (), ehargs, detprms);
                        goto __call1;
                    }
                }
                throw new ScriptBadCallNoException (callNo);

                /*
                 * Find first event that matches either the return or background masks.
                 */
            findevent:
                Monitor.Enter (m_QueueLock);
                for (lln = m_EventQueue.First; lln != null; lln = lln.Next)
                {
                    evt  = lln.Value;
                    evc  = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode), evt.EventName);
                    evc1 = (int)evc;
                    evc2 = evc1 - 32;
                    if ((((uint)evc1 < (uint)32) && (((mask1 >> evc1) & 1) != 0)) ||
                        (((uint)evc2 < (uint)32) && (((mask2 >> evc2) & 1) != 0)))
                        goto remfromq;
                }

                /*
                 * Nothing found, sleep while one comes in.
                 */
                m_SleepUntil = sleepUntil;
                m_SleepEventMask1 = mask1;
                m_SleepEventMask2 = mask2;
                Monitor.Exit (m_QueueLock);
                suspendOnCheckRunTemp = true;
                callNo = 0;
            __call0:
                CheckRunQuick ();
                goto checktmo;

                /*
                 * Found one, remove it from queue.
                 */
            remfromq:
                m_EventQueue.Remove (lln);
                if ((uint)evc1 < (uint)m_EventCounts.Length)
                    m_EventCounts[evc1] --;

                Monitor.Exit (m_QueueLock);
                m_InstEHEvent ++;

                /*
                 * See if returnable or background event.
                 */
                if ((((uint)evc1 < (uint)32) && (((returnMask1 >> evc1) & 1) != 0)) ||
                    (((uint)evc2 < (uint)32) && (((returnMask2 >> evc2) & 1) != 0)))
                {

                    /*
                     * Returnable event, return its parameters in a list.
                     * Also set the detect parameters to what the event has.
                     */
                    int plen = evt.Params.Length;
                    object[] plist = new object[plen+1];
                    plist[0] = (LSL_Integer)evc1;
                    for (int i = 0; i < plen;)
                    {
                        object ob = evt.Params[i];
                        if (ob is int)
                            ob = (LSL_Integer)(int)ob;
                        else if (ob is double)
                            ob = (LSL_Float)(double)ob;
                        else if (ob is string)
                            ob = (LSL_String)(string)ob;
                        plist[++i] = ob;
                    }
                    m_DetectParams = evt.DetectParams;
                    return new LSL_List (plist);
                }

                /*
                 * It is a background event, simply call its event handler,
                 * then check event queue again.
                 */
                callNo = 1;
            __call1:
                ScriptEventHandler seh = m_ObjCode.scriptEventHandlerTable[stateCode,evc1];
                if (seh == null)
                    goto checktmo;

                DetectParams[]  saveDetParams = this.m_DetectParams;
                object[]        saveEHArgs    = this.ehArgs;
                ScriptEventCode saveEventCode = this.eventCode;

                this.m_DetectParams = evt.DetectParams;
                this.ehArgs         = evt.Params;
                this.eventCode      = evc;

                try
                {
                    seh (this);
                }
                finally
                {
                    m_DetectParams = saveDetParams;
                    ehArgs         = saveEHArgs;
                    eventCode      = saveEventCode;
                }

                /*
                 * Keep waiting until we find a returnable event or timeout.
                 */
            checktmo:
                if (DateTime.UtcNow < sleepUntil)
                    goto findevent;

                /*
                 * We timed out, return an empty list.
                 */
                return emptyList;
            }
            finally
            {
                if (callMode != CallMode_NORMAL)
                {

                    /*
                     * Stack frame is being saved by CheckRun...().
                     * Save everything we need at the __call<n> labels so we can restore it
                     * when we need to.
                     */
                    sv    = CaptureStackFrame ("xmrEventDequeue", callNo, 9);
                    sv[0] = sleepUntil.ToString ();                  // needed at __call0,__call1
                    sv[1] = returnMask1;                             // needed at __call0,__call1
                    sv[2] = returnMask2;                             // needed at __call0,__call1
                    sv[3] = mask1;                                   // needed at __call0,__call1
                    sv[4] = mask2;                                   // needed at __call0,__call1
                    if (callNo == 1)
                    {
                        sv[5] = evc1;                                // needed at __call1
                        sv[6] = (int)evc;                            // needed at __call1
                        sv[7] = DetPrmsToObjArr (evt.DetectParams);  // needed at __call1
                        sv[8] = evt.Params;                          // needed at __call1
                    }
                }
            }
        }

        /**
         * @brief Enqueue an event
         * @param ev = as returned by xmrEventDequeue saying which event type to queue
         *             and what argument list to pass to it.  The llDetect...() parameters
         *             are as currently set for the script (use xmrEventLoadDets to set how
         *             you want them to be different).
         */
        public override void xmrEventEnqueue (LSL_List ev)
        {
            object[] data = ev.Data;
            ScriptEventCode evc = (ScriptEventCode)ListInt (data[0]);

            int nargs = data.Length - 1;
            object[] args = new object[nargs];
            Array.Copy (data, 1, args, 0, nargs);

            PostEvent (new EventParams (evc.ToString (), args, m_DetectParams));
        }

        /**
         * @brief Save current detect params into a list
         * @returns a list containing current detect param values
         */
        private const int saveDPVer = 1;

        public override LSL_List xmrEventSaveDets ()
        {
            object[] obs = DetPrmsToObjArr (m_DetectParams);
            return new LSL_List (obs);
        }

        private static object[] DetPrmsToObjArr (DetectParams[] dps)
        {
            int len = dps.Length;
            object[] obs = new object[len*16+1];
            int j = 0;
            obs[j++] = (LSL_Integer)saveDPVer;
            for (int i = 0; i < len; i ++)
            {
                DetectParams dp = dps[i];
                obs[j++] = (LSL_String)dp.Key.ToString();    // UUID
                obs[j++] = dp.OffsetPos;                     // vector
                obs[j++] = (LSL_Integer)dp.LinkNum;          // integer
                obs[j++] = (LSL_String)dp.Group.ToString();  // UUID
                obs[j++] = (LSL_String)dp.Name;              // string
                obs[j++] = (LSL_String)dp.Owner.ToString();  // UUID
                obs[j++] = dp.Position;                      // vector
                obs[j++] = dp.Rotation;                      // rotation
                obs[j++] = (LSL_Integer)dp.Type;             // integer
                obs[j++] = dp.Velocity;                      // vector
                obs[j++] = dp.TouchST;                       // vector
                obs[j++] = dp.TouchNormal;                   // vector
                obs[j++] = dp.TouchBinormal;                 // vector
                obs[j++] = dp.TouchPos;                      // vector
                obs[j++] = dp.TouchUV;                       // vector
                obs[j++] = (LSL_Integer)dp.TouchFace;        // integer
            }
            return obs;
        }


        /**
         * @brief Load current detect params from a list
         * @param dpList = as returned by xmrEventSaveDets()
         */
        public override void xmrEventLoadDets (LSL_List dpList)
        {
            m_DetectParams = ObjArrToDetPrms (dpList.Data);
        }

        private static DetectParams[] ObjArrToDetPrms (object[] objs)
        {
            int j = 0;
            if ((objs.Length % 16 != 1) || (ListInt (objs[j++]) != saveDPVer))
                throw new Exception ("invalid detect param format");

            int len = objs.Length / 16;
            DetectParams[] dps = new DetectParams[len];

            for (int i = 0; i < len; i ++)
            {
                DetectParams dp = new DetectParams ();

                dp.Key         = new UUID (ListStr (objs[j++]));
                dp.OffsetPos   = (LSL_Vector)objs[j++];
                dp.LinkNum     = ListInt (objs[j++]);
                dp.Group       = new UUID (ListStr (objs[j++]));
                dp.Name        = ListStr (objs[j++]);
                dp.Owner       = new UUID (ListStr (objs[j++]));
                dp.Position    = (LSL_Vector)objs[j++];
                dp.Rotation    = (LSL_Rotation)objs[j++];
                dp.Type        = ListInt (objs[j++]);
                dp.Velocity    = (LSL_Vector)objs[j++];

                SurfaceTouchEventArgs stea = new SurfaceTouchEventArgs ();

                stea.STCoord   = LSLVec2OMVec ((LSL_Vector)objs[j++]);
                stea.Normal    = LSLVec2OMVec ((LSL_Vector)objs[j++]);
                stea.Binormal  = LSLVec2OMVec ((LSL_Vector)objs[j++]);
                stea.Position  = LSLVec2OMVec ((LSL_Vector)objs[j++]);
                stea.UVCoord   = LSLVec2OMVec ((LSL_Vector)objs[j++]);
                stea.FaceIndex = ListInt (objs[j++]);

                dp.SurfaceTouchArgs = stea;

                dps[i] = dp;
            }

            return dps;
        }

        /**
         * @brief The script is executing a 'state <newState>;' command.
         * Tell outer layers to cancel any event triggers, like llListen(),
         * then tell outer layers which events the new state has handlers for.
         * We also clear the event queue as per http://wiki.secondlife.com/wiki/State
         */
        public override void StateChange()
        {
            /*
             * Cancel any llListen()s etc.
             * But llSetTimerEvent() should persist.
             */
            object[] timers = m_XMRLSLApi.acm.TimerPlugin.GetSerializationData(m_ItemID);
            AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID);
            m_XMRLSLApi.acm.TimerPlugin.CreateFromData(m_LocalID, m_ItemID, UUID.Zero, timers);

            /*
             * Tell whoever cares which event handlers the new state has.
             */
            m_Part.SetScriptEvents(m_ItemID, GetStateEventFlags(stateCode));

            /*
             * Clear out any old events from the queue.
             */
            lock (m_QueueLock)
            {
                m_EventQueue.Clear();
                for (int i = m_EventCounts.Length; -- i >= 0;)
                    m_EventCounts[i] = 0;
            }
        }

        /**
         * @brief Script is calling xmrStackLeft().
         */
        public override int xmrStackLeft ()
        {
            return microthread.StackLeft ();
        }
    }

    /**
     * @brief Thrown by things like llResetScript() to unconditionally
     *        unwind as script and reset it to the default state_entry
     *        handler.  We don't want script-level try/catch to intercept
     *        these so scripts can't interfere with the behavior.
     */
    public class ScriptResetException : Exception, IXMRUncatchable { }

    /**
     * @brief Thrown by things like llDie() to unconditionally unwind as 
     *        script.  We don't want script-level try/catch to intercept
     *        these so scripts can't interfere with the behavior.
     */
    public class ScriptDieException : Exception, IXMRUncatchable { }
}