예제 #1
0
    def step(self, gamestate, smashbot_state, opponent_state):
        self._propagate = (gamestate, smashbot_state, 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

        needswavedash = smashbot_state.action in [Action.DOWN_B_GROUND, Action.DOWN_B_STUN, \
            Action.DOWN_B_GROUND_START, Action.LANDING_SPECIAL, Action.SHIELD, Action.SHIELD_START, \
            Action.SHIELD_RELEASE, Action.SHIELD_STUN, Action.SHIELD_REFLECT]
        if needswavedash:
            self.pickchain(Chains.Wavedash)
            return

        # If opponent is on a side platform and we're not
        on_main_platform = smashbot_state.position.y < 1 and smashbot_state.on_ground
        if opponent_state.position.y > 1 and opponent_state.on_ground and on_main_platform and gamestate.stage != melee.enums.Stage.FOUNTAIN_OF_DREAMS:
            self.pickchain(Chains.BoardSidePlatform,
                           [opponent_state.position.x > 0])
            return

        # If opponent is on top platform
        on_side_platform = (5 < smashbot_state.position.y <
                            35) and smashbot_state.on_ground
        top_platform_height, top_platform_left, top_platform_right = melee.top_platform_position(
            gamestate.stage)
        opp_top_platform = False
        if top_platform_height is not None:
            opp_top_platform = (
                opponent_state.position.y + 1 >= top_platform_height) and (
                    top_platform_left < opponent_state.position.x <
                    top_platform_right)

        if on_side_platform and opp_top_platform:
            self.pickchain(Chains.BoardTopPlatform)
            return

        # Jump over Samus Bomb
        samus_bomb = opponent_state.character == Character.SAMUS and opponent_state.action == Action.SWORD_DANCE_4_MID
        # Falcon rapid jab
        falcon_rapid_jab = opponent_state.action == Action.LOOPING_ATTACK_MIDDLE
        # Are they facing the right way, though?
        facing_wrong_way = opponent_state.facing != (
            opponent_state.position.x < smashbot_state.position.x)

        if (samus_bomb or falcon_rapid_jab) and opponent_state.position.y < 5:
            landing_spot = opponent_state.position.x
            if opponent_state.position.x < smashbot_state.position.x:
                landing_spot -= 10
            else:
                landing_spot += 10

            # Don't jump off the stage
            if abs(landing_spot) < melee.stages.EDGE_GROUND_POSITION[
                    gamestate.stage] and not facing_wrong_way:
                self.pickchain(Chains.JumpOver, [landing_spot])
                return

        self.chain = None
        self.pickchain(Chains.DashDance, [opponent_state.position.x])
예제 #2
0
    def step(self, gamestate, smashbot_state, opponent_state):
        platform_center = 0
        platform_height = 0

        plat_position = melee.top_platform_position(gamestate.stage)
        if plat_position:
            platform_center = (plat_position[1] + plat_position[2]) / 2
            platform_height = plat_position[0]
        else:
            self.interruptible = True
            self.controller.empty_input()
            return

        on_side_platform = smashbot_state.on_ground and smashbot_state.position.y > 5
        above_top_platform = (not smashbot_state.on_ground) and (smashbot_state.position.y + smashbot_state.ecb.bottom.y > platform_height) and \
            plat_position[1] < smashbot_state.position.x < plat_position[2]

        if smashbot_state.on_ground and smashbot_state.action != Action.KNEE_BEND:
            self.interruptible = True

        if on_side_platform:
            self.interruptible = True
            self.controller.empty_input()
            return

        # If we're crouching, release holding Y
        if smashbot_state.action == Action.KNEE_BEND:
            self.controller.release_button(melee.Button.BUTTON_Y)
            self.interruptible = False
            return

        # Don't jump into Peach's dsmash or SH early dair spam
        dsmashactive = opponent_state.action == Action.DOWNSMASH and opponent_state.action_frame <= 22
        if opponent_state.action == Action.DAIR or dsmashactive:
            self.interruptible = True
            self.controller.press_button(melee.Button.BUTTON_L)
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5, 0)
            return

        jump_frame = 30
        if gamestate.stage == Stage.BATTLEFIELD:
            jump_frame = 14
        if gamestate.stage == Stage.DREAMLAND:
            jump_frame = 16
        if gamestate.stage == Stage.YOSHIS_STORY:
            jump_frame = 21

        # Double jump
        if smashbot_state.action in [
                Action.JUMPING_FORWARD, Action.JUMPING_BACKWARD
        ]:
            if smashbot_state.action_frame == jump_frame:
                if random.randint(0, 3) == 0:
                    self.controller.press_button(melee.Button.BUTTON_Y)
                    self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5,
                                                0.5)
                    self.interruptible = False
                    return
                else:
                    self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5,
                                                0)
                    return

        # Drift into opponent
        if smashbot_state.action in [
                Action.JUMPING_ARIAL_FORWARD, Action.JUMPING_ARIAL_BACKWARD,
                Action.JUMPING_FORWARD, Action.JUMPING_BACKWARD
        ]:
            self.interruptible = False
            self.controller.release_button(melee.Button.BUTTON_Y)
            self.controller.tilt_analog(
                melee.Button.BUTTON_MAIN,
                int(smashbot_state.position.x < opponent_state.position.x),
                0.5)
            return

        # Dash at the position of the opponent
        if smashbot_state.on_ground and smashbot_state.position.y < 10:
            self.interruptible = True
            pivotpoint = opponent_state.position.x
            pivotpoint = min(plat_position[2] - 7, pivotpoint)
            pivotpoint = max(plat_position[1] + 7, pivotpoint)

            if abs(smashbot_state.position.x -
                   pivotpoint) < 5 and smashbot_state.action == Action.TURNING:
                self.interruptible = False
                self.controller.press_button(melee.Button.BUTTON_Y)
                return

            if smashbot_state.action == Action.TURNING and smashbot_state.action_frame == 1:
                return
            if smashbot_state.action == Action.DASHING and smashbot_state.action_frame >= 11:
                self.controller.tilt_analog(melee.Button.BUTTON_MAIN,
                                            int(not smashbot_state.facing), .5)
                return
            self.controller.tilt_analog(
                melee.Button.BUTTON_MAIN,
                int(smashbot_state.position.x < pivotpoint), 0.5)
            return

        self.controller.empty_input()
