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