flightControls.py (15540B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\services\flightControls.py 2 import collections 3 import math 4 import geo2 5 import blue 6 import uthread 7 import destiny 8 import trinity 9 import evecamera 10 from carbon.common.script.sys.service import Service 11 from eveexceptions.exceptionEater import ExceptionEater 12 import carbonui.const as uiconst 13 from carbonui.uicore import uicore 14 from eve.common.lib import appConst as const 15 16 class FlightControls(Service): 17 __guid__ = 'svc.flightControls' 18 __dependencies__ = ['michelle', 'machoNet'] 19 __notifyevents__ = ['OnBallparkSetState', 20 'OnSessionChanged', 21 'DoBallsAdded', 22 'OnMapShortcut', 23 'OnRestoreDefaultShortcuts', 24 'DoSimClockRebase', 25 'OnActiveCameraChanged'] 26 27 def Run(self, *args): 28 Service.Run(self, *args) 29 self.controls = KeyboardControls() 30 self.simulation = FlightSimulation(callback=self.OnGotoDirection, controls=self.controls, throttle=self.GetThrottle()) 31 self.statistics = FlightControlStatistics(self.controls) 32 self.running = True 33 self.simulation.Start() 34 self.statistics.Start() 35 36 def Start(self): 37 self.running = True 38 self.simulation.Start() 39 self.AttachShip() 40 41 def Stop(self): 42 self.running = False 43 self.simulation.DetachShip() 44 self.simulation.Stop() 45 46 def GetThrottle(self): 47 return int(self.machoNet.GetGlobalConfig().get('flightControlsThrottle', 400)) / float(1000) * const.SEC 48 49 def OnGotoDirection(self, direction): 50 remote = self.michelle.GetRemotePark() 51 if remote: 52 remote.CmdSteerDirection(*direction) 53 self.statistics.Increment() 54 55 def AttachShip(self): 56 if self.running: 57 self.controls.PrimeKeys() 58 ballpark = self.michelle.GetBallpark() 59 if ballpark and session.shipid and not session.structureid: 60 self.simulation.AttachShip(ballpark.GetBall(session.shipid)) 61 else: 62 self.simulation.DetachShip() 63 64 def DoSimClockRebase(self, times): 65 self.simulation.AdjustTimes(times[1] - times[0]) 66 self.controls.Reset() 67 68 def OnBallparkSetState(self): 69 self.AttachShip() 70 self.controls.Reset() 71 72 def OnSessionChanged(self, isRemote, session, change): 73 self.AttachShip() 74 self.controls.Reset() 75 76 def DoBallsAdded(self, balls): 77 with ExceptionEater('FlightControls.DoBallsAdded'): 78 if any([ ball for ball, item in balls if item.itemID == session.shipid ]): 79 self.AttachShip() 80 self.controls.Reset() 81 82 def OnMapShortcut(self, *args): 83 self.controls.PrimeKeys() 84 85 def OnRestoreDefaultShortcuts(self, *args): 86 self.controls.PrimeKeys() 87 88 def OnActiveCameraChanged(self, cameraID): 89 self.simulation.SetWorldRelative(cameraID != evecamera.CAM_SHIPPOV) 90 91 92 class FlightSimulation(object): 93 ENGAGE_TIME = 6.0 94 RELEASE_TIME = 4.0 95 96 def __init__(self, callback, controls, throttle = None): 97 self.callback = callback 98 self.controls = controls 99 self.throttle = throttle or 0 100 self.ballpark = blue.classes.CreateInstance('destiny.Ballpark') 101 self.ballpark.tickInterval = 100 102 self.ball = self.ballpark.AddBall(1, 1.0, 0.0, 0, True, False, False, False, False, 0, 0, 0, 0, 0, 0, 0, 0) 103 self.ship = None 104 self.thread = None 105 self.controlling = False 106 self.direction = None 107 self.lastCalled = 0 108 self.worldRelative = True 109 self.curve = trinity.Tr2QuaternionLerpCurve() 110 111 def Start(self): 112 if self.thread is None: 113 self.ballpark.Start() 114 self.thread = uthread.new(self.Update) 115 self.thread.context = 'FlightSimulation::Update' 116 117 def Stop(self): 118 if self.thread is not None: 119 self.ballpark.Pause() 120 self.thread.kill() 121 self.thread = None 122 123 def SetWorldRelative(self, relative): 124 self.worldRelative = relative 125 126 def AdjustTimes(self, delta): 127 self.lastCalled += delta 128 self.ballpark.AdjustTimes(delta) 129 130 def AttachShip(self, ship): 131 if ship is None or self.ship != ship: 132 self.DetachShip(self.ship) 133 self.ship = ship 134 self.controlling = False 135 if getattr(self, 'localModel', None): 136 self.DisableDebug() 137 self.EnableDebug() 138 139 def DetachShip(self, ship = None): 140 if ship in (self.ship, None): 141 if self.ship and getattr(self.ship, 'model', None): 142 self.ship.model.rotationCurve = self.ship 143 self.ship = None 144 self.controlling = False 145 146 def Update(self): 147 while True: 148 blue.synchro.Yield() 149 if self.ship and self.controls and self.callback: 150 with ExceptionEater('FlightSimulation'): 151 self.AttachBalls() 152 self.ProcessInput() 153 self.UpdateDirection() 154 self.TriggerCallback() 155 156 def AttachBalls(self): 157 self.ballpark.tickInterval = 100 158 if getattr(self.ship, 'model', None): 159 self.ship.model.rotationCurve = self.curve 160 velocity = self.ship.GetVectorDotAt(blue.os.GetSimTime()) 161 self.ballpark.SetBallVelocity(self.ball.id, velocity.x, velocity.y, velocity.z) 162 self.curve.startCurve = self.ball 163 self.curve.endCurve = self.ship 164 self.curve.start = 0 165 self.curve.length = 1 166 167 def CanControl(self): 168 return self.ship.mode in (destiny.DSTBALL_ORBIT, destiny.DSTBALL_FOLLOW, destiny.DSTBALL_GOTO) 169 170 def ProcessInput(self): 171 if self.CanControl(): 172 yaw, pitch = self.controls.GetYawPitch() 173 else: 174 yaw, pitch = (0, 0) 175 currentYaw, currentPitch, currentRoll = self.curve.GetQuaternionAt(blue.os.GetSimTime()).GetYawPitchRoll() 176 if self.worldRelative: 177 currentPitch = max(-math.pi / 2.0, min(math.pi / 2.0, currentPitch + pitch)) 178 current = geo2.QuaternionRotationSetYawPitchRoll(currentYaw, currentPitch, 0) 179 rotation = geo2.QuaternionRotationSetYawPitchRoll(yaw, 0, 0) 180 else: 181 current = geo2.QuaternionRotationSetYawPitchRoll(currentYaw, currentPitch, currentRoll) 182 rotation = geo2.QuaternionRotationSetYawPitchRoll(yaw, pitch, 0) 183 result = geo2.QuaternionMultiply(rotation, current) 184 self.direction = geo2.QuaternionTransformVector(result, (0, 0, 1)) 185 if not self.controlling: 186 self.controlling = bool(yaw or pitch) 187 188 def UpdateDirection(self): 189 self.ball.x, self.ball.y, self.ball.z = self.ship.x, self.ship.y, self.ship.z 190 self.ballpark.SetBallMass(self.ball.id, self.ship.mass) 191 self.ballpark.SetBallAgility(self.ball.id, self.ship.Agility) 192 self.ballpark.SetBallRadius(self.ball.id, self.ship.radius) 193 self.ballpark.SetMaxSpeed(self.ball.id, self.ship.maxVelocity) 194 self.ballpark.SetSpeedFraction(self.ball.id, self.ship.speedFraction) 195 if self.controlling: 196 self.ballpark.GotoDirection(self.ball.id, self.direction[0], self.direction[1], self.direction[2]) 197 if self.curve.endCurve != self.ball: 198 self.curve.startCurve = self.ship 199 self.curve.endCurve = self.ball 200 self.curve.length = self.ENGAGE_TIME 201 self.curve.start = blue.os.GetSimTime() - long(max(0, self.curve.start + self.ENGAGE_TIME * const.SEC - blue.os.GetSimTime())) 202 else: 203 if blue.os.GetSimTime() - self.lastCalled > self.RELEASE_TIME * const.SEC and self.curve.endCurve != self.ship: 204 if self.curve.endCurve != self.ship: 205 self.curve.startCurve = self.ball 206 self.curve.endCurve = self.ship 207 self.curve.length = self.ENGAGE_TIME 208 self.curve.start = blue.os.GetSimTime() - long(max(0, self.curve.start + self.ENGAGE_TIME * const.SEC - blue.os.GetSimTime())) 209 if self.curve.endCurve == self.ship: 210 velocity = self.ship.GetVectorDotAt(blue.os.GetSimTime()) 211 self.ballpark.GotoDirection(self.ball.id, velocity.x, velocity.y, velocity.z) 212 213 def TriggerCallback(self): 214 if self.controlling and self.lastCalled + self.throttle < blue.os.GetSimTime(): 215 self.lastCalled = blue.os.GetSimTime() 216 self.callback(self.direction) 217 self.controlling = False 218 219 def EnableDebug(self): 220 scaling = [(self.ship.radius + 20) * 2, (self.ship.radius + 20) * 2, (self.ship.radius + 20) * 2] 221 self.localModel = trinity.EveRootTransform() 222 self.localGfx = trinity.Load('res:/model/global/gridSphere.red') 223 self.localGfx.scaling = geo2.Vec3Scale(scaling, 1.0) 224 self.localModel.name = 'FlightSimulationLocal' 225 self.localModel.translationCurve = self.ball 226 self.localModel.rotationCurve = self.ball 227 self.localModel.children.append(self.localGfx) 228 sm.GetService('sceneManager').GetRegisteredScene('default').objects.append(self.localModel) 229 self.remoteModel = trinity.EveRootTransform() 230 self.remoteGfx = trinity.Load('res:/model/global/gridSphere.red') 231 self.remoteGfx.scaling = geo2.Vec3Scale(scaling, 1.5) 232 color = self.remoteGfx.mesh.additiveAreas[0].effect.parameters[1].value 233 self.remoteGfx.mesh.additiveAreas[0].effect.parameters[1].value = (color[0], 234 color[2], 235 color[1], 236 color[3]) 237 self.remoteModel.name = 'FlightSimulationRemote' 238 self.remoteModel.translationCurve = self.ship 239 self.remoteModel.rotationCurve = self.ship 240 self.remoteModel.children.append(self.remoteGfx) 241 sm.GetService('sceneManager').GetRegisteredScene('default').objects.append(self.remoteModel) 242 self.curveModel = trinity.EveRootTransform() 243 self.curveGfx = trinity.Load('res:/model/global/gridSphere.red') 244 self.curveGfx.scaling = geo2.Vec3Scale(scaling, 1.25) 245 color = self.curveGfx.mesh.additiveAreas[0].effect.parameters[1].value 246 self.curveGfx.mesh.additiveAreas[0].effect.parameters[1].value = (color[2], 247 color[1], 248 color[0], 249 color[3]) 250 self.curveModel.name = 'FlightSimulationRemote' 251 self.curveModel.translationCurve = self.ball 252 self.curveModel.rotationCurve = self.curve 253 self.curveModel.children.append(self.curveGfx) 254 sm.GetService('sceneManager').GetRegisteredScene('default').objects.append(self.curveModel) 255 256 def DisableDebug(self): 257 sm.GetService('sceneManager').GetRegisteredScene('default').objects.fremove(self.localModel) 258 self.localModel = None 259 sm.GetService('sceneManager').GetRegisteredScene('default').objects.fremove(self.remoteModel) 260 self.remoteModel = None 261 sm.GetService('sceneManager').GetRegisteredScene('default').objects.fremove(self.curveModel) 262 self.curveModel = None 263 264 def ToggleDebug(self): 265 if getattr(self, 'localModel', None): 266 self.DisableDebug() 267 else: 268 self.EnableDebug() 269 270 271 class KeyboardControls(object): 272 DECAY = 1.2 273 ACCEL = 1 274 MAX = 1.3 275 276 def __init__(self): 277 self.keyUp = [] 278 self.keyDown = [] 279 self.keyLeft = [] 280 self.keyRight = [] 281 self.Reset() 282 283 def Reset(self): 284 self.yaw = 0.0 285 self.pitch = 0.0 286 self.time = 0 287 288 def PrimeKeys(self): 289 self.keyUp = uicore.cmd.GetShortcutByFuncName('CmdFlightControlsUp') or [] 290 self.keyDown = uicore.cmd.GetShortcutByFuncName('CmdFlightControlsDown') or [] 291 self.keyLeft = uicore.cmd.GetShortcutByFuncName('CmdFlightControlsLeft') or [] 292 self.keyRight = uicore.cmd.GetShortcutByFuncName('CmdFlightControlsRight') or [] 293 294 def GetYawPitch(self): 295 now = blue.os.GetSimTime() 296 delta = (now - self.time) / float(const.SEC) 297 self.time = now 298 if self.yaw > 0: 299 self.yaw = max(0, self.yaw - self.DECAY * delta) 300 else: 301 self.yaw = min(0, self.yaw + self.DECAY * delta) 302 if self.pitch > 0: 303 self.pitch = max(0, self.pitch - self.DECAY * delta) 304 else: 305 self.pitch = min(0, self.pitch + self.DECAY * delta) 306 if trinity.app.IsActive() and uicore.registry.GetFocus() == uicore.desktop: 307 if self.KeysAreDown(self.keyUp): 308 self.pitch = max(-self.MAX, self.pitch - (self.ACCEL + self.DECAY) * delta) 309 if self.KeysAreDown(self.keyDown): 310 self.pitch = min(self.MAX, self.pitch + (self.ACCEL + self.DECAY) * delta) 311 if self.KeysAreDown(self.keyRight): 312 self.yaw = max(-self.MAX, self.yaw - (self.ACCEL + self.DECAY) * delta) 313 if self.KeysAreDown(self.keyLeft): 314 self.yaw = min(self.MAX, self.yaw + (self.ACCEL + self.DECAY) * delta) 315 return (self.yaw, self.pitch) 316 317 def KeysAreDown(self, mapping): 318 if mapping and all([ uicore.uilib.Key(key) for key in mapping ]): 319 if all([ not uicore.uilib.Key(key) for key in uiconst.MODKEYS if key not in mapping ]): 320 return True 321 return False 322 323 324 class FlightControlStatistics(object): 325 326 def __init__(self, controls): 327 self.ships = {} 328 self.counters = collections.defaultdict(int) 329 self.thread = None 330 self.controls = controls 331 332 def Start(self): 333 if self.thread is None: 334 self.thread = uthread.new(self.Update) 335 self.thread.context = 'FlightControlStatistics::Update' 336 337 def Stop(self): 338 if self.thread is not None: 339 self.thread.kill() 340 self.thread = None 341 342 def Update(self): 343 while True: 344 with ExceptionEater('FlightControlStatistics::Update'): 345 self.Flush() 346 blue.synchro.Sleep(self.GetInterval()) 347 348 def GetInterval(self): 349 return int(sm.GetService('machoNet').GetGlobalConfig().get('flightControlStatisticsInterval', 300)) * 1000 350 351 def Increment(self): 352 self.counters[session.solarsystemid, session.shipid] += 1 353 self.SaveShip() 354 355 def SaveShip(self): 356 with ExceptionEater('FlightControlStatistics::SaveShip'): 357 if session.shipid and session.shipid not in self.ships: 358 self.ships[session.shipid] = sm.GetService('godma').GetItem(session.shipid).typeID 359 360 def Flush(self): 361 for (solarSystemID, shipID), counter in self.counters.iteritems(): 362 with ExceptionEater('eventLog'): 363 if solarSystemID and shipID and counter: 364 sm.ProxySvc('eventLog').LogClientEvent('flightControls', ['solarSystemID', 365 'shipTypeID', 366 'counter', 367 'keyUp', 368 'keyDown', 369 'keyLeft', 370 'keyRight'], 'Counter', solarSystemID, self.ships.get(shipID), counter, '+'.join([ uicore.cmd.GetKeyNameFromVK(key) for key in self.controls.keyUp ]), '+'.join([ uicore.cmd.GetKeyNameFromVK(key) for key in self.controls.keyDown ]), '+'.join([ uicore.cmd.GetKeyNameFromVK(key) for key in self.controls.keyLeft ]), '+'.join([ uicore.cmd.GetKeyNameFromVK(key) for key in self.controls.keyRight ])) 371 372 self.counters.clear() 373 self.ships.clear()