예제 #3
0
파일: approach.py 프로젝트: jinai/SmashBot
    def step(self, gamestate, smashbot_state, opponent_state):
        self._propagate = (gamestate, smashbot_state, 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

        needswavedash = smashbot_state.action in [Action.DOWN_B_GROUND, Action.DOWN_B_STUN, \
            Action.DOWN_B_GROUND_START, Action.LANDING_SPECIAL, Action.SHIELD, Action.SHIELD_START, \
            Action.SHIELD_RELEASE, Action.SHIELD_STUN, Action.SHIELD_REFLECT]
        if needswavedash:
            self.pickchain(Chains.Wavedash, [True])
            return

        # Are we behind in the game?
        losing = smashbot_state.stock < opponent_state.stock or (
            smashbot_state.stock == opponent_state.stock
            and smashbot_state.percent > opponent_state.percent)
        opp_top_platform = False
        top_platform_height, top_platform_left, top_platform_right = melee.top_platform_position(
            gamestate.stage)
        if top_platform_height is not None:
            opp_top_platform = (
                opponent_state.position.y + 1 >= top_platform_height) and (
                    top_platform_left - 1 < opponent_state.position.x <
                    top_platform_right + 1)

        # If opponent is on a side platform and we're not
        on_main_platform = smashbot_state.position.y < 1 and smashbot_state.on_ground
        if not opp_top_platform:
            if opponent_state.position.y > 10 and opponent_state.on_ground and on_main_platform:
                self.pickchain(Chains.BoardSidePlatform,
                               [opponent_state.position.x > 0])
                return

        # If opponent is on top platform. Unless we're ahead. Then let them camp
        if opp_top_platform and losing and random.randint(0, 20) == 0:
            self.pickchain(Chains.BoardTopPlatform)
            return

        # Jump over Samus Bomb
        # TODO Don't jump on top of an existing bomb
        samus_bomb = opponent_state.character == Character.SAMUS and opponent_state.action == Action.SWORD_DANCE_4_MID
        if samus_bomb and opponent_state.position.y < 5:
            landing_spot = opponent_state.position.x
            if opponent_state.position.x < smashbot_state.position.x:
                landing_spot -= 10
            else:
                landing_spot += 10

            # Don't jump off the stage
            if abs(landing_spot) < melee.stages.EDGE_GROUND_POSITION[
                    gamestate.stage]:
                self.pickchain(Chains.JumpOver, [landing_spot])
                return

        # SHFFL at opponent sometimes (33% chance per approach)
        if self.random_approach < 33:
            if not self.framedata.is_attack(opponent_state.character,
                                            opponent_state.action):
                # We need to be dashing towards our opponent. Not too close to the ledge
                vertical_distance = abs(smashbot_state.position.y -
                                        opponent_state.position.y)
                facing_opponent = smashbot_state.facing == (
                    smashbot_state.position.x < opponent_state.position.x)
                if smashbot_state.action == Action.DASHING and facing_opponent:
                    if vertical_distance < 20 and gamestate.distance < 35 and abs(
                            melee.stages.EDGE_GROUND_POSITION[gamestate.stage]
                            - abs(smashbot_state.position.x)) > 35:
                        self.pickchain(Chains.Shffl, [SHFFL_DIRECTION.NEUTRAL])
                        return

        self.chain = None
        self.pickchain(Chains.DashDance, [opponent_state.position.x])
예제 #4
0
    def step(self, gamestate, smashbot_state, opponent_state):
        if self.logger:
            self.logger.log("Notes",
                            " right side platform: " +
                            str(self.right_platform) + " ",
                            concat=True)

        platform_center = 0
        platform_height, platform_left, platform_right = melee.side_platform_position(
            self.right_platform, gamestate.stage)
        if platform_height is not None:
            platform_center = (platform_left + platform_right) / 2

        top_platform_height, _, _ = melee.top_platform_position(
            gamestate.stage)

        # Where to dash dance to
        pivot_point = platform_center
        # If opponent is on the platform, get right under them
        if platform_left < opponent_state.position.x < platform_right:
            pivot_point = opponent_state.position.x

        # Unless we don't need to attack them, then it's safe to just board asap
        if not self.attack and (platform_left < smashbot_state.position.x <
                                platform_right):
            pivot_point = smashbot_state.position.x

        # If we're just using the side platform as a springboard, then go closer in than the middle
        if opponent_state.position.y >= top_platform_height:
            if smashbot_state.position.x > 0:
                pivot_point = platform_left + 8
            else:
                pivot_point = platform_right - 8

        if smashbot_state.on_ground:
            self.interruptible = True
            # If we're already on the platform, just do nothing. We shouldn't be here
            if smashbot_state.position.y > 5:
                self.controller.release_all()
                return

        # Are we in position to jump?
        if (abs(smashbot_state.position.x - pivot_point) <
                5) and smashbot_state.action == Action.TURNING:
            self.interruptible = False
            self.controller.press_button(melee.Button.BUTTON_Y)
            return

        # If we're crouching, keep holding Y
        if smashbot_state.action == Action.KNEE_BEND:
            self.controller.press_button(melee.Button.BUTTON_Y)
            self.interruptible = False
            return

        # Jump out of shine
        if smashbot_state.action in [Action.DOWN_B_AIR]:
            self.controller.press_button(melee.Button.BUTTON_Y)
            return

        # Can we shine our opponent right now, while we're in the air?
        foxshinerange = 11.8
        shineable = smashbot_state.action in [
            Action.JUMPING_FORWARD, Action.JUMPING_BACKWARD
        ]
        if self.attack and shineable and gamestate.distance < foxshinerange:
            self.controller.press_button(melee.Button.BUTTON_B)
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5, 0)
            return

        # Waveland down
        aerials = [
            Action.NAIR, Action.FAIR, Action.UAIR, Action.BAIR, Action.DAIR
        ]
        if smashbot_state.ecb.bottom.y + smashbot_state.position.y > platform_height and smashbot_state.action not in aerials:
            self.interruptible = True
            self.controller.press_button(melee.Button.BUTTON_L)
            # When we're choosing to not attack, just get close to the opponent if we're already
            x = int(
                smashbot_state.position.x < opponent_state.position.x) * 0.8
            if not self.attack and abs(smashbot_state.position.x -
                                       opponent_state.position.x) < 5:
                x = 0.5
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN, x, 0)
            return

        # Don't jump into Peach's dsmash or SH early dair spam
        dsmashactive = opponent_state.action == Action.DOWNSMASH and opponent_state.action_frame <= 22
        if shineable and (opponent_state.action == Action.DAIR
                          or dsmashactive):
            self.interruptible = True
            self.controller.press_button(melee.Button.BUTTON_L)
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5, 0)
            return

        # If we see the opponent jump, they cannot protect themselves from uair.
        # Does not look for KNEE_BEND because Smashbot needs to discern between SH and FH
        y_afternineframes = opponent_state.position.y
        gravity = self.framedata.characterdata[
            opponent_state.character]["Gravity"]
        y_speed = opponent_state.speed_y_self
        for i in range(1, 10):
            y_afternineframes += y_speed
            y_speed -= gravity

        aerialsminusdair = [Action.NAIR, Action.FAIR, Action.UAIR, Action.BAIR]
        if shineable and (opponent_state.action
                          in [Action.JUMPING_FORWARD, Action.JUMPING_BACKWARD]
                          or opponent_state.action
                          in aerialsminusdair) and y_afternineframes < 50:
            self.controller.press_button(melee.Button.BUTTON_A)
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5, 1)
            return

        # Last resort, just dash at the center of the platform
        if smashbot_state.on_ground:
            self.interruptible = True
            #If we're starting the turn around animation, keep pressing that way or
            #   else we'll get stuck in the slow turnaround
            if smashbot_state.action == Action.TURNING and smashbot_state.action_frame == 1:
                return

            #Dash back, since we're about to start running
            if smashbot_state.action == Action.DASHING and smashbot_state.action_frame >= 11:
                self.controller.tilt_analog(melee.Button.BUTTON_MAIN,
                                            int(not smashbot_state.facing), .5)
                return
            else:
                self.controller.tilt_analog(
                    melee.Button.BUTTON_MAIN,
                    int(smashbot_state.position.x < pivot_point), .5)
                return
        # Mash analog L presses to L-cancel if Smashbot is throwing out an aerial
        elif not smashbot_state.on_ground and smashbot_state.action in aerials:
            self.interruptible = False
            if gamestate.frame % 2 == 0:
                self.controller.press_shoulder(Button.BUTTON_L, 1)
            else:
                self.controller.press_shoulder(Button.BUTTON_L, 0)
            return
        else:
            self.controller.empty_input()
