def step(self, gamestate, smashbot_state, opponent_state): self._propagate = (gamestate, smashbot_state, opponent_state) # -1 means auto-adjust difficulty based on stocks remaining if self.set_difficulty == -1: self.difficulty = smashbot_state.stock else: self.difficulty = self.set_difficulty if SelfDestruct.shouldsd(gamestate, smashbot_state, opponent_state): self.picktactic(Tactics.SelfDestruct) return # Reset the approach state after 1 second # Or if opponent becomes invulnerable if self.approach and ((abs(self.approach_frame - gamestate.frame) > 60) or (opponent_state.invulnerability_left > 0)): self.approach_frame = -123 self.approach = False # Randomly approach sometimes rather than keeping distance # Should happen on average once per 2 seconds # The effect will last for about 1 second # On the first two difficulties, just always approach if (random.randint(0, 120) == 0 or (self.difficulty >= 4 and opponent_state.action != Action.CROUCHING )) and (opponent_state.invulnerability_left == 0): self.approach = True self.approach_frame = gamestate.frame if self.logger: self.logger.log("Notes", " approach: " + str(self.approach) + " ", concat=True) if Mitigate.needsmitigation(smashbot_state): self.picktactic(Tactics.Mitigate) return if self.tactic and not self.tactic.isinteruptible(): self.tactic.step(gamestate, smashbot_state, opponent_state) return # If we're stuck in a lag state, just do nothing. Trying an action might just # buffer an input we don't want if Wait.shouldwait(gamestate, smashbot_state, opponent_state, self.framedata): self.picktactic(Tactics.Wait) return if Recover.needsrecovery(smashbot_state, opponent_state, gamestate): self.picktactic(Tactics.Recover) return if Celebrate.deservescelebration(smashbot_state, opponent_state): self.picktactic(Tactics.Celebrate) return # Difficulty 5 is a debug / training mode # Don't do any attacks, and don't do any shielding # Take attacks, DI, and recover if self.difficulty == 5: self.picktactic(Tactics.KeepDistance) return if Defend.needsprojectiledefense(smashbot_state, opponent_state, gamestate, self.logger): self.picktactic(Tactics.Defend) return # If we can infinite our opponent, do that! if Infinite.caninfinite(smashbot_state, opponent_state, gamestate, self.framedata, self.difficulty): self.picktactic(Tactics.Infinite) return # If we can juggle opponent in the air, do that if Juggle.canjuggle(smashbot_state, opponent_state, gamestate, self.framedata, self.difficulty): self.picktactic(Tactics.Juggle) return # If we can punish our opponent for a laggy move, let's do that if Punish.canpunish(smashbot_state, opponent_state, gamestate, self.framedata): self.picktactic(Tactics.Punish) return # Do we need to defend an attack? if Defend.needsdefense(smashbot_state, opponent_state, gamestate, self.framedata): self.picktactic(Tactics.Defend) return # Can we edge guard them? if Edgeguard.canedgeguard(smashbot_state, opponent_state, gamestate): self.picktactic(Tactics.Edgeguard) return # Can we shield pressure them? if Pressure.canpressure(opponent_state, gamestate): self.picktactic(Tactics.Pressure) return if Retreat.shouldretreat(smashbot_state, opponent_state, gamestate, not self.approach): self.picktactic(Tactics.Retreat) return if Challenge.canchallenge(smashbot_state, opponent_state, gamestate, self.framedata, self.difficulty): self.picktactic(Tactics.Challenge) return if Approach.shouldapproach(smashbot_state, opponent_state, gamestate, self.framedata, self.logger) or \ (self.approach and not Approach.approach_too_dangerous(smashbot_state, opponent_state, gamestate, self.framedata)): self.picktactic(Tactics.Approach) return self.picktactic(Tactics.KeepDistance)
def step(self, gamestate, smashbot_state, opponent_state): self._propagate = (gamestate, smashbot_state, opponent_state) recoverhigh = self.canrecoverhigh(gamestate, opponent_state) #If we can't interrupt the chain, just continue it if self.chain != None and not self.chain.interruptible: self.chain.step(gamestate, smashbot_state, opponent_state) return if Dropdownshine.inrange(smashbot_state, opponent_state, self.framedata): self.pickchain(Chains.Dropdownshine) return if smashbot_state.action == Action.EDGE_CATCHING: self.pickchain(Chains.Nothing) return # How many frames will it take to get to our opponent right now? onedge = smashbot_state.action in [ Action.EDGE_HANGING, Action.EDGE_CATCHING ] # Stand up if opponent attacks us proj_incoming = Defend.needsprojectiledefense( smashbot_state, opponent_state, gamestate) and smashbot_state.invulnerability_left <= 2 samusgrapple = opponent_state.character == Character.SAMUS and opponent_state.action == Action.SWORD_DANCE_4_LOW and \ -25 < opponent_state.position.y < 0 and smashbot_state.invulnerability_left <= 2 hitframe = self.framedata.in_range(opponent_state, smashbot_state, gamestate.stage) framesleft = hitframe - opponent_state.action_frame if proj_incoming or samusgrapple or ( hitframe != 0 and onedge and framesleft < 5 and smashbot_state.invulnerability_left < 2): # Unless the attack is a grab, then don't bother if not self.framedata.is_grab(opponent_state.character, opponent_state.action): if self.isupb(opponent_state): #TODO: Make this a chain self.chain = None self.controller.press_button(Button.BUTTON_L) return else: self.chain = None self.pickchain(Chains.DI, [0.5, 0.65]) return # For pikachu, we want to be up on the stage to edgeguard. Not on edge if opponent_state.character == Character.PIKACHU and smashbot_state.action == Action.EDGE_HANGING and smashbot_state.invulnerability_left == 0: if opponent_state.position.y < -20: self.chain = None self.pickchain(Chains.Edgedash, [False]) return # Special exception for Fox/Falco illusion # Since it is dumb and technically a projectile if opponent_state.character in [Character.FOX, Character.FALCO]: if opponent_state.action in [Action.SWORD_DANCE_2_MID]: self.chain = None self.pickchain(Chains.DI, [0.5, 0.65]) return # What recovery options does opponent have? landonstage = False grabedge = False # they have to commit to an up-b to recover mustupb = False canrecover = True djheight = self.framedata.dj_height(opponent_state) edgegrabframes = self.snaptoedgeframes(gamestate, opponent_state) # How heigh can they go with a jump? potentialheight = djheight + opponent_state.position.y if potentialheight < -23: mustupb = True # Now consider UP-B # Have they already UP-B'd? if self.isupb(opponent_state): if self.upbstart == 0: self.upbstart = opponent_state.position.y # If they are halfway through the up-b, then subtract out what they've alrady used potentialheight = self.upbheight(opponent_state) + self.upbstart elif opponent_state.action == Action.DEAD_FALL: potentialheight = opponent_state.position.y else: potentialheight += self.upbheight(opponent_state) # Cpt Falcon's up-b causes him to distort his model by a crazy amount. Giving him # the ability to get on the stage easier. Adjust for this adjustedheight = potentialheight if opponent_state.character == Character.CPTFALCON and self.isupb( opponent_state): adjustedheight += 12 # Adjust upwards a little to have some wiggle room if adjustedheight > -5: landonstage = True if potentialheight > -23: grabedge = True if potentialheight < -30: mustupb = True canrecover = False # Split the logic into two: # A) We are on the edge # B) We are on the stage if smashbot_state.action in [ Action.EDGE_HANGING, Action.EDGE_CATCHING ]: # If opponent can't recover, then just get onto the stage! if not canrecover: #TODO: Make this a chain self.chain = None self.controller.press_button(Button.BUTTON_L) return # Don't roll up too early for Falcon falconupearly = opponent_state.character == Character.CPTFALCON and \ opponent_state.action == Action.SWORD_DANCE_3_LOW and opponent_state.action_frame <= 12 # Roll up to edgehog if self.isupb( opponent_state) and not landonstage and not falconupearly: #TODO: Make this a chain self.chain = None self.controller.press_button(Button.BUTTON_L) return # Challenge rising UP-B's with a shine if we're in range # except for pikachu and falcon/ganon if self.isupb( opponent_state ) and opponent_state.speed_y_self >= 0 and gamestate.distance < 10: if opponent_state.character not in [ Character.PIKACHU, Character.GANONDORF, Character.CPTFALCON ]: self.pickchain(Chains.Dropdownshine) return # Edgestall # For Fox and Falco, we have a different edgestall strategy. Only edgestall if they start a FireFox if opponent_state.character in [Character.FOX, Character.FALCO]: # Are they in the start of a firefox? # But make sure they can't grab the edge in the middle of it edgedistance = abs(opponent_state.position.x) - ( melee.stages.EDGE_GROUND_POSITION[gamestate.stage] + 15) in_immediate_range = (-5 > opponent_state.position.y > -23) and (edgedistance < 15) in_fly_range = opponent_state.action_frame > ( (edgedistance - 15) / 3) if opponent_state.action == Action.SWORD_DANCE_3_LOW and not in_fly_range and not in_immediate_range: self.pickchain(Chains.Edgestall) return # We must be on the first frame, or else it's dangerous elif smashbot_state.action == Action.EDGE_HANGING and smashbot_state.action_frame == 1: if edgegrabframes > 29 and smashbot_state.invulnerability_left >= 29: self.pickchain(Chains.Edgestall) return # We are in danger of being attacked! # It's unsafe to be in shine range of opponent. We can't react to the attack! if gamestate.distance < 11.8 and opponent_state.character in [Character.FOX, Character.FALCO, Character.JIGGLYPUFF] and \ smashbot_state.invulnerability_left <= 1: # If we can, challenge their shine at the edge if self.difficulty >= 3 and edgegrabframes > 2: if Dropdownshine.inrange(smashbot_state, opponent_state, self.framedata): self.pickchain(Chains.Dropdownshine) return self.chain = None self.pickchain(Chains.DI, [0.5, 0.65]) return framesleft = Punish.framesleft(opponent_state, self.framedata, smashbot_state) # Samus UP_B invulnerability samusupbinvuln = opponent_state.action in [Action.SWORD_DANCE_3_MID, Action.SWORD_DANCE_3_LOW] and \ opponent_state.character == Character.SAMUS and opponent_state.action_frame <= 5 # Shine them, as long as they aren't attacking right now frameadvantage = framesleft > 2 or smashbot_state.invulnerability_left > 2 if gamestate.distance < 11.8 and edgegrabframes > 2 and frameadvantage and not samusupbinvuln: if Dropdownshine.inrange(smashbot_state, opponent_state, self.framedata): self.pickchain(Chains.Dropdownshine) return # Illusion high if self.illusionhighframes(gamestate, opponent_state) <= 5: if smashbot_state.invulnerability_left > 7: self.pickchain(Chains.Edgebair) return # If opponent is recovering high with illusion, bair them at the right time if (opponent_state.character == Character.FOX and opponent_state.action_frame == 15) or (opponent_state.character == Character.FALCO and opponent_state.action_frame == 10): if opponent_state.action == Action.SWORD_DANCE_2_HIGH and opponent_state.position.y > -4: self.pickchain(Chains.Edgebair) return if self.firefoxhighframes(gamestate, opponent_state) <= 5: self.pickchain(Chains.Edgebair) return # Do nothing self.chain = None self.pickchain(Chains.Nothing) return # We are on the stage else: edge_x = melee.stages.EDGE_GROUND_POSITION[gamestate.stage] edgedistance = abs(edge_x - abs(smashbot_state.position.x)) randomgrab = False if random.randint(0, 20) == 0: randomgrab = True # Don't make this guaranteed, even on most aggressive mode. Make it common, but not predictable if self.difficulty == 4 and random.randint(0, 10) == 0: randomgrab = True # For pikachu and jiggs don't grab the edge unless they're sitting, camping if opponent_state.character in [ Character.PIKACHU, Character.JIGGLYPUFF ] and opponent_state.action != Action.EDGE_HANGING: randomgrab = False # TODO Don't grab the edge if opponent is # They're camping. Camp back if gamestate.custom["ledge_grab_count"] > 3: # Get into position away from the edge. pivotpoint = 0 if abs(smashbot_state.position.x - pivotpoint) > 5: self.chain = None self.pickchain(Chains.DashDance, [pivotpoint]) return elif len(gamestate.projectiles) == 0: # Laser self.pickchain(Chains.Laser) return # Can we challenge their ledge? framesleft = Punish.framesleft(opponent_state, self.framedata, smashbot_state) # Sheik shino stall is safe to grab edge from on these frames if opponent_state.character == Character.SHEIK and opponent_state.action == Action.SWORD_DANCE_1_AIR and opponent_state.action_frame < 5: self.pickchain(Chains.Grabedge, [True]) return # Puff sing stall is safe to grab from if opponent_state.character == Character.JIGGLYPUFF and opponent_state.action in [ Action.DOWN_B_AIR, Action.SHINE_RELEASE_AIR ] and opponent_state.action_frame < 10: self.pickchain(Chains.Grabedge, [True]) return # Grab edge out from under Pika quick-attack startup if opponent_state.character == Character.PIKACHU and opponent_state.action == Action.SWORD_DANCE_4_MID and opponent_state.action_frame < 7: self.pickchain(Chains.Grabedge, [True]) return # Grab the edge when opponent starts a FireFox if opponent_state.character in [ Character.FOX, Character.FALCO ] and opponent_state.action == Action.SWORD_DANCE_3_LOW and ( opponent_state.action_frame < 22): # But not if they're in range to grab the edge themselves edgedistance = abs(opponent_state.position.x) - ( melee.stages.EDGE_GROUND_POSITION[gamestate.stage] + 15) in_immediate_range = (-5 > opponent_state.position.y > -28) and (edgedistance < 15) if not in_immediate_range: self.pickchain(Chains.Grabedge, [True]) return if ( not recoverhigh or randomgrab ) and not onedge and opponent_state.invulnerability_left < 5 and edgedistance < 10 and smashbot_state.on_ground: if (randomgrab or framesleft > 10) and opponent_state.action not in [ Action.EDGE_ROLL_SLOW, Action.EDGE_ROLL_QUICK, Action.EDGE_GETUP_SLOW, Action.EDGE_GETUP_QUICK, Action.EDGE_ATTACK_SLOW, Action.EDGE_ATTACK_QUICK ]: if not self.framedata.is_attack(opponent_state.character, opponent_state.action): ff_early = False if opponent_state.character in [ Character.FOX, Character.FALCO ] and opponent_state.action == Action.SWORD_DANCE_3_LOW: if opponent_state.action_frame < 20: ff_early = True if not ff_early: self.pickchain(Chains.Grabedge, [True]) return # Dash dance near the edge pivotpoint = opponent_state.position.x # Don't run off the stage though, adjust this back inwards a little if it's off edgebuffer = 2 # Against Jigglypuff, we need to respect the ledge invulnerability. DD inwards more if opponent_state.character == Character.JIGGLYPUFF and opponent_state.invulnerability_left > 0: if self.logger: self.logger.log("Notes", "staying safe: " + str(opponent_state.invulnerability_left) + " ", concat=True) if opponent_state.position.x > 0: pivotpoint -= 10 else: pivotpoint += 10 pivotpoint = min(pivotpoint, edge_x - edgebuffer) pivotpoint = max(pivotpoint, (-edge_x) + edgebuffer) self.chain = None self.pickchain(Chains.DashDance, [pivotpoint])
def step(self): opponent_state = self.opponent_state smashbot_state = self.smashbot_state recoverhigh = self.canrecoverhigh() #If we can't interrupt the chain, just continue it if self.chain != None and not self.chain.interruptible: self.chain.step() return if Dropdownshine.inrange(self.smashbot_state, self.opponent_state, self.framedata): self.pickchain(Chains.Dropdownshine) return if smashbot_state.action == Action.EDGE_CATCHING: self.pickchain(Chains.Nothing) return # How many frames will it take to get to our opponent right now? onedge = smashbot_state.action in [ Action.EDGE_HANGING, Action.EDGE_CATCHING ] opponentonedge = opponent_state.action in [ Action.EDGE_HANGING, Action.EDGE_CATCHING ] # Stand up if opponent attacks us proj_incoming = Defend.needsprojectiledefense( self.smashbot_state, self.opponent_state, self.gamestate) and smashbot_state.invulnerability_left <= 2 samusgrapple = opponent_state.character == Character.SAMUS and opponent_state.action == Action.SWORD_DANCE_4_LOW and \ -25 < opponent_state.y < 0 and smashbot_state.invulnerability_left <= 2 hitframe = self.framedata.inrange(opponent_state, smashbot_state, self.gamestate.stage) framesleft = hitframe - opponent_state.action_frame if proj_incoming or samusgrapple or ( hitframe != 0 and onedge and framesleft < 5 and smashbot_state.invulnerability_left < 2): # Unless the attack is a grab, then don't bother if not self.framedata.isgrab(opponent_state.character, opponent_state.action): if self.isupb(): #TODO: Make this a chain self.chain = None self.controller.press_button(Button.BUTTON_L) return else: self.chain = None self.pickchain(Chains.DI, [0.5, 0.65]) return # Special exception for Fox/Falco illusion # Since it is dumb and technically a projectile if opponent_state.character in [Character.FOX, Character.FALCO]: if opponent_state.action in [Action.SWORD_DANCE_2_MID]: self.chain = None self.pickchain(Chains.DI, [0.5, 0.65]) return # What recovery options does opponent have? landonstage = False grabedge = False # they have to commit to an up-b to recover mustupb = False canrecover = True djheight = self.framedata.getdjheight(opponent_state) edgegrabframes = self.snaptoedgeframes() # How heigh can they go with a jump? potentialheight = djheight + opponent_state.y if potentialheight < -23: mustupb = True # Now consider UP-B # Have they already UP-B'd? if self.isupb(): if self.upbstart == 0: self.upbstart = opponent_state.y # If they are halfway through the up-b, then subtract out what they've alrady used potentialheight = self.upbheight() + self.upbstart elif opponent_state.action == Action.DEAD_FALL: potentialheight = opponent_state.y else: potentialheight += self.upbheight() # Cpt Falcon's up-b causes him to distort his model by a crazy amount. Giving him # the ability to get on the stage easier. Adjust for this adjustedheight = potentialheight if opponent_state.character == Character.CPTFALCON and self.isupb(): adjustedheight += 12 # Adjust upwards a little to have some wiggle room if adjustedheight > -5: landonstage = True if potentialheight > -23: grabedge = True if potentialheight < -30: mustupb = True canrecover = False # Split the logic into two: # A) We are on the edge # B) We are on the stage if smashbot_state.action in [ Action.EDGE_HANGING, Action.EDGE_CATCHING ]: # If opponent can't recover, then just get onto the stage! if not canrecover: #TODO: Make this a chain self.chain = None self.controller.press_button(Button.BUTTON_L) return # Don't roll up too early for Falcon falconupearly = opponent_state.character == Character.CPTFALCON and \ opponent_state.action == Action.SWORD_DANCE_3_LOW and opponent_state.action_frame <= 12 # Roll up to edgehog if self.isupb() and not landonstage and not falconupearly: #TODO: Make this a chain self.chain = None self.controller.press_button(Button.BUTTON_L) return # Edgestall # For Fox and Falco, we have a different edgestall strategy. Only edgestall if they start a FireFox if opponent_state.character in [Character.FOX, Character.FALCO]: # Are they in the start of a firefox? # But make sure they can't grab the edge in the middle of it edgedistance = abs(opponent_state.x) - ( melee.stages.edgegroundposition(self.gamestate.stage) + 15) inrange = (-5 > opponent_state.y > -23) and (edgedistance < 15) if opponent_state.action == Action.SWORD_DANCE_3_LOW and opponent_state.action_frame <= 5 and not inrange: self.pickchain(Chains.Edgestall) return # We must be on the first frame, or else it's dangerous elif smashbot_state.action == Action.EDGE_HANGING and smashbot_state.action_frame == 1: if edgegrabframes > 29 and smashbot_state.invulnerability_left >= 29: self.pickchain(Chains.Edgestall) return # We are in danger of being attacked! # It's unsafe to be in shine range of opponent. We can't react to the attack! if self.gamestate.distance < 11.8 and opponent_state.character in [Character.FOX, Character.FALCO, Character.JIGGLYPUFF] and \ smashbot_state.invulnerability_left <= 1: # If we can, challenge their shine at the edge if self.difficulty >= 3 and edgegrabframes > 2: self.pickchain(Chains.Dropdownshine) return self.chain = None self.pickchain(Chains.DI, [0.5, 0.65]) return framesleft = Punish.framesleft(self.opponent_state, self.framedata) # Samus UP_B invulnerability samusupbinvuln = opponent_state.action in [Action.SWORD_DANCE_3_MID, Action.SWORD_DANCE_3_LOW] and \ opponent_state.character == Character.SAMUS and opponent_state.action_frame <= 5 # Shine them, as long as they aren't attacking right now frameadvantage = framesleft > 2 or smashbot_state.invulnerability_left > 2 if self.gamestate.distance < 11.8 and edgegrabframes > 2 and frameadvantage and not samusupbinvuln: self.pickchain(Chains.Dropdownshine) return # Illusion high if self.illusionhighframes() <= 5: if smashbot_state.invulnerability_left > 7: self.pickchain(Chains.Edgebair) return if self.firefoxhighframes() <= 5: self.pickchain(Chains.Edgebair) return # Do nothing self.chain = None self.pickchain(Chains.Nothing) return # We are on the stage else: edge_x = melee.stages.edgegroundposition(self.gamestate.stage) edgedistance = abs(edge_x - abs(self.smashbot_state.x)) randomgrab = False if random.randint(0, 20) == 0: randomgrab = True if self.difficulty == 4: randomgrab = True # Can we challenge their ledge? framesleft = Punish.framesleft(self.opponent_state, self.framedata) if not recoverhigh and not onedge and opponent_state.invulnerability_left < 5 and edgedistance < 10: if randomgrab or framesleft > 10: wavedash = True if self.framedata.isattack(opponent_state.character, opponent_state.action): wavedash = False self.pickchain(Chains.Grabedge, [wavedash]) return # Dash dance near the edge pivotpoint = opponent_state.x # Don't run off the stage though, adjust this back inwards a little if it's off edgebuffer = 5 pivotpoint = min(pivotpoint, edge_x - edgebuffer) pivotpoint = max(pivotpoint, (-edge_x) + edgebuffer) self.chain = None self.pickchain(Chains.DashDance, [pivotpoint])
def step(self): # If we have stopped approaching, reset the state if type(self.tactic) != Tactics.Approach: self.approach = False if Mitigate.needsmitigation(self.smashbot_state): self.picktactic(Tactics.Mitigate) return if self.tactic and not self.tactic.isinteruptible(): self.tactic.step() return # If we're stuck in a lag state, just do nothing. Trying an action might just # buffer an input we don't want if Wait.shouldwait(self.smashbot_state, self.framedata): self.picktactic(Tactics.Wait) return if Recover.needsrecovery(self.smashbot_state, self.opponent_state, self.gamestate): self.picktactic(Tactics.Recover) return if Celebrate.deservescelebration(self.smashbot_state, self.opponent_state): self.picktactic(Tactics.Celebrate) return # Difficulty 5 is a debug / training mode # Don't do any attacks, and don't do any shielding # Take attacks, DI, and recover if self.difficulty == 5: self.picktactic(Tactics.KeepDistance) return if Defend.needsprojectiledefense(self.smashbot_state, self.opponent_state, self.gamestate): self.picktactic(Tactics.Defend) return # If we can infinite our opponent, do that! if Infinite.caninfinite(self.smashbot_state, self.opponent_state, self.gamestate, self.framedata, self.difficulty): self.picktactic(Tactics.Infinite) return # If we can punish our opponent for a laggy move, let's do that if Punish.canpunish(self.smashbot_state, self.opponent_state, self.gamestate, self.framedata): self.picktactic(Tactics.Punish) return # Do we need to defend an attack? if Defend.needsdefense(self.smashbot_state, self.opponent_state, self.gamestate, self.framedata): self.picktactic(Tactics.Defend) return # Can we edge guard them? if Edgeguard.canedgeguard(self.smashbot_state, self.opponent_state, self.gamestate): self.picktactic(Tactics.Edgeguard) return # Can we shield pressure them? if Pressure.canpressure(self.opponent_state, self.gamestate): self.picktactic(Tactics.Pressure) return if Retreat.shouldretreat(self.smashbot_state, self.opponent_state, self.gamestate): self.picktactic(Tactics.Retreat) return # Is opponent starting a jump? jumping = self.opponent_state.action == Action.KNEE_BEND if self.opponent_state.action in [Action.JUMPING_FORWARD, Action.JUMPING_BACKWARD] and \ self.opponent_state.speed_y_self > 0: jumping = True # Randomly approach some times rather than keeping distance if self.smashbot_state.action == Action.TURNING and random.randint( 0, 40) == 0: self.approach = True if (jumping and self.opponent_state.invulnerability_left <= 0 ) or self.approach: self.picktactic(Tactics.Approach) return self.picktactic(Tactics.KeepDistance)