Skip to content

Instantly share code, notes, and snippets.

@Raniz85
Last active January 19, 2017 10:28
Show Gist options
  • Save Raniz85/e7371eb5a0426b8e87b9a755bc6cdd07 to your computer and use it in GitHub Desktop.
Save Raniz85/e7371eb5a0426b8e87b9a755bc6cdd07 to your computer and use it in GitHub Desktop.
Simple Python script for grinding Dangerous Woman Tour - Main Hall FFBE. Requires Python 3 and ADB. Don't forget to enable debug mode on your device. Future improvements is to determine state from screenshots instead of waiting a specific time and support completion of the daily quest. USE AT YOUR OWN RISK!
#!/usr/bin/env python3
# Copyright 2017 Raniz
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import subprocess, time, argparse, sys, itertools, atexit
class FfBeController:
positions = {
'center': (0.5, 0.5),
'main_hall': (0.5, 0.34),
'missions_next': (0.5, 0.871),
'companion': (0.5, 0.351),
'depart': (0.5, 0.848),
'auto': (0.156, 0.957),
'char1': (0.25, 0.666),
'char2': (0.75, 0.666),
'results_next': (0.5, 0.836),
'friend_dont_request': (0.25, 0.727),
'refresh_lapis': (0.75, 0.564),
}
def __init__(self, width, height):
self.width = width
self.height = height
self.process = None
def connect_adb(self):
status = self.process.poll() if self.process is not None else -1
if status is not None:
if self.process:
print('ADB exited with status {}'.format(status))
self.process.stdin.close()
self.process.stdout.close()
self.process = subprocess.Popen(['adb', 'shell'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
self.process.stdin.write('true\n'.encode('utf-8'))
atexit.register(self.process.terminate)
print('Started ADB as {}'.format(self.process.pid))
def adb_command(self, *args):
command = ' '.join(args) + '\n'
self.connect_adb()
self.process.stdin.write(command.encode('utf-8'))
self.process.stdin.flush()
# Wait for the command prompt to appear
while self.process.poll() is None and self.process.stdout.read(1) != b'$':
pass
def wait(self, message, sleep_time, tap=None):
if not message.endswith('...'):
message = message + '...'
end = time.time() + sleep_time
pad_length = 0
if tap:
locations = itertools.cycle(tap)
while time.time() < end:
remaining = end - time.time()
text = message.format(remaining)
pad_length = max(len(text), pad_length)
sys.stdout.write('\r')
sys.stdout.write(text.ljust(pad_length))
sys.stdout.flush()
if tap:
if isinstance(tap, str):
self.tap(tap)
else:
self.tap(*tap)
else:
time.sleep(min(0.1, remaining))
print('\r' + message.format(sleep_time) + ' Done')
def get_collected_energy(self, time_started):
# One energy every 5 minutes
return int((time.time() - time_started) / 300)
def wait_for_energy(self, time_started, energy=1):
sleep_time = 300 - (time.time() - time_started) % 300 + max(energy - 1,0) * 300
self.wait('Waiting {:.0f}s for ' + str(energy) + ' energy', sleep_time)
def tap(self, *locations):
command = []
for location in locations:
x, y = FfBeController.positions[location]
command.append('input tap {} {}'.format(x * self.width, y * self.height))
self.adb_command(' && '.join(command))
def input(self, *args):
command = ['input'] + list(args)
self.adb_command(*command)
def run_es(self, select_party=False, lapis_refresh=False):
print('Selecting Dangerous Woman Tour - Main Hall')
self.tap('main_hall')
if lapis_refresh:
self.wait('Waiting {:.1f}s for lapis refresh screen', 1)
print('Potentially refreshing with lapis')
self.tap('refresh_lapis')
self.wait('Waiting {:.1f}s for energy to be refreshed', 2)
self.wait('Waiting {:.1f}s for missions screen', 1.5)
print('Advancing past missions')
self.tap('missions_next')
self.wait('Waiting {:0.1f}s for companion screen', 2.5)
print('Selecting companion')
self.tap('companion')
self.wait('Waiting {:0.1f}s for party selection screen', 2.5)
if select_party:
input('Please select the party you want to grind with, then press enter')
print('Departing')
self.tap('depart')
self.wait('Waiting {:0.1f}s for quest to load', 7)
print('Attacking')
self.tap('auto')
self.wait('Waiting {:0.1f}s for quest to finish', 130)
print('Moving past results')
self.tap('center')
time.sleep(1)
self.tap('results_next')
self.wait('Waiting {:0.1f}s for character screen', 2)
print('Advancing to summon screen')
self.tap('center')
time.sleep(1)
self.tap('results_next')
self.wait('Waiting {:0.1f}s for summon screen', 5)
print('Advancing to loot screen')
self.tap('center')
time.sleep(1)
self.tap('results_next')
self.wait('Waiting {:0.1f}s for loot screen', 1)
print('Ending quest')
self.tap('center')
time.sleep(2)
self.tap('results_next')
self.tap('friend_dont_request')
self.wait('Waiting {:0.1f}s for friend request screen to show up', 2)
print("Pre-emptively selecting Main Hall in case a friend request doesn't show up")
self.tap('main_hall')
print('Possibly not requesting friend')
self.tap('friend_dont_request')
def loop_es(self, initial_energy, rank, lapis_refresh=False, max_rounds=-1):
time_started = time.time()
energy_spent = 0
if rank > 100:
max_energy = 110 + int(rank % 100 / 2)
else:
max_energy = rank + 10
times_refreshed = 0
while True:
energy_remaining = initial_energy + self.get_collected_energy(time_started) - energy_spent * 13 + times_refreshed * max_energy
print('{} energy remains'.format(energy_remaining))
if not lapis_refresh:
# Only wait for energy if we're not refreshing
if energy_remaining < 13:
self.wait_for_energy(time_started, 13 - energy_remaining)
elif energy_remaining < 13:
# We refresh now
times_refreshed += 1
energy_remaining += max_energy
self.run_es(energy_spent == 0, lapis_refresh)
energy_spent += 1
print('Main Hall completed {} times'.format(energy_spent))
if max_rounds > 0 and energy_spent == max_rounds:
print('Maximum number of clears reached, exiting')
return
print(''.ljust(40, '='))
self.wait('Waiting {:.1f}s for Main Hall to show up', 4)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--width', default=1440, type=int, help='Width of your screen in pixels')
parser.add_argument('--height', default=2560, type=int, help='Height of your screen in pixels')
parser.add_argument('--lapis-refresh', action='store_true', help='Use lapis to refresh energy when empty')
parser.add_argument('--stop-after', default=-1, type=int, help='The maximum number of rounds to run, leave off to run indefinitely')
parser.add_argument('energy', type=int, help='The amount of energy you have left')
parser.add_argument('rank', type=int, help='Your current rank')
args = parser.parse_args()
input("Navigate to the Dangerous Woman Tour quest selection screen and ensure that you've\n" +
"already completed the 'Vortex Hero' daily quest, then press enter")
controller = FfBeController(args.width, args.height)
controller.loop_es(args.energy, args.rank, lapis_refresh=args.lapis_refresh, max_rounds=args.stop_after)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment