fxSequencer.py (27288B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\parklife\fxSequencer.py 2 import sys 3 import blue 4 import blue.heapq as heapq 5 import log 6 import telemetry 7 import evegraphics.settings as gfxsettings 8 import locks 9 import uthread 10 import utillib 11 from carbon.common.script.sys.service import Service 12 from carbon.common.script.util.commonutils import IsFullLogging 13 from eve.client.script.environment.effects.GenericEffect import GenericEffect, STOP_REASON_BALL_REMOVED, STOP_REASON_DEFAULT 14 from eve.client.script.environment.effects.Repository import GetClassification 15 from eve.client.script.environment.effects.effectConsts import FX_MERGE_GUID, FX_MERGE_MODULE, FX_MERGE_SHIP, FX_MERGE_TARGET 16 from eve.client.script.environment.effects.shipRenderEffect import ShipRenderEffect 17 from eve.client.script.environment.spaceObject.playerOwnedStructure import PlayerOwnedStructure 18 from eveexceptions import ExceptionEater 19 from evegraphics.explosions.spaceObjectExplosionManager import SpaceObjectExplosionManager 20 from fsdBuiltData.client.effects import GetEffectGuids 21 from destructionEffect import destructionType as destructionEffectType 22 from fsdBuiltData.client.travelEffects import IsTravelEffect 23 SECOND = 10000000 24 FX_TURRET_EFFECT_GUIDS = ['effects.Laser', 25 'effects.ProjectileFiredForEntities', 26 'effects.ProjectileFired', 27 'effects.HybridFired', 28 'effects.TractorBeam', 29 'effects.Salvaging', 30 'effects.TriglavianBeam'] 31 FX_PROTECTED_EFFECT_GUIDS = ['effects.GateActivity', 32 'effects.WormholeActivity', 33 'effects.JumpIn', 34 'effects.JumpOut', 35 'effects.JumpOutWormhole', 36 'effects.JumpOutAbyssal', 37 'effects.JumpOutAbyssalBetween', 38 'effects.JumpBridgeIn', 39 'effects.JumpBridgeOut', 40 'effects.Warping', 41 'effects.Cloaking', 42 'effects.Uncloak', 43 'effects.Cloak', 44 'effects.CloakNoAmim', 45 'effects.CloakRegardless', 46 'effects.StructureOffline', 47 'effects.StructureOnlined', 48 'effects.AnchorDrop', 49 'effects.AnchorLift', 50 'effects.SiegeMode', 51 'effects.TriageMode', 52 'effects.WarpDisruptFieldGenerating', 53 'effects.WarpScramble', 54 'effects.Tethering', 55 'effects.StellarHarvester', 56 'effects.DynamicDisruptionBubble'] 57 FX_LONG_ONESHOT_GUIDS = ['effects.SuperWeaponAmarr', 58 'effects.SuperWeaponCaldari', 59 'effects.SuperWeaponGallente', 60 'effects.SuperWeaponMinmatar'] 61 62 class Trigger(utillib.KeyVal): 63 64 def __le__(self, other): 65 return self.stamp <= other.stamp 66 67 def __eq__(self, other): 68 return self.shipID == other.shipID and self.moduleID == other.moduleID and self.targetID == other.targetID and self.guid == other.guid 69 70 71 class Activation(): 72 73 def __init__(self, broker): 74 self.triggers = [] 75 self.effect = None 76 self.guid = None 77 self.stamp = -1 78 self.duration = -1 79 self.broker = broker 80 self.key = None 81 self.isOn = False 82 self.balls = set() 83 self.isZombie = False 84 85 def __le__(self, other): 86 return self.stamp <= other.stamp 87 88 def Zombify(self): 89 self.isZombie = True 90 self.triggers = [] 91 self.balls = set() 92 self.broker = None 93 94 def TriggerAlreadyExists(self, trigger): 95 for t in self.triggers: 96 if trigger.moduleID == t.moduleID: 97 return True 98 99 return False 100 101 @telemetry.ZONE_METHOD 102 def AddTrigger(self, trigger): 103 if self.TriggerAlreadyExists(trigger): 104 return 105 heapq.heappush(self.triggers, trigger) 106 stampUpdated = self.UpdateRepeatTime() 107 if self.effect is None: 108 try: 109 self.effect = self.GetEffectFromGuid(trigger) 110 except: 111 self.effect = GenericEffect(trigger) 112 log.LogException() 113 sys.exc_clear() 114 115 self.guid = trigger.guid 116 self.balls = set(self.effect.GetBalls()) 117 self.PrepareAndStart() 118 self.isOn = True 119 else: 120 self.balls.add(trigger.shipID) 121 self.balls.add(trigger.targetID) 122 return stampUpdated 123 124 @telemetry.ZONE_METHOD 125 def PrepareAndStart(self): 126 try: 127 self.effect.Prepare() 128 self.effect.Start(self.duration) 129 except: 130 log.LogException() 131 sys.exc_clear() 132 133 @telemetry.ZONE_METHOD 134 def RemoveTrigger(self, trigger): 135 try: 136 ix = self.triggers.index(trigger) 137 except ValueError: 138 ix = -1 139 140 if ix >= 0: 141 del self.triggers[ix] 142 heapq.heapify(self.triggers) 143 self.PurgeTriggers() 144 return self.UpdateRepeatTime() 145 146 @telemetry.ZONE_METHOD 147 def UpdateRepeatTime(self): 148 lastTime = -1 149 candidate = None 150 for trigger in self.triggers: 151 if trigger.stamp > lastTime: 152 lastTime = trigger.stamp 153 candidate = trigger 154 155 if candidate is None: 156 return -1 157 now = blue.os.GetSimTime() 158 duration = int(candidate.duration) 159 durationInBluetime = SECOND * duration / 1000 160 wholeRepeatsLeft = long((candidate.stamp - now) / durationInBluetime) 161 if wholeRepeatsLeft == candidate.repeat: 162 wholeRepeatsLeft -= 1 163 newStamp = candidate.stamp - wholeRepeatsLeft * durationInBluetime 164 if newStamp == self.stamp: 165 return 0 166 self.stamp = newStamp 167 self.duration = candidate.duration 168 return 1 169 170 @telemetry.ZONE_METHOD 171 def Repeat(self): 172 if self.effect is None: 173 raise RuntimeError('Activation::Repeat: No effect defined') 174 stampUpdated = self.PurgeTriggers() 175 if not self.triggers: 176 self.Stop() 177 return -1 178 if not self.isOn: 179 return stampUpdated 180 try: 181 self.effect.Repeat(self.duration) 182 except: 183 log.LogException() 184 sys.exc_clear() 185 186 return stampUpdated 187 188 @telemetry.ZONE_METHOD 189 def Stop(self, reason = STOP_REASON_DEFAULT): 190 if not self.isOn: 191 return 192 if self.effect is None: 193 raise RuntimeError('Activation::Stop: No effect defined') 194 try: 195 self.effect.Stop(reason) 196 except: 197 log.LogException() 198 sys.exc_clear() 199 200 self.isOn = False 201 202 @telemetry.ZONE_METHOD 203 def PurgeTriggers(self): 204 now = blue.os.GetSimTime() 205 while self.triggers and self.triggers[0].stamp <= now: 206 heapq.heappop(self.triggers) 207 208 return self.UpdateRepeatTime() 209 210 def GetBalls(self): 211 return self.balls 212 213 def GetName(self): 214 if self.triggers: 215 return self.triggers[0].guid 216 else: 217 return 'unknown' 218 219 def GetEffectFromGuid(self, trigger): 220 classification = GetClassification(trigger.guid) 221 if classification is None: 222 raise RuntimeError('Activation::Unable to load effect for guid:', trigger.guid) 223 classType = classification[0] 224 args = classification[1:] 225 return classType(trigger, *args) 226 227 228 def GetFxSequencer(): 229 return sm.GetService('FxSequencer') 230 231 232 class FxSequencer(Service): 233 __guid__ = 'svc.FxSequencer' 234 __exportedcalls__ = {'ClearAll': [], 235 'NotifyModelLoaded': [], 236 'EnableGuids': [], 237 'DisableGuids': [], 238 'GetDisabledGuids': []} 239 __notifyevents__ = ['OnSpecialFX', 240 'DoBallRemove', 241 'DoSimClockRebase', 242 'DoBallsRemove', 243 'OnGraphicSettingsChanged'] 244 __dependencies__ = ['michelle'] 245 __startupdependencies__ = ['settings'] 246 247 def __init__(self): 248 Service.__init__(self) 249 self.activations = {} 250 self.stopCandidates = [] 251 self.ballRegister = {} 252 self.delayedtriggers = {} 253 self.killLoop = True 254 self.sequencerTasklet = None 255 self.disabledGuids = {} 256 257 def Run(self, memStream = None): 258 Service.Run(self, memStream) 259 self.lock = locks.RLock() 260 self.disabledGuids = settings.user.ui.Get('disabledGuids', {}) 261 self.sequencerTasklet = uthread.new(self.SequencerLoop) 262 self.sequencerTasklet.context = 'FxSequencer::SequencerLoop' 263 if gfxsettings.Get(gfxsettings.UI_TURRETS_ENABLED): 264 self.EnableGuids(FX_TURRET_EFFECT_GUIDS) 265 else: 266 self.DisableGuids(FX_TURRET_EFFECT_GUIDS) 267 candidateEffects = [] 268 for guid in GetEffectGuids(): 269 if guid not in FX_TURRET_EFFECT_GUIDS and guid not in FX_PROTECTED_EFFECT_GUIDS: 270 candidateEffects.append(guid) 271 272 if gfxsettings.Get(gfxsettings.UI_EFFECTS_ENABLED): 273 self.EnableGuids(candidateEffects) 274 else: 275 self.DisableGuids(candidateEffects) 276 277 def Stop(self, ms): 278 Service.Stop(self) 279 self.killLoop = True 280 blue.pyos.synchro.WakeupAtSim(self.sequencerTasklet, 0, -1) 281 self.sequencerTasklet = None 282 283 def DoSimClockRebase(self, times): 284 oldSimTime, newSimTime = times 285 offset = newSimTime - oldSimTime 286 for activation in self.activations.itervalues(): 287 activation.stamp += offset 288 for trigger in activation.triggers: 289 with ExceptionEater('Adjusting time on an FX trigger'): 290 trigger.stamp += offset 291 if trigger.startTime: 292 trigger.startTime += offset 293 294 for triggerList in self.delayedtriggers.itervalues(): 295 for trigger in triggerList: 296 with ExceptionEater('Adjusting time on a delayed FX trigger'): 297 if trigger.startTime: 298 trigger.startTime += offset 299 300 def OnGraphicSettingsChanged(self, changes): 301 if gfxsettings.UI_TURRETS_ENABLED in changes: 302 if gfxsettings.Get(gfxsettings.UI_TURRETS_ENABLED): 303 self.EnableGuids(fxTurretGuids) 304 else: 305 self.DisableGuids(fxTurretGuids) 306 if gfxsettings.UI_EFFECTS_ENABLED in changes: 307 candidateEffects = [] 308 for guid in GetEffectGuids(): 309 if guid not in fxTurretGuids and guid not in fxProtectedGuids: 310 candidateEffects.append(guid) 311 312 if len(candidateEffects) > 0: 313 if gfxsettings.Get(gfxsettings.UI_EFFECTS_ENABLED): 314 self.EnableGuids(candidateEffects) 315 else: 316 self.DisableGuids(candidateEffects) 317 318 @telemetry.ZONE_METHOD 319 def OnSpecialFX(self, shipID, moduleID, moduleTypeID, targetID, otherTypeID, guid, isOffensive, start, active, duration = -1, repeat = None, startTime = None, timeFromStart = 0, graphicInfo = None): 320 if start == 1 and guid in self.disabledGuids: 321 return 322 if IsTravelEffect(guid) and sm.GetService('subway').Enabled(): 323 return 324 if startTime is not None and guid in FX_LONG_ONESHOT_GUIDS: 325 now = blue.os.GetSimTime() 326 if now - startTime > 150000000: 327 return 328 classification = GetClassification(guid) 329 if classification is not None: 330 effect = classification[1] 331 mergeFlags = getattr(effect, 'mergeFlags') 332 else: 333 effect = None 334 mergeFlags = [0] 335 trigger = Trigger(shipID=shipID, moduleID=moduleID, moduleTypeID=moduleTypeID, targetID=targetID, otherTypeID=otherTypeID, guid=guid, isOffensive=isOffensive, duration=duration, repeat=repeat, startTime=startTime, timeFromStart=timeFromStart, graphicInfo=graphicInfo, mergeFlags=mergeFlags, controllerVariableTriggers=getattr(effect, 'controllerVariableTriggers', {}), locatorSetOverride=getattr(effect, 'locatorSetOverride', None)) 336 if trigger.repeat is None or trigger.repeat <= 0: 337 trigger.repeat = 1 338 else: 339 trigger.repeat += 1 340 if guid is None: 341 self.LogWarn('FxSequencer::OnSpecialFx: No guid in trigger for moduleTypeID', moduleTypeID, 'and graphicInfo', graphicInfo) 342 return 343 splitGuid = guid.split('.') 344 if len(splitGuid) != 2: 345 self.LogInfo('FxSequencer::OnSpecialFx: No guid in trigger') 346 return 347 niceName = guid.split('.')[1] 348 if shipID == session.shipid: 349 ship = 'for myself' 350 elif IsFullLogging(): 351 ship = 'for ship: %s' % shipID 352 else: 353 ship = 'for another ship' 354 if start == 1: 355 if active == 0: 356 self.LogInfo('FxSequencer::OnSpecialFX: Got ONE-SHOT event:', niceName, ship, '[', moduleID, '] with duration:', duration) 357 if trigger.repeat is not None and trigger.repeat > 0: 358 self.LogInfo('Starting REPEAT event:', niceName, ship, '[', moduleID, '] with duration:', duration / 1000.0, 'and repeat:', trigger.repeat) 359 self.AddTrigger(trigger) 360 else: 361 self.LogInfo('FxSequencer::OnSpecialFX: Starting TOGGLE event:', niceName, ship, '[', moduleID, ']') 362 if targetID is not None: 363 if targetID == session.shipid: 364 target = 'myself' 365 else: 366 target = targetID 367 self.LogInfo('targeted at:', target, 'and is', ['not', ''][isOffensive], 'offensive') 368 else: 369 self.RemoveTrigger(trigger) 370 self.LogInfo('FxSequencer::OnSpecialFX: Stopping', niceName, 'for ship:', ship, 'and module:', moduleID) 371 372 def EnableGuids(self, guids): 373 for guid in guids: 374 if guid in self.disabledGuids: 375 del self.disabledGuids[guid] 376 377 settings.user.ui.Set('disabledGuids', self.disabledGuids) 378 self.LogInfo('FxSequencer::EnableGuids', guids, ' Current disabled guids:', self.disabledGuids) 379 380 def DisableGuids(self, guids): 381 for guid in guids: 382 if guid not in self.disabledGuids and guid not in FX_PROTECTED_EFFECT_GUIDS: 383 self.disabledGuids[guid] = None 384 385 settings.user.ui.Set('disabledGuids', self.disabledGuids) 386 self.LogInfo('FxSequencer::DisableGuids', guids, ' Current disabled guids:', self.disabledGuids) 387 388 def GetDisabledGuids(self): 389 return self.disabledGuids 390 391 @telemetry.ZONE_METHOD 392 def AddTrigger(self, trigger): 393 with self.lock: 394 telemetry.APPEND_TO_ZONE(trigger.guid) 395 self.LogInfo('FxSequencer::AddTrigger: Entering') 396 shipBall = None 397 targetBall = None 398 if trigger.shipID is not None: 399 shipBall = self.GetBall(trigger.shipID) 400 if trigger.targetID is not None: 401 targetBall = self.GetBall(trigger.targetID) 402 classification = GetClassification(trigger.guid) 403 if trigger.shipID is not None and shipBall is None: 404 self.LogWarn('FxSequencer::AddTrigger: ship not in ballpark. Trigger ignored') 405 return 406 if classification and classification[0] is ShipRenderEffect: 407 if hasattr(shipBall, 'model') and not hasattr(shipBall.model, 'overlayEffects'): 408 self.LogInfo('This ball', shipBall.id, 'of type', self.GetItem(trigger.shipID).typeID, 'does not support the ship effect', trigger.guid) 409 return 410 if isinstance(shipBall, PlayerOwnedStructure) and getattr(trigger, 'moduleID', None): 411 trigger.shipID = trigger.moduleID 412 if trigger.targetID is not None and targetBall is None: 413 self.LogWarn('FxSequencer::AddTrigger: target not in ballpark. Trigger ignored') 414 return 415 delayed = False 416 if trigger.shipID is not None and getattr(shipBall, 'model', None) is None: 417 if trigger.shipID in self.delayedtriggers: 418 self.delayedtriggers[trigger.shipID].append(trigger) 419 else: 420 self.delayedtriggers[trigger.shipID] = [trigger] 421 delayed = True 422 if trigger.targetID is not None and getattr(targetBall, 'model', None) is None: 423 if trigger.targetID in self.delayedtriggers: 424 self.delayedtriggers[trigger.targetID].append(trigger) 425 else: 426 self.delayedtriggers[trigger.targetID] = [trigger] 427 delayed = True 428 if delayed: 429 return 430 key = self.GetKey(trigger) 431 if trigger.duration is None or trigger.duration <= 0.0: 432 if classification is not None: 433 trigger.duration = getattr(classification[1], 'duration', None) 434 if trigger.repeat is None or trigger.repeat == 0: 435 trigger.repeat = 1 436 trigger.__dict__['stamp'] = blue.os.GetSimTime() + long(trigger.duration * trigger.repeat / 1000.0 * SECOND) 437 if key in self.activations and trigger.guid not in ('effects.Cloak', 'effects.Uncloak'): 438 activation = self.activations[key] 439 activation.AddTrigger(trigger) 440 self.AddActivationToBallRegister(activation) 441 effect = activation.effect 442 if effect and trigger.graphicInfo: 443 effect.UpdateGraphicInfo(trigger.graphicInfo) 444 else: 445 self.AddActivation(trigger, key) 446 self.LogInfo('FxSequencer::AddTrigger: Done') 447 448 @telemetry.ZONE_METHOD 449 def RemoveTrigger(self, trigger): 450 self.LogInfo('FxSequencer::RemoveTrigger: Entering') 451 shipBall = None 452 if trigger.shipID is not None: 453 shipBall = self.GetBall(trigger.shipID) 454 if getattr(shipBall, '__guid__', '') == 'spaceObject.PlayerOwnedStructure' and getattr(trigger, 'moduleID', None) is not None: 455 trigger.shipID = trigger.moduleID 456 key = self.GetKey(trigger) 457 if key not in self.activations: 458 self.LogInfo('FxSequencer::RemoveTrigger: Trigger not found') 459 return 460 activation = self.activations[key] 461 updateTime = activation.RemoveTrigger(trigger) 462 if updateTime == -1: 463 self.LogInfo('FxSequencer::RemoveTrigger: Activation empty. Deleting it.') 464 self.RemoveActivation(activation, fromSequencer=True) 465 self.LogInfo('FxSequencer::RemoveTrigger:', trigger.guid, 'Done') 466 467 @telemetry.ZONE_METHOD 468 def SequencerLoop(self): 469 self.killLoop = False 470 reason = None 471 self.LogInfo('FxSequencer: Sequencer loop starting') 472 while not getattr(self, 'killLoop', True): 473 self.CheckForStopCandidates() 474 if self.stopCandidates: 475 wakeupTime = self.stopCandidates[0].stamp 476 else: 477 wakeupTime = blue.os.GetSimTime() + 60 * SECOND 478 reason = blue.pyos.synchro.SleepUntilSim(wakeupTime) 479 480 self.LogInfo('FxSequencer: Sequencer loop terminated with reason', reason) 481 482 @telemetry.ZONE_METHOD 483 def CheckForStopCandidates(self): 484 now = blue.os.GetSimTime() 485 while self.stopCandidates and self.stopCandidates[0].isZombie: 486 heapq.heappop(self.stopCandidates) 487 488 while self.stopCandidates and self.stopCandidates[0].stamp < now: 489 activation = heapq.heappop(self.stopCandidates) 490 state = activation.Repeat() 491 if state == -1: 492 self.LogInfo('FxSequencer::CheckForStopCandidates: activation expired') 493 self.RemoveActivation(activation) 494 else: 495 heapq.heappush(self.stopCandidates, activation) 496 while self.stopCandidates and self.stopCandidates[0].isZombie: 497 heapq.heappop(self.stopCandidates) 498 499 now = blue.os.GetSimTime() 500 501 @telemetry.ZONE_METHOD 502 def DoBallsRemove(self, pythonBalls, isRelease): 503 for ball, slimItem, terminal in pythonBalls: 504 self.RemoveAllBallActivations(ball.id) 505 506 @telemetry.ZONE_METHOD 507 def DoBallRemove(self, ball, slimItem, terminal): 508 self.RemoveAllBallActivations(ball.id) 509 510 @telemetry.ZONE_METHOD 511 def GetKey(self, trigger): 512 key = [] 513 if FX_MERGE_GUID in trigger.mergeFlags: 514 key.append(trigger.guid) 515 if FX_MERGE_SHIP in trigger.mergeFlags: 516 key.append(trigger.shipID) 517 if FX_MERGE_MODULE in trigger.mergeFlags: 518 key.append(trigger.moduleID) 519 if FX_MERGE_TARGET in trigger.mergeFlags: 520 key.append(trigger.targetID) 521 if len(key) == 0: 522 log.LogWarn('Key is empty for trigger:', trigger) 523 return 524 return tuple(key) 525 526 @telemetry.ZONE_METHOD 527 def AddActivation(self, trigger, key = None): 528 if key is None: 529 key = self.GetKey(trigger) 530 activation = Activation(self) 531 activation.key = key 532 self.activations[key] = activation 533 activation.AddTrigger(trigger) 534 self.AddActivationToBallRegister(activation) 535 self.AddActivationToSequencer(activation) 536 537 @telemetry.ZONE_METHOD 538 def AddActivationToSequencer(self, activation): 539 currentStopTime = activation.stamp 540 reschedule = True 541 if self.stopCandidates: 542 if self.stopCandidates[0].stamp <= currentStopTime: 543 reschedule = False 544 heapq.heappush(self.stopCandidates, activation) 545 if reschedule: 546 try: 547 self.LogInfo('AddActivationToSequencer: just going for some rescheduling here') 548 blue.pyos.synchro.WakeupAtSim(self.sequencerTasklet, currentStopTime) 549 except: 550 self.LogInfo('AddActivationToSequencer: Activation rescedule failed, sequencer will wake up by itself') 551 sys.exc_clear() 552 553 self.LogInfo('AddActivationToSequencer: Activation added') 554 555 @telemetry.ZONE_METHOD 556 def RemoveActivation(self, activation, fromSequencer = False, reason = STOP_REASON_DEFAULT): 557 niceName = activation.GetName() 558 activation.Stop(reason) 559 if activation.key in self.activations: 560 del self.activations[activation.key] 561 self.RemoveActivationFromBallRegister(activation) 562 if fromSequencer: 563 self.RemoveActivationFromSequencer(activation) 564 self.LogInfo('FxSequencer::RemoveActivation:', niceName, 'activation removed.', len(self.activations), 'remaining') 565 566 @telemetry.ZONE_METHOD 567 def RemoveActivationFromSequencer(self, activation): 568 activation.Zombify() 569 try: 570 if self.stopCandidates: 571 blue.pyos.synchro.WakeupAtSim(self.sequencerTasklet, self.stopCandidates[0].stamp) 572 else: 573 blue.pyos.synchro.WakeupAtSim(self.sequencerTasklet, blue.os.GetSimTime() + 60 * SECOND) 574 except: 575 pass 576 577 self.LogInfo('RemoveActivationFromSequencer:', activation) 578 579 def ClearAll(self): 580 self.LogInfo('FxSequencer::ClearAll') 581 for activation in self.activations.values(): 582 self.RemoveActivation(activation, fromSequencer=True) 583 584 self.stopCandidates = [] 585 if len(self.delayedtriggers): 586 self.LogWarn('FxSequencer::ClearAll: Model Loaded notifications never came through?') 587 self.delayedtriggers = {} 588 if len(self.stopCandidates) or len(self.activations) or len(self.ballRegister): 589 self.LogWarn('FxSequencer::ClearAll: Incomplete reset') 590 591 def AreModelsLoadedForTrigger(self, trigger): 592 if trigger.targetID is not None and trigger.targetID in self.delayedtriggers: 593 return False 594 if trigger.shipID is not None and trigger.shipID in self.delayedtriggers: 595 return False 596 return True 597 598 @telemetry.ZONE_METHOD 599 def NotifyModelLoaded(self, ballID): 600 if ballID in self.delayedtriggers: 601 initiateTriggers = self.delayedtriggers.pop(ballID) 602 self.LogInfo('FxSequencer::NotifyModelLoaded', ballID, 'initiating', len(initiateTriggers), 'triggers') 603 for trigger in initiateTriggers: 604 if self.AreModelsLoadedForTrigger(trigger): 605 self.AddTrigger(trigger) 606 607 def GetAllBallActivations(self, ballID): 608 if ballID in self.ballRegister: 609 return self.ballRegister[ballID] 610 return set() 611 612 def GetAllBallActivationNames(self, ballID): 613 return set([ a.GetName() for a in self.GetAllBallActivations(ballID) ]) 614 615 def RemoveAllBallActivations(self, ballID): 616 if ballID in self.ballRegister: 617 self.LogInfo('FxSequencer::RemoveAllBallActivations for ball', ballID) 618 self.RemoveActivationsForBallID(ballID) 619 620 def RemoveActivationAtTime(self, activation, g): 621 uthread.new(self._RemoveActivationAtTime, activation, g) 622 623 def _RemoveActivationAtTime(self, activation, g): 624 blue.synchro.SleepUntilSim(g) 625 self.RemoveActivation(activation, fromSequencer=True, reason=STOP_REASON_BALL_REMOVED) 626 627 def RemoveActivationsForBallID(self, ballID): 628 629 def explosionTimeCallback(sequencer, activationObject): 630 return lambda g, w: sequencer.RemoveActivationAtTime(activationObject, g) 631 632 for activation in tuple(self.ballRegister.get(ballID, [])): 633 removeImmediately = True 634 if ballID == activation.effect.GetEffectTargetID(): 635 ball = self.GetBall(ballID) 636 if destructionEffectType.isExplosionOrOverride(ball.destructionEffectId): 637 removeImmediately = False 638 SpaceObjectExplosionManager.GetExplosionTimesWhenAvailable(ballID, explosionTimeCallback(self, activation)) 639 if removeImmediately: 640 self.RemoveActivation(activation, fromSequencer=True, reason=STOP_REASON_BALL_REMOVED) 641 642 def AddActivationToBallRegister(self, activation): 643 for ballID in activation.GetBalls(): 644 if ballID is not None: 645 if ballID in self.ballRegister: 646 self.ballRegister[ballID].add(activation) 647 else: 648 self.ballRegister[ballID] = set([activation]) 649 sm.ScatterEvent('OnAddFX', activation.guid, ballID) 650 651 def RemoveActivationFromBallRegister(self, activation): 652 for ballID in activation.GetBalls(): 653 if ballID in self.ballRegister: 654 try: 655 self.ballRegister[ballID].remove(activation) 656 except KeyError: 657 pass 658 659 if len(self.ballRegister[ballID]) == 0: 660 del self.ballRegister[ballID] 661 sm.ScatterEvent('OnRemoveFX', activation.guid, ballID) 662 663 def GetBall(self, ballID): 664 return self.michelle.GetBall(ballID) 665 666 def GetBallpark(self): 667 return self.michelle.GetBallpark() 668 669 def GetItem(self, itemID): 670 return self.michelle.GetItem(itemID) 671 672 def GetScene(self): 673 return sm.GetService('sceneManager').GetRegisteredScene('default') 674 675 def GetTypeAttribute(self, moduleTypeID, attributeEmpFieldRange): 676 return sm.GetService('godma').GetTypeAttribute(moduleTypeID, attributeEmpFieldRange) 677 678 679 fxTurretGuids = FX_TURRET_EFFECT_GUIDS 680 fxProtectedGuids = FX_PROTECTED_EFFECT_GUIDS 681 exports = {'FxSequencer.fxTurretGuids': fxTurretGuids, 682 'FxSequencer.fxProtectedGuids': fxProtectedGuids}