예제 #5
0
    def step(self, gamestate, smashbot_state, opponent_state):
        self._propagate = (gamestate, smashbot_state, 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

        # Get over to where they will end up at the end of hitstun
        end_x, end_y = self.framedata.project_hit_location(opponent_state)
        frames_left = opponent_state.hitstun_frames_left

        if self.framedata.is_roll(opponent_state.character,
                                  opponent_state.action):
            end_x = self.framedata.roll_end_position(opponent_state,
                                                     gamestate.stage)
            frames_left = self.framedata.last_roll_frame(
                opponent_state.character,
                opponent_state.action) - opponent_state.action_frame

        facing_away = (smashbot_state.position.x <
                       end_x) != smashbot_state.facing
        if smashbot_state.action == Action.TURNING and smashbot_state.action_frame == 1:
            facing_away = not facing_away

        on_ground = opponent_state.on_ground or opponent_state.position.y < 1 or opponent_state.action in [
            Action.TECH_MISS_UP, Action.TECH_MISS_DOWN
        ]

        # Make sure we don't dashdance off the platform during a juggle
        side_platform_height, side_platform_left, side_platform_right = melee.side_platform_position(
            smashbot_state.position.x > 0, gamestate.stage)
        top_platform_height, top_platform_left, top_platform_right = melee.top_platform_position(
            gamestate.stage)
        if smashbot_state.position.y < 5:
            end_x = min(end_x, melee.EDGE_GROUND_POSITION[gamestate.stage] - 5)
            end_x = max(end_x,
                        -melee.EDGE_GROUND_POSITION[gamestate.stage] + 5)
        elif (side_platform_height is not None
              ) and abs(smashbot_state.position.y - side_platform_height) < 5:
            end_x = min(end_x, side_platform_right - 5)
            end_x = max(end_x, side_platform_left + 5)
        elif (top_platform_height is not None
              ) and abs(smashbot_state.position.y - top_platform_height) < 5:
            end_x = min(end_x, top_platform_right - 5)
            end_x = max(end_x, top_platform_left + 5)

        # TODO Slideoff detection

        if self.logger:
            self.logger.log("Notes",
                            " Predicted End Position: " + str(end_x) + " " +
                            str(end_y) + " ",
                            concat=True)
            self.logger.log("Notes",
                            " on_ground: " + str(on_ground),
                            concat=True)
            self.logger.log("Notes",
                            " frames left: " + str(frames_left) + " ",
                            concat=True)

        # Need to pivot the uptilt
        # Uptilt's hitbox is pretty forgiving if we do it a few frames early, so no big deal there
        if not on_ground:
            # If we can just throw out an uptilt and hit now, do it. No need to wait for them to fall further
            end_early_x, end_early_y = self.framedata.project_hit_location(
                opponent_state, 7)
            if self.logger:
                self.logger.log("Notes",
                                " uptilt early End Position: " +
                                str(end_early_x) + " " + str(end_early_y) +
                                " ",
                                concat=True)
            in_range = (abs(end_early_x - smashbot_state.position.x) < 8) and (
                abs(end_early_y - smashbot_state.position.y) < 12)
            if smashbot_state.action == Action.TURNING and in_range and (
                    7 <= frames_left <= 9):
                self.pickchain(Chains.Tilt, [TILT_DIRECTION.UP])
                return
            # Check each height level, can we do an up-air right now?
            for height_level in AirAttack.height_levels():
                height = AirAttack.attack_height(height_level)
                commitment = AirAttack.frame_commitment(height)
                end_early_x, end_early_y = self.framedata.project_hit_location(
                    opponent_state, commitment)
                if (commitment < frames_left) and (
                        abs(smashbot_state.position.x - end_early_x) <
                        20) and (abs(end_early_y - height) < 5):
                    if self.logger:
                        self.logger.log("Notes",
                                        " Early End Position: " +
                                        str(end_early_x) + " " +
                                        str(end_early_y) + " ",
                                        concat=True)
                        self.logger.log("Notes",
                                        " height: " + str(height),
                                        concat=True)
                        self.logger.log("Notes",
                                        " commitment: " + str(commitment),
                                        concat=True)
                    self.chain = None
                    self.pickchain(
                        Chains.AirAttack,
                        [end_early_x, end_early_y, AIR_ATTACK_DIRECTION.UP])
                    return
            # Just dash dance to where they will end up
            # TODO board platform to get closer?
            if frames_left > 9:
                self.chain = None
                self.pickchain(Chains.DashDance, [end_x])
                return
        else:
            if self.framedata.is_roll(opponent_state.character,
                                      opponent_state.action):
                # We still have plenty of time, so just get closer to the DD spot
                #   Even if we're already close
                if frames_left > 10:
                    # Do we need to jump up to the side platform?
                    side_plat_height, side_plat_left, side_plat_right = melee.side_platform_position(
                        opponent_state.position.x > 0, gamestate.stage)
                    # TODO 13 is the fastest getup attack of the legal character, do a lookup for the actual one
                    if opponent_state.action in [
                            Action.TECH_MISS_UP, Action.TECH_MISS_DOWN
                    ]:
                        frames_left += 13
                    if side_plat_height is not None and (
                            frames_left >
                            25) and abs(side_plat_height -
                                        opponent_state.position.y) < 5:
                        # But only if we're already mostly there
                        smashbot_on_side_plat = smashbot_state.on_ground and abs(
                            smashbot_state.position.y - side_plat_height) < 5
                        if side_plat_left < smashbot_state.position.x < side_plat_right:
                            self.chain = None
                            self.pickchain(
                                Chains.BoardSidePlatform,
                                [opponent_state.position.x > 0, False])
                            return

                    if self.logger:
                        self.logger.log("Notes",
                                        " DD at: " + str(end_x),
                                        concat=True)
                        self.logger.log("Notes",
                                        " plat at: " + str(side_plat_left) +
                                        " " + str(side_plat_right),
                                        concat=True)
                    self.chain = None
                    self.pickchain(Chains.DashDance, [end_x])
                    return
                # We need to get to a position where our back is to the end position. We'll do a pivot stand to get there
                if (abs(smashbot_state.position.x - end_x) < 5):
                    # Pivot
                    if smashbot_state.action == Action.DASHING:
                        self.chain = None
                        self.pickchain(Chains.Run, [not smashbot_state.facing])
                        return
                    if smashbot_state.action in [
                            Action.TURNING, Action.STANDING
                    ]:
                        if 7 <= frames_left <= 9:
                            if facing_away and gamestate.distance < 20:
                                self.pickchain(Chains.Tilt,
                                               [TILT_DIRECTION.UP])
                                return
                            # Can't grab a tech miss. Don't try
                            elif opponent_state.action not in [
                                    Action.TECH_MISS_UP, Action.TECH_MISS_DOWN
                            ] and gamestate.distance < 10:
                                self.pickchain(Chains.GrabAndThrow,
                                               [THROW_DIRECTION.UP])
                                return
                        if frames_left == 1 and gamestate.distance < 10:
                            self.pickchain(Chains.Waveshine)
                            return
                        else:
                            self.pickchain(Chains.Nothing)
                            return

                # If we're a little further away than 5 units, but still in range
                elif (abs(smashbot_state.position.x - end_x) < 10):
                    # Don't dashdance here. Just stand still
                    if smashbot_state.action == Action.TURNING and smashbot_state.action_frame > 1:
                        self.pickchain(Chains.Nothing)
                        return

                    self.chain = None
                    self.pickchain(Chains.DashDance, [end_x])
                    return

                # We're further than 5 units away, so DD into their end position
                self.chain = None
                self.pickchain(Chains.DashDance, [end_x])
                return

        self.chain = None
        self.pickchain(Chains.DashDance, [end_x])
예제 #6
0
    def step(self, gamestate, smashbot_state, opponent_state):
        platform_center = 0
        platform_height = 0

        position = melee.top_platform_position(gamestate.stage)
        if position:
            platform_center = (position[1] + position[2]) / 2
            platform_height = position[0]

        on_side_platform = smashbot_state.on_ground and smashbot_state.position.y > 5
        above_top_platform = (not smashbot_state.on_ground) and (smashbot_state.position.y + smashbot_state.ecb.bottom.y > platform_height) and \
            position[1] < smashbot_state.position.x < position[2]

        if smashbot_state.on_ground and smashbot_state.action != Action.KNEE_BEND:
            self.interruptible = True

        # Stage 1, get to the inside edge of the side platform
        #   Are we in position to jump? We want to be dashing inwards on a side plat
        if on_side_platform:
            # Get the x coord of the inner edge of the plat
            right_edge = (melee.side_platform_position(True,
                                                       gamestate.stage))[2]
            if right_edge - abs(smashbot_state.position.x) < 8:
                if smashbot_state.action in [
                        Action.DASHING, Action.RUNNING
                ] and (smashbot_state.facing
                       == (smashbot_state.position.x < 0)):
                    self.interruptible = False
                    self.controller.press_button(melee.Button.BUTTON_Y)
                    return
                else:
                    # Dash inwards
                    self.interruptible = False
                    self.controller.tilt_analog(
                        melee.Button.BUTTON_MAIN,
                        int(smashbot_state.position.x < 0), 0.5)
                    return
            else:
                # Dash inwards
                self.interruptible = False
                self.controller.tilt_analog(melee.Button.BUTTON_MAIN,
                                            int(smashbot_state.position.x < 0),
                                            0.5)
                return

        # If we're crouching, keep holding Y
        if smashbot_state.action == Action.KNEE_BEND:
            self.controller.press_button(melee.Button.BUTTON_Y)
            self.interruptible = False
            return

        # Jump when falling
        if smashbot_state.action == Action.FALLING and smashbot_state.jumps_left > 0 and smashbot_state.action_frame > 6:
            self.interruptible = False
            self.controller.press_button(melee.Button.BUTTON_Y)
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN,
                                        int(smashbot_state.position.x < 0),
                                        0.5)
            return

        # Jump out of shine
        if smashbot_state.action in [Action.DOWN_B_AIR]:
            self.controller.press_button(melee.Button.BUTTON_Y)
            return

        # Can we shine our opponent right now, while we're in the air?
        foxshinerange = 11.8
        shineable = smashbot_state.action in [
            Action.JUMPING_FORWARD, Action.JUMPING_BACKWARD
        ]
        if shineable and gamestate.distance < foxshinerange:
            self.controller.press_button(melee.Button.BUTTON_B)
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5, 0)
            return

        # Waveland down
        aerials = [
            Action.NAIR, Action.FAIR, Action.UAIR, Action.BAIR, Action.DAIR
        ]
        if above_top_platform and smashbot_state.action not in aerials:
            self.interruptible = True
            self.controller.press_button(melee.Button.BUTTON_L)
            # If opponent is in front of us, waveland towards them
            if smashbot_state.facing == (smashbot_state.position.x <
                                         opponent_state.position.x):
                self.controller.tilt_analog(
                    melee.Button.BUTTON_MAIN,
                    int(smashbot_state.position.x < opponent_state.position.x),
                    0.2)
            else:
                self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5, 0)
            return

        # Don't jump into Peach's dsmash or SH early dair spam
        dsmashactive = opponent_state.action == Action.DOWNSMASH and opponent_state.action_frame <= 22
        if shineable and (opponent_state.action == Action.DAIR
                          or dsmashactive):
            self.interruptible = True
            self.controller.press_button(melee.Button.BUTTON_L)
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0.5, 0)
            return

        # Hold inwards while we're jumping
        if shineable:
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN,
                                        int(smashbot_state.position.x < 0),
                                        0.5)
            return

        # Last resort, just dash at the center of the platform
        if smashbot_state.on_ground:
            self.interruptible = True
            #If we're starting the turn around animation, keep pressing that way or
            #   else we'll get stuck in the slow turnaround
            if smashbot_state.action == Action.TURNING and smashbot_state.action_frame == 1:
                return

            #Dash back, since we're about to start running
            if smashbot_state.action == Action.DASHING and smashbot_state.action_frame >= 11:
                self.controller.tilt_analog(melee.Button.BUTTON_MAIN,
                                            int(not smashbot_state.facing), .5)
                return
            if smashbot_state.position.x > platform_center + 2:
                self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 0, .5)
                return
            if smashbot_state.position.x < platform_center - 2:
                self.controller.tilt_analog(melee.Button.BUTTON_MAIN, 1, .5)
                return
            self.controller.tilt_analog(melee.Button.BUTTON_MAIN,
                                        int(smashbot_state.facing), .5)
            return
        # Mash analog L presses to L-cancel if Smashbot is throwing out an aerial
        elif not smashbot_state.on_ground and smashbot_state.action in aerials:
            self.interruptible = False
            if gamestate.frame % 2 == 0:
                self.controller.press_shoulder(Button.BUTTON_L, 1)
            else:
                self.controller.press_shoulder(Button.BUTTON_L, 0)
            return
        else:
            self.controller.empty_input()