├── README.md ├── setup.cfg ├── dolphin ├── fields.py ├── __init__.py ├── reward.py ├── movie.py ├── box_wrapper.py ├── default.py ├── memory_watcher.py ├── pad.py ├── ctype_util.py ├── menu_manager.py ├── util.py ├── ssbm.py ├── dolphin.py ├── state_manager.py ├── ssbm_env.py └── state.py ├── LICENSE ├── .gitignore └── setup.py /README.md: -------------------------------------------------------------------------------- 1 | # gym-dolphin 2 | Nintendo GameCube and Wii games for OpenAI gym using the Dolphin emulator. 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | #universal=1 6 | -------------------------------------------------------------------------------- /dolphin/fields.py: -------------------------------------------------------------------------------- 1 | def getField(obj, field): 2 | if isinstance(field, str): 3 | return getattr(obj, field) 4 | else: 5 | return obj[field] 6 | 7 | def setField(obj, field, val, validate=True): 8 | if isinstance(field, str): 9 | if validate: 10 | if not hasattr(obj, field): 11 | raise TypeError("%s object has no field %s" % (type(obj), field)) 12 | setattr(obj, field, val) 13 | else: # assume an array 14 | obj[field] = val 15 | 16 | def getPath(obj, path): 17 | for field in path: 18 | obj = getField(obj, field) 19 | return obj 20 | 21 | # doesn't work with empty path :( 22 | def setPath(obj, path, val): 23 | obj = getPath(obj, path[:-1]) 24 | setField(obj, path[-1], val) 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vlad Firoiu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dolphin/__init__.py: -------------------------------------------------------------------------------- 1 | from gym.envs.registration import register 2 | from gym.scoreboard.registration import add_task, add_group 3 | 4 | from .ssbm_env import SSBMEnv, simpleSSBMEnv 5 | 6 | register( 7 | id='vladfi1/SSBM-v0', 8 | entry_point='dolphin:simpleSSBMEnv', 9 | reward_threshold=1, 10 | timestep_limit=9999999, 11 | kwargs=dict( 12 | cpu=9, 13 | gui=True, 14 | stage='battlefield', 15 | mute=True, 16 | speed=0, 17 | ), 18 | nondeterministic=True, 19 | ) 20 | 21 | register( 22 | id='vladfi1/SSBM-headless-v0', 23 | entry_point='dolphin:simpleSSBMEnv', 24 | reward_threshold=1, 25 | timestep_limit=9999999, 26 | kwargs=dict( 27 | cpu=9, 28 | stage='battlefield', 29 | ), 30 | nondeterministic=True, 31 | ) 32 | 33 | # Scoreboard registration 34 | # ========================== 35 | add_group( 36 | id= 'ssbm', 37 | name= 'Super Smash Bros. Melee', 38 | description= 'Beat the in-game AIs at SSBM.' 39 | ) 40 | 41 | """ 42 | add_task( 43 | id='{}/meta-SuperMarioBros-v0'.format(USERNAME), 44 | group='ssbm', 45 | summary='Compilation of all 32 levels of Super Mario Bros. on Nintendo platform - Screen version.', 46 | ) 47 | """ 48 | 49 | -------------------------------------------------------------------------------- /dolphin/reward.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from . import util 3 | 4 | # see https://docs.google.com/spreadsheets/d/1JX2w-r2fuvWuNgGb6D3Cs4wHQKLFegZe2jhbBuIhCG8/edit#gid=13 5 | dyingActions = set(range(0xA)) 6 | 7 | def isDyingAction(action_state): 8 | return action_state in dyingActions 9 | 10 | def isDying(player): 11 | return isDyingAction(player.action_state) 12 | 13 | # players tend to be dead for many frames in a row 14 | # here we prune all but the first frame of the death 15 | def processDeaths(deaths): 16 | return np.array(util.zipWith(lambda prev, next: float((not prev) and next), deaths, deaths[1:])) 17 | 18 | def processDamages(percents): 19 | return np.array(util.zipWith(lambda prev, next: max(next-prev, 0), percents, percents[1:])) 20 | 21 | # from player 1's perspective 22 | def computeRewards(states, enemies=[0], allies=[1], damage_ratio=0.01): 23 | players = enemies + allies 24 | 25 | deaths = {p : processDeaths([isDying(state.players[p]) for state in states]) for p in players} 26 | damages = {p : processDamages([state.players[p].percent for state in states]) for p in players} 27 | 28 | losses = {p : deaths[p] + damage_ratio * damages[p] for p in players} 29 | 30 | return sum(losses[p] for p in enemies) - sum(losses[p] for p in allies) 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /dolphin/movie.py: -------------------------------------------------------------------------------- 1 | from .pad import * 2 | 3 | def pushButton(button): 4 | return lambda pad: pad.press_button(button) 5 | 6 | def releaseButton(button): 7 | return lambda pad: pad.release_button(button) 8 | 9 | def tiltStick(stick, x, y): 10 | return lambda pad: pad.tilt_stick(stick, x, y) 11 | 12 | neutral = tiltStick(Stick.MAIN, 0.5, 0.5) 13 | left = tiltStick(Stick.MAIN, 0, 0.5) 14 | down = tiltStick(Stick.MAIN, 0.5, 0) 15 | up = tiltStick(Stick.MAIN, 0.5, 1) 16 | right = tiltStick(Stick.MAIN, 1, 0.5) 17 | 18 | endless_netplay = [ 19 | # time 20 | (0, left), 21 | 22 | # infinite time 23 | (26, down), 24 | (19, left), 25 | (25, neutral), 26 | 27 | # exit settings 28 | (1, pushButton(Button.START)), 29 | (1, releaseButton(Button.START)), 30 | 31 | # enter stage select 32 | (28, pushButton(Button.START)), 33 | (1, releaseButton(Button.START)), 34 | 35 | (10, neutral) 36 | ] 37 | 38 | stages = dict( 39 | battlefield = [ 40 | (0, up), 41 | (2, neutral), 42 | 43 | #(60 * 60, neutral), 44 | 45 | # start game 46 | (20, pushButton(Button.START)), 47 | (1, releaseButton(Button.START)), 48 | ], 49 | 50 | final_destination = [ 51 | (0, tiltStick(Stick.MAIN, 1, 0.8)), 52 | (5, neutral), 53 | 54 | #(60 * 60, neutral), 55 | 56 | # start game 57 | (20, pushButton(Button.START)), 58 | (1, releaseButton(Button.START)), 59 | ] 60 | ) 61 | 62 | class Movie: 63 | def __init__(self, actions, pad): 64 | self.actions = actions 65 | self.frame = 0 66 | self.index = 0 67 | self.pad = pad 68 | 69 | def move(self, state): 70 | if not self.done(): 71 | frame, action = self.actions[self.index] 72 | if self.frame == frame: 73 | action(self.pad) 74 | self.index += 1 75 | self.frame = 0 76 | else: 77 | self.frame += 1 78 | 79 | def done(self): 80 | return self.index == len(self.actions) 81 | -------------------------------------------------------------------------------- /dolphin/box_wrapper.py: -------------------------------------------------------------------------------- 1 | import gym 2 | from gym.spaces import * 3 | 4 | import numpy as np 5 | 6 | __all__ = ['BoxWrapper'] 7 | 8 | class FlattenConvertor: 9 | def __init__(self, space): 10 | assert(isinstance(space, Box)) 11 | self.in_space = space 12 | self.out_space = Box(space.low.flatten(), space.high.flatten()) 13 | 14 | def __call__(self, x): 15 | assert(self.in_space.contains(x)) 16 | return x.flatten() 17 | 18 | class OneHotConvertor: 19 | def __init__(self, space): 20 | assert(isinstance(space, Discrete)) 21 | self.in_space = space 22 | self.out_space = Box(0, 1, [space.n]) 23 | 24 | def __call__(self, x): 25 | assert(self.in_space.contains(x)) 26 | a = np.zeros([self.in_space.n]) 27 | a[x] = 1 28 | return a 29 | 30 | class ConcatConvertor: 31 | def __init__(self, space): 32 | assert(isinstance(space, Tuple)) 33 | 34 | self.in_space = space 35 | 36 | self.convertors = list(map(convertor, space.spaces)) 37 | 38 | low = np.concatenate([c.out_space.low for c in self.convertors]) 39 | high = np.concatenate([c.out_space.high for c in self.convertors]) 40 | 41 | self.out_space = Box(low, high) 42 | 43 | def __call__(self, xs): 44 | #assert(self.in_space.contains(xs)) 45 | return np.concatenate([c(x) for c, x in zip(self.convertors, xs)]) 46 | 47 | def convertor(space): 48 | if isinstance(space, Box): 49 | return FlattenConvertor(space) 50 | elif isinstance(space, Discrete): 51 | return OneHotConvertor(space) 52 | elif isinstance(space, Tuple): 53 | return ConcatConvertor(space) 54 | else: 55 | raise ValueError("Unsupported space %s" % space) 56 | 57 | class BoxWrapper(gym.Wrapper): 58 | "Turns any observation space into a box." 59 | def __init__(self, env): 60 | super(BoxWrapper, self).__init__(env) 61 | self.convertor = convertor(env.observation_space) 62 | self.observation_space = self.convertor.out_space 63 | 64 | def _step(self, action): 65 | obs, reward, done, info = self.env.step(action) 66 | obs = self.convertor(obs) 67 | return obs, reward, done, info 68 | 69 | def _reset(self): 70 | obs = self.env._reset() 71 | return self.convertor(obs) 72 | 73 | -------------------------------------------------------------------------------- /dolphin/default.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | class Default: 4 | _options = [] 5 | 6 | _members = [] 7 | 8 | def __init__(self, init_members=True, **kwargs): 9 | self._kwargs = kwargs 10 | 11 | for opt in self._options: 12 | value = None 13 | if opt.name in kwargs: 14 | value = kwargs[opt.name] 15 | if value is None: 16 | value = opt.default 17 | setattr(self, opt.name, value) 18 | 19 | if init_members: 20 | self._init_members(**kwargs) 21 | 22 | def _init_members(self, **kwargs): 23 | for name, cls in self._members: 24 | setattr(self, name, cls(**kwargs)) 25 | 26 | def items(self): 27 | for opt in self._options: 28 | yield opt.name, getattr(self, opt.name) 29 | for name, _ in self._members: 30 | yield name, getattr(self, name) 31 | 32 | def label(self): 33 | label = self.__class__.__name__ 34 | for item in self.items(): 35 | label += "_%s_%s" % item 36 | return label 37 | 38 | def __repr__(self): 39 | fields = ", ".join("%s=%s" % (name, str(value)) for name, value in self.items()) 40 | return "%s(%s)" % (self.__class__.__name__, fields) 41 | 42 | @classmethod 43 | def full_opts(cls): 44 | yield from cls._options 45 | for _, cls_ in cls._members: 46 | yield from cls_.full_opts() 47 | 48 | def __getstate__(self): 49 | return self._kwargs 50 | def __setstate__(self, d): 51 | self.__init__(**d) 52 | 53 | def dump(self, f): 54 | pickle.dump(self._kwargs, f) 55 | 56 | @classmethod 57 | def load(cls, f, **override): 58 | kwargs = pickle.load(f) 59 | kwargs.update(**override) 60 | return cls(**kwargs) 61 | 62 | class Option: 63 | def __init__(self, name, **kwargs): 64 | self.name = name 65 | self.default = None 66 | self.__dict__.update(kwargs) 67 | self.kwargs = kwargs.copy() 68 | 69 | # don't pass default on to argparse 70 | self.kwargs['default'] = None 71 | 72 | def update_parser(self, parser): 73 | flag = "--" + self.name 74 | if flag in parser._option_string_actions: 75 | print("warning: already have option %s. skipping"%self.name) 76 | else: 77 | parser.add_argument(flag, **self.kwargs) 78 | 79 | -------------------------------------------------------------------------------- /dolphin/memory_watcher.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import zmq 3 | import os 4 | import socket 5 | 6 | from . import util 7 | 8 | def parseMessage(message): 9 | lines = message.splitlines() 10 | 11 | diffs = util.chunk(lines, 2) 12 | 13 | for diff in diffs: 14 | diff[1] = binascii.unhexlify(diff[1].zfill(8)) 15 | 16 | return diffs 17 | 18 | class MemoryWatcherZMQ: 19 | def __init__(self, path): 20 | context = zmq.Context() 21 | 22 | self.socket = context.socket(zmq.REP) 23 | self.socket.bind("ipc://" + path) 24 | 25 | self.messages = None 26 | 27 | def get_messages(self): 28 | if self.messages is None: 29 | message = self.socket.recv() 30 | message = message.decode('utf-8') 31 | self.messages = parseMessage(message) 32 | 33 | return self.messages 34 | 35 | def advance(self): 36 | self.socket.send(b'') 37 | self.messages = None 38 | 39 | class MemoryWatcher: 40 | """Reads and parses game memory changes. 41 | 42 | Pass the location of the socket to the constructor, then either manually 43 | call next() on this class to get a single change, or else use it like a 44 | normal iterator. 45 | """ 46 | def __init__(self, path): 47 | """Creates the socket if it does not exist, and then opens it.""" 48 | try: 49 | os.unlink(path) 50 | except OSError: 51 | pass 52 | self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) 53 | self.sock.settimeout(1) 54 | self.sock.bind(path) 55 | 56 | def __iter__(self): 57 | """Iterate over this class in the usual way to get memory changes.""" 58 | return self 59 | 60 | def __del__(self): 61 | """Closes the socket.""" 62 | self.sock.close() 63 | 64 | def __next__(self): 65 | """Returns the next (address, value) tuple, or None on timeout. 66 | 67 | address is the string provided by dolphin, set in Locations.txt. 68 | value is a four-byte string suitable for interpretation with struct. 69 | """ 70 | try: 71 | data = self.sock.recvfrom(1024)[0].decode('utf-8').splitlines() 72 | except socket.timeout: 73 | return None 74 | assert len(data) == 2 75 | # Strip the null terminator, pad with zeros, then convert to bytes 76 | return data[0], binascii.unhexlify(data[1].strip('\x00').zfill(8)) 77 | 78 | def get_messages(self): 79 | res = next(self) 80 | if res is not None: 81 | return [res] 82 | return [] 83 | 84 | def advance(self): 85 | pass 86 | 87 | -------------------------------------------------------------------------------- /dolphin/pad.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import os 3 | from threading import Thread 4 | 5 | @enum.unique 6 | class Button(enum.Enum): 7 | A = 0 8 | B = 1 9 | X = 2 10 | Y = 3 11 | Z = 4 12 | START = 5 13 | L = 6 14 | R = 7 15 | D_UP = 8 16 | D_DOWN = 9 17 | D_LEFT = 10 18 | D_RIGHT = 11 19 | 20 | @enum.unique 21 | class Trigger(enum.Enum): 22 | L = 0 23 | R = 1 24 | 25 | @enum.unique 26 | class Stick(enum.Enum): 27 | MAIN = 0 28 | C = 1 29 | 30 | class Pad: 31 | """Writes out controller inputs.""" 32 | def __init__(self, path): 33 | """Opens the fifo. Blocks until the other end is listening.""" 34 | self.pipe = None 35 | try: 36 | os.mkfifo(path) 37 | except OSError: 38 | pass 39 | self.pipe = open(path, 'w', buffering=1) 40 | 41 | def __del__(self): 42 | """Closes the fifo.""" 43 | if self.pipe: 44 | self.pipe.close() 45 | 46 | def press_button(self, button): 47 | """Press a button.""" 48 | assert button in Button 49 | self.pipe.write('PRESS {}\n'.format(button.name)) 50 | 51 | def release_button(self, button): 52 | """Release a button.""" 53 | assert button in Button 54 | self.pipe.write('RELEASE {}\n'.format(button.name)) 55 | 56 | def press_trigger(self, trigger, amount): 57 | """Press a trigger. Amount is in [0, 1], with 0 as released.""" 58 | assert trigger in Trigger 59 | # assert 0 <= amount <= 1 60 | self.pipe.write('SET {} {:.2f}\n'.format(trigger.name, amount)) 61 | 62 | def tilt_stick(self, stick, x, y): 63 | """Tilt a stick. x and y are in [0, 1], with 0.5 as neutral.""" 64 | assert stick in Stick 65 | try: 66 | assert 0 <= x <= 1 and 0 <= y <= 1 67 | except AssertionError: 68 | import ipdb; ipdb.set_trace() 69 | self.pipe.write('SET {} {:.2f} {:.2f}\n'.format(stick.name, x, y)) 70 | 71 | def send_controller(self, controller): 72 | for button in Button: 73 | field = 'button_' + button.name 74 | if hasattr(controller, field): 75 | if getattr(controller, field): 76 | self.press_button(button) 77 | else: 78 | self.release_button(button) 79 | 80 | # for trigger in Trigger: 81 | # field = 'trigger_' + trigger.name 82 | # self.press_trigger(trigger, getattr(controller, field)) 83 | 84 | for stick in Stick: 85 | field = 'stick_' + stick.name 86 | value = getattr(controller, field) 87 | self.tilt_stick(stick, value.x, value.y) 88 | -------------------------------------------------------------------------------- /dolphin/ctype_util.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from enum import IntEnum 3 | from itertools import product 4 | import numpy as np 5 | from numpy import random 6 | 7 | def copy(src, dst): 8 | """Copies the contents of src to dst""" 9 | pointer(dst)[0] = src 10 | 11 | knownCTypes = set([c_float, c_uint, c_int, c_bool]) 12 | 13 | def toString(struct): 14 | fields = [field + "=" + str(getattr(struct, field)) for (field, _) in struct._fields_] 15 | return "%s{%s}" % (struct.__class__.__name__, ", ".join(fields)) 16 | 17 | def toTuple(value, ctype=None): 18 | if ctype is None: 19 | ctype = type(value) 20 | if ctype in knownCTypes: 21 | return value 22 | if issubclass(ctype, Structure): 23 | return tuple(toTuple(getattr(value, f), t) for f, t in ctype._fields_) 24 | # an array type 25 | return tuple(toTuple(v, ctype._type_) for v in value) 26 | 27 | def toDict(value, ctype=None): 28 | if ctype is None: 29 | ctype = type(value) 30 | if ctype in knownCTypes: 31 | return value 32 | if issubclass(ctype, Structure): 33 | return {f: toDict(getattr(value, f), t) for f, t in ctype._fields_} 34 | # an array type 35 | return [toDict(v, ctype._type_) for v in value] 36 | 37 | def hashStruct(struct): 38 | return hash(toTuple(struct)) 39 | 40 | def eqStruct(struct1, struct2): 41 | return toTuple(struct1) == toTuple(struct2) 42 | 43 | def toCType(t): 44 | if issubclass(t, IntEnum): 45 | return c_uint 46 | return t 47 | 48 | # class decorator 49 | def pretty_struct(cls): 50 | cls._fields_ = [(name, toCType(t)) for name, t in cls._fields] 51 | cls.__repr__ = toString 52 | cls.__hash__ = hashStruct 53 | cls.__eq__ = eqStruct 54 | 55 | cls.allValues = classmethod(allValues) 56 | cls.randomValue = classmethod(randomValue) 57 | 58 | return cls 59 | 60 | def allValues(ctype): 61 | if issubclass(ctype, IntEnum): 62 | return list(ctype) 63 | 64 | if issubclass(ctype, Structure): 65 | names, types = zip(*ctype._fields) 66 | values = [allValues(t) for t in types] 67 | 68 | def make(vals): 69 | obj = ctype() 70 | for name, val in zip(names, vals): 71 | setattr(obj, name, val) 72 | return obj 73 | 74 | return [make(vals) for vals in product(*values)] 75 | 76 | # TODO: handle bounded ints via _fields 77 | # TODO: handle arrays 78 | raise TypeError("Unsupported type %s" % ctype) 79 | 80 | def randomValue(ctype): 81 | if issubclass(ctype, IntEnum): 82 | return random.choice(list(ctype)) 83 | 84 | if issubclass(ctype, Structure): 85 | obj = ctype() 86 | for name, type_ in ctype._fields: 87 | setattr(obj, name, randomValue(type_)) 88 | return obj 89 | 90 | # TODO: handle arrays 91 | raise TypeError("Unsupported type %s" % ctype) 92 | -------------------------------------------------------------------------------- /dolphin/menu_manager.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from .pad import * 4 | 5 | characters = dict( 6 | fox = (-23.5, 11.5), 7 | falco = (-30, 11), 8 | falcon = (18, 18), 9 | roy = (18, 5), 10 | marth = (11, 5), 11 | zelda = (11, 11), 12 | sheik = (11, 11), 13 | mewtwo = (-2, 5), 14 | luigi = (-16, 18), 15 | puff = (-10, 5), 16 | kirby = (-2, 11), 17 | peach = (-2, 18), 18 | ganon = (23, 18), 19 | samus = (3, 11), 20 | ) 21 | 22 | settings = (0, 24) 23 | 24 | class MoveTo: 25 | def __init__(self, target, pid, pad, relative=False): 26 | self.target = target 27 | self.pid = pid 28 | self.pad = pad 29 | self.reached = False 30 | self.relative = relative 31 | 32 | def move(self, state): 33 | player = state.players[self.pid] 34 | 35 | if self.relative: 36 | self.target[0] += player.cursor_x 37 | self.target[1] += player.cursor_y 38 | self.relative = False 39 | 40 | dx = self.target[0] - player.cursor_x 41 | dy = self.target[1] - player.cursor_y 42 | mag = math.sqrt(dx * dx + dy * dy) 43 | if mag < 0.3: 44 | self.pad.tilt_stick(Stick.MAIN, 0.5, 0.5) 45 | self.reached = True 46 | else: 47 | self.pad.tilt_stick(Stick.MAIN, 0.5 * (dx / (mag+1)) + 0.5, 0.5 * (dy / (mag+1)) + 0.5) 48 | self.reached = False 49 | 50 | def done(self): 51 | return self.reached 52 | 53 | class Wait: 54 | def __init__(self, frames): 55 | self.frames = frames 56 | 57 | def done(self): 58 | return self.frames == 0 59 | 60 | def move(self, state): 61 | self.frames -= 1 62 | 63 | class Action: 64 | def __init__(self, action, pad): 65 | self.action = action 66 | self.pad = pad 67 | self.acted = False 68 | 69 | def done(self): 70 | return self.acted 71 | 72 | def move(self, state): 73 | self.action(self.pad) 74 | self.acted = True 75 | 76 | class Sequential: 77 | def __init__(self, *actions): 78 | self.actions = actions 79 | self.index = 0 80 | 81 | def move(self, state): 82 | if not self.done(): 83 | action = self.actions[self.index] 84 | if action.done(): 85 | self.index += 1 86 | else: 87 | action.move(state) 88 | 89 | def done(self): 90 | return self.index == len(self.actions) 91 | 92 | class Parallel: 93 | def __init__(self, *actions): 94 | self.actions = actions 95 | self.complete = False 96 | 97 | def move(self, state): 98 | self.complete = True 99 | for action in self.actions: 100 | if not action.done(): 101 | action.move(state) 102 | self.complete = False 103 | 104 | def done(self): 105 | return self.complete 106 | 107 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | 3 | See: 4 | https://packaging.python.org/en/latest/distributing.html 5 | https://github.com/pypa/sampleproject 6 | """ 7 | 8 | # Always prefer setuptools over distutils 9 | from setuptools import setup, find_packages 10 | # To use a consistent encoding 11 | from codecs import open 12 | from os import path 13 | 14 | here = path.abspath(path.dirname(__file__)) 15 | 16 | # Get the long description from the README file 17 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 18 | long_description = f.read() 19 | 20 | setup( 21 | name='gym-dolphin', 22 | 23 | # Versions should comply with PEP440. For a discussion on single-sourcing 24 | # the version across setup.py and the project code, see 25 | # https://packaging.python.org/en/latest/single_source_version.html 26 | version='0.1.0', 27 | 28 | description='Nintendo GameCube and Wii games for OpenAI gym using the Dolphin emulator.', 29 | long_description=long_description, 30 | 31 | # The project's main homepage. 32 | url='https://github.com/vladfi1/gym-dolphin', 33 | 34 | # Author details 35 | author='Vlad Firoiu', 36 | #author_email='pypa-dev@googlegroups.com', 37 | 38 | # Choose your license 39 | license='MIT', 40 | 41 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 42 | classifiers=[ 43 | # How mature is this project? Common values are 44 | # 3 - Alpha 45 | # 4 - Beta 46 | # 5 - Production/Stable 47 | 'Development Status :: 3 - Alpha', 48 | 49 | # Indicate who your project is intended for 50 | 'Intended Audience :: Developers', 51 | 'Topic :: Scientific/Engineering :: Artificial Intelligence', 52 | 53 | # Pick your license as you wish (should match "license" above) 54 | 'License :: OSI Approved :: MIT License', 55 | 56 | # Specify the Python versions you support here. In particular, ensure 57 | # that you indicate whether you support Python 2, Python 3 or both. 58 | 'Programming Language :: Python :: 3', 59 | 'Programming Language :: Python :: 3.3', 60 | 'Programming Language :: Python :: 3.4', 61 | 'Programming Language :: Python :: 3.5', 62 | ], 63 | 64 | # What does your project relate to? 65 | keywords='gym dolphin ssbm', 66 | 67 | # You can just specify the packages manually here if your project is 68 | # simple. Or you can use find_packages(). 69 | #packages=find_packages(exclude=['contrib', 'docs', 'tests']), 70 | packages=['.'], 71 | 72 | # Alternatively, if you want to distribute just a my_module.py, uncomment 73 | # this: 74 | # py_modules=["my_module"], 75 | 76 | # List run-time dependencies here. These will be installed by pip when 77 | # your project is installed. For an analysis of "install_requires" vs pip's 78 | # requirements files see: 79 | # https://packaging.python.org/en/latest/requirements.html 80 | install_requires=['attrs', 'zmq', 'gym'], 81 | 82 | # List additional groups of dependencies here (e.g. development 83 | # dependencies). You can install these using the following syntax, 84 | # for example: 85 | # $ pip install -e .[dev,test] 86 | #extras_require={ 87 | # 'dev': ['check-manifest'], 88 | # 'test': ['coverage'], 89 | #}, 90 | 91 | # If there are data files included in your packages that need to be 92 | # installed, specify them here. If using Python 2.6 or less, then these 93 | # have to be included in MANIFEST.in as well. 94 | package_data={ 95 | #'sample': ['package_data.dat'], 96 | }, 97 | 98 | # Although 'package_data' is the preferred approach, in some case you may 99 | # need to place data files outside of your packages. See: 100 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa 101 | # In this case, 'data_file' will be installed into '/my_data' 102 | #data_files=[('my_data', ['data/data_file'])], 103 | 104 | # To provide executable scripts, use entry points in preference to the 105 | # "scripts" keyword. Entry points provide cross-platform support and allow 106 | # pip to create the appropriate form of executable for the target platform. 107 | #entry_points={ 108 | # 'console_scripts': [ 109 | # 'sample=sample:main', 110 | # ], 111 | #}, 112 | ) 113 | -------------------------------------------------------------------------------- /dolphin/util.py: -------------------------------------------------------------------------------- 1 | from numpy import random 2 | import functools 3 | import operator 4 | from threading import Thread 5 | import hashlib 6 | import os 7 | 8 | def foldl(f, init, l): 9 | for x in l: 10 | init = f(init, x) 11 | return init 12 | 13 | def foldl1(f, l): 14 | return foldl(f, l[0], l[1:]) 15 | 16 | def foldr(f, init, l): 17 | for x in reversed(l): 18 | init = f(x, init) 19 | return init 20 | 21 | def foldr1(f, l): 22 | return foldr(f, l[-1], l[:-1]) 23 | 24 | def scanl(f, init, l): 25 | r = [init] 26 | for x in l: 27 | r.append(f(r[-1], x)) 28 | return r 29 | 30 | def scanl1(f, l): 31 | return scanl(f, l[0], l[1:]) 32 | 33 | def scanr(f, init, l): 34 | r = [init] 35 | for x in reversed(l): 36 | r.append(f(x, r[-1])) 37 | r.reverse() 38 | return r 39 | 40 | def scanr1(f, l): 41 | return scanr(f, l[-1], l[:-1]) 42 | 43 | def zipWith(f, *sequences): 44 | return [f(*args) for args in zip(*sequences)] 45 | 46 | def compose(*fs): 47 | "compose(f1, f2, ..., fn)(x) = f1(f2( ... fn(x)))" 48 | def composed(x): 49 | for f in reversed(fs): 50 | x = f(x) 51 | return x 52 | return composed 53 | 54 | def deepMap(f, obj): 55 | if isinstance(obj, dict): 56 | return {k : deepMap(f, v) for k, v in obj.items()} 57 | if isinstance(obj, (list, tuple)): 58 | return type(obj)(deepMap(f, x) for x in obj) 59 | return f(obj) 60 | 61 | def deepValues(obj): 62 | if isinstance(obj, dict): 63 | for v in obj.values(): 64 | for x in deepValues(v): 65 | yield x 66 | elif isinstance(obj, list): 67 | for v in obj: 68 | for x in deepValues(v): 69 | yield x 70 | else: # note that tuples are values, not lists 71 | yield obj 72 | 73 | def deepZip(*objs): 74 | if len(objs) == 0: 75 | return [] 76 | 77 | first = objs[0] 78 | if isinstance(first, dict): 79 | return {k : deepZip(*[obj[k] for obj in objs]) for k in first} 80 | if isinstance(first, (list, tuple)): 81 | return zipWith(deepZip, *objs) 82 | return objs 83 | 84 | def flip(p): 85 | return random.binomial(1, p) 86 | 87 | def product(xs): 88 | return functools.reduce(operator.mul, xs, 1.0) 89 | 90 | def async_map(f, xs): 91 | n = len(xs) 92 | ys = n * [None] 93 | 94 | def run(i): 95 | ys[i] = f(xs[i]) 96 | 97 | threads = n * [None] 98 | for i in range(n): 99 | threads[i] = Thread(target=run, args=[i]) 100 | threads[i].start() 101 | 102 | def wait(): 103 | for p in threads: 104 | p.join() 105 | return ys 106 | 107 | return wait 108 | 109 | def chunk(l, n): 110 | return [l[i:i+n] for i in range(0, len(l), n)] 111 | 112 | class MovingAverage: 113 | def __init__(self, rate=0.99, initial=0): 114 | self.rate = rate 115 | self.avg = initial 116 | 117 | def append(val): 118 | self.avg *= self.rate 119 | self.avg += (1.0 - self.rate) * val 120 | 121 | class CircularQueue: 122 | def __init__(self, size=None, init=None, array=None): 123 | if array: 124 | self.size = len(array) 125 | self.array = array 126 | else: 127 | self.size = size 128 | self.array = [init] * size 129 | self.index = 0 130 | 131 | def push(self, obj): 132 | self.array[self.index] = obj 133 | self.increment() 134 | return self.array[self.index] 135 | 136 | def peek(self): 137 | return self.array[self.index] 138 | 139 | def increment(self): 140 | self.index += 1 141 | self.index %= self.size 142 | 143 | def __getitem__(self, index): 144 | if index < 0 or index >= self.size: 145 | raise IndexError 146 | 147 | return self.array[(self.index + index) % self.size] 148 | 149 | def __len__(self): 150 | return self.size 151 | 152 | def as_list(self): 153 | return self.array[self.index:] + self.array[:self.index] 154 | 155 | def hashString(s): 156 | s = s.encode() 157 | return hashlib.md5(s).hexdigest() 158 | 159 | def port(s): 160 | print("PORT", s) 161 | return 5536 + int(hashString(s), 16) % 60000 162 | 163 | def makedirs(path): 164 | if not os.path.exists(path): 165 | os.makedirs(path) 166 | 167 | def update(dikt, **kwargs): 168 | for k, v in kwargs.items(): 169 | if v is not None: 170 | dikt[k] = v 171 | elif k not in dikt: 172 | dikt[k] = None 173 | 174 | def load_params(path, key=None): 175 | import json 176 | with open(path + 'params') as f: 177 | params = json.load(f) 178 | 179 | # support old-style separation of params into train and agent 180 | if key and key in params: 181 | params.update(params[key]) 182 | 183 | params.update(path=path) 184 | return params 185 | 186 | -------------------------------------------------------------------------------- /dolphin/ssbm.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from .ctype_util import * 3 | from enum import IntEnum 4 | import os 5 | import numpy as np 6 | import itertools 7 | 8 | @pretty_struct 9 | class Stick(Structure): 10 | _fields = [ 11 | ('x', c_float), 12 | ('y', c_float), 13 | ] 14 | 15 | def __init__(self, x=0.5, y=0.5): 16 | self.x = x 17 | self.y = y 18 | 19 | def reset(self): 20 | self.x = 0.5 21 | self.y = 0.5 22 | 23 | @pretty_struct 24 | class RealControllerState(Structure): 25 | _fields = [ 26 | ('button_A', c_bool), 27 | ('button_B', c_bool), 28 | ('button_X', c_bool), 29 | ('button_Y', c_bool), 30 | ('button_Z', c_bool), 31 | ('button_L', c_bool), 32 | ('button_R', c_bool), 33 | ('button_START', c_bool), 34 | 35 | ('trigger_L', c_float), 36 | ('trigger_R', c_float), 37 | 38 | ('stick_MAIN', Stick), 39 | ('stick_C', Stick), 40 | ] 41 | 42 | def __init__(self): 43 | self.reset() 44 | 45 | def reset(self): 46 | "Resets controller to neutral position." 47 | self.button_A = False 48 | self.button_B = False 49 | self.button_X = False 50 | self.button_Y = False 51 | self.button_L = False 52 | self.button_R = False 53 | 54 | self.analog_L = 0.0 55 | self.analog_R = 0.0 56 | 57 | self.stick_MAIN.reset() 58 | self.stick_C.reset() 59 | 60 | @pretty_struct 61 | class PlayerMemory(Structure): 62 | _fields = [ 63 | ('percent', c_uint), 64 | ('stock', c_uint), 65 | # True is right, false is left 66 | ('facing', c_float), 67 | ('x', c_float), 68 | ('y', c_float), 69 | ('z', c_float), 70 | ('action_state', c_uint), 71 | ('action_counter', c_uint), 72 | ('action_frame', c_float), 73 | ('character', c_uint), 74 | ('invulnerable', c_bool), 75 | ('hitlag_frames_left', c_float), 76 | ('hitstun_frames_left', c_float), 77 | ('jumps_used', c_uint), 78 | ('charging_smash', c_bool), 79 | ('in_air', c_bool), 80 | ('speed_air_x_self', c_float), 81 | ('speed_ground_x_self', c_float), 82 | ('speed_y_self', c_float), 83 | ('speed_x_attack', c_float), 84 | ('speed_y_attack', c_float), 85 | ('shield_size', c_float), 86 | 87 | ('cursor_x', c_float), 88 | ('cursor_y', c_float), 89 | 90 | # NOTE: the sticks here are [-1, 1], 91 | # not [0, 1] like in pad.py 92 | ('controller', RealControllerState) 93 | ] 94 | 95 | @pretty_struct 96 | class GameMemory(Structure): 97 | _fields = [ 98 | ('players', PlayerMemory * 4), 99 | 100 | ('frame', c_uint), 101 | ('menu', c_uint), 102 | ('stage', c_uint) 103 | ] 104 | 105 | class SimpleButton(IntEnum): 106 | NONE = 0 107 | A = 1 108 | B = 2 109 | Z = 3 110 | Y = 4 111 | L = 5 112 | 113 | axis_granularity = 3 114 | axis_positions = np.linspace(0, 1, axis_granularity) 115 | 116 | @pretty_struct 117 | class SimpleStick(Structure): 118 | _fields = [ 119 | ('x', c_uint), 120 | ('y', c_uint) 121 | ] 122 | 123 | def __init__(self, x, y): 124 | self.x = x 125 | self.y = y 126 | 127 | def reset(self): 128 | self.x = axis_granularity // 2 129 | self.y = self.x 130 | 131 | def realStick(self): 132 | return Stick(axis_positions[self.x], axis_positions[self.y]) 133 | 134 | simpleSticks = [SimpleStick(x, y) for x, y in itertools.product(range(axis_granularity), repeat=2)] 135 | 136 | @pretty_struct 137 | class SimpleControllerState(Structure): 138 | _fields = [ 139 | ('button', SimpleButton), 140 | ('stick_MAIN', SimpleStick), 141 | ] 142 | 143 | def __init__(self, button=None, stick=None): 144 | self.reset() 145 | if button is not None: 146 | self.button = button 147 | if stick is not None: 148 | self.stick_MAIN = stick 149 | 150 | def reset(self): 151 | self.button = SimpleButton.NONE 152 | self.stick_MAIN.reset() 153 | 154 | def realController(self): 155 | controller = RealControllerState() 156 | if self.button is not SimpleButton.NONE: 157 | setattr(controller, "button_%s" % SimpleButton(self.button).name, True) 158 | 159 | controller.stick_MAIN = self.stick_MAIN.realStick() 160 | return controller 161 | 162 | def fromIndex(index): 163 | return simpleControllerStates[index] 164 | 165 | # simpleControllerStates = SimpleControllerState.allValues() 166 | simpleControllerStates = [SimpleControllerState(button, stick) for button in SimpleButton for stick in simpleSticks] 167 | 168 | for i, c in enumerate(simpleControllerStates): 169 | c.index = i 170 | 171 | @pretty_struct 172 | class SimpleStateAction(Structure): 173 | _fields = [ 174 | ('state', GameMemory), 175 | ('prev_action', c_uint), 176 | ('action', c_uint), 177 | ] 178 | -------------------------------------------------------------------------------- /dolphin/dolphin.py: -------------------------------------------------------------------------------- 1 | dolphin_ini = """ 2 | [General] 3 | LastFilename = SSBM.iso 4 | ShowLag = False 5 | ShowFrameCount = False 6 | ISOPaths = 2 7 | RecursiveISOPaths = False 8 | NANDRootPath = 9 | WirelessMac = 10 | ISOPath0 = 11 | ISOPath1 = ./ 12 | [Interface] 13 | ConfirmStop = True 14 | UsePanicHandlers = True 15 | OnScreenDisplayMessages = True 16 | HideCursor = False 17 | AutoHideCursor = False 18 | MainWindowPosX = 100 19 | MainWindowPosY = 156 20 | MainWindowWidth = 400 21 | MainWindowHeight = 328 22 | Language = 0 23 | ShowToolbar = True 24 | ShowStatusbar = True 25 | ShowLogWindow = False 26 | ShowLogConfigWindow = False 27 | ExtendedFPSInfo = False 28 | ThemeName40 = Clean 29 | PauseOnFocusLost = False 30 | [Display] 31 | FullscreenResolution = Auto 32 | Fullscreen = False 33 | RenderToMain = False 34 | RenderWindowXPos = 0 35 | RenderWindowYPos = 0 36 | RenderWindowWidth = 640 37 | RenderWindowHeight = 528 38 | RenderWindowAutoSize = False 39 | KeepWindowOnTop = False 40 | ProgressiveScan = False 41 | PAL60 = True 42 | DisableScreenSaver = True 43 | ForceNTSCJ = False 44 | [GameList] 45 | ListDrives = False 46 | ListWad = True 47 | ListElfDol = True 48 | ListWii = True 49 | ListGC = True 50 | ListJap = True 51 | ListPal = True 52 | ListUsa = True 53 | ListAustralia = True 54 | ListFrance = True 55 | ListGermany = True 56 | ListItaly = True 57 | ListKorea = True 58 | ListNetherlands = True 59 | ListRussia = True 60 | ListSpain = True 61 | ListTaiwan = True 62 | ListWorld = True 63 | ListUnknown = True 64 | ListSort = 3 65 | ListSortSecondary = 0 66 | ColorCompressed = True 67 | ColumnPlatform = True 68 | ColumnBanner = True 69 | ColumnNotes = True 70 | ColumnFileName = False 71 | ColumnID = False 72 | ColumnRegion = True 73 | ColumnSize = True 74 | ColumnState = True 75 | [Core] 76 | HLE_BS2 = True 77 | TimingVariance = 40 78 | CPUCore = 1 79 | Fastmem = True 80 | CPUThread = {cpu_thread} 81 | DSPHLE = True 82 | SkipIdle = True 83 | SyncOnSkipIdle = True 84 | SyncGPU = False 85 | SyncGpuMaxDistance = 200000 86 | SyncGpuMinDistance = -200000 87 | SyncGpuOverclock = 1.00000000 88 | FPRF = False 89 | AccurateNaNs = False 90 | DefaultISO = 91 | DVDRoot = 92 | Apploader = 93 | EnableCheats = True 94 | SelectedLanguage = 0 95 | OverrideGCLang = False 96 | DPL2Decoder = False 97 | Latency = 2 98 | MemcardAPath = {user}/GC/MemoryCardA.USA.raw 99 | MemcardBPath = {user}/GC/MemoryCardB.USA.raw 100 | AgpCartAPath = 101 | AgpCartBPath = 102 | SlotA = 255 103 | SlotB = 255 104 | SerialPort1 = 255 105 | BBA_MAC = 106 | SIDevice0 = 6 107 | AdapterRumble0 = True 108 | SimulateKonga0 = False 109 | SIDevice1 = 6 110 | AdapterRumble1 = True 111 | SimulateKonga1 = False 112 | SIDevice2 = 0 113 | AdapterRumble2 = True 114 | SimulateKonga2 = False 115 | SIDevice3 = 0 116 | AdapterRumble3 = True 117 | SimulateKonga3 = False 118 | WiiSDCard = False 119 | WiiKeyboard = False 120 | WiimoteContinuousScanning = False 121 | WiimoteEnableSpeaker = False 122 | RunCompareServer = False 123 | RunCompareClient = False 124 | EmulationSpeed = {speed} 125 | FrameSkip = 0x00000000 126 | Overclock = 1.00000000 127 | OverclockEnable = False 128 | GFXBackend = {gfx} 129 | GPUDeterminismMode = auto 130 | PerfMapDir = 131 | [Movie] 132 | PauseMovie = False 133 | Author = 134 | DumpFrames = {dump_frames} 135 | DumpFramesSilent = True 136 | ShowInputDisplay = True 137 | [DSP] 138 | EnableJIT = True 139 | DumpAudio = False 140 | DumpUCode = False 141 | Backend = {audio} 142 | Volume = 50 143 | CaptureLog = False 144 | [Input] 145 | BackgroundInput = True 146 | [FifoPlayer] 147 | LoopReplay = True 148 | """ 149 | 150 | gale01_ini = """ 151 | [Gecko_Enabled] 152 | $Netplay Community Settings 153 | """ 154 | 155 | pipeConfig = """ 156 | Buttons/A = `Button A` 157 | Buttons/B = `Button B` 158 | Buttons/X = `Button X` 159 | Buttons/Y = `Button Y` 160 | Buttons/Z = `Button Z` 161 | Main Stick/Up = `Axis MAIN Y +` 162 | Main Stick/Down = `Axis MAIN Y -` 163 | Main Stick/Left = `Axis MAIN X -` 164 | Main Stick/Right = `Axis MAIN X +` 165 | Triggers/L = `Button L` 166 | Triggers/R = `Button R` 167 | D-Pad/Up = `Button D_UP` 168 | D-Pad/Down = `Button D_DOWN` 169 | D-Pad/Left = `Button D_LEFT` 170 | D-Pad/Right = `Button D_RIGHT` 171 | Buttons/Start = `Button START` 172 | C-Stick/Up = `Axis C Y +` 173 | C-Stick/Down = `Axis C Y -` 174 | C-Stick/Left = `Axis C X -` 175 | C-Stick/Right = `Axis C X +` 176 | """ 177 | #Triggers/L-Analog = `Axis L -+` 178 | #Triggers/R-Analog = `Axis R -+` 179 | 180 | def generatePipeConfig(player, count): 181 | config = "[GCPad%d]\n" % (player+1) 182 | config += "Device = Pipe/%d/phillip%d\n" % (count, player) 183 | config += pipeConfig 184 | return config 185 | 186 | # TODO: make this configurable 187 | def generateGCPadNew(pids=[1]): 188 | config = "" 189 | count = 0 190 | for p in sorted(pids): 191 | config += generatePipeConfig(p, count) 192 | count += 1 193 | return config 194 | 195 | import tempfile 196 | import shutil 197 | import os 198 | from . import util 199 | from .default import * 200 | 201 | class SetupUser(Default): 202 | _options = [ 203 | Option('gfx', type=str, default="Null", help="graphics backend"), 204 | Option('cpu_thread', action="store_true", default=True, help="Use separate gpu and cpu threads."), 205 | Option('cpus', type=int, nargs='+', default=[1], help="Which players are cpu-controlled."), 206 | Option('audio', type=str, default="No audio backend", help="audio backend"), 207 | Option('speed', type=int, default=0, help='framerate - 100=normal, 0=unlimited'), 208 | Option('dump_frames', action="store_true", default=False, help="dump frames from dolphin to disk"), 209 | ] 210 | 211 | def __call__(self, user, ): 212 | configDir = user + 'Config/' 213 | util.makedirs(configDir) 214 | 215 | with open(configDir + 'GCPadNew.ini', 'w') as f: 216 | f.write(generateGCPadNew(self.cpus)) 217 | 218 | with open(configDir + 'Dolphin.ini', 'w') as f: 219 | config_args = dict( 220 | user=user, 221 | gfx=self.gfx, 222 | cpu_thread=self.cpu_thread, 223 | dump_frames=self.dump_frames, 224 | audio=self.audio, 225 | speed=self.speed 226 | ) 227 | f.write(dolphin_ini.format(**config_args)) 228 | 229 | gameSettings = user + "GameSettings/" 230 | util.makedirs(gameSettings) 231 | 232 | with open(gameSettings+'GALE01.ini', 'w') as f: 233 | f.write(gale01_ini) 234 | 235 | util.makedirs(user + 'Dump/Frames/') 236 | 237 | import subprocess 238 | 239 | class DolphinRunner(Default): 240 | _options = [ 241 | Option('exe', type=str, default='dolphin-emu-headless', help="dolphin executable"), 242 | Option('user', type=str, help="path to dolphin user directory"), 243 | Option('iso', type=str, default="SSBM.iso", help="path to SSBM iso"), 244 | Option('movie', type=str, help="path to dolphin movie file to play at startup"), 245 | Option('setup', type=int, default=1, help="setup custom dolphin directory"), 246 | Option('gui', action="store_true", default=False, help="run with graphics and sound at normal speed"), 247 | Option('mute', action="store_true", default=False, help="mute game audio"), 248 | ] 249 | 250 | _members = [ 251 | ('setupUser', SetupUser) 252 | ] 253 | 254 | def __init__(self, **kwargs): 255 | Default.__init__(self, init_members=False, **kwargs) 256 | 257 | if self.user is None: 258 | self.user = tempfile.mkdtemp() + '/' 259 | 260 | if self.gui: 261 | self.exe = 'dolphin-emu-nogui' 262 | 263 | if 'speed' not in kwargs: 264 | kwargs['speed'] = 1 265 | 266 | if 'gfx' not in kwargs: 267 | kwargs['gfx'] = 'OGL' 268 | 269 | if self.mute: 270 | kwargs.update(audio = 'No audio backend') 271 | else: 272 | kwargs.update(audio = 'ALSA') 273 | 274 | if self.setup: 275 | self._init_members(**kwargs) 276 | self.setupUser(self.user) 277 | 278 | def __call__(self): 279 | args = [self.exe, "--user", self.user, "--exec", self.iso] 280 | if self.movie is not None: 281 | args += ["--movie", self.movie] 282 | 283 | return subprocess.Popen(args) 284 | 285 | -------------------------------------------------------------------------------- /dolphin/state_manager.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import attr 3 | from . import ssbm, fields 4 | 5 | def generic_wrapper(value, wrapper, default): 6 | if wrapper is not None: 7 | try: 8 | value = wrapper(value) 9 | except ValueError: 10 | value = default 11 | return value 12 | 13 | intStruct = struct.Struct('>i') 14 | 15 | byte_mask = 0xFF 16 | short_mask = 0xFFFF 17 | int_mask = 0xFFFFFFFF 18 | 19 | @attr.s 20 | class IntHandler: 21 | shift = attr.ib(default=0) 22 | mask = attr.ib(default=int_mask) 23 | wrapper = attr.ib(default=None) 24 | default = attr.ib(default=0) 25 | 26 | def __call__(self, value): 27 | transformed = (intStruct.unpack(value)[0] >> self.shift) & self.mask 28 | return generic_wrapper(transformed, self.wrapper, self.default) 29 | 30 | intHandler = IntHandler() 31 | byteHandler = IntHandler(shift=24, mask=byte_mask) 32 | shortHandler = IntHandler(shift=16, mask=short_mask) 33 | 34 | floatStruct = struct.Struct('>f') 35 | 36 | @attr.s 37 | class FloatHandler: 38 | wrapper = attr.ib(default=None) 39 | default = attr.ib(default=0.0) 40 | 41 | def __call__(self, value): 42 | as_float = floatStruct.unpack(value)[0] 43 | return generic_wrapper(as_float, self.wrapper, self.default) 44 | 45 | floatHandler = FloatHandler() 46 | 47 | @attr.s 48 | class Handler: 49 | path = attr.ib() 50 | handler = attr.ib() 51 | 52 | def __call__(self, obj, value): 53 | fields.setPath(obj, self.path, self.handler(value)) 54 | 55 | # TODO: use numbers instead of strings to hash addresses 56 | def add_address(x, y): 57 | """Returns a string representation of the sum of the two parameters. 58 | 59 | x is a hex string address that can be converted to an int. 60 | y is an int. 61 | """ 62 | return "{0:08X}".format(int(x, 16) + y) 63 | 64 | # see https://docs.google.com/spreadsheets/d/1JX2w-r2fuvWuNgGb6D3Cs4wHQKLFegZe2jhbBuIhCG8 65 | 66 | global_addresses = {} 67 | 68 | global_addresses['80479D60'] = Handler(['frame'], intHandler) 69 | global_addresses['80479D30'] = Handler(['menu'], IntHandler(mask=byte_mask))#, Menu, Menu.Characters) 70 | global_addresses['804D6CAD'] = Handler(['stage'], shortHandler)#, Stage, Stage.Unselected) 71 | 72 | def playerAddresses(player_id, addresses=None): 73 | if addresses is None: 74 | addresses = {} 75 | 76 | player_path = ['players', player_id] 77 | 78 | def playerHandler(field, handler): 79 | return Handler(player_path + field.split('/'), handler) 80 | 81 | cursor_x_address = add_address('81118DEC', -0xB80 * player_id) 82 | cursor_y_address = add_address('81118DF0', -0xB80 * player_id) 83 | addresses[cursor_x_address] = playerHandler('cursor_x', floatHandler) 84 | addresses[cursor_y_address] = playerHandler('cursor_y', floatHandler) 85 | 86 | type_address = add_address('803F0E08', 0x24 * player_id) 87 | type_handler = playerHandler('type', byteHandler) #, PlayerType, PlayerType.Unselected) 88 | character_handler = playerHandler('character', IntHandler(8, byte_mask)) #, Character, Character.Unselected) 89 | addresses[type_address] = [character_handler]#, type_handler] 90 | 91 | button_address = add_address('0x804C1FAC', 0x44 * player_id) 92 | button_locs = dict( 93 | Z = 4, 94 | L = 5, 95 | R = 6, 96 | A = 8, 97 | B = 9, 98 | X = 10, 99 | Y = 11, 100 | START = 12 101 | ).items() 102 | addresses[button_address] = [playerHandler('controller/button_%s' % b, IntHandler(mask=1< x or x > self.high: 64 | if self.verbose: 65 | warn("%f out of bounds in real space \"%s\"" % (x, name)) 66 | x = clip(x, self.low, self.high) 67 | return np.array([x]) 68 | 69 | class DiscreteConv: 70 | def __init__(self, size): 71 | self.space = spaces.Discrete(size) 72 | 73 | def __call__(self, x, name=None, verbose=True): 74 | if 0 > x or x >= self.space.n: 75 | if verbose: 76 | warn("%d out of bounds in discrete space \"%s\"" % (x, name)) 77 | x = 0 78 | return x 79 | 80 | class StructConv: 81 | def __init__(self, spec): 82 | self.spec = spec 83 | 84 | self.space = spaces.Tuple([conv.space for _, conv in spec]) 85 | 86 | def __call__(self, struct, **kwargs): 87 | return [conv(getattr(struct, name), name=name) for name, conv in self.spec] 88 | 89 | class ArrayConv: 90 | def __init__(self, conv, permutation): 91 | self.conv = conv 92 | self.permutation = permutation 93 | 94 | self.space = spaces.Tuple([conv.space for _ in permutation]) 95 | 96 | def __call__(self, array, **kwargs): 97 | return [self.conv(array[i]) for i in self.permutation] 98 | 99 | maxCharacter = 32 # should be large enough? 100 | 101 | maxAction = 0x017E 102 | numActions = 1 + maxAction 103 | 104 | frameConv = RealConv(0, 100, 'frame') 105 | speedConv = RealConv(-20, 20, 'speed') # generally less than 1 in magnitude 106 | 107 | player_spec = [ 108 | ('percent', RealConv(0, 999)), 109 | ('facing', RealConv(-1, 1)), 110 | ('x', RealConv(-250, 250)), 111 | ('y', RealConv(-250, 250)), 112 | ('action_state', DiscreteConv(numActions)), 113 | ('action_frame', frameConv), 114 | ('character', DiscreteConv(maxCharacter)), 115 | ('invulnerable', boolConv), 116 | ('hitlag_frames_left', frameConv), 117 | ('hitstun_frames_left', frameConv), 118 | ('jumps_used', DiscreteConv(8)), 119 | ('charging_smash', boolConv), 120 | ('in_air', boolConv), 121 | ('speed_air_x_self', speedConv), 122 | ('speed_ground_x_self', speedConv), 123 | ('speed_y_self', speedConv), 124 | ('speed_x_attack', speedConv), 125 | ('speed_y_attack', speedConv), 126 | ('shield_size', RealConv(0, 100)), 127 | ] 128 | 129 | playerConv = StructConv(player_spec) 130 | 131 | def gameSpec(self=0, enemy=1, swap=False): 132 | players = [self, enemy] 133 | if swap: 134 | players.reverse() 135 | 136 | return [ 137 | ('players', ArrayConv(playerConv, players)), 138 | ('stage', DiscreteConv(32)), 139 | ] 140 | 141 | game_spec = gameSpec() 142 | gameConv = StructConv(game_spec) 143 | 144 | gameConv1 = StructConv(gameSpec(swap=True)) 145 | 146 | class SSBMEnv(gym.Env, Default): 147 | _options = [ 148 | #Option('user', type=str, help="dolphin user directory"), 149 | Option('zmq', type=bool, default=True, help="use zmq for memory watcher"), 150 | Option('stage', type=str, default="final_destination", choices=movie.stages.keys(), help="which stage to play on"), 151 | #Option('enemy', type=str, help="load enemy agent from file"), 152 | #Option('enemy_reload', type=int, default=0, help="enemy reload interval"), 153 | Option('cpu', type=int, default=1, help="enemy cpu level"), 154 | ] + [Option('p%d' % i, type=str, choices=characters.keys(), default="falcon", help="character for player %d" % i) for i in [1, 2]] 155 | 156 | _members = [ 157 | ('dolphinRunner', DolphinRunner) 158 | ] 159 | 160 | def __init__(self, **kwargs): 161 | Default.__init__(self, init_members=False, **kwargs) 162 | 163 | self.observation_space = gameConv.space 164 | self.action_space = controller_space 165 | self.realController = realController 166 | 167 | self.first_frame = True 168 | self.toggle = False 169 | 170 | self.prev_state = ssbm.GameMemory() 171 | self.state = ssbm.GameMemory() 172 | # track players 1 and 2 (pids 0 and 1) 173 | self.sm = state_manager.StateManager([0, 1]) 174 | 175 | self.pids = [1] 176 | self.cpus = {1: None} 177 | self.characters = {1: self.p2} 178 | 179 | if self.cpu: 180 | self.pids.append(0) 181 | self.cpus[0] = self.cpu 182 | self.characters[0] = self.p1 183 | 184 | self._init_members(cpus=self.pids, **kwargs) 185 | self.user = self.dolphinRunner.user 186 | 187 | self.write_locations() 188 | print('Creating MemoryWatcher.') 189 | mwType = memory_watcher.MemoryWatcher 190 | if self.zmq: 191 | mwType = memory_watcher.MemoryWatcherZMQ 192 | self.mw = mwType(self.user + '/MemoryWatcher/MemoryWatcher') 193 | 194 | pipe_dir = self.user + '/Pipes/' 195 | print('Creating Pads at %s.' % pipe_dir) 196 | os.makedirs(self.user + '/Pipes/', exist_ok=True) 197 | 198 | paths = [pipe_dir + 'phillip%d' % i for i in self.pids] 199 | self.get_pads = util.async_map(Pad, paths) 200 | 201 | time.sleep(2) # give pads time to set up 202 | 203 | self.dolphin_process = self.dolphinRunner() 204 | 205 | time.sleep(2) # let dolphin connect to the memory watcher 206 | 207 | try: 208 | self.pads = self.get_pads() 209 | except KeyboardInterrupt: 210 | print("Pipes not initialized!") 211 | return 212 | 213 | self.setup() 214 | 215 | def write_locations(self): 216 | path = self.user + '/MemoryWatcher/' 217 | os.makedirs(path, exist_ok=True) 218 | print('Writing locations to:', path) 219 | with open(path + 'Locations.txt', 'w') as f: 220 | f.write('\n'.join(self.sm.locations())) 221 | 222 | def _close(self): 223 | self.dolphin_process.terminate() 224 | import shutil 225 | shutil.rmtree(self.user) 226 | 227 | def update_state(self): 228 | ctutil.copy(self.state, self.prev_state) 229 | messages = self.mw.get_messages() 230 | for message in messages: 231 | self.sm.handle(self.state, *message) 232 | 233 | def setup(self): 234 | self.update_state() 235 | 236 | pick_chars = [] 237 | 238 | tapA = [ 239 | (0, movie.pushButton(Button.A)), 240 | (0, movie.releaseButton(Button.A)), 241 | ] 242 | 243 | for pid, pad in zip(self.pids, self.pads): 244 | actions = [] 245 | 246 | cpu = self.cpus[pid] 247 | 248 | if cpu: 249 | actions.append(MoveTo([0, 20], pid, pad, True)) 250 | actions.append(movie.Movie(tapA, pad)) 251 | actions.append(movie.Movie(tapA, pad)) 252 | actions.append(MoveTo([0, -14], pid, pad, True)) 253 | actions.append(movie.Movie(tapA, pad)) 254 | actions.append(MoveTo([cpu * 1.1, 0], pid, pad, True)) 255 | actions.append(movie.Movie(tapA, pad)) 256 | #actions.append(Wait(10000)) 257 | 258 | actions.append(MoveTo(characters[self.characters[pid]], pid, pad)) 259 | actions.append(movie.Movie(tapA, pad)) 260 | 261 | pick_chars.append(Sequential(*actions)) 262 | 263 | pick_chars = Parallel(*pick_chars) 264 | 265 | enter_settings = Sequential( 266 | MoveTo(settings, self.pids[0], self.pads[0]), 267 | movie.Movie(tapA, self.pads[0]) 268 | ) 269 | 270 | # sets the game mode and picks the stage 271 | start_game = movie.Movie(movie.endless_netplay + movie.stages[self.stage], self.pads[0]) 272 | 273 | self.navigate_menus = Sequential(pick_chars, enter_settings, start_game) 274 | 275 | char_stages = [menu.value for menu in [Menu.Characters, Menu.Stages]] 276 | 277 | print("Navigating menus.") 278 | while self.state.menu in char_stages: 279 | self.mw.advance() 280 | last_frame = self.state.frame 281 | self.update_state() 282 | 283 | if self.state.frame > last_frame: 284 | self.navigate_menus.move(self.state) 285 | 286 | if self.navigate_menus.done(): 287 | for pid, pad in zip(self.pids, self.pads): 288 | if self.characters[pid] == 'sheik': 289 | pad.press_button(Button.A) 290 | 291 | print("setup finished") 292 | assert(self.state.menu == Menu.Game.value) 293 | 294 | # get rid of weird initial conditions 295 | for _ in range(10): 296 | self.mw.advance() 297 | self.update_state() 298 | 299 | def _seed(self, seed=None): 300 | from gym.utils import seeding 301 | self.np_random, seed = seeding.np_random(seed) 302 | return [seed] 303 | 304 | def _step(self, action): 305 | #import ipdb; ipdb.set_trace() 306 | #assert self.action_space.contains(action), "%r (%s) invalid" % (action, type(action)) 307 | 308 | self.pads[0].send_controller(self.realController(action)) 309 | 310 | self.mw.advance() 311 | self.update_state() 312 | 313 | observation = gameConv(self.state) 314 | reward = computeRewards([self.prev_state, self.state])[0] 315 | 316 | return observation, reward, False, {} 317 | 318 | def _reset(self): 319 | return gameConv(self.state) 320 | 321 | #def _render(self): 322 | # pass 323 | 324 | def simpleSSBMEnv(act_every=3, **kwargs): 325 | env = SSBMEnv(**kwargs) 326 | 327 | # TODO: make this a wrapper 328 | env.action_space = spaces.Discrete(len(ssbm.simpleControllerStates)) 329 | env.realController = lambda action: ssbm.simpleControllerStates[action].realController() 330 | 331 | from .box_wrapper import BoxWrapper 332 | env = BoxWrapper(env) 333 | 334 | from gym.wrappers import SkipWrapper 335 | return SkipWrapper(3)(env) 336 | 337 | -------------------------------------------------------------------------------- /dolphin/state.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | @enum.unique 4 | class PlayerType(enum.Enum): 5 | Human = 0 6 | CPU = 1 7 | Demo = 2 8 | Unselected = 3 9 | 10 | @enum.unique 11 | class Character(enum.Enum): 12 | Doc = 0 13 | Mario = 1 14 | Luigi = 2 15 | Bowser = 3 16 | Peach = 4 17 | Yoshi = 5 18 | DK = 6 19 | Falcon = 7 20 | Ganon = 8 21 | Falco = 9 22 | Fox = 10 23 | Ness = 11 24 | Icies = 12 25 | Kirby = 13 26 | Samus = 14 27 | Zelda = 15 28 | Link = 16 29 | YoungLink = 17 30 | Pichu = 18 31 | Pikachu = 19 32 | Jiggs = 20 33 | Mewtwo = 21 34 | GnW = 22 35 | Marth = 23 36 | Roy = 24 37 | Unselected = 25 38 | 39 | @enum.unique 40 | class Stage(enum.Enum): 41 | PeachCastle = 0 42 | Rainbow = 1 43 | Kongo = 2 44 | Japes = 3 45 | GreatBay = 4 46 | Temple = 5 47 | Story = 6 48 | Island = 7 49 | FoD = 8 50 | Greens = 9 51 | Corneria = 10 52 | Venom = 11 53 | Brinstar = 12 54 | Depths = 13 55 | Onett = 14 56 | Fourside = 15 57 | Mute = 16 58 | BigBlue = 17 59 | Pokemon = 18 60 | Floats = 19 61 | Kingdom = 20 62 | Kingdom2 = 21 63 | Icicle = 22 64 | FlatZone = 23 65 | Battlefield = 24 66 | Final = 25 67 | DreamLand = 26 68 | Island64 = 27 69 | Kongo64 = 28 70 | Random = 29 71 | Unselected = 30 72 | 73 | @enum.unique 74 | class Menu(enum.Enum): 75 | Characters = 0 76 | Stages = 1 77 | Game = 2 78 | PostGame = 4 79 | 80 | @enum.unique 81 | class ActionState(enum.Enum): 82 | DeadDown = 0x0000 83 | DeadLeft = 0x0001 84 | DeadRight = 0x0002 85 | DeadUp = 0x0003 86 | DeadUpStar = 0x0004 87 | DeadUpStarIce = 0x0005 88 | DeadUpFall = 0x0006 89 | DeadUpFallHitCamera = 0x0007 90 | DeadUpFallHitCameraFlat = 0x0008 91 | DeadUpFallIce = 0x0009 92 | DeadUpFallHitCameraIce = 0x000A 93 | Sleep = 0x000B 94 | Rebirth = 0x000C 95 | RebirthWait = 0x000D 96 | Wait = 0x000E 97 | WalkSlow = 0x000F 98 | WalkMiddle = 0x0010 99 | WalkFast = 0x0011 100 | Turn = 0x0012 101 | TurnRun = 0x0013 102 | Dash = 0x0014 103 | Run = 0x0015 104 | RunDirect = 0x0016 105 | RunBrake = 0x0017 106 | KneeBend = 0x0018 107 | JumpF = 0x0019 108 | JumpB = 0x001A 109 | JumpAerialF = 0x001B 110 | JumpAerialB = 0x001C 111 | Fall = 0x001D 112 | FallF = 0x001E 113 | FallB = 0x001F 114 | FallAerial = 0x0020 115 | FallAerialF = 0x0021 116 | FallAerialB = 0x0022 117 | FallSpecial = 0x0023 118 | FallSpecialF = 0x0024 119 | FallSpecialB = 0x0025 120 | DamageFall = 0x0026 121 | Squat = 0x0027 122 | SquatWait = 0x0028 123 | SquatRv = 0x0029 124 | Landing = 0x002A 125 | LandingFallSpecial = 0x002B 126 | Attack11 = 0x002C 127 | Attack12 = 0x002D 128 | Attack13 = 0x002E 129 | Attack100Start = 0x002F 130 | Attack100Loop = 0x0030 131 | Attack100End = 0x0031 132 | AttackDash = 0x0032 133 | AttackS3Hi = 0x0033 134 | AttackS3HiS = 0x0034 135 | AttackS3S = 0x0035 136 | AttackS3LwS = 0x0036 137 | AttackS3Lw = 0x0037 138 | AttackHi3 = 0x0038 139 | AttackLw3 = 0x0039 140 | AttackS4Hi = 0x003A 141 | AttackS4HiS = 0x003B 142 | AttackS4S = 0x003C 143 | AttackS4LwS = 0x003D 144 | AttackS4Lw = 0x003E 145 | AttackHi4 = 0x003F 146 | AttackLw4 = 0x0040 147 | AttackAirN = 0x0041 148 | AttackAirF = 0x0042 149 | AttackAirB = 0x0043 150 | AttackAirHi = 0x0044 151 | AttackAirLw = 0x0045 152 | LandingAirN = 0x0046 153 | LandingAirF = 0x0047 154 | LandingAirB = 0x0048 155 | LandingAirHi = 0x0049 156 | LandingAirLw = 0x004A 157 | DamageHi1 = 0x004B 158 | DamageHi2 = 0x004C 159 | DamageHi3 = 0x004D 160 | DamageN1 = 0x004E 161 | DamageN2 = 0x004F 162 | DamageN3 = 0x0050 163 | DamageLw1 = 0x0051 164 | DamageLw2 = 0x0052 165 | DamageLw3 = 0x0053 166 | DamageAir1 = 0x0054 167 | DamageAir2 = 0x0055 168 | DamageAir3 = 0x0056 169 | DamageFlyHi = 0x0057 170 | DamageFlyN = 0x0058 171 | DamageFlyLw = 0x0059 172 | DamageFlyTop = 0x005A 173 | DamageFlyRoll = 0x005B 174 | LightGet = 0x005C 175 | HeavyGet = 0x005D 176 | LightThrowF = 0x005E 177 | LightThrowB = 0x005F 178 | LightThrowHi = 0x0060 179 | LightThrowLw = 0x0061 180 | LightThrowDash = 0x0062 181 | LightThrowDrop = 0x0063 182 | LightThrowAirF = 0x0064 183 | LightThrowAirB = 0x0065 184 | LightThrowAirHi = 0x0066 185 | LightThrowAirLw = 0x0067 186 | HeavyThrowF = 0x0068 187 | HeavyThrowB = 0x0069 188 | HeavyThrowHi = 0x006A 189 | HeavyThrowLw = 0x006B 190 | LightThrowF4 = 0x006C 191 | LightThrowB4 = 0x006D 192 | LightThrowHi4 = 0x006E 193 | LightThrowLw4 = 0x006F 194 | LightThrowAirF4 = 0x0070 195 | LightThrowAirB4 = 0x0071 196 | LightThrowAirHi4 = 0x0072 197 | LightThrowAirLw4 = 0x0073 198 | HeavyThrowF4 = 0x0074 199 | HeavyThrowB4 = 0x0075 200 | HeavyThrowHi4 = 0x0076 201 | HeavyThrowLw4 = 0x0077 202 | SwordSwing1 = 0x0078 203 | SwordSwing3 = 0x0079 204 | SwordSwing4 = 0x007A 205 | SwordSwingDash = 0x007B 206 | BatSwing1 = 0x007C 207 | BatSwing3 = 0x007D 208 | BatSwing4 = 0x007E 209 | BatSwingDash = 0x007F 210 | ParasolSwing1 = 0x0080 211 | ParasolSwing3 = 0x0081 212 | ParasolSwing4 = 0x0082 213 | ParasolSwingDash = 0x0083 214 | HarisenSwing1 = 0x0084 215 | HarisenSwing3 = 0x0085 216 | HarisenSwing4 = 0x0086 217 | HarisenSwingDash = 0x0087 218 | StarRodSwing1 = 0x0088 219 | StarRodSwing3 = 0x0089 220 | StarRodSwing4 = 0x008A 221 | StarRodSwingDash = 0x008B 222 | LipStickSwing1 = 0x008C 223 | LipStickSwing3 = 0x008D 224 | LipStickSwing4 = 0x008E 225 | LipStickSwingDash = 0x008F 226 | ItemParasolOpen = 0x0090 227 | ItemParasolFall = 0x0091 228 | ItemParasolFallSpecial = 0x0092 229 | ItemParasolDamageFall = 0x0093 230 | LGunShoot = 0x0094 231 | LGunShootAir = 0x0095 232 | LGunShootEmpty = 0x0096 233 | LGunShootAirEmpty = 0x0097 234 | FireFlowerShoot = 0x0098 235 | FireFlowerShootAir = 0x0099 236 | ItemScrew = 0x009A 237 | ItemScrewAir = 0x009B 238 | DamageScrew = 0x009C 239 | DamageScrewAir = 0x009D 240 | ItemScopeStart = 0x009E 241 | ItemScopeRapid = 0x009F 242 | ItemScopeFire = 0x00A0 243 | ItemScopeEnd = 0x00A1 244 | ItemScopeAirStart = 0x00A2 245 | ItemScopeAirRapid = 0x00A3 246 | ItemScopeAirFire = 0x00A4 247 | ItemScopeAirEnd = 0x00A5 248 | ItemScopeStartEmpty = 0x00A6 249 | ItemScopeRapidEmpty = 0x00A7 250 | ItemScopeFireEmpty = 0x00A8 251 | ItemScopeEndEmpty = 0x00A9 252 | ItemScopeAirStartEmpty = 0x00AA 253 | ItemScopeAirRapidEmpty = 0x00AB 254 | ItemScopeAirFireEmpty = 0x00AC 255 | ItemScopeAirEndEmpty = 0x00AD 256 | LiftWait = 0x00AE 257 | LiftWalk1 = 0x00AF 258 | LiftWalk2 = 0x00B0 259 | LiftTurn = 0x00B1 260 | GuardOn = 0x00B2 261 | Guard = 0x00B3 262 | GuardOff = 0x00B4 263 | GuardSetOff = 0x00B5 264 | GuardReflect = 0x00B6 265 | DownBoundU = 0x00B7 266 | DownWaitU = 0x00B8 267 | DownDamageU = 0x00B9 268 | DownStandU = 0x00BA 269 | DownAttackU = 0x00BB 270 | DownFowardU = 0x00BC 271 | DownBackU = 0x00BD 272 | DownSpotU = 0x00BE 273 | DownBoundD = 0x00BF 274 | DownWaitD = 0x00C0 275 | DownDamageD = 0x00C1 276 | DownStandD = 0x00C2 277 | DownAttackD = 0x00C3 278 | DownFowardD = 0x00C4 279 | DownBackD = 0x00C5 280 | DownSpotD = 0x00C6 281 | Passive = 0x00C7 282 | PassiveStandF = 0x00C8 283 | PassiveStandB = 0x00C9 284 | PassiveWall = 0x00CA 285 | PassiveWallJump = 0x00CB 286 | PassiveCeil = 0x00CC 287 | ShieldBreakFly = 0x00CD 288 | ShieldBreakFall = 0x00CE 289 | ShieldBreakDownU = 0x00CF 290 | ShieldBreakDownD = 0x00D0 291 | ShieldBreakStandU = 0x00D1 292 | ShieldBreakStandD = 0x00D2 293 | FuraFura = 0x00D3 294 | Catch = 0x00D4 295 | CatchPull = 0x00D5 296 | CatchDash = 0x00D6 297 | CatchDashPull = 0x00D7 298 | CatchWait = 0x00D8 299 | CatchAttack = 0x00D9 300 | CatchCut = 0x00DA 301 | ThrowF = 0x00DB 302 | ThrowB = 0x00DC 303 | ThrowHi = 0x00DD 304 | ThrowLw = 0x00DE 305 | CapturePulledHi = 0x00DF 306 | CaptureWaitHi = 0x00E0 307 | CaptureDamageHi = 0x00E1 308 | CapturePulledLw = 0x00E2 309 | CaptureWaitLw = 0x00E3 310 | CaptureDamageLw = 0x00E4 311 | CaptureCut = 0x00E5 312 | CaptureJump = 0x00E6 313 | CaptureNeck = 0x00E7 314 | CaptureFoot = 0x00E8 315 | EscapeF = 0x00E9 316 | EscapeB = 0x00EA 317 | Escape = 0x00EB 318 | EscapeAir = 0x00EC 319 | ReboundStop = 0x00ED 320 | Rebound = 0x00EE 321 | ThrownF = 0x00EF 322 | ThrownB = 0x00F0 323 | ThrownHi = 0x00F1 324 | ThrownLw = 0x00F2 325 | ThrownLwWomen = 0x00F3 326 | Pass = 0x00F4 327 | Ottotto = 0x00F5 328 | OttottoWait = 0x00F6 329 | FlyReflectWall = 0x00F7 330 | FlyReflectCeil = 0x00F8 331 | StopWall = 0x00F9 332 | StopCeil = 0x00FA 333 | MissFoot = 0x00FB 334 | CliffCatch = 0x00FC 335 | CliffWait = 0x00FD 336 | CliffClimbSlow = 0x00FE 337 | CliffClimbQuick = 0x00FF 338 | CliffAttackSlow = 0x0100 339 | CliffAttackQuick = 0x0101 340 | CliffEscapeSlow = 0x0102 341 | CliffEscapeQuick = 0x0103 342 | CliffJumpSlow1 = 0x0104 343 | CliffJumpSlow2 = 0x0105 344 | CliffJumpQuick1 = 0x0106 345 | CliffJumpQuick2 = 0x0107 346 | AppealR = 0x0108 347 | AppealL = 0x0109 348 | ShoulderedWait = 0x010A 349 | ShoulderedWalkSlow = 0x010B 350 | ShoulderedWalkMiddle = 0x010C 351 | ShoulderedWalkFast = 0x010D 352 | ShoulderedTurn = 0x010E 353 | ThrownFF = 0x010F 354 | ThrownFB = 0x0110 355 | ThrownFHi = 0x0111 356 | ThrownFLw = 0x0112 357 | CaptureCaptain = 0x0113 358 | CaptureYoshi = 0x0114 359 | YoshiEgg = 0x0115 360 | CaptureKoopa = 0x0116 361 | CaptureDamageKoopa = 0x0117 362 | CaptureWaitKoopa = 0x0118 363 | ThrownKoopaF = 0x0119 364 | ThrownKoopaB = 0x011A 365 | CaptureKoopaAir = 0x011B 366 | CaptureDamageKoopaAir = 0x011C 367 | CaptureWaitKoopaAir = 0x011D 368 | ThrownKoopaAirF = 0x011E 369 | ThrownKoopaAirB = 0x011F 370 | CaptureKirby = 0x0120 371 | CaptureWaitKirby = 0x0121 372 | ThrownKirbyStar = 0x0122 373 | ThrownCopyStar = 0x0123 374 | ThrownKirby = 0x0124 375 | BarrelWait = 0x0125 376 | Bury = 0x0126 377 | BuryWait = 0x0127 378 | BuryJump = 0x0128 379 | DamageSong = 0x0129 380 | DamageSongWait = 0x012A 381 | DamageSongRv = 0x012B 382 | DamageBind = 0x012C 383 | CaptureMewtwo = 0x012D 384 | CaptureMewtwoAir = 0x012E 385 | ThrownMewtwo = 0x012F 386 | ThrownMewtwoAir = 0x0130 387 | WarpStarJump = 0x0131 388 | WarpStarFall = 0x0132 389 | HammerWait = 0x0133 390 | HammerWalk = 0x0134 391 | HammerTurn = 0x0135 392 | HammerKneeBend = 0x0136 393 | HammerFall = 0x0137 394 | HammerJump = 0x0138 395 | HammerLanding = 0x0139 396 | KinokoGiantStart = 0x013A 397 | KinokoGiantStartAir = 0x013B 398 | KinokoGiantEnd = 0x013C 399 | KinokoGiantEndAir = 0x013D 400 | KinokoSmallStart = 0x013E 401 | KinokoSmallStartAir = 0x013F 402 | KinokoSmallEnd = 0x0140 403 | KinokoSmallEndAir = 0x0141 404 | Entry = 0x0142 405 | EntryStart = 0x0143 406 | EntryEnd = 0x0144 407 | DamageIce = 0x0145 408 | DamageIceJump = 0x0146 409 | CaptureMasterhand = 0x0147 410 | CapturedamageMasterhand = 0x0148 411 | CapturewaitMasterhand = 0x0149 412 | ThrownMasterhand = 0x014A 413 | CaptureKirbyYoshi = 0x014B 414 | KirbyYoshiEgg = 0x014C 415 | CaptureLeadead = 0x014D 416 | CaptureLikelike = 0x014E 417 | DownReflect = 0x014F 418 | CaptureCrazyhand = 0x0150 419 | CapturedamageCrazyhand = 0x0151 420 | CapturewaitCrazyhand = 0x0152 421 | ThrownCrazyhand = 0x0153 422 | BarrelCannonWait = 0x0154 423 | Wait1 = 0x0155 424 | Wait2 = 0x0156 425 | Wait3 = 0x0157 426 | Wait4 = 0x0158 427 | WaitItem = 0x0159 428 | SquatWait1 = 0x015A 429 | SquatWait2 = 0x015B 430 | SquatWaitItem = 0x015C 431 | GuardDamage = 0x015D 432 | EscapeN = 0x015E 433 | AttackS4Hold = 0x015F 434 | HeavyWalk1 = 0x0160 435 | HeavyWalk2 = 0x0161 436 | ItemHammerWait = 0x0162 437 | ItemHammerMove = 0x0163 438 | ItemBlind = 0x0164 439 | DamageElec = 0x0165 440 | FuraSleepStart = 0x0166 441 | FuraSleepLoop = 0x0167 442 | FuraSleepEnd = 0x0168 443 | WallDamage = 0x0169 444 | CliffWait1 = 0x016A 445 | CliffWait2 = 0x016B 446 | SlipDown = 0x016C 447 | Slip = 0x016D 448 | SlipTurn = 0x016E 449 | SlipDash = 0x016F 450 | SlipWait = 0x0170 451 | SlipStand = 0x0171 452 | SlipAttack = 0x0172 453 | SlipEscapeF = 0x0173 454 | SlipEscapeB = 0x0174 455 | AppealS = 0x0175 456 | Zitabata = 0x0176 457 | CaptureKoopaHit = 0x0177 458 | ThrownKoopaEndF = 0x0178 459 | ThrownKoopaEndB = 0x0179 460 | CaptureKoopaAirHit = 0x017A 461 | ThrownKoopaAirEndF = 0x017B 462 | ThrownKoopaAirEndB = 0x017C 463 | ThrownKirbyDrinkSShot = 0x017D 464 | ThrownKirbySpitSShot = 0x017E 465 | Unselected = 0x8000 466 | 467 | class State: 468 | """Databag that is handled by StateManager.""" 469 | pass 470 | --------------------------------------------------------------------------------