ballparks.py (60438B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\behaviors\actions\ballparks.py 2 import logging 3 import math 4 from random import choice, random, randint 5 import geo2 6 import evetypes 7 import gametime 8 import uthread2 9 from ballpark.const import DESTINY_MODE_WARP 10 from ballpark.locationgenerator import GetRandomDeepSpaceLocation, GetRandomPlacementNearRandomPlanet 11 from ballpark.messenger.const import MESSAGE_ON_BALL_MODE_CHANGED 12 from ballpark.warpDisruptionBalls import GetWarpBubbleItemIdsWithinRangeOfBall 13 from behaviors import status 14 from behaviors.behaviortree import UnrecoverableBehaviorError 15 from behaviors.blackboards import scopes 16 from behaviors.const.combat import COMBAT_ORBIT_MWD_DISTANCE_FACTOR_SHORT_DISTANCE, COMBAT_ORBIT_MWD_DISTANCE_THRESHOLD 17 from behaviors.const.combat import COMBAT_ORBIT_MWD_DISTANCE_MAXIMUM, COMBAT_ORBIT_COMMANDER_VELOCITY_FACTOR 18 from behaviors.const.combat import COMBAT_ORBIT_MWD_DISTANCE_MINIMUM, COMBAT_ORBIT_MWD_DISTANCE_FACTOR_LONG_DISTANCE 19 from behaviors.entity import NPCWarpError, NpcWarpScrambledError, NpcWarpBubbledError, NpcWarpPathBubbledError 20 from behaviors.exceptions import BehaviorAuthoringException 21 from behaviors.tasks import Task, BlockingTask 22 from behaviors.utility.ballparks import check_ball_released, get_balls_in_bubble, get_ball, is_ball_in_range 23 from behaviors.utility.ballparks import get_ball_position, is_ball_in_park, get_ball_warp_to_position 24 from behaviors.utility.ballparks import get_id_and_types_in_bubble_by_typelist 25 from behaviors.utility.ballparks import get_slim_item 26 from behaviors.utility.dogmatic import get_type_attribute_value 27 from behaviors.utility.inventory import get_inventory_item 28 from behaviors.utility.roles import is_commander, has_combat_role 29 from brennivin.itertoolsext import Bundle 30 from carbon.common.lib.const import SEC 31 from carbon.common.script.util import mathCommon 32 from carbon.common.script.util.mathCommon import RandomVector 33 from ccpProfile import TimedFunction 34 from dogma.const import attributeEntityCruiseSpeed, attributeMaxVelocity 35 from dogma.const import attributeWarpSpeedMultiplier 36 from eve.common.lib.appConst import AU 37 from eve.common.script.sys.idCheckers import IsEvePlayerCharacter 38 from eveexceptions import UserError 39 from eveuniverse.security import SecurityClassFromLevel, securityClassZeroSec 40 from inventorycommon.const import categoryCharge, groupStargate, groupStation 41 from spacecomponents.common.componentConst import NPC_WARP_BEACON, STORE_SLIM_ITEM_FIELD_IN_ITEM_SETTINGS 42 from spacecomponents.common.helper import HasStoreSlimItemFieldInItemSettingsComponent 43 from spacecomponents.server.components.storeslimitemfieldinitemsettings import StoreSlimItemFieldInItemSettings 44 logger = logging.getLogger(__name__) 45 ADDITIONAL_EXPLORATION_GROUPS = (scopes.ScopeTypes.EntityGroup, 'ADDITIONAL_EXPLORATION_GROUPS') 46 ADDITIONAL_EXPLORATION_ACTION = (scopes.ScopeTypes.EntityGroup, 'ADDITIONAL_EXPLORATION_ACTION') 47 ALIGN_START_TIME = (scopes.ScopeTypes.Item, 'ALIGN_START_TIME') 48 ALIGN_DIRECTION_NORMAL = (scopes.ScopeTypes.Item, 'ALIGN_DIRECTION_NORMAL') 49 UNSAFE_EXPLORATION_GROUPS = [groupStargate, groupStation] 50 51 class WarpToNewLocation(BlockingTask): 52 53 @TimedFunction('behaviors::actions::ballparks::WarpToNewLocation::OnEnter') 54 def OnEnter(self): 55 super(WarpToNewLocation, self).OnEnter() 56 self.SetStatusToFailed() 57 coordinate = self.GetDestinationCoordinate() 58 jitterRadius = self.GetJitterRadius() 59 warpAtRange = self.GetWarpinDistance() 60 warpSpeed = self.GetWarpSpeed() 61 if coordinate: 62 try: 63 logger.debug('Behavior entity=%s is starting warp', self.context.myItemId) 64 self.context.myBall.NPCWarpTo(coordinate, jitterRadius=jitterRadius, warpAtRange=warpAtRange, warpSpeed=warpSpeed) 65 self.SubscribeItem(self.context.myItemId, MESSAGE_ON_BALL_MODE_CHANGED, self.DestinyModeChange) 66 self.SendBlackboardValue(self.attributes.warpScrambledAddress, False) 67 self.SetStatusToBlocked() 68 except NpcWarpBubbledError: 69 logger.debug('Behavior entity=%s is warp bubbled and cant warp anywhere', self.context.myItemId) 70 self.SendBlackboardValue(self.attributes.warpScrambledAddress, True) 71 self.context.myBall.NPCEndWarp() 72 except NpcWarpScrambledError: 73 logger.debug('Behavior entity=%s is warp scrambled and cant warp anywhere', self.context.myItemId) 74 self.SendBlackboardValue(self.attributes.warpScrambledAddress, True) 75 self.context.myBall.NPCEndWarp() 76 except NpcWarpPathBubbledError: 77 logger.debug('Behavior entity=%s path is warp bubbled', self.context.myItemId) 78 self.context.myBall.NPCEndWarp() 79 except NPCWarpError as e: 80 logger.debug('Unable to warp: %s', e) 81 try: 82 self.context.myBall.NPCEndWarp() 83 except (UserError, NPCWarpError): 84 raise UnrecoverableBehaviorError('Unable to end after a failed warp. Entity is probably dead.') 85 86 def GetWarpSpeed(self): 87 warpSpeed = self.context.ballpark.dogmaLM.GetAttributeValue(self.context.myItemId, attributeWarpSpeedMultiplier) 88 if not warpSpeed or warpSpeed < 0.01: 89 return 6.0 90 return warpSpeed 91 92 def GetDestinationCoordinate(self): 93 return self.GetLastBlackboardValue(self.attributes.locationMessage) 94 95 def DestinyModeChange(self, oldMode, newMode): 96 if oldMode == DESTINY_MODE_WARP and self.context.ballpark is not None: 97 self.UnsubscribeItem(self.context.myItemId, MESSAGE_ON_BALL_MODE_CHANGED, self.DestinyModeChange) 98 self.SetStatusToSuccess() 99 logger.debug('Behavior entity=%s is exiting warp', self.context.myItemId) 100 101 def CleanUp(self): 102 if not self.IsInvalid(): 103 self.UnsubscribeItem(self.context.myItemId, MESSAGE_ON_BALL_MODE_CHANGED, self.DestinyModeChange) 104 self.SetStatusToInvalid() 105 106 def GetJitterRadius(self): 107 if hasattr(self.attributes, 'jitterRadius') and self.attributes.jitterRadius is not None: 108 return self.attributes.jitterRadius 109 else: 110 return 20000 111 112 def GetWarpinDistance(self): 113 if self.HasAttribute('warpAtDistanceAddress'): 114 valueFromAddress = self.GetLastBlackboardValue(self.attributes.warpAtDistanceAddress) 115 if valueFromAddress is None: 116 logger.debug('Behavior entity=%s has a warp distance address, but the value is None. Defaulting to 0.0', self.context.myItemId) 117 else: 118 return valueFromAddress 119 return 0.0 120 121 122 class WarpToItem(WarpToNewLocation): 123 124 def GetDestinationCoordinate(self): 125 item_id = self.GetLastBlackboardValue(self.attributes.warpToItemIdAddress) 126 if item_id is None: 127 return 128 if not is_ball_in_park(self, item_id): 129 return 130 coordinate = self.context.ballpark.GetWarpDestinationPoint(self.context.myItemId, item_id) 131 return coordinate 132 133 134 class SelectCoordinateNearLocationAction(Task): 135 136 @TimedFunction('behaviors::actions::ballparks::SelectCoordinateNearLocationAction::OnEnter') 137 def OnEnter(self): 138 locationID = self.GetLastBlackboardValue(self.attributes.locationAddress) 139 referenceCoordinates = self.GetReferenceCoordinates(locationID) 140 selectedCoordinates = self.ComputeRandomCoordinate(referenceCoordinates) 141 self.SendBlackboardValue(self.attributes.coordinateAddress, selectedCoordinates) 142 self.status = status.TaskSuccessStatus 143 144 def GetReferenceCoordinates(self, locationID): 145 if locationID is None: 146 loc = (self.context.myBall.x, self.context.myBall.y, self.context.myBall.z) 147 else: 148 loc = self.GetLocation(locationID) 149 return loc 150 151 def ComputeRandomCoordinate(self, locationCoordinates): 152 offset = mathCommon.RandomVector(self.attributes.maxDistance, self.attributes.maxDistance - self.attributes.minDistance) 153 selectedCoordinates = geo2.Vec3AddD(locationCoordinates, offset) 154 return selectedCoordinates 155 156 def GetLocation(self, locationID): 157 loc = cfg.evelocations.Get(locationID) 158 return (loc.x, loc.y, loc.z) 159 160 161 class SelectCoordinateNearCoordinateAction(Task): 162 163 @TimedFunction('behaviors::actions::ballparks::SelectCoordinateNearCoordinateAction::OnEnter') 164 def OnEnter(self): 165 sourcePoint = self._GetSourceCoordinates() 166 selectedCoordinates = self.ComputeRandomCoordinate(sourcePoint or self.context.myBall.GetPosition()) 167 self.SendBlackboardValue(self.attributes.destinationCoordinateAddress, selectedCoordinates) 168 self.status = status.TaskSuccessStatus 169 170 def _GetSourceCoordinates(self): 171 return self.GetLastBlackboardValue(self.attributes.sourceCoordinateAddress) 172 173 def ComputeRandomCoordinate(self, locationCoordinates): 174 offset = mathCommon.RandomVector(self.attributes.maxDistance, self.attributes.maxDistance - self.attributes.minDistance) 175 selectedCoordinates = geo2.Vec3AddD(locationCoordinates, offset) 176 return selectedCoordinates 177 178 179 class SelectCoordinateNearBall(SelectCoordinateNearCoordinateAction): 180 181 def _GetSourceCoordinates(self): 182 ball_id = self.GetLastBlackboardValue(self.attributes.targetBallAddress) 183 ball = get_ball(self, ball_id) 184 return (ball.x, ball.y, ball.z) 185 186 187 class ApproachCoordinatesAction(Task): 188 189 @TimedFunction('behaviors::actions::ballparks::ApproachCoordinatesAction::OnEnter') 190 def OnEnter(self): 191 coordinate = self.GetLastBlackboardValue(self.attributes.coordinateAddress) 192 if coordinate is None: 193 self.status = status.TaskFailureStatus 194 return 195 if self.attributes.blocking: 196 self.context.myBall.GotoPoint(coordinate, notificationRange=self.attributes.notifyRange, callback=(self.OnApproachNotify, ())) 197 self.status = status.TaskSuspendedStatus 198 else: 199 self.context.myBall.GotoPoint(coordinate) 200 self.status = status.TaskSuccessStatus 201 202 def OnApproachNotify(self, entered, *args): 203 logger.debug('OnApproachNotify(%s)', (entered, self.behaviorTree, self.context)) 204 self.context.myBall.StopMovementEvents() 205 if self.status is not status.TaskInvalidStatus: 206 self.status = status.TaskSuccessStatus 207 self.status.OnUpdated(self) 208 209 210 def clampBetween(value, low, high): 211 return max(min(value, high), low) 212 213 214 class AlignToCoordinatesAction(Task): 215 216 @TimedFunction('behaviors::actions::ballparks::AlignToCoordinatesAction::OnEnter') 217 def OnEnter(self): 218 self.SendBlackboardValue(ALIGN_START_TIME, gametime.GetSimTime()) 219 self._MakeSureTargetPointIsFarAway() 220 self.SetStatusToRunning() 221 222 def _MakeSureTargetPointIsFarAway(self): 223 minDist = 30000 224 coordinate = self.GetLastBlackboardValue(self.attributes.coordinateAddress) 225 myPos = self.context.myBall.GetPosition() 226 direction = geo2.Vec3SubtractD(coordinate, myPos) 227 if geo2.Vec3LengthSqD(direction) < minDist * minDist: 228 normalizedDirection = geo2.Vec3NormalizeD(direction) 229 scaledDirection = geo2.Vec3ScaleD(normalizedDirection, minDist) 230 coordinate = geo2.Vec3AddD(myPos, scaledDirection) 231 self.SendBlackboardValue(self.attributes.coordinateAddress, coordinate) 232 233 def _HasTimedOut(self): 234 timeOutSec = getattr(self.attributes, 'timeoutSeconds', 60.0) 235 startTime = self.GetLastBlackboardValue(ALIGN_START_TIME) 236 current_time = gametime.GetSimTime() 237 return current_time - startTime > timeOutSec * SEC 238 239 def _HasSuccessfullyAligned(self, alignDirectionNormal): 240 acceptableDeviation = getattr(self.attributes, 'acceptableAngleDeviationDegrees', 1.0) 241 fighterDirectionNormal = geo2.Vec3NormalizeD((self.context.myBall.vx, self.context.myBall.vy, self.context.myBall.vz)) 242 dotProduct = geo2.Vec3DotD(alignDirectionNormal, fighterDirectionNormal) 243 dotProduct = clampBetween(dotProduct, -1.0, 1.0) 244 angle = math.acos(dotProduct) 245 return math.degrees(angle) < acceptableDeviation 246 247 def _StopMovement(self): 248 self.context.myBall.StopMovement() 249 250 def _UpdateGotoPointAndReturnNormalizedDirection(self): 251 coordinate = self.GetLastBlackboardValue(self.attributes.coordinateAddress) 252 if coordinate is None: 253 self.SetStatusToFailed() 254 return 255 myPos = self.context.myBall.GetPosition() 256 direction = geo2.Vec3SubtractD(coordinate, myPos) 257 normalizedDirection = geo2.Vec3NormalizeD(direction) 258 gotoDistance = 5000.0 259 gotoVector = geo2.Vec3ScaleD(normalizedDirection, gotoDistance) 260 gotoPoint = geo2.Vec3AddD(myPos, gotoVector) 261 self.context.myBall.GotoPoint(gotoPoint) 262 return normalizedDirection 263 264 def Update(self): 265 normalizedDirection = self._UpdateGotoPointAndReturnNormalizedDirection() 266 if self._HasSuccessfullyAligned(normalizedDirection): 267 self.SetStatusToSuccess() 268 self._StopMovement() 269 elif self._HasTimedOut(): 270 self.SetStatusToFailed() 271 self._StopMovement() 272 273 def CleanUp(self): 274 if not self.IsRunning(): 275 self.SetStatusToInvalid() 276 277 278 class ApproachObject(Task): 279 280 @TimedFunction('behaviors::actions::ballparks::ApproachObject::OnEnter') 281 def OnEnter(self): 282 self.SetStatusToFailed() 283 itemId = self.GetLastBlackboardValue(self.attributes.itemIdAddress) 284 if itemId is None: 285 return 286 if not self.context.ballpark.HasBall(itemId): 287 return 288 approachRange = self._GetApproachRange() 289 if self.attributes.blocking: 290 notifyRange = max(self.attributes.notifyRange, approachRange) 291 self.context.myBall.ApproachObject(itemId, notificationRange=notifyRange, approachRange=approachRange, callback=(self.OnApproachNotify, ())) 292 self.SetStatusToSuspended() 293 else: 294 self.context.myBall.ApproachObject(itemId, approachRange=approachRange) 295 self.SetStatusToSuccess() 296 297 def _GetApproachRange(self): 298 if getattr(self.attributes, 'approachRangeAddress', None): 299 return self.GetLastBlackboardValue(self.attributes.approachRangeAddress) 300 return self.attributes.approachRange 301 302 def OnApproachNotify(self, entered, *args): 303 logger.debug('OnApproachNotify(%s)', (entered, self.behaviorTree, self.context)) 304 self.context.myBall.StopMovementEvents() 305 if not self.IsInvalid(): 306 self.SetStatusToSuccess() 307 self.status.OnUpdated(self) 308 309 310 class OrbitAtDistanceAction(Task): 311 312 def OrbitNonBlocking(self, orbitTargetId, orbitRange): 313 self.context.myBall.OrbitObject(orbitTargetId, approachRange=orbitRange) 314 self.status = status.TaskSuccessStatus 315 316 @TimedFunction('behaviors::actions::ballparks::OrbitAtDistanceAction::OnEnter') 317 def OnEnter(self): 318 check_ball_released(self.context.myBall, raises=UnrecoverableBehaviorError) 319 orbitTargetId = self.GetLastBlackboardValue(self.attributes.orbitTargetAddress) 320 if orbitTargetId is None: 321 self.SetStatusToFailed() 322 return 323 if not self.context.ballpark.HasBall(orbitTargetId): 324 self.SetStatusToFailed() 325 return 326 try: 327 orbitRange = self._GetOrbitRange() 328 if self.attributes.blocking: 329 self.OrbitBlocking(orbitTargetId, orbitRange) 330 else: 331 self.OrbitNonBlocking(orbitTargetId, orbitRange) 332 self._set_velocity_fraction() 333 except UserError: 334 self.SetStatusToFailed() 335 336 def _GetOrbitRange(self): 337 if getattr(self.attributes, 'orbitRangeAddress', None): 338 return self.GetLastBlackboardValue(self.attributes.orbitRangeAddress) 339 if getattr(self.attributes, 'orbitRange', None): 340 return self.attributes.orbitRange 341 raise BehaviorAuthoringException('BehaviorID: %s has no orbit range defined for task: %s', self.behaviorTree.GetBehaviorId(), type(self).__name__) 342 343 def OrbitBlocking(self, orbitTargetId, orbitRange): 344 if getattr(self.attributes, 'blockUntilRangeAddress', None): 345 blockUntilRange = self.GetLastBlackboardValue(self.attributes.blockUntilRangeAddress) 346 else: 347 blockUntilRange = self.attributes.blockUntilRange or 1000.0 348 self.context.myBall.OrbitObject(orbitTargetId, approachRange=orbitRange, notificationRange=blockUntilRange, callback=(self.OnInOrbitNotify, ())) 349 self.status = status.TaskSuspendedStatus 350 351 def OnInOrbitNotify(self, entered, *args): 352 logger.debug('OnInOrbitNotify(%s)', (entered, self.behaviorTree, self.context)) 353 self.context.myBall.StopMovementEvents() 354 if not self.IsInvalid(): 355 self.SetStatusToSuccess() 356 self.status.OnUpdated(self) 357 358 def _set_velocity_fraction(self): 359 if self.HasAttribute('velocityFraction'): 360 self.context.ballpark.SetSpeedFraction(self.context.myItemId, self.attributes.velocityFraction) 361 362 def CleanUp(self): 363 if not self.IsInvalid() and self.HasAttribute('velocityFraction'): 364 self.context.ballpark.SetSpeedFraction(self.context.myItemId, 1.0) 365 self.SetStatusToInvalid() 366 367 368 class FullStopAction(Task): 369 370 @TimedFunction('behaviors::actions::ballparks::FullStopAction::OnEnter') 371 def OnEnter(self): 372 self.context.myBall.StopMovement() 373 self.status = status.TaskSuccessStatus 374 375 376 class SelectTargetToAnalyzeAction(Task): 377 378 @TimedFunction('behaviors::actions::ballparks::SelectTargetToAnalyzeAction::OnEnter') 379 def OnEnter(self): 380 logger.debug('SelectTargetToAnalyzeAction starting') 381 ballsInBubble = self.context.ballpark.bubbles.get(self.context.myBall.newBubbleId) 382 self.status = status.TaskFailureStatus 383 if not ballsInBubble: 384 logger.error('No bubbles to pick from') 385 return 386 targetCandidates = self.GetTargetCandidates(ballsInBubble) 387 logger.debug('Target Candidates for Analysis: %s' % targetCandidates) 388 targetId = self.FindSuitableTarget(targetCandidates) 389 if targetId is not None: 390 self.SendBlackboardValue(self.attributes.selectedTargetAddress, targetId) 391 self.status = status.TaskSuccessStatus 392 else: 393 logger.debug('no suitable target found') 394 395 def GetTargetCandidates(self, ballsInBubble): 396 targetCandidates = [] 397 for ballId in ballsInBubble.keys(): 398 if ballId <= 0: 399 continue 400 if self.context.myItemId == ballId: 401 continue 402 targetCandidates.append(ballId) 403 404 return targetCandidates 405 406 def FindSuitableTarget(self, targetCandidates): 407 targetId = None 408 maxDistance = self.attributes.maxDistance 409 GetSurfaceDist = self.context.ballpark.GetSurfaceDist 410 getSlim = self.context.ballpark.slims.get 411 ignoredCategories = self.attributes.ignoredCategories 412 ignoredGroups = self.attributes.ignoredGroups 413 ignoredTypes = self.attributes.ignoredTypes 414 shouldIgnoreMobileTargets = self.attributes.shouldIgnoreMobileTargets 415 while targetId is None and targetCandidates: 416 index = randint(0, len(targetCandidates) - 1) 417 ballId = targetCandidates.pop(index) 418 slimItem = getSlim(ballId) 419 if slimItem is None: 420 continue 421 if slimItem.categoryID in ignoredCategories: 422 continue 423 if slimItem.groupID in ignoredGroups: 424 continue 425 if slimItem.typeID in ignoredTypes: 426 continue 427 ball = self.context.ballpark.GetBall(ballId) 428 if ball.isCloaked: 429 continue 430 if shouldIgnoreMobileTargets and ball.isFree: 431 continue 432 distance = GetSurfaceDist(self.context.myItemId, ballId) 433 if distance and distance <= maxDistance: 434 targetId = ballId 435 436 return targetId 437 438 439 class GetBallWarpToLocation(Task): 440 441 @TimedFunction('behaviors::actions::ballparks::GetBallWarpToLocation::OnEnter') 442 def OnEnter(self): 443 self.SetStatusToFailed() 444 ballId = self.GetLastBlackboardValue(self.attributes.ballIdAddress) 445 if ballId is None: 446 return 447 try: 448 self.context.ballpark.CheckBall(self.context.myItemId) 449 except (UserError, RuntimeError): 450 raise UnrecoverableBehaviorError('Entity no longer in ballpark. Probably dead.') 451 452 position = get_ball_warp_to_position(self, ballId) 453 self.SendBlackboardValue(self.attributes.locationAddress, position) 454 self.SetStatusToSuccess() 455 456 457 class GetItemIdByTypeId(Task): 458 459 @TimedFunction('behaviors::actions::ballparks::GetItemIdByTypeId::OnEnter') 460 def OnEnter(self): 461 self.SetStatusToFailed() 462 if not self.attributes.typeIds: 463 return 464 itemId = self.GetItemIdFromBallpark(self.context.myBall.id, self.attributes.typeIds) 465 if itemId: 466 self.SendBlackboardValue(self.attributes.itemIdAddress, itemId) 467 self.SetStatusToSuccess() 468 469 def GetItemIdFromBallpark(self, myBallId, typeIds): 470 itemId = None 471 getSlim = self.context.ballpark.slims.get 472 itemsInBubble = self.context.ballpark.GetItemsInBubble(None, itemID=myBallId) 473 while itemId is None and itemsInBubble: 474 index = randint(0, len(itemsInBubble) - 1) 475 ballId = itemsInBubble.pop(index) 476 slimItem = getSlim(ballId) 477 if slimItem is None: 478 continue 479 if slimItem.typeID not in typeIds: 480 continue 481 itemId = ballId 482 483 return itemId 484 485 486 class GetItemIdByTypeIdsFromBlackboard(GetItemIdByTypeId): 487 488 @TimedFunction('behaviors::actions::ballparks::GetItemIdByTypeId::OnEnter') 489 def OnEnter(self): 490 self.SetStatusToFailed() 491 if self.attributes.typeIdsAddress is None: 492 return 493 typeIds = self.GetLastBlackboardValue(self.attributes.typeIdsAddress) 494 if not typeIds: 495 return 496 itemId = self.GetItemIdFromBallpark(self.context.myBall.id, typeIds) 497 if itemId: 498 self.SendBlackboardValue(self.attributes.itemIdAddress, itemId) 499 self.SetStatusToSuccess() 500 501 502 class GetRandomDeepSpacePosition(Task): 503 504 @TimedFunction('behaviors::actions::ballparks::GetRandomDeepSpacePosition::OnEnter') 505 def OnEnter(self): 506 self.SetStatusToFailed() 507 location = GetRandomDeepSpaceLocation(self.context.ballpark.solarsystemID, minPlacementDistance=500) 508 if location: 509 self.SendBlackboardValue(self.attributes.locationAddress, location) 510 self.SetStatusToSuccess() 511 512 513 class GetRandomPositionNearRandomPlanet(Task): 514 515 @TimedFunction('behaviors::actions::ballparks::GetRandomPositionNearRandomPlanet::OnEnter') 516 def OnEnter(self): 517 self.SetStatusToFailed() 518 location = GetRandomPlacementNearRandomPlanet(self.context.ballpark.solarsystemID, self.attributes.minDistanceAU * AU, self.attributes.maxDistanceAU * AU) 519 if location: 520 self.SendBlackboardValue(self.attributes.locationAddress, location) 521 self.SetStatusToSuccess() 522 523 524 class TransferToLocation(Task): 525 526 @TimedFunction('behaviors::actions::ballparks::TransferToLocation::OnEnter') 527 def OnEnter(self): 528 self.status = status.TaskFailureStatus 529 location = self.GetLastBlackboardValue(self.attributes.locationAddress) 530 if location: 531 self.context.ballpark.ChangeBallPosition(self.context.myBall.id, *location) 532 self.status = status.TaskSuccessStatus 533 534 535 class GetObjectSpaceLocation(Task): 536 537 @TimedFunction('behaviors::actions::ballparks::GetObjectSpaceLocation::OnEnter') 538 def OnEnter(self): 539 itemId = self.GetLastBlackboardValue(self.attributes.itemIdAddress) 540 if not itemId: 541 self.status = status.TaskFailureStatus 542 return 543 location = self.context.ballpark.GetBallPosition(itemId) 544 self.SendBlackboardValue(self.attributes.locationAddress, location) 545 self.status = status.TaskSuccessStatus 546 547 548 class MakeSelfInvulnerable(Task): 549 550 @TimedFunction('behaviors::actions::ballparks::MakeSelfInvulnerable::OnEnter') 551 def OnEnter(self): 552 self.context.ballpark.MakeInvulnerablePermanently(self.context.myBall.id, self.attributes.reason) 553 self.status = status.TaskSuccessStatus 554 555 556 class CancelInvulnerabilityForSelf(Task): 557 558 @TimedFunction('behaviors::actions::ballparks::CancelInvulnerabilityForSelf::OnEnter') 559 def OnEnter(self): 560 self.context.ballpark.CancelCurrentInvulnerability(self.context.myBall.id, self.attributes.reason) 561 self.status = status.TaskSuccessStatus 562 563 564 class Cloak(Task): 565 566 @TimedFunction('behaviors::actions::ballparks::Cloak::OnEnter') 567 def OnEnter(self): 568 self.context.ballpark.CloakShip(self.context.myItemId) 569 self.status = status.TaskSuccessStatus 570 571 572 class Uncloak(Task): 573 574 @TimedFunction('behaviors::actions::ballparks::Uncloak::OnEnter') 575 def OnEnter(self): 576 self.context.ballpark.UncloakShip(self.context.myItemId) 577 self.status = status.TaskSuccessStatus 578 579 580 class SelectItemByTypes(Task): 581 582 @TimedFunction('behaviors::actions::ballparks::SelectItemByTypes::OnEnter') 583 def OnEnter(self): 584 ballpark = self.context.ballpark 585 bubbleId = self.context.myBall.newBubbleId 586 ballIdsInBubble = ballpark.bubbles.get(bubbleId) 587 self.status = status.TaskFailureStatus 588 if ballIdsInBubble: 589 selectedItem = self.FindItemByType(ballIdsInBubble, ballpark.slims) 590 if selectedItem is not None: 591 self.SendBlackboardValue(self.attributes.selectedItemAddress, selectedItem) 592 self.status = status.TaskSuccessStatus 593 594 def FindItemByType(self, ballIdsInBubble, slims): 595 for ballId in ballIdsInBubble: 596 slim = slims.get(ballId) 597 if slim and slim.typeID in self.attributes.typeIds: 598 return ballId 599 600 601 class SelectItemByGroups(Task): 602 603 @TimedFunction('behaviors::actions::ballparks::SelectItemByTypes::OnEnter') 604 def OnEnter(self): 605 ballpark = self.context.ballpark 606 bubbleId = self.context.myBall.newBubbleId 607 ballIdsInBubble = ballpark.bubbles.get(bubbleId) 608 self.status = status.TaskFailureStatus 609 if ballIdsInBubble: 610 selectedItem = self.FindItemByGroup(ballIdsInBubble, ballpark.slims) 611 if selectedItem is not None: 612 self.SendBlackboardValue(self.attributes.selectedItemAddress, selectedItem) 613 self.status = status.TaskSuccessStatus 614 615 def FindItemByGroup(self, ballIdsInBubble, slims): 616 for ballId in ballIdsInBubble: 617 slim = slims.get(ballId) 618 if slim and slim.groupID in self.attributes.groupIds: 619 return ballId 620 621 622 class StoreSunToBlackboardValue(Task): 623 624 @TimedFunction('behaviors::actions::ballparks::SelectItemByTypes::OnEnter') 625 def OnEnter(self): 626 self.SetStatusToFailed() 627 sunID = self.context.ballpark.sunID 628 if sunID: 629 self.SendBlackboardValue(self.attributes.targetAddress, sunID) 630 self.status = status.TaskSuccessStatus 631 self.SetStatusToSuccess() 632 633 634 class BallNotInParkError(Exception): 635 pass 636 637 638 class SelectClosestPointOnObject(Task): 639 640 @TimedFunction('behaviors::actions::ballparks::SelectClosestPointOnObject::OnEnter') 641 def OnEnter(self): 642 objectId = self.GetLastBlackboardValue(self.attributes.objectIdAddress) 643 try: 644 closestSphere = self.GetClosestSphere(objectId) 645 closestPoint = self.GetClosestSurfacePoint(closestSphere) 646 self.SendBlackboardValue(self.attributes.closestPointAddress, closestPoint) 647 self.status = status.TaskSuccessStatus 648 except KeyError: 649 self.status = status.TaskFailureStatus 650 651 def GetClosestSphere(self, objectId): 652 ball = self.context.ballpark.balls[objectId] 653 pos = (ball.x, ball.y, ball.z) 654 myPos = (self.context.myBall.x, self.context.myBall.y, self.context.myBall.z) 655 if len(ball.miniBalls) == 0: 656 return Bundle(pos=(ball.x, ball.y, ball.z), radius=ball.radius) 657 658 def GetDistance(m): 659 miniPos = (m.x, m.y, m.z) 660 return geo2.Vec3DistanceD(myPos, geo2.Vec3AddD(pos, miniPos)) 661 662 miniBall = sorted(ball.miniBalls, key=GetDistance)[0] 663 return Bundle(pos=geo2.Vec3AddD(pos, (miniBall.x, miniBall.y, miniBall.z)), radius=miniBall.radius) 664 665 def GetClosestSurfacePoint(self, otherSphere): 666 myPos = (self.context.myBall.x, self.context.myBall.y, self.context.myBall.z) 667 vecToOther = geo2.Vec3SubtractD(otherSphere.pos, myPos) 668 distanceToOther = geo2.Vec3LengthD(vecToOther) 669 vecToPoint = geo2.Vec3ScaleD(vecToOther, 1.0 - otherSphere.radius / distanceToOther) 670 return geo2.Vec3AddD(myPos, vecToPoint) 671 672 673 class GetBallsInBubbleByOwnerIds(Task): 674 675 @TimedFunction('behaviors::actions::ballparks::GetBallsInBubbleByOwnerIds::OnEnter') 676 def OnEnter(self): 677 ballIds = self.GetBallsInBubbleForOwnerIds() 678 self.UpdateObjectList(ballIds) 679 self.status = status.TaskSuccessStatus 680 681 def GetBallsInBubbleForOwnerIds(self): 682 shipBallsInBubble = self.context.ballpark.GetShipsInSameBubble(self.context.myItemId) 683 ownerIds = self.GetLastBlackboardValue(self.attributes.ownerListAddress) 684 ballIds = set() 685 for shipBallId in shipBallsInBubble: 686 slim = self.context.ballpark.slims.get(shipBallId) 687 if slim.ownerID in ownerIds: 688 ballIds.add(shipBallId) 689 690 return ballIds 691 692 def UpdateObjectList(self, ballIds): 693 if not ballIds: 694 return 695 existingBallIds = self.GetExistingBallIds() 696 if ballIds.issubset(existingBallIds): 697 return 698 existingBallIds.update(ballIds) 699 self.SendBlackboardValue(self.attributes.objectListAddress, existingBallIds) 700 701 def GetExistingBallIds(self): 702 return self.GetLastBlackboardValue(self.attributes.objectListAddress) or set() 703 704 705 class EntityLootItem(Task): 706 707 @TimedFunction('behaviors::actions::ballparks::EntityLootItem::OnEnter') 708 def OnEnter(self): 709 itemId = self.GetItemForCollecting() 710 if itemId: 711 item = self.context.ballpark.inventory2.GetItem(itemId) 712 self.context.ballpark.RemoveItemFromPark(item) 713 self.AddObjectToLoot(item) 714 self.status = status.TaskSuccessStatus 715 else: 716 self.status = status.TaskFailureStatus 717 718 def GetItemForCollecting(self): 719 return self.GetLastBlackboardValue(self.attributes.itemIdAddress) 720 721 def AddObjectToLoot(self, item): 722 logger.debug('Entity: %s looting typeId: %s & itemId: %s from park.' % (self.context.myBall.id, item.typeID, item.itemID)) 723 self.context.myBall.AddTypeAndAmountToAdditionalLoot(item.typeID) 724 725 def SetChannel(self, context): 726 self.channel = scopes.GetChannelFromAddress(context, self.attributes.itemIdAddress) 727 728 729 class StoreSlimItemFieldInItemSettingsAction(Task): 730 731 @TimedFunction('behaviors::actions::ballparks::StoreSlimItemFieldInItemSettingsAction::OnEnter') 732 def OnEnter(self): 733 targetId = self.GetLastBlackboardValue(self.attributes.targetAddress) 734 if targetId is None: 735 logger.info('StoreSlimItemFieldInItemSettingsAction::Failed to get targetID from blackboard') 736 self.SetStatusToFailed() 737 return 738 targetSlimItem = self.context.ballpark.slims.get(targetId, None) 739 if targetSlimItem is None: 740 logger.info('StoreSlimItemFieldInItemSettingsAction::Ball not in park') 741 self.SetStatusToFailed() 742 return 743 if not HasStoreSlimItemFieldInItemSettingsComponent(targetSlimItem.typeID): 744 logger.error('StoreSlimItemFieldInItemSettingsAction::Trying to set persistent pose for type %s which does not have the persistent pose spacecomponent attached.', targetSlimItem.typeID) 745 self.SetStatusToFailed() 746 return 747 slimItemFieldName = self.GetLastBlackboardValue(self.attributes.slimItemFieldName) 748 if slimItemFieldName is None: 749 logger.info('StoreSlimItemFieldInItemSettingsAction::Failed to get slim item field name from blackboard') 750 self.SetStatusToFailed() 751 return 752 if slimItemFieldName not in StoreSlimItemFieldInItemSettings.GetPersistentSlimItemAttributesForType(targetSlimItem.typeID): 753 logger.info("StoreSlimItemFieldInItemSettingsAction::Can't modify field '%s' as it is not listed as one of the persistent fields on the space component.", slimItemFieldName) 754 self.SetStatusToFailed() 755 return 756 slimItemFieldValue = self.GetLastBlackboardValue(self.attributes.slimItemFieldValue) 757 if slimItemFieldValue is None: 758 logger.info('StoreSlimItemFieldInItemSettingsAction::Failed to get slim item field value from blackboard') 759 self.SetStatusToFailed() 760 return 761 persistentPoseComponent = self.context.ballpark.componentRegistry.GetComponentForItem(targetId, STORE_SLIM_ITEM_FIELD_IN_ITEM_SETTINGS) 762 persistentPoseComponent.SaveSlimItemSetting(self.context.ballpark, slimItemFieldName, slimItemFieldValue) 763 self.SetStatusToSuccess() 764 765 766 class StoreBlackboardToSlimItemAttribute(Task): 767 768 @TimedFunction('behaviors::actions::ballparks::StoreBlackboardToSlimItemAttribute::OnEnter') 769 def OnEnter(self): 770 targetId = self.GetLastBlackboardValue(self.attributes.targetAddress) 771 message = self.GetLastBlackboardValue(self.attributes.messageAddress) 772 self.context.ballpark.UpdateSlimItemField(targetId, self.attributes.attributeName, message) 773 self.SetStatusToSuccess() 774 775 776 class StoreSlimItemAttributeToBlackboard(Task): 777 778 @TimedFunction('behaviors::actions::ballparks::StoreSlimItemAttributeToBlackboard::OnEnter') 779 def OnEnter(self): 780 targetId = self.GetLastBlackboardValue(self.attributes.targetAddress) 781 slimItem = self.context.ballpark.slims.get(targetId) 782 attributeValue = getattr(slimItem, self.attributes.attributeName, None) 783 self.SendBlackboardValue(self.attributes.messageAddress, attributeValue) 784 self.SetStatusToSuccess() 785 786 787 class FindPointToEscapeWarpTo(Task): 788 789 @TimedFunction('behaviors::actions::ballparks::FindPointToEscapeWarpTo::OnEnter') 790 def OnEnter(self): 791 location = self.SelectLocation() 792 if location: 793 self.SendBlackboardValue(self.attributes.locationMessage, location) 794 self.SetStatusToSuccess() 795 else: 796 self.SetStatusToFailed() 797 798 def SelectLocation(self): 799 return GetRandomPlacementNearRandomPlanet(self.context.ballpark.solarsystemID, 1000, 10000) 800 801 802 class FindPointToExplore(Task): 803 804 @TimedFunction('behaviors::actions::ballparks::FindPointToExplore::OnEnter') 805 def OnEnter(self): 806 location = self.SelectLocation() 807 if location: 808 self.SendBlackboardValue(self.attributes.locationMessage, location) 809 self.status = status.TaskSuccessStatus 810 else: 811 self.status = status.TaskFailureStatus 812 813 def SelectLocation(self): 814 generalList = self.GetGeneralList() 815 specificList = self._GetSpecificDungeonEntryPointsOfInterest() 816 if len(specificList) > 0 and (len(generalList) == 0 or random() < self.attributes.pickSpecificChance): 817 result = self._GetPointToExplore(specificList) 818 else: 819 result = self._GetPointToExplore(generalList) 820 return result 821 822 def GetGeneralList(self): 823 distributionOverwrite = self.GetLastBlackboardValue(ADDITIONAL_EXPLORATION_ACTION) 824 if distributionOverwrite: 825 return self._GetExplorationBallsFromDistribution() 826 behavioralList = self._GetBallsOfInterestFromGlobals(self.attributes.groupsOfInterest) 827 distributionList = self._GetExplorationBallsFromDistribution() 828 warpBeaconsInSystem = self._GetWarpBeaconsInSystem() 829 dungeonArchetypeList = self._GetDungeonArchetypeEntryPointsOfInterest() 830 return list(set(behavioralList + distributionList + warpBeaconsInSystem + dungeonArchetypeList)) 831 832 def _GetPointToExplore(self, warpObjectList): 833 if len(warpObjectList) > 0: 834 hasNoValidLocation = True 835 count = 0 836 objectToWarpToo = None 837 while hasNoValidLocation and count < 10: 838 objectToWarpToo = choice(warpObjectList) 839 hasNoValidLocation = self.context.ballpark.InSameBubble(objectToWarpToo.id, self.context.myBall.id) 840 count += 1 841 842 if objectToWarpToo: 843 position = self.context.ballpark.GetWarpDestinationPoint(self.context.myBall.id, objectToWarpToo.id) 844 return position 845 else: 846 return 847 else: 848 return 849 850 def _GetExplorationBallsFromDistribution(self): 851 additionalExplorationGroupIDs = self.GetLastBlackboardValue(ADDITIONAL_EXPLORATION_GROUPS) 852 return self._GetBallsOfInterestFromGlobals(additionalExplorationGroupIDs) 853 854 def _GetBallsOfInterestFromGlobals(self, groups): 855 groups = groups or [] 856 categories = self._get_categories_of_interest() 857 if not groups and not categories: 858 return [] 859 safe_balls_of_interest = [] 860 for ballID, ball in self._get_all_global_balls(): 861 item = get_inventory_item(self, ballID) 862 if self._is_ball_of_interest(item, groups, categories) and self._is_safe_to_visit(item): 863 safe_balls_of_interest.append(ball) 864 865 return safe_balls_of_interest 866 867 def _GetWarpBeaconsInSystem(self): 868 warpBeacons = self.context.ballpark.componentRegistry.GetInstancesWithComponentClass(NPC_WARP_BEACON) 869 warpBeaconBalls = [] 870 for warpBeacon in warpBeacons: 871 warpBeaconBalls.append(self.context.ballpark.GetBall(warpBeacon.itemID)) 872 873 return warpBeaconBalls 874 875 def _GetDungeonArchetypeEntryPointsOfInterest(self): 876 archetypes_of_interest = self._get_archetypes_of_interest() 877 if archetypes_of_interest: 878 if self.attributes.shouldConsiderUnspawnedDungeons: 879 return self.context.keeper.GetInstalledDungeonEntryObjectsOfArchetype(self.context.solarSystemId, archetypes_of_interest) 880 else: 881 return self.context.keeper.GetDungeonEntryObjectsOfArchetype(self.context.solarSystemId, archetypes_of_interest) 882 return [] 883 884 def _GetSpecificDungeonEntryPointsOfInterest(self): 885 dungeons_interest = self._get_dungeons_of_interest() 886 if dungeons_interest: 887 if self.attributes.shouldConsiderUnspawnedDungeons: 888 return self.context.keeper.GetInstalledDungeonEntryObjectsOfDungeonID(self.context.solarSystemId, dungeons_interest) 889 else: 890 return self.context.keeper.GetDungeonEntryObjectsOfDungeonID(self.context.solarSystemId, dungeons_interest) 891 return [] 892 893 def _get_categories_of_interest(self): 894 if self.HasAttribute('categoriesOfInterest'): 895 return self.attributes.categoriesOfInterest 896 return [] 897 898 def _get_archetypes_of_interest(self): 899 if self.HasAttribute('dungeonArchetypesOfInterest'): 900 return self.attributes.dungeonArchetypesOfInterest 901 return [] 902 903 def _get_dungeons_of_interest(self): 904 if self.HasAttribute('dungeonsOfInterest'): 905 return self.attributes.dungeonsOfInterest 906 return [] 907 908 def _get_all_global_balls(self): 909 return self.context.ballpark.globals.iteritems() 910 911 def _is_ball_of_interest(self, item, groups, categories): 912 return item.groupID in groups or item.categoryID in categories 913 914 def _is_safe_to_visit(self, item): 915 if not self._should_avoid_unsafe_locations_in_empire_space(): 916 return True 917 if item.groupID not in UNSAFE_EXPLORATION_GROUPS: 918 return True 919 if IsEvePlayerCharacter(item.ownerID): 920 return True 921 return SecurityClassFromLevel(self.context.ballpark.security) == securityClassZeroSec 922 923 def _should_avoid_unsafe_locations_in_empire_space(self): 924 if not self.HasAttribute('avoidUnsafeLocationsInEmpireSpace'): 925 return False 926 return self.attributes.avoidUnsafeLocationsInEmpireSpace 927 928 929 class GetBallGotoPointAction(Task): 930 931 @TimedFunction('behaviors::actions::ballparks::GetBallGotoPointAction::OnEnter') 932 def OnEnter(self): 933 ballId = self.GetLastBlackboardValue(self.attributes.ballIdAddress) 934 ball = self.context.ballpark.GetBall(ballId) 935 gotoPoint = (ball.gotoX, ball.gotoY, ball.gotoZ) 936 self.SendBlackboardValue(self.attributes.gotoPointAddress, gotoPoint) 937 self.SetStatusToSuccess() 938 939 940 class ManageMicroWarpDriveToTarget(Task): 941 942 def __init__(self, attributes): 943 super(ManageMicroWarpDriveToTarget, self).__init__(attributes) 944 self.microWarpDriveThread = None 945 self.isActive = False 946 947 @TimedFunction('behaviors::actions::ballparks::ManageMicroWarpDriveToTarget::OnEnter') 948 def OnEnter(self): 949 self.SetStatusToFailed() 950 self.targetId = self.GetLastBlackboardValue(self.attributes.targetAddress) 951 if self.targetId is None or not self._IsTargetInSameBubble(): 952 return 953 if self.microWarpDriveThread is None: 954 self.microWarpDriveThread = uthread2.start_tasklet(self._ManageMicroWarpDrive) 955 self.SetStatusToSuccess() 956 957 def _IsTargetInSameBubble(self): 958 return self.context.ballpark.InSameBubble(self.context.myItemId, self.targetId) 959 960 def _ManageMicroWarpDrive(self): 961 while not self.IsInvalid() and self._AreBallsStillThere(): 962 shouldMicroWarpDrive = self._ShouldMicroWarpDrive() 963 if self.isActive != shouldMicroWarpDrive: 964 self._SetMovementSpeed(shouldMicroWarpDrive) 965 uthread2.SleepSim(self.attributes.microWarpDriveDuration / 1000) 966 967 self.microWarpDriveThread = None 968 969 def _AreBallsStillThere(self): 970 if not self.context.ballpark.HasBall(self.targetId): 971 return False 972 if not self.context.ballpark.HasBall(self.context.myItemId): 973 return False 974 if self.context.ballpark.IsCloaked(self.targetId): 975 return False 976 return True 977 978 def _ShouldMicroWarpDrive(self): 979 if not self._IsOutsideMicroWarpDriveDistance(): 980 return False 981 return random() < self.attributes.microWarpDriveChance 982 983 def _IsOutsideMicroWarpDriveDistance(self): 984 distance = self.context.ballpark.DistanceBetween(self.context.myItemId, self.targetId) 985 microWarpdriveDistance = self._GetMicroWarpDriveDistance() 986 return distance >= microWarpdriveDistance 987 988 def _GetMicroWarpDriveDistance(self): 989 if self.HasAttribute('microwarpdriveRangeAddress'): 990 return self.GetLastBlackboardValue(self.attributes.microwarpdriveRangeAddress) 991 else: 992 orbitDistance = self.GetLastBlackboardValue(self.attributes.orbitRangeAddress) 993 if orbitDistance < COMBAT_ORBIT_MWD_DISTANCE_THRESHOLD: 994 microWarpdriveDistance = orbitDistance * COMBAT_ORBIT_MWD_DISTANCE_FACTOR_SHORT_DISTANCE 995 return max(microWarpdriveDistance, COMBAT_ORBIT_MWD_DISTANCE_MINIMUM) 996 microWarpdriveDistance = orbitDistance * COMBAT_ORBIT_MWD_DISTANCE_FACTOR_LONG_DISTANCE 997 return min(microWarpdriveDistance, COMBAT_ORBIT_MWD_DISTANCE_MAXIMUM) 998 999 def _SetMovementSpeed(self, shouldMicroWarpDrive): 1000 if shouldMicroWarpDrive: 1001 self.context.myBall.SetMovementSpeed(cruise=False) 1002 else: 1003 self.context.myBall.SetMovementSpeed(cruise=True) 1004 self.isActive = shouldMicroWarpDrive 1005 1006 def CleanUp(self): 1007 if not self.IsInvalid(): 1008 self.isActive = False 1009 self._SetMovementSpeed(False) 1010 self.SetStatusToInvalid() 1011 1012 1013 class ManageMicroWarpDriveToCoordinates(ManageMicroWarpDriveToTarget): 1014 1015 @TimedFunction('behaviors::actions::ballparks::ManageMicroWarpDriveToCoordinates::OnEnter') 1016 def OnEnter(self): 1017 self.SetStatusToFailed() 1018 self.coordinates = self.GetLastBlackboardValue(self.attributes.coordinateAddress) 1019 if self.coordinates is None or not self._IsTargetInSameBubble(): 1020 return 1021 if self.microWarpDriveThread is None: 1022 self.microWarpDriveThread = uthread2.start_tasklet(self._ManageMicroWarpDrive) 1023 self.SetStatusToSuccess() 1024 1025 def _IsTargetInSameBubble(self): 1026 myBubbleId = self.context.myBall.newBubbleId 1027 bubbleIdAtCoordinates = self.context.ballpark.GetBubbleAtCoordinates(*self.coordinates) 1028 return myBubbleId == bubbleIdAtCoordinates 1029 1030 def _AreBallsStillThere(self): 1031 return self.context.ballpark.HasBall(self.context.myItemId) 1032 1033 def _IsOutsideMicroWarpDriveDistance(self): 1034 ballPosition = (self.context.myBall.x, self.context.myBall.y, self.context.myBall.z) 1035 distance = geo2.Vec3DistanceD(ballPosition, self.coordinates) 1036 microWarpdriveDistance = self._GetMicroWarpDriveDistance() 1037 return distance >= microWarpdriveDistance 1038 1039 1040 class ModifyOrbitVelocity(Task): 1041 1042 @TimedFunction('behaviors::actions::ballparks::ModifyOrbitVelocity::OnEnter') 1043 def OnEnter(self): 1044 self.SetStatusToFailed() 1045 self.orbitVelocity = self.attributes.orbitVelocity 1046 customVelocity = self._GetCustomOrbitVelocity() 1047 if not customVelocity: 1048 entityVelocity = self.context.myBall.GetEntityVelocity() 1049 entityVelocity.set_custom_orbit_velocity(customVelocity) 1050 self.SetStatusToSuccess() 1051 1052 def _GetCustomOrbitVelocity(self): 1053 if is_commander(self, self.context.myItemId): 1054 slowestVelocityOfGroup = self._GetSlowestOrbitVelocityOfGroup() 1055 if slowestVelocityOfGroup: 1056 return slowestVelocityOfGroup * COMBAT_ORBIT_COMMANDER_VELOCITY_FACTOR 1057 raise BehaviorAuthoringException('Behavior %d received None or 0 as slowest combat velocity' % self.behaviorTree.GetBehaviorId()) 1058 return self.attributes.orbitVelocity 1059 1060 def _GetSlowestOrbitVelocityOfGroup(self): 1061 slowestVelocityForGroup = None 1062 group = self.context.entityLocation.GetGroupManager().GetGroup(self.context.myEntityGroupId) 1063 for entityId in group.GetGroupMembers(): 1064 entityItem = self.context.ballpark.inventory2.GetItem(entityId) 1065 if not has_combat_role(self, entityItem.itemID): 1066 continue 1067 entityTypeVelocity = self._GetOrbitVelocityForType(entityItem) 1068 if entityTypeVelocity and (slowestVelocityForGroup is None or entityTypeVelocity < slowestVelocityForGroup): 1069 slowestVelocityForGroup = entityTypeVelocity 1070 1071 return slowestVelocityForGroup 1072 1073 def _GetOrbitVelocityForType(self, entityItem): 1074 entityTypeCruiseSpeed = get_type_attribute_value(self, entityItem.typeID, attributeEntityCruiseSpeed) 1075 entityTypeMaxVelocity = get_type_attribute_value(self, entityItem.typeID, attributeMaxVelocity) 1076 return entityTypeCruiseSpeed or entityTypeMaxVelocity 1077 1078 def CleanUp(self): 1079 if not self.IsInvalid(): 1080 if self.orbitVelocity is not None: 1081 entityVelocity = self.context.myBall.GetEntityVelocity() 1082 entityVelocity.remove_custom_orbit_velocity() 1083 self.SetStatusToInvalid() 1084 1085 1086 class SelectClosestItemAndTypeByEveTypeList(Task): 1087 1088 @TimedFunction('behaviors::actions::ballparks::SelectClosestItemAndTypeByEveTypeList::OnEnter') 1089 def OnEnter(self): 1090 self.SetStatusToFailed() 1091 logger.debug('SelectClosestItemAndTypeByEveTypeList starting') 1092 if self.HasValidTarget(): 1093 self.SetStatusToSuccess() 1094 return 1095 closestItemId, closestTypeId = self.GetClosestItemAndTypeOfInterest() 1096 if closestItemId is not None: 1097 self._update_blackboards(closestItemId, closestTypeId) 1098 self.SetStatusToSuccess() 1099 else: 1100 logger.debug('no suitable target found') 1101 1102 def GetClosestItemAndTypeOfInterest(self): 1103 ballsOfInterest = get_id_and_types_in_bubble_by_typelist(self.context.ballpark, self.context.myBall.newBubbleId, self.attributes.eveTypeListId) 1104 closestItemId = None 1105 closestTypeId = None 1106 closestDistance = 0 1107 for ballId, typeId in ballsOfInterest: 1108 distanceToBall = self.GetDistanceToBall(ballId) 1109 if not closestItemId or distanceToBall < closestDistance: 1110 closestItemId = ballId 1111 closestTypeId = typeId 1112 closestDistance = distanceToBall 1113 1114 return (closestItemId, closestTypeId) 1115 1116 def GetDistanceToBall(self, ballId): 1117 return self.context.ballpark.DistanceBetween(self.context.myBall.id, ballId) 1118 1119 def HasValidTarget(self): 1120 targetId = self.GetLastBlackboardValue(self.attributes.selectedTargetAddress) 1121 if not targetId or not self.context.ballpark.HasBall(targetId): 1122 return False 1123 targetBall = self.context.ballpark.GetBall(targetId) 1124 return targetBall.newBubbleId == self.context.myBall.newBubbleId 1125 1126 def _update_blackboards(self, closestItemId, closestTypeId): 1127 self.SendBlackboardValue(self.attributes.selectedTargetAddress, closestItemId) 1128 self.SendBlackboardValue(self.attributes.selectedTypeAddress, closestTypeId) 1129 1130 1131 class SelectClosestItemByEveTypeList(SelectClosestItemAndTypeByEveTypeList): 1132 1133 def _update_blackboards(self, closestItemId, _): 1134 self.SendBlackboardValue(self.attributes.selectedTargetAddress, closestItemId) 1135 1136 1137 class GetBallLocation(Task): 1138 1139 @TimedFunction('behaviors::actions::ballparks::GetBallLocation::OnEnter') 1140 def OnEnter(self): 1141 self.SetStatusToFailed() 1142 targetBallId = self.GetLastBlackboardValue(self.attributes.ballIdAddress) 1143 if targetBallId and is_ball_in_park(self, targetBallId): 1144 targetBall = get_ball(self, targetBallId) 1145 targetCoordinates = (targetBall.x, targetBall.y, targetBall.z) 1146 self.SendBlackboardValue(self.attributes.coordinateAddress, targetCoordinates) 1147 self.SetStatusToSuccess() 1148 1149 1150 class PostOwnPosition(Task): 1151 1152 @TimedFunction('behaviors::actions::ballparks::PostOwnPosition::OnEnter') 1153 def OnEnter(self): 1154 position = (self.context.myBall.x, self.context.myBall.y, self.context.myBall.z) 1155 self.SendBlackboardValue(self.attributes.coordinateAddress, position) 1156 self.SetStatusToSuccess() 1157 1158 1159 class GotoPosition(Task): 1160 1161 @TimedFunction('behaviors::actions::ballparks::GotoPosition::OnEnter') 1162 def OnEnter(self): 1163 position = self.GetLastBlackboardValue(self.attributes.coordinateAddress) 1164 self.context.ballpark.GotoPoint(self.context.myItemId, *position) 1165 self.SetStatusToSuccess() 1166 1167 1168 class GetItemIdsInBubbleWithinRange(Task): 1169 1170 @TimedFunction('behaviors::actions::ballparks::GetItemIdsInBubbleWithinRange::OnEnter') 1171 def OnEnter(self): 1172 self.SetStatusToSuccess() 1173 center_ball_id = self._get_center_ball_id() 1174 if center_ball_id is None: 1175 self.SetStatusToFailed() 1176 return 1177 center_ball = get_ball(self, center_ball_id) 1178 balls_in_bubble = get_balls_in_bubble(self, center_ball.newBubbleId) 1179 balls_of_interest = self._get_balls_of_interest(center_ball_id, balls_in_bubble) 1180 self.SendBlackboardValue(self.attributes.ballsWithinRangeAddress, balls_of_interest) 1181 1182 def _get_center_ball_id(self): 1183 if self.HasAttribute('centerBallAddress'): 1184 return self.GetLastBlackboardValue(self.attributes.centerBallAddress) 1185 return self.context.myItemId 1186 1187 def _get_balls_of_interest(self, center_ball_id, balls_in_bubble): 1188 balls_of_interest = [] 1189 for ball_id in balls_in_bubble.keys(): 1190 slim_item = get_slim_item(self, ball_id) 1191 if not slim_item: 1192 continue 1193 if any((filter_function(slim_item) for filter_function in self._get_filter_functions())): 1194 if self._is_ball_within_range(center_ball_id, ball_id): 1195 balls_of_interest.append(ball_id) 1196 1197 return balls_of_interest 1198 1199 def _get_filter_functions(self): 1200 return [self._is_ball_of_category_interest, self._is_ball_of_group_interest] 1201 1202 def _is_ball_of_category_interest(self, slim_item): 1203 categories_of_interest = getattr(self.attributes, 'categoriesOfInterest', []) 1204 return slim_item.categoryID in categories_of_interest 1205 1206 def _is_ball_of_group_interest(self, slim_item): 1207 groups_of_interest = getattr(self.attributes, 'groupsOfInterest', []) 1208 return slim_item.groupID in groups_of_interest 1209 1210 def _is_ball_within_range(self, center_ball_id, target_ball_id): 1211 if self.HasAttribute('maxDistanceAddress'): 1212 max_distance = self.GetLastBlackboardValue(self.attributes.maxDistanceAddress) 1213 elif self.HasAttribute('maxDistance'): 1214 max_distance = self.attributes.maxDistance 1215 else: 1216 raise BehaviorAuthoringException('Behavior Task: GetItemIdsInBubbleWithinRange is missing maxDistance for behavior=%s', self.behaviorTree.GetBehaviorId()) 1217 return is_ball_in_range(self, center_ball_id, target_ball_id, max_distance) 1218 1219 1220 class GetWarpBubbleItemIdsBlockingMeFromWarp(Task): 1221 1222 @TimedFunction('behaviors::actions::ballparks::GetWarpBubbleItemIdsBlockingMeFromWarp::OnEnter') 1223 def OnEnter(self): 1224 self.SetStatusToSuccess() 1225 ship_ids = self._get_warp_bubble_owner_ship_ids() 1226 logger.debug('Behavior: GetWarpBubbleItemIdsBlockingMeFromWarp found targets=%s', ship_ids) 1227 self.SendBlackboardValue(self.attributes.itemIdSetAddress, ship_ids) 1228 1229 def _get_warp_bubble_owner_ship_ids(self): 1230 warp_disrupt_probe_ids, warp_disrupt_ship_ids = self._get_blocking_warp_bubble_item_ids() 1231 warp_disrupt_ship_ids.extend(self._get_targets_for_warp_disrupt_probes(warp_disrupt_probe_ids)) 1232 return warp_disrupt_ship_ids 1233 1234 def _get_blocking_warp_bubble_item_ids(self): 1235 return GetWarpBubbleItemIdsWithinRangeOfBall(self.context.ballpark, self.context.myBall.id) 1236 1237 def _get_targets_for_warp_disrupt_probes(self, warp_disrupt_probe_ids): 1238 target_ids_of_interest = [] 1239 for probe_id in warp_disrupt_probe_ids: 1240 source_id = self._get_targetable_source_for_probe(probe_id) 1241 if source_id is not None: 1242 target_ids_of_interest.append(source_id) 1243 1244 return target_ids_of_interest 1245 1246 def _get_targetable_source_for_probe(self, probe_id): 1247 probe_category_id = get_inventory_item(self, probe_id).categoryID 1248 if probe_category_id != categoryCharge: 1249 return probe_id 1250 return get_slim_item(self, probe_id).sourceShipID 1251 1252 1253 class SelectCoordinatesFromDirectionIntoDistance(Task): 1254 1255 def OnEnter(self): 1256 self.SetStatusToSuccess() 1257 coordinates = self._get_coordinates() 1258 self.SendBlackboardValue(self.attributes.coordinatesAddress, coordinates) 1259 1260 def _get_coordinates(self): 1261 direction = self._get_direction() 1262 coordinates = self._get_coordinates_based_on_direction_into_distance(direction) 1263 return coordinates 1264 1265 def _get_direction(self): 1266 direction = self.GetLastBlackboardValue(self.attributes.directionAddress) 1267 return geo2.Vec3NormalizeD(direction) 1268 1269 def _get_coordinates_based_on_direction_into_distance(self, direction): 1270 my_ball = get_ball(self, self.context.myItemId) 1271 coordinates = geo2.Vec3ScaleD(direction, self.attributes.distance) 1272 return [my_ball.x + coordinates[0], my_ball.y + coordinates[1], my_ball.z + coordinates[2]] 1273 1274 1275 class GetCoordinatesReducedByDistance(Task): 1276 1277 def OnEnter(self): 1278 self.SetStatusToFailed() 1279 coordinates = self.GetLastBlackboardValue(self.attributes.sourceCoordinateAddress) 1280 my_position = get_ball_position(self, self.context.myItemId) 1281 if self.HasAttribute('distanceAddress'): 1282 distance = self.GetLastBlackboardValue(self.attributes.distanceAddress) 1283 if distance: 1284 direction = geo2.Vec3DirectionD(my_position, coordinates) 1285 coordinates = geo2.Vec3AddD(coordinates, geo2.Vec3ScaleD(direction, distance)) 1286 self.SendBlackboardValue(self.attributes.destinationCoordinateAddress, coordinates) 1287 self.SetStatusToSuccess() 1288 1289 1290 class FlyIntoRandomDirection(Task): 1291 1292 def OnEnter(self): 1293 self.context.ballpark.GotoDirection(self.context.myItemId, *RandomVector()) 1294 self.context.ballpark.SetSpeedFraction(self.context.myItemId, self.attributes.velocityFraction) 1295 self.SetStatusToSuspended() 1296 1297 def CleanUp(self): 1298 if not self.IsInvalid(): 1299 self.context.ballpark.SetSpeedFraction(self.context.myItemId, 1.0) 1300 self.SetStatusToInvalid() 1301 1302 1303 class FindSpecificItemLocation(Task): 1304 1305 def OnEnter(self): 1306 location = self.select_location() 1307 if location: 1308 self.SendBlackboardValue(self.attributes.locationMessageAddress, location) 1309 self.SetStatusToSuccess() 1310 else: 1311 self.SetStatusToFailed() 1312 1313 def select_location(self): 1314 specific_slims = self._get_slims_by_typelist() 1315 if len(specific_slims) > 0: 1316 chosen_slim_item = choice(specific_slims) 1317 self.SendBlackboardValue(self.attributes.chosenItemOfInterestAddress, chosen_slim_item) 1318 coordinates = self.context.ballpark.GetWarpDestinationPoint(self.context.myItemId, chosen_slim_item) 1319 return coordinates 1320 else: 1321 return None 1322 1323 def _get_slims_by_typelist(self): 1324 slims_list = [] 1325 if self.attributes.specificInterestTypelist is not None: 1326 type_ids = evetypes.GetTypeIDsByListID(self.attributes.specificInterestTypelist) 1327 for slim_item in self.context.ballpark.slims.itervalues(): 1328 if slim_item.typeID in type_ids: 1329 slims_list.append(slim_item.itemID) 1330 1331 self.SendBlackboardValue(self.attributes.itemsOfInterestAddress, slims_list) 1332 return slims_list 1333 1334 1335 class SelectAllItemIdsInBubbleByTypeList(Task): 1336 1337 @TimedFunction('behaviors::actions::ballparks::SelectAllItemIdInBubbleByEveTypeList::OnEnter') 1338 def OnEnter(self): 1339 balls_of_interest = get_id_and_types_in_bubble_by_typelist(self.context.ballpark, self.context.myBall.newBubbleId, self.attributes.typeListId) 1340 item_ids = [ item_id for item_id, type_id in balls_of_interest ] 1341 self.SendBlackboardValue(self.attributes.selectedTargetsAddress, item_ids) 1342 self.SetStatusToSuccess() 1343 1344 1345 class CountItemsInBubbleByTypeList(Task): 1346 1347 @TimedFunction('behaviors::actions::ballparks::SelectAllItemIdInBubbleByEveTypeList::OnEnter') 1348 def OnEnter(self): 1349 balls_of_interest = get_id_and_types_in_bubble_by_typelist(self.context.ballpark, self.context.myBall.newBubbleId, self.attributes.typeListId) 1350 self.SendBlackboardValue(self.attributes.itemCountAddress, len(balls_of_interest)) 1351 self.SetStatusToSuccess() 1352 1353 1354 class TransferItemToLocation(Task): 1355 1356 @TimedFunction('behaviors::actions::ballparks::TransferItemToLocation::OnEnter') 1357 def OnEnter(self): 1358 self.status = status.TaskFailureStatus 1359 item_id = self.GetLastBlackboardValue(self.attributes.itemIdAddress) 1360 location = self.GetLastBlackboardValue(self.attributes.locationAddress) 1361 if location and item_id: 1362 self.context.ballpark.ChangeBallPosition(item_id, *location) 1363 self.status = status.TaskSuccessStatus