NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

scenarioMgr.py (43740B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\parklife\scenarioMgr.py
      2 from __future__ import print_function
      3 import random
      4 import sys
      5 import math
      6 import blue
      7 import decometaclass
      8 import evecamera
      9 import evetypes
     10 import geo2
     11 import log
     12 import telemetry
     13 import trinity
     14 import uthread
     15 from carbon.common.script.sys.service import Service
     16 from carbon.common.script.sys.serviceConst import ROLE_CONTENT, ROLE_SERVICE
     17 from carbonui import uiconst
     18 from carbonui.uicore import uicore
     19 from eve.client.script.parklife import dungeonEditorTools
     20 from eve.client.script.parklife import dungeonHelper
     21 from eve.client.script.ui.util import uix
     22 from eve.common.lib import appConst as const
     23 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     24 SAVE_TIME = 30 * const.SEC
     25 UNLOCK_TIME = 15 * const.SEC
     26 UNLOCK_OBJECT_MODEL_TIMEOUT = 30.0 * const.SEC
     27 
     28 class FakeBall(decometaclass.WrapBlueClass('destiny.ClientBall')):
     29     pass
     30 
     31 
     32 class ScenarioMgr(Service):
     33     __guid__ = 'svc.scenario'
     34     __exportedcalls__ = {'BindClientToLevelEditor': [ROLE_SERVICE],
     35      'ChoosePathStep': [ROLE_SERVICE]}
     36     __notifyevents__ = ['DoBallsAdded',
     37      'DoBallRemove',
     38      'OnReleaseBallpark',
     39      'OnDungeonEdit',
     40      'DoBallsRemove']
     41     __dependencies__ = ['michelle']
     42     __startupdependencies__ = ['settings']
     43 
     44     def __init__(self):
     45         super(ScenarioMgr, self).__init__()
     46         self.selection = []
     47         self.selectionObjs = []
     48         self.ignoreAxis = None
     49         self.lookatID = None
     50         self.ed = None
     51         view = trinity.TriView()
     52         view.SetLookAtPosition((0, 2, -2), (0, 3, 0), (0, 1, 0))
     53         projection = trinity.TriProjection()
     54         projection.PerspectiveFov(1, trinity.device.viewport.GetAspectRatio(), 1, 3000)
     55         self.clientToolsScene = None
     56         self.clientToolsScene = scene = self.GetClientToolsScene()
     57         self.cursors = {'Translation': dungeonEditorTools.TranslationTool(view, projection, scene),
     58          'Rotation': dungeonEditorTools.RotationTool(view, projection, scene),
     59          'Scaling': dungeonEditorTools.ScalingTool(view, projection, scene)}
     60         self.currentCursor = None
     61         self.isActive = False
     62         self.isMoving = False
     63         self.isSaving = False
     64         self.isUnlocking = False
     65         self.dungeonOrigin = None
     66         self.playerLocation = None
     67         self.fakeBallTransforms = {}
     68         self.backupTranslations = {}
     69         self.backupRotations = {}
     70         self.unsavedChanges = {}
     71         self.unsavedTime = {}
     72         self.lockedObjects = {}
     73         self.lockedTime = {}
     74         self.lastChangeTimestamp = None
     75         self.lastUpdateRecievedTimestamp = None
     76         self.selectionCenter = (0.0, 0.0, 0.0)
     77         self.groupRotation = geo2.Vector(0, 0, 0, 1)
     78         self.addSelectedTaskletCount = 0
     79         self.currentHardGroup = None
     80         self.hardGroupRotations = {}
     81         self.rotatedSelectionGroups = {}
     82         self.editDungeonID = None
     83         self.editRoomID = None
     84         self.editRoomPos = None
     85         self.groupsWithNoModel = [const.groupCosmicAnomaly, const.groupCosmicSignature]
     86 
     87     def Run(self, memStream = None):
     88         super(ScenarioMgr, self).Run(memStream)
     89         self.ed = None
     90 
     91     def GetLevelEditor(self):
     92         if self.ed is None and session.role & ROLE_CONTENT == ROLE_CONTENT:
     93             self.BindClientToLevelEditor()
     94         return self.ed
     95 
     96     def BindClientToLevelEditor(self):
     97         self.ed = sm.RemoteSvc('keeper').GetLevelEditor()
     98         self.ed.Bind()
     99 
    100     def EditRoom(self, dungeonID, roomID):
    101         ed = self.GetLevelEditor()
    102         if ed:
    103             ed.EditDungeon(dungeonID, roomID=roomID)
    104         else:
    105             print('I have no ed instance')
    106 
    107     def PlayDungeon(self, dungeonID, roomID, godmode = 1):
    108         ed = self.GetLevelEditor()
    109         if ed:
    110             if roomID is not None:
    111                 roomID = int(roomID)
    112             ed.PlayDungeon(dungeonID, roomID=roomID, godmode=godmode)
    113         else:
    114             print('I have no ed instance')
    115 
    116     def ResetDungeon(self):
    117         ed = self.GetLevelEditor()
    118         if ed:
    119             for ballID in self.fakeBallTransforms.keys():
    120                 self.RestoreObjectBall(ballID)
    121 
    122             ed.Reset()
    123             self.lastChangeTimestamp = None
    124             self.unsavedChanges = {}
    125             self.lockedObjects = {}
    126         else:
    127             print('I have no ed instance')
    128 
    129     def GotoRoom(self, roomID):
    130         ed = self.GetLevelEditor()
    131         if roomID is not None and ed:
    132             ed.GotoRoom(int(roomID))
    133 
    134     def ChoosePathStep(self, defaultPathStepID, pathSteps, namesByDungeonID):
    135         deadEndLabel = 'Dead end'
    136         l = []
    137         for pathStep in pathSteps:
    138             l.append(('%s<t>%s<t>%s' % (pathStep.pathStepID, namesByDungeonID.get(pathStep.destDungeonID, deadEndLabel), ['No', 'Yes'][pathStep.pathStepID == defaultPathStepID]), pathStep.pathStepID))
    139 
    140         windowTitle = 'GMH EP Choice'
    141         listTitle = 'Select the next step in the escalating path. If you do not choose a step, or do not choose one within a reasonable amount of time, the random pick will be chosen for you.'
    142         choice = uix.ListWnd(l, 'generic', windowTitle, hint=listTitle, isModal=1, scrollHeaders=['ID', 'Dungeon', 'Default'], minw=180)
    143         if choice:
    144             choice = choice[1]
    145         return choice
    146 
    147     def Stop(self, stream):
    148         self.HideCursor()
    149         super(ScenarioMgr, self).Stop(stream)
    150 
    151     def IsSelected(self, ballID):
    152         return ballID in self.selection
    153 
    154     def IsSelectedByObjID(self, objectID):
    155         if objectID in self.selectionObjs:
    156             return 1
    157         return 0
    158 
    159     def AreAllSelected(self, objectList):
    160         for slimItem in objectList:
    161             if slimItem.dunObjectID not in self.selectionObjs:
    162                 return False
    163 
    164         return True
    165 
    166     def GetSelectedObjIDs(self):
    167         return self.selectionObjs[:]
    168 
    169     def WaitForObjectCreationByID(self, objectIDs):
    170         bp = sm.GetService('michelle').GetBallpark()
    171         attemptsLeft = 120
    172         pendingObjectIDs = set(objectIDs)
    173         objectItemIDs = []
    174         while len(pendingObjectIDs) and attemptsLeft:
    175             blue.pyos.synchro.SleepWallclock(500)
    176             arrivedObjectIDs = set()
    177             for itemID, slimItem in bp.slimItems.iteritems():
    178                 if slimItem.dunObjectID in pendingObjectIDs:
    179                     arrivedObjectIDs.add(slimItem.dunObjectID)
    180                     objectItemIDs.append(slimItem.itemID)
    181 
    182             pendingObjectIDs -= arrivedObjectIDs
    183             attemptsLeft -= 1
    184             self.LogInfo('WaitForObjectCreationByID', attemptsLeft, len(pendingObjectIDs) * 100.0 / len(objectIDs), pendingObjectIDs)
    185 
    186         if attemptsLeft == 0:
    187             raise RuntimeError('Failed to identify the arrival of specified objects')
    188 
    189     def SetSelectionByID(self, objectIDs):
    190         self.SetActiveHardGroup(None)
    191         self.selection = []
    192         self.selectionObjs = []
    193         dunObjects = self.GetDunObjects()
    194         sleepTime = 0
    195         for objectID in objectIDs:
    196             objectWasAdded = False
    197             while not objectWasAdded and sleepTime < 5000:
    198                 for slimItem in dunObjects:
    199                     if slimItem.dunObjectID == objectID:
    200                         self.AddSelected(slimItem.itemID)
    201                         objectWasAdded = True
    202 
    203                 if not objectWasAdded:
    204                     sleepTime += 200
    205                     blue.synchro.SleepWallclock(200)
    206                     dunObjects = self.GetDunObjects()
    207 
    208             if not objectWasAdded:
    209                 self.LogError('scenarioMgr could not add objectID', objectID, "to the selection--can't find it in the dungeon!")
    210                 log.LogTraceback()
    211 
    212     def _SendSelectionEvent(self, objects = None):
    213         if objects is None:
    214             objects = self.selectionObjs
    215         objectID = objects and objects[0] or None
    216         sm.ScatterEvent('OnSelectObjectInGame', 'SelectDungeonObject', dungeonID=self.GetEditingDungeonID(), roomID=self.GetEditingRoomID(), objectID=objectID)
    217 
    218     def IncrementAddSelectedTaskletCount(self):
    219         self.addSelectedTaskletCount += 1
    220 
    221     def DecrementAddSelectedTaskletCount(self):
    222         self.addSelectedTaskletCount -= 1
    223         if self.addSelectedTaskletCount == 0:
    224             self.DoFullRefresh()
    225         elif self.addSelectedTaskletCount < 0:
    226             self.LogError('We have negative AddSelected tasklets--this is bad')
    227 
    228     def DoFullRefresh(self):
    229         self.RefreshSelection()
    230         uthread.new(self.GetLevelEditor().ObjectSelection, self.selectionObjs)
    231         sm.ScatterEvent('OnDESelectionChanged')
    232         self._SendSelectionEvent()
    233 
    234     def AddSelected(self, ballID):
    235         self.IncrementAddSelectedTaskletCount()
    236         uthread.new(self.AddSelected_thread, ballID).context = 'svc.scenario.AddSelected'
    237 
    238     def AddSelected_thread(self, ballID):
    239         try:
    240             self._AddSelected_thread(ballID)
    241         finally:
    242             self.DecrementAddSelectedTaskletCount()
    243 
    244     def _AddSelected_thread(self, ballID):
    245         if ballID in self.selection:
    246             return
    247         slimItem = sm.GetService('michelle').GetItem(ballID)
    248         for i in xrange(10):
    249             locked, byWho = dungeonHelper.IsObjectLocked(slimItem.dunObjectID)
    250             if locked != True or byWho != []:
    251                 break
    252             blue.synchro.SleepWallclock(500)
    253         else:
    254             self.LogError('AddSelected_thread tried to add ball', ballID, 'for dungeon object', slimItem.dunObjectID, 'to selection, but it does not appear to exist.')
    255             return
    256 
    257         if locked:
    258             lockedBy = ', '.join((userName for userId, userName in byWho))
    259             prompt_player_fsd_dialog('AdminNotify', {'text': 'Unable to select object %d as its locked by %s' % (slimItem.dunObjectID, lockedBy)})
    260             sm.ScatterEvent('OnDESelectionChanged')
    261             return
    262         if not self.currentCursor:
    263             self.currentCursor = 'Translation'
    264         self.selection.append(ballID)
    265         if slimItem.dunObjectID not in self.selectionObjs:
    266             self.selectionObjs.append(slimItem.dunObjectID)
    267         self.groupRotation = geo2.Vector(0, 0, 0, 1)
    268         self.ReplaceObjectBall(ballID, slimItem)
    269 
    270     def ReplaceObjectBall(self, ballID, slimItem):
    271         if ballID in self.fakeBallTransforms:
    272             return
    273         targetBall = sm.GetService('michelle').GetBall(ballID)
    274         targetModel = getattr(targetBall, 'model', None)
    275         modelWaitEntryTime = blue.os.GetWallclockTime()
    276         if slimItem.groupID not in self.groupsWithNoModel:
    277             while not targetModel:
    278                 blue.pyos.synchro.SleepWallclock(100)
    279                 targetModel = getattr(targetBall, 'model', None)
    280                 if blue.os.GetWallclockTime() > modelWaitEntryTime + const.SEC * 15.0:
    281                     self.LogError('ReplaceObjectBall gave up on waiting for the object model to load.')
    282                     return False
    283 
    284         bp = sm.GetService('michelle').GetBallpark()
    285         replacementBall = bp.AddBall(-ballID, 1.0, 0.0, 0, 0, 0, 0, 0, 0, targetBall.x, targetBall.y, targetBall.z, 0, 0, 0, 0, 1.0)
    286         replacementBall = FakeBall(replacementBall)
    287         replacementBall.__dict__['id'] = ballID
    288         tf = trinity.EveRootTransform()
    289         tf.translationCurve = replacementBall
    290         if targetModel and targetModel.rotationCurve and hasattr(targetModel.rotationCurve, 'value'):
    291             tf.rotationCurve = targetModel.rotationCurve
    292         elif hasattr(targetModel, 'rotation'):
    293             tf.rotationCurve = trinity.Tr2RotationAdapter()
    294             tf.rotationCurve.value = targetModel.rotation
    295         self.fakeBallTransforms[ballID] = tf
    296         if targetModel:
    297             self.backupTranslations[ballID] = targetModel.translationCurve
    298             self.backupRotations[ballID] = targetModel.rotationCurve
    299             targetModel.translationCurve = tf.translationCurve
    300             targetModel.rotationCurve = tf.rotationCurve
    301 
    302     def RestoreObjectBall(self, ballID):
    303         targetBall = sm.GetService('michelle').GetBall(ballID)
    304         targetModel = getattr(targetBall, 'model', None)
    305         if targetModel:
    306             targetModel.translationCurve = self.backupTranslations[ballID]
    307             targetModel.rotationCurve = self.backupRotations[ballID]
    308         del self.fakeBallTransforms[ballID]
    309 
    310     def RemoveSelected(self, ballID, bpRemoval = None, silent = False):
    311         if ballID not in self.selection:
    312             return
    313         self.selection.remove(ballID)
    314         self.groupRotation = geo2.Vector(0, 0, 0, 1)
    315         self.RestoreObjectBall(ballID)
    316         if bpRemoval == None:
    317             slimItem = sm.GetService('michelle').GetItem(ballID)
    318             self.selectionObjs.remove(slimItem.dunObjectID)
    319         if not silent:
    320             self.RefreshSelection()
    321             sm.ScatterEvent('OnDESelectionChanged')
    322             self._SendSelectionEvent()
    323 
    324     def UnselectAll(self):
    325         while len(self.selection) > 1:
    326             self.RemoveSelected(self.selection[0], silent=True)
    327 
    328         if self.selection:
    329             self.RemoveSelected(self.selection[0])
    330 
    331     def StopMovingCursor(self):
    332         self.cursors[self.currentCursor].ReleaseAxis()
    333         self.StartSavingChanges()
    334 
    335     def StartSavingChanges(self):
    336         if not self.isSaving:
    337             self.isSaving = True
    338             uthread.new(self.SavingChanges_thread).context = 'svc.scenario::SavingChanges'
    339 
    340     def SavingChanges_thread(self):
    341         try:
    342             while self.unsavedTime:
    343                 sleepTill = min(self.unsavedTime.itervalues()) + SAVE_TIME
    344                 sleepTill += const.SEC
    345                 sleepTime = (sleepTill - blue.os.GetWallclockTime()) / const.MSEC
    346                 if sleepTime > 0:
    347                     blue.synchro.SleepWallclock(sleepTime)
    348                 while self.isMoving:
    349                     blue.synchro.Yield()
    350 
    351                 if sleepTime < 0 and not self.isMoving:
    352                     self.unsavedTime = {}
    353                 savingIDs = []
    354                 threshhold = blue.os.GetWallclockTime() - SAVE_TIME
    355                 for ballID, updateTime in self.unsavedTime.iteritems():
    356                     if threshhold > self.unsavedTime[ballID]:
    357                         savingIDs.append(ballID)
    358 
    359                 if savingIDs or self.rotatedSelectionGroups:
    360                     self.SaveChanges(savingIDs)
    361 
    362         finally:
    363             self.isSaving = False
    364 
    365     def SaveAllChanges(self):
    366         self.SaveChanges(self.unsavedChanges.keys())
    367 
    368     def SaveChanges(self, savingIDs):
    369         for groupName, orientation in self.rotatedSelectionGroups.iteritems():
    370             sm.ScatterEvent('OnDungeonSelectionGroupRotation', self.currentHardGroup, *orientation)
    371 
    372         self.rotatedSelectionGroups = {}
    373         if savingIDs:
    374             michelle = sm.StartService('michelle')
    375             uthread.new(prompt_player_fsd_dialog, 'AdminNotify', {'text': 'LevelEd: Saving changes to database...'}).context = 'gameui.ServerMessage'
    376             for ballID in savingIDs[:]:
    377                 slimItem = michelle.GetItem(ballID)
    378                 if not slimItem:
    379                     savingIDs.remove(ballID)
    380                     continue
    381                 if dungeonHelper.IsObjectLocked(slimItem.dunObjectID)[0]:
    382                     savingIDs.remove(ballID)
    383                     uthread.new(prompt_player_fsd_dialog, 'AdminNotify', {'text': 'Object %d is Locked' % slimItem.dunObjectID}).context = 'gameui.ServerMessage'
    384                     self.RestoreObjectBall(ballID)
    385 
    386             for ballID in savingIDs:
    387                 self._LockBall(ballID, michelle)
    388 
    389             self.RefreshSelection()
    390             for ballID in savingIDs:
    391                 self._SaveBall(ballID, michelle)
    392 
    393     def _LockBall(self, ballID, michelle):
    394         slimItem = michelle.GetItem(ballID)
    395         if not slimItem:
    396             return
    397         objectID = slimItem.dunObjectID
    398         if objectID not in self.lockedObjects:
    399             self.lockedObjects[objectID] = int(bool(self.unsavedChanges[ballID] & dungeonEditorTools.CHANGE_TRANSLATION) or bool(self.unsavedChanges[ballID] & dungeonEditorTools.CHANGE_ROTATION) or bool(self.unsavedChanges[ballID] & dungeonEditorTools.CHANGE_SCALE))
    400             self.lockedTime[objectID] = blue.os.GetWallclockTime()
    401             self.UnlockObjectsOnTimeout()
    402 
    403     def _SaveBall(self, ballID, michelle):
    404         try:
    405             slimItem = michelle.GetItem(ballID)
    406             if not slimItem:
    407                 return
    408             objectID = slimItem.dunObjectID
    409             targetBall = michelle.GetBall(slimItem.itemID)
    410             targetModel = getattr(targetBall, 'model', None)
    411             if not targetModel and not self.unsavedChanges[ballID] & dungeonEditorTools.CHANGE_TRANSLATION | dungeonEditorTools.CHANGE_SCALE:
    412                 del self.lockedObjects[objectID]
    413             if targetModel and self.unsavedChanges[ballID] & dungeonEditorTools.CHANGE_ROTATION:
    414                 quat = targetModel.rotationCurve.value
    415                 yaw, pitch, roll = geo2.QuaternionRotationGetYawPitchRoll(quat)
    416                 yaw = yaw / math.pi * 180.0
    417                 pitch = pitch / math.pi * 180.0
    418                 roll = roll / math.pi * 180.0
    419         finally:
    420             del self.unsavedChanges[ballID]
    421             del self.unsavedTime[ballID]
    422 
    423     def SetSelectedQuantity(self, minQuantity, maxQuantity):
    424         slimItems = self.GetSelObjects()
    425         for slimItem in slimItems:
    426             evetypes.RaiseIFNotExists(slimItem.typeID)
    427             if slimItem.categoryID == const.categoryAsteroid or slimItem.groupID in (const.groupHarvestableCloud, const.groupCloud):
    428                 quantity = minQuantity + (maxQuantity - minQuantity) * random.random()
    429                 dungeonHelper.SetObjectQuantity(slimItem.dunObjectID, quantity)
    430 
    431     def SetSelectedRadius(self, minRadius, maxRadius):
    432         slimItems = self.GetSelObjects()
    433         for slimItem in slimItems:
    434             evetypes.RaiseIFNotExists(slimItem.typeID)
    435             if slimItem.categoryID == const.categoryAsteroid or slimItem.groupID in (const.groupHarvestableCloud, const.groupCloud):
    436                 radius = minRadius + (maxRadius - minRadius) * random.random()
    437                 dungeonHelper.SetObjectRadius(slimItem.dunObjectID, radius)
    438 
    439     def JitterSelection(self, x, y, z):
    440         slimItems = self.GetSelObjects()
    441         if len(slimItems) == 0:
    442             return
    443         commandArgs = []
    444         for slimItem in slimItems:
    445             ox = slimItem.dunX + x * random.random() * 2 - x
    446             oy = slimItem.dunY + y * random.random() * 2 - y
    447             oz = slimItem.dunZ + z * random.random() * 2 - z
    448             dungeonHelper.SetObjectPosition(slimItem.dunObjectID, ox, oy, oz)
    449 
    450     def JitterRotationSelection(self, yaw, pitch, roll):
    451         slimItems = self.GetSelObjects()
    452         if len(slimItems) == 0:
    453             return
    454         commandArgs = []
    455         for slimItem in slimItems:
    456             oYaw, oPitch, oRoll = dungeonHelper.GetObjectRotation(slimItem.dunObjectID)
    457             oYaw += yaw * random.random() * 2 - yaw
    458             oPitch += pitch * random.random() * 2 - pitch
    459             oRoll += roll * random.random() * 2 - roll
    460             dungeonHelper.SetObjectRotation(slimItem.dunObjectID, oYaw, oPitch, oRoll)
    461 
    462     def ArrangeSelection(self, x, y, z):
    463         slimItems = self.GetSelObjects()
    464         if len(slimItems) < 2:
    465             return
    466         minV = trinity.TriVector(slimItems[0].dunX, slimItems[0].dunY, slimItems[0].dunZ)
    467         maxV = minV.CopyTo()
    468         commandArgs = []
    469         centreAxis = trinity.TriVector()
    470         for slimItem in slimItems:
    471             centreAxis.x = centreAxis.x + slimItem.dunX
    472             centreAxis.y = centreAxis.y + slimItem.dunY
    473             centreAxis.z = centreAxis.z + slimItem.dunZ
    474 
    475         centreAxis.Scale(1.0 / len(slimItems))
    476         stepCount = float(len(slimItems))
    477         totalOffset = trinity.TriVector(x * stepCount, y * stepCount, z * stepCount)
    478         totalOffset.Scale(-0.5)
    479         counter = 0
    480         for slimItem in slimItems:
    481             offset = trinity.TriVector(x, y, z)
    482             offset.Scale(counter)
    483             pos = centreAxis + totalOffset + offset
    484             counter += 1
    485             dungeonHelper.SetObjectPosition(slimItem.dunObjectID, pos.x, pos.y, pos.z)
    486 
    487     def SetRotate(self, y, p, r):
    488         slimItems = self.GetSelObjects()
    489         if len(slimItems) == 0:
    490             return
    491         for slimItem in slimItems:
    492             dungeonHelper.SetObjectRotation(slimItem.dunObjectID, y, p, r)
    493 
    494     def RotateSelected(self, yaw, pitch, roll):
    495         slimItems = self.GetSelObjects()
    496         if len(slimItems) == 0:
    497             return
    498         yawRad = yaw / 180.0 * math.pi
    499         pitchRad = pitch / 180.0 * math.pi
    500         rollRad = roll / 180.0 * math.pi
    501         rotationToAdd = geo2.QuaternionRotationSetYawPitchRoll(yawRad, pitchRad, rollRad)
    502         posCtr = geo2.VectorD(0, 0, 0)
    503         for slimItem in slimItems:
    504             posCtr += geo2.VectorD(slimItem.dunX, slimItem.dunY, slimItem.dunZ)
    505 
    506         geo2.Scale(posCtr, 1.0 / len(slimItems))
    507         for slimItem in slimItems:
    508             rot = getattr(slimItem, 'dunRotation', None)
    509             slimItemRotation = geo2.QuaternionIdentity()
    510             if rot is not None:
    511                 yaw, pitch, roll = rot
    512                 slimItemRotation = geo2.QuaternionRotationSetYawPitchRoll(yaw / 180.0 * math.pi, pitch / 180.0 * math.pi, roll / 180.0 * math.pi)
    513             y, p, r = geo2.QuaternionRotationGetYawPitchRoll(slimItemRotation)
    514             slimItemRotation = geo2.QuaternionMultiply(rotationToAdd, slimItemRotation)
    515             y, p, r = geo2.QuaternionRotationGetYawPitchRoll(slimItemRotation)
    516             y = y / math.pi * 180.0
    517             p = p / math.pi * 180.0
    518             r = r / math.pi * 180.0
    519             translation = geo2.VectorD(slimItem.dunX, slimItem.dunY, slimItem.dunZ)
    520             translation -= posCtr
    521             geo2.QuaternionTransformVector(rotationToAdd, translation)
    522             translation += posCtr
    523             dungeonHelper.SetObjectPosition(slimItem.dunObjectID, translation.x, translation.y, translation.z)
    524             dungeonHelper.SetObjectRotation(slimItem.dunObjectID, y, p, r)
    525 
    526     def ClearSelection(self):
    527         self.selection = []
    528         self.SetActiveHardGroup(None)
    529         self.RefreshSelection()
    530 
    531     def RefreshSelection(self):
    532         self.LogInfo('ScenarioMgr: RefreshSelection')
    533         uthread.new(self.RefreshSelection_thread).context = 'svc.scenario.RefreshSelection'
    534 
    535     def RefreshSelection_thread(self):
    536         if getattr(self, 'refreshingSelection', None) != None:
    537             return
    538         self.refreshingSelection = 1
    539         if not self.isActive:
    540             self.ShowCursor()
    541         scene = sm.GetService('sceneManager').GetRegisteredScene('default')
    542         dungeonEditorObjects = [ obj for obj in scene.objects if obj.name == '_dungeon_editor_' ]
    543         for obj in dungeonEditorObjects:
    544             scene.objects.fremove(obj)
    545 
    546         bp = sm.GetService('michelle').GetBallpark()
    547         if bp is None:
    548             self.refreshingSelection = None
    549             return
    550         balls = []
    551         self.HideCursor()
    552         showAggrRadius = settings.user.ui.Get('showAggrRadius', 0)
    553         aggrSettingsAll = settings.user.ui.Get('aggrSettingsAll', 0)
    554         if showAggrRadius == 1 or aggrSettingsAll == 1:
    555             aggrSphere = blue.resMan.LoadObject('res:/model/global/gridSphere.red')
    556             aggrSphere.scaling = (1, 1, 1)
    557         for ballID in bp.balls:
    558             ball = bp.GetBall(ballID)
    559             if ball is None:
    560                 continue
    561             slimItem = sm.GetService('michelle').GetItem(ballID)
    562             if slimItem and slimItem.dunObjectID in self.lockedObjects:
    563                 if self.lockedObjects[slimItem.dunObjectID] > 0:
    564                     scale = 1.5
    565                     self.AddBoundingCube(ball, blue.resMan.LoadObject('res:/Model/UI/redGlassCube.red'), scale=scale)
    566                     balls.append(ball)
    567                 else:
    568                     scale = 1.5
    569                     self.AddBoundingCube(ball, blue.resMan.LoadObject('res:/Model/UI/yellowGlassCube.red'), scale=scale)
    570                     balls.append(ball)
    571 
    572         for ballID in self.selection[:]:
    573             ball = bp.GetBall(ballID)
    574             if ball is None:
    575                 self.selection.remove(ballID)
    576                 continue
    577             scale = 1.5
    578             self.AddBoundingCube(ball, blue.resMan.LoadObject('res:/Model/UI/blueLineCube.red'), scale=scale)
    579             balls.append(ball)
    580 
    581         if aggrSettingsAll == 1 or showAggrRadius == 1:
    582             if aggrSettingsAll == 1:
    583                 slimItems = self.GetDunObjects()
    584             else:
    585                 slimItems = self.GetSelObjects()
    586             self.LogInfo('ScenarioMgr: showAggrRadius')
    587             for slimItem in slimItems:
    588                 if hasattr(slimItem, 'dunGuardCommands'):
    589                     gc = slimItem.dunGuardCommands
    590                     if gc == None:
    591                         continue
    592                     aggressionRange = gc.aggressionRange
    593                     ball = bp.GetBall(slimItem.itemID)
    594                     aggrSphereInst = trinity.EveRootTransform()
    595                     aggrSphereInst.name = '_dungeon_editor_'
    596                     aggrSphereInst.children.append(aggrSphere.CloneTo())
    597                     aggrSphereInst.translationCurve = ball
    598                     aggrSphereInst.scaling = (aggressionRange * 2, aggressionRange * 2, aggressionRange * 2)
    599                     scene.objects.append(aggrSphereInst)
    600 
    601         if len(balls):
    602             self.ShowCursor()
    603         self.LogInfo('ScenarioMgr: RefreshSelection Done')
    604         self.refreshingSelection = None
    605 
    606     def AddBoundingCube(self, ball, model, scale = 1.0, rotation = geo2.Vector(0, 0, 0, 1)):
    607         scene = sm.GetService('sceneManager').GetRegisteredScene('default')
    608         transform = trinity.EveRootTransform()
    609         transform.name = '_dungeon_editor_'
    610         transform.children.append(model)
    611         if hasattr(ball, 'model') and ball.model is not None:
    612             transform.translationCurve = ball.model.translationCurve
    613         else:
    614             try:
    615                 transform.translationCurve = self.fakeBallTransforms[ball.id].translationCurve
    616             except KeyError:
    617                 self.LogError('AddBoundingCube failed because ball', ball.id, "didn't have a fake ball!")
    618 
    619         transform.rotationCurve = trinity.Tr2RotationAdapter()
    620         transform.rotationCurve.value = (rotation.x,
    621          rotation.y,
    622          rotation.z,
    623          rotation.w)
    624         scaleFromBall = max(ball.radius, 150)
    625         scale = scale * scaleFromBall
    626         model.scaling = (scale, scale, scale)
    627         scene.objects.append(transform)
    628 
    629     def DoBallsAdded(self, *args, **kw):
    630         import stackless
    631         t = stackless.getcurrent()
    632         timer = t.PushTimer(blue.pyos.taskletTimer.GetCurrent() + '::scenarioMgr')
    633         try:
    634             return self.DoBallsAdded_(*args, **kw)
    635         finally:
    636             t.PopTimer(timer)
    637 
    638     def DoBallsAdded_(self, lst):
    639         for each in lst:
    640             self.DoBallAdd(*each)
    641 
    642     def DoBallAdd(self, ball, slimItem):
    643         if slimItem is None:
    644             bp = sm.GetService('michelle').GetBallpark()
    645             if bp is None:
    646                 return
    647             slimItem = bp.GetInvItem(ball.id)
    648             if slimItem is None:
    649                 return
    650         if slimItem.itemID is None or slimItem.itemID < 0:
    651             return
    652         slimItem = sm.GetService('michelle').GetItem(slimItem.itemID)
    653         toLookAt = None
    654         if slimItem.dunObjectID in self.selectionObjs:
    655             if getattr(self, 'lookingAt', [-1])[0] == slimItem.dunObjectID:
    656                 toLookAt = ball.id
    657             self.AddSelected(ball.id)
    658         self.UnlockObject(slimItem.itemID, slimItem.dunObjectID, force=False)
    659         sm.ScatterEvent('OnDungeonObjectProperties', slimItem.dunObjectID)
    660 
    661     @telemetry.ZONE_METHOD
    662     def DoBallsRemove(self, pythonBalls, isRelease):
    663         if not self.selection:
    664             return
    665         for ball, slimItem, terminal in pythonBalls:
    666             if ball and ball.id in self.selection:
    667                 self.RemoveSelected(slimItem.itemID, 1)
    668 
    669     def DoBallRemove(self, ball, slimItem, terminal):
    670         if ball is None:
    671             return
    672         if ball.id in self.selection:
    673             self.RemoveSelected(slimItem.itemID, 1)
    674 
    675     def OnReleaseBallpark(self):
    676         pass
    677 
    678     def ShowCursor(self):
    679         self.LogInfo('ScenarioMgr: ShowCursor')
    680         if self.currentCursor:
    681             uthread.new(self._UpdateCursor).context = 'svc.scenario.ShowCursor'
    682         self.isActive = True
    683 
    684     def HideCursor(self):
    685         self.LogInfo('ScenarioMgr: HideCursor')
    686         if self.currentCursor:
    687             self.cursors[self.currentCursor].Hide()
    688         self.isActive = False
    689 
    690     def DetachTacticalCamera(self):
    691         cam = sm.GetService('sceneManager').GetActiveCamera()
    692         if cam.cameraID == evecamera.CAM_TACTICAL:
    693             cam.Detach()
    694 
    695     def MoveCursor(self, tf, dx, dy, camera):
    696         if not self.isMoving:
    697             self.LogInfo('ScenarioMgr: MoveCursor')
    698             uthread.new(self.MoveCursor_thread).context = 'svc.scenario.MoveCursor'
    699             self.DetachTacticalCamera()
    700 
    701     def MoveCursor_thread(self):
    702         self.isMoving = True
    703         lib = uicore.uilib
    704         while lib.leftbtn:
    705             if self.currentCursor:
    706                 x, y, z = self.GetSelectionCenter()
    707                 playerPos = self.GetPlayerClientBall()
    708                 try:
    709                     rotation = self.GetSelectionRotation()
    710                     singleObjectRotation = self.currentCursor == 'Rotation' and len(self.selection) == 1
    711                     multipleObjectRotation = self.currentCursor == 'Rotation' and len(self.selection) > 1
    712                     if singleObjectRotation:
    713                         self.cursors[self.currentCursor].Rotate((rotation[0],
    714                          rotation[1],
    715                          rotation[2],
    716                          rotation[3]))
    717                     elif multipleObjectRotation:
    718                         self.cursors[self.currentCursor].Rotate((rotation[0],
    719                          rotation[1],
    720                          rotation[2],
    721                          -rotation[3]))
    722                     self.cursors[self.currentCursor].Translate(geo2.Vector(x - playerPos.x, y - playerPos.y, z - playerPos.z))
    723                     self.cursors[self.currentCursor].Transform(uicore.uilib.x, uicore.uilib.y)
    724                     if multipleObjectRotation:
    725                         rotation = self.cursors[self.currentCursor].GetRotation()
    726                         self.SetGroupSelectionRotation((rotation[0],
    727                          rotation[1],
    728                          rotation[2],
    729                          -rotation[3]))
    730                 except:
    731                     self.LogError('Error when attempting to move the dungeon editor object manipulation tool.')
    732                     log.LogException()
    733                     sys.exc_clear()
    734                     self.isMoving = False
    735                     return
    736 
    737             self.lastChangeTimestamp = blue.os.GetWallclockTime()
    738             blue.synchro.Yield()
    739 
    740         self.isMoving = False
    741         self.StopMovingCursor()
    742 
    743     def UpdateUnsavedObjectChanges(self, ballID, changeType):
    744         if ballID not in self.unsavedChanges:
    745             self.unsavedChanges[ballID] = dungeonEditorTools.CHANGE_NONE
    746         self.unsavedChanges[ballID] = self.unsavedChanges[ballID] | changeType
    747         self.unsavedTime[ballID] = blue.os.GetWallclockTime()
    748         self.StartSavingChanges()
    749         slimItem = sm.StartService('michelle').GetItem(ballID)
    750         if slimItem and getattr(slimItem, 'dunObjectID', None) is not None:
    751             sm.ScatterEvent('OnDungeonObjectProperties', slimItem.dunObjectID)
    752 
    753     def RefreshWhenDeletesAreFinished(self, slimItemList):
    754         MAX_SLEEP_TIME = 10000
    755         ballStillHere = True
    756         sleepTime = 0
    757         while ballStillHere and sleepTime < MAX_SLEEP_TIME:
    758             ballStillHere = False
    759             for oldSlim in slimItemList:
    760                 ball, newSlim = self.GetBallAndSlimItemFromObjectID(oldSlim.dunObjectID)
    761                 if ball is not None:
    762                     ballStillHere = True
    763                     break
    764 
    765             if ballStillHere:
    766                 sleepTime += 500
    767                 blue.synchro.SleepWallclock(500)
    768 
    769         sm.ScatterEvent('OnDEObjectListChanged')
    770 
    771     def GetDunObjects(self):
    772         dunObjs = []
    773         bp = sm.StartService('michelle').GetBallpark()
    774         if not bp:
    775             return []
    776         for ballID in bp.balls:
    777             slimItem = sm.StartService('michelle').GetItem(ballID)
    778             if getattr(slimItem, 'dunObjectID', None) is not None:
    779                 dunObjs.append(slimItem)
    780 
    781         return dunObjs
    782 
    783     def GetBallAndSlimItemFromObjectID(self, objectID):
    784         michelleSvc = sm.StartService('michelle')
    785         bp = michelleSvc.GetBallpark()
    786         if not bp:
    787             return (None, None)
    788         for ballID, ball in bp.balls.iteritems():
    789             slimItem = michelleSvc.GetItem(ballID)
    790             if getattr(slimItem, 'dunObjectID', None) == objectID:
    791                 return (ball, slimItem)
    792 
    793         return (None, None)
    794 
    795     def GetLockedObjects(self):
    796         return self.lockedObjects
    797 
    798     def UnlockObject(self, ballID, dunObjectID, force = False):
    799         if dunObjectID in self.lockedObjects and force:
    800             del self.lockedObjects[dunObjectID]
    801             del self.lockedTime[dunObjectID]
    802             self.RefreshSelection()
    803         else:
    804             uthread.new(self.UnlockObjectWhenInitialized, ballID, dunObjectID).context = 'svc.scenario.UnlockObject'
    805 
    806     def UnlockObjectWhenInitialized(self, ballID, dunObjectID):
    807         if dunObjectID not in self.lockedObjects:
    808             return
    809         michelleSvc = sm.StartService('michelle')
    810         targetBall = michelleSvc.GetBall(ballID)
    811         targetModel = getattr(targetBall, 'model', None)
    812         modelWaitEntryTime = blue.os.GetWallclockTime()
    813         slimItem = michelleSvc.GetItem(ballID)
    814         if slimItem.groupID not in self.groupsWithNoModel:
    815             while not targetModel:
    816                 blue.pyos.synchro.SleepWallclock(1000)
    817                 targetModel = getattr(targetBall, 'model', None)
    818                 if blue.os.GetWallclockTime() > modelWaitEntryTime + UNLOCK_OBJECT_MODEL_TIMEOUT:
    819                     self.LogWarn('UnlockObjectWhenInitialized gave up after', UNLOCK_OBJECT_MODEL_TIMEOUT, 'sec on waiting for the object model to load.', ballID, dunObjectID)
    820                     return False
    821 
    822         self.lockedObjects[dunObjectID] -= 1
    823         pendingChanges = 0
    824         for count in self.lockedObjects.itervalues():
    825             if count > 0:
    826                 pendingChanges += count
    827 
    828         if not pendingChanges:
    829             self.lockedObjects = {}
    830             self.lockedTime = {}
    831             uthread.new(prompt_player_fsd_dialog, 'AdminNotify', {'text': 'LevelEd: All changes have been confirmed.'}).context = 'gameui.ServerMessage'
    832         else:
    833             pendingObjects = len([ dunObjectID for dunObjectID in self.lockedObjects if self.lockedObjects[dunObjectID] > 0 ])
    834             if pendingChanges == 1:
    835                 changeString = '%d unconfirmed change' % pendingChanges
    836             else:
    837                 changeString = '%d unconfirmed changes' % pendingChanges
    838             if pendingObjects == 1:
    839                 dict = {'text': 'LevelEd: %s pending in 1 object.' % changeString}
    840             else:
    841                 dict = {'text': 'LevelEd: %s pending in %d objects.' % (changeString, len([ dunObjectID for dunObjectID in self.lockedObjects if self.lockedObjects[dunObjectID] > 0 ]))}
    842             uthread.new(prompt_player_fsd_dialog, 'AdminNotify', dict).context = 'gameui.ServerMessage'
    843         self.RefreshSelection()
    844 
    845     def UnlockObjectsOnTimeout(self):
    846         if not self.isUnlocking:
    847             self.isUnlocking = True
    848             uthread.new(self.UnlockObjectsOnTimeout_Thread).context = 'svc.scenario::UnlockObjectsOnTimeout'
    849 
    850     def UnlockObjectsOnTimeout_Thread(self):
    851         if self.currentCursor:
    852             self.cursors[self.currentCursor].ReleaseAxis()
    853         try:
    854             while self.lockedTime:
    855                 sleepTill = min(self.lockedTime.itervalues()) + UNLOCK_TIME
    856                 sleepTime = (sleepTill - blue.os.GetWallclockTime()) / const.MSEC
    857                 if sleepTime > 0:
    858                     blue.synchro.SleepWallclock(sleepTime)
    859                 threshhold = blue.os.GetWallclockTime() - UNLOCK_TIME
    860                 unlockedAny = False
    861                 for ballID, updateTime in self.lockedTime.items():
    862                     if threshhold > self.lockedTime[ballID]:
    863                         del self.lockedObjects[ballID]
    864                         del self.lockedTime[ballID]
    865                         unlockedAny = True
    866 
    867                 if unlockedAny:
    868                     self.RefreshSelection()
    869                     uthread.new(prompt_player_fsd_dialog, 'AdminNotify', {'text': 'LevelEd: Timeout waiting for change confirmation; releasing locked objects.'}).context = 'gameui.ServerMessage'
    870 
    871         finally:
    872             self.isUnlocking = False
    873 
    874     def GetSelObjects(self):
    875         selObjs = []
    876         for each in self.GetDunObjects():
    877             if self.IsSelected(each.itemID):
    878                 selObjs.append(each)
    879 
    880         return selObjs
    881 
    882     def GetSelectionCenter(self):
    883         x = 0
    884         y = 0
    885         z = 0
    886         count = 0
    887         for ballID in self.selection:
    888             if ballID in self.fakeBallTransforms:
    889                 x += self.fakeBallTransforms[ballID].translationCurve.x
    890                 y += self.fakeBallTransforms[ballID].translationCurve.y
    891                 z += self.fakeBallTransforms[ballID].translationCurve.z
    892                 count += 1
    893 
    894         if count:
    895             x /= count
    896             y /= count
    897             z /= count
    898         self.selectionCenter = (x, y, z)
    899         return self.selectionCenter
    900 
    901     def GetSelectionRotation(self):
    902         if self.currentCursor != 'Rotation' or not len(self.selection):
    903             return geo2.Vector(0, 0, 0, 1)
    904         if self.currentHardGroup in self.hardGroupRotations:
    905             return self.GetHardGroupRotation(self.currentHardGroup)
    906         if len(self.selection) > 1:
    907             return self.groupRotation
    908         ballID = self.selection[0]
    909         if ballID in self.fakeBallTransforms:
    910             targetBall = sm.GetService('michelle').GetBall(ballID)
    911             return geo2.QuaternionRotationSetYawPitchRoll(targetBall.yaw, targetBall.pitch, targetBall.roll)
    912 
    913     def SetGroupSelectionRotation(self, rotation):
    914         self.groupRotation = rotation
    915         if self.currentHardGroup in self.hardGroupRotations:
    916             self.hardGroupRotations[self.currentHardGroup] = rotation
    917             self.rotatedSelectionGroups[self.currentHardGroup] = rotation
    918 
    919     def SetCursor(self, cursorName):
    920         if cursorName in self.cursors:
    921             if self.currentCursor:
    922                 self.cursors[self.currentCursor].Hide()
    923             self.currentCursor = cursorName
    924             self.cursors[self.currentCursor].Show()
    925 
    926     def _UpdateCursor(self):
    927         if self.currentCursor:
    928             self.cursors[self.currentCursor].Show()
    929         while self.currentCursor and self.cursors[self.currentCursor].IsShown():
    930             playerClientBall = self.GetPlayerClientBall()
    931             selectionCenter = geo2.VectorD(self.GetSelectionCenter())
    932             playerPosition = geo2.VectorD(playerClientBall.x, playerClientBall.y, playerClientBall.z)
    933             toolPosition = geo2.Vec3SubtractD(selectionCenter, playerPosition)
    934             self.cursors[self.currentCursor].Rotate(self.GetSelectionRotation())
    935             self.cursors[self.currentCursor].Translate(toolPosition)
    936             self.cursors[self.currentCursor].UpdatePrimitives()
    937             keyDown = uicore.uilib.Key
    938             if keyDown(uiconst.VK_MENU):
    939                 if keyDown(uiconst.VK_W):
    940                     self.SetCursor('Translation')
    941                 if keyDown(uiconst.VK_E):
    942                     self.SetCursor('Rotation')
    943                 if keyDown(uiconst.VK_R):
    944                     self.SetCursor('Scaling')
    945             blue.synchro.Yield()
    946 
    947     def GetPlayerClientBall(self):
    948         bp = sm.StartService('michelle').GetBallpark()
    949         if not bp or not bp.ego:
    950             return
    951         return bp.balls[bp.ego]
    952 
    953     def GetDungeonOrigin(self):
    954         return self.dungeonOrigin.translation
    955 
    956     def SetDungeonOrigin(self):
    957         bp = sm.StartService('michelle').GetBallpark()
    958         if not bp or not bp.ego:
    959             return
    960         ego = bp.balls[bp.ego]
    961         self.dungeonOrigin = trinity.EveTransform()
    962         self.dungeonOrigin.translation = geo2.Vector(ego.x, ego.y, ego.z)
    963         self.dungeonOrigin.rotation = geo2.QuaternionRotationSetYawPitchRoll(ego.yaw, ego.pitch, ego.roll)
    964 
    965     def IsActive(self):
    966         return self.isActive
    967 
    968     def GetCursor(self):
    969         return self.cursors.get(self.currentCursor, None)
    970 
    971     def GetPickAxis(self):
    972         if self.currentCursor:
    973             return self.cursors[self.currentCursor].PickAxis(uicore.uilib.x, uicore.uilib.y)
    974 
    975     def AddHardGroup(self, groupName, orientation = None):
    976         if orientation is None:
    977             orientation = geo2.Vector(0, 0, 0, 1)
    978         self.hardGroupRotations[groupName] = orientation
    979 
    980     def RemoveHardGroup(self, groupName):
    981         if groupName in self.hardGroupRotations:
    982             del self.hardGroupRotations[groupName]
    983             if groupName in self.rotatedSelectionGroups:
    984                 del self.rotatedSelectionGroups[groupName]
    985 
    986     def RemoveAllHardGroups(self):
    987         self.hardGroupRotations = {}
    988         self.rotatedSelectionGroups = {}
    989 
    990     def RenameHardGroup(self, oldGroupName, newGroupName):
    991         if oldGroupName == newGroupName:
    992             return
    993         if oldGroupName in self.hardGroupRotations:
    994             self.hardGroupRotations[newGroupName] = self.hardGroupRotations[oldGroupName]
    995             del self.hardGroupRotations[oldGroupName]
    996             if self.currentHardGroup == oldGroupName:
    997                 self.currentHardGroup = newGroupName
    998 
    999     def SetActiveHardGroup(self, groupName):
   1000         self.currentHardGroup = groupName
   1001 
   1002     def GetActiveHardGroup(self):
   1003         return self.currentHardGroup
   1004 
   1005     def GetHardGroupRotation(self, groupName):
   1006         return self.hardGroupRotations.get(groupName, None)
   1007 
   1008     def OnDungeonEdit(self, dungeonID, roomID, roomPos):
   1009         self.editDungeonID = dungeonID
   1010         self.editRoomID = roomID
   1011         self.editRoomPos = roomPos
   1012         self.SetDungeonOrigin()
   1013         self.lastChangeTimestamp = None
   1014         self.unsavedChanges = {}
   1015         self.lockedObjects = {}
   1016 
   1017     def GetEditingDungeonID(self):
   1018         return self.editDungeonID
   1019 
   1020     def GetEditingRoomID(self):
   1021         return self.editRoomID
   1022 
   1023     def GetEditingRoomPosition(self):
   1024         return self.editRoomPos
   1025 
   1026     def GetClientToolsScene(self):
   1027         if self.clientToolsScene is not None:
   1028             return self.clientToolsScene
   1029         rj = sm.GetService('sceneManager').fisRenderJob
   1030         scene = rj.GetClientToolsScene()
   1031         if scene is not None:
   1032             self.clientToolsScene = scene
   1033             return self.clientToolsScene
   1034         self.clientToolsScene = trinity.Tr2PrimitiveScene()
   1035         rj.SetClientToolsScene(self.clientToolsScene)
   1036         return self.clientToolsScene