summaryrefslogtreecommitdiff
path: root/pokemontools/battle_animations.py
diff options
context:
space:
mode:
Diffstat (limited to 'pokemontools/battle_animations.py')
-rw-r--r--pokemontools/battle_animations.py324
1 files changed, 324 insertions, 0 deletions
diff --git a/pokemontools/battle_animations.py b/pokemontools/battle_animations.py
new file mode 100644
index 0000000..ffc89e4
--- /dev/null
+++ b/pokemontools/battle_animations.py
@@ -0,0 +1,324 @@
+# coding: utf-8
+
+import os
+from new import classobj
+
+import configuration
+conf = configuration.Config()
+
+from crystal import (
+ SingleByteParam,
+ PointerLabelParam,
+ DecimalParam,
+ BigEndianParam,
+ Command,
+ load_rom
+)
+
+from gbz80disasm import get_local_address, get_global_address
+from audio import sort_asms
+
+
+from wram import read_constants
+
+rom = bytearray(load_rom())
+
+sfx_constants = read_constants(os.path.join(conf.path, 'constants/sfx_constants.asm'))
+class SoundEffectParam(SingleByteParam):
+ def to_asm(self):
+ if self.byte in sfx_constants.keys():
+ sfx_constant = sfx_constants[self.byte]
+ return sfx_constant
+ return SingleByteParam.to_asm(self)
+
+anim_gfx_constants = read_constants(os.path.join(conf.path, 'constants/gfx_constants.asm'))
+class AnimGFXParam(SingleByteParam):
+ def to_asm(self):
+ if self.byte in anim_gfx_constants.keys():
+ return anim_gfx_constants[self.byte]
+ return SingleByteParam.to_asm(self)
+
+anims = read_constants(os.path.join(conf.path, 'constants/animation_constants.asm'))
+objs = { k: v for k, v in anims.items() if 'ANIM_OBJ' in v }
+bgs = { k: v for k, v in anims.items() if 'ANIM_BG' in v }
+anims = { k: v.replace('ANIM_','') for k, v in anims.items() }
+from move_constants import moves
+anims.update(moves)
+
+class AnimObjParam(SingleByteParam):
+ def to_asm(self):
+ if self.byte in objs.keys():
+ return objs[self.byte]
+ return SingleByteParam.to_asm(self)
+
+class BGEffectParam(SingleByteParam):
+ def to_asm(self):
+ if self.byte in bgs.keys():
+ return bgs[self.byte]
+ return SingleByteParam.to_asm(self)
+
+
+battle_animation_commands = {
+ 0xd0: ['anim_obj', ['obj', AnimObjParam], ['x', DecimalParam], ['y', DecimalParam], ['param', SingleByteParam]],
+ 0xd1: ['anim_1gfx', ['gfx1', AnimGFXParam]],
+ 0xd2: ['anim_2gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam]],
+ 0xd3: ['anim_3gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam]],
+ 0xd4: ['anim_4gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam], ['gfx4', AnimGFXParam]],
+ 0xd5: ['anim_5gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam], ['gfx4', AnimGFXParam], ['gfx5', AnimGFXParam]],
+ 0xd6: ['anim_incobj', ['id', SingleByteParam]],
+ 0xd7: ['anim_setobj', ['id', SingleByteParam], ['obj', AnimObjParam]], # bug: second param is interpreted as a command if not found in the object array
+ 0xd8: ['anim_incbgeffect', ['effect', BGEffectParam]],
+ 0xd9: ['anim_enemyfeetobj'],
+ 0xda: ['anim_playerheadobj'],
+ 0xdb: ['anim_checkpokeball'],
+ 0xdc: ['anim_transform'],
+ 0xdd: ['anim_raisesub'],
+ 0xde: ['anim_dropsub'],
+ 0xdf: ['anim_resetobp0'],
+ 0xe0: ['anim_sound', ['tracks', SingleByteParam], ['id', SoundEffectParam]],
+ 0xe1: ['anim_cry', ['pitch', SingleByteParam]],
+ 0xe2: ['anim_minimizeopp'], # unused
+ 0xe3: ['anim_oamon'],
+ 0xe4: ['anim_oamoff'],
+ 0xe5: ['anim_clearobjs'],
+ 0xe6: ['anim_beatup'],
+ 0xe7: ['anim_0xe7'], # nothing
+ 0xe8: ['anim_updateactorpic'],
+ 0xe9: ['anim_minimize'],
+ 0xea: ['anim_0xea'], # nothing
+ 0xeb: ['anim_0xeb'], # nothing
+ 0xec: ['anim_0xec'], # nothing
+ 0xed: ['anim_0xed'], # nothing
+ 0xee: ['anim_jumpand', ['value', SingleByteParam], ['address', PointerLabelParam]],
+ 0xef: ['anim_jumpuntil', ['address', PointerLabelParam]],
+ 0xf0: ['anim_bgeffect', ['effect', BGEffectParam], ['unknown', SingleByteParam], ['unknown', SingleByteParam], ['unknown', SingleByteParam]],
+ 0xf1: ['anim_bgp', ['colors', SingleByteParam]],
+ 0xf2: ['anim_obp0', ['colors', SingleByteParam]],
+ 0xf3: ['anim_obp1', ['colors', SingleByteParam]],
+ 0xf4: ['anim_clearsprites'],
+ 0xf5: ['anim_0xf5'], # nothing
+ 0xf6: ['anim_0xf6'], # nothing
+ 0xf7: ['anim_0xf7'], # nothing
+ 0xf8: ['anim_jumpif', ['value', SingleByteParam], ['address', PointerLabelParam]],
+ 0xf9: ['anim_setvar', ['value', SingleByteParam]],
+ 0xfa: ['anim_incvar'],
+ 0xfb: ['anim_jumpvar', ['value', SingleByteParam], ['address', PointerLabelParam]],
+ 0xfc: ['anim_jump', ['address', PointerLabelParam]],
+ 0xfd: ['anim_loop', ['count', SingleByteParam], ['address', PointerLabelParam]],
+ 0xfe: ['anim_call', ['address', PointerLabelParam]],
+ 0xff: ['anim_ret'],
+}
+
+battle_animation_enders = [
+ 'anim_jump',
+ 'anim_ret',
+]
+
+def create_battle_animation_classes():
+ classes = []
+ for cmd, command in battle_animation_commands.items():
+ cmd_name = command[0]
+ params = {
+ 'id': cmd,
+ 'size': 1,
+ 'end': cmd_name in battle_animation_enders,
+ 'macro_name': cmd_name,
+ 'param_types': {},
+ }
+ for i, (name, class_) in enumerate(command[1:]):
+ params['param_types'][i] = {'name': name, 'class': class_}
+ params['size'] += class_.size
+ class_name = cmd_name + 'Command'
+ class_ = classobj(class_name, (Command,), params)
+ globals()[class_name] = class_
+ classes += [class_]
+ return classes
+
+battle_animation_classes = create_battle_animation_classes()
+
+
+class BattleAnimWait(Command):
+ macro_name = 'anim_wait'
+ size = 1
+ end = macro_name in battle_animation_enders
+ param_types = {
+ 0: {'name': 'duration', 'class': DecimalParam},
+ }
+ override_byte_check = True
+
+
+class BattleAnim:
+ """
+ A list of battle animation commands read from a given address.
+
+ Results in a list of commands (self.output) and a list of labels (self.labels).
+ Format is (address, asm, last_address). Includes any subroutines and their output.
+
+ To convert to text, use self.to_asm().
+
+ For combining multiple BattleAnims, take self.output + self.labels from each
+ and sort with sort_asms.
+ """
+
+ def __init__(self, address, base_label=None, label=None, used_labels=[], macros=[]):
+ self.start_address = address
+ self.address = address
+
+ self.base_label = base_label
+ if self.base_label == None:
+ self.base_label = 'BattleAnim_' + hex(self.start_address)
+
+ self.label = label
+ if self.label == None:
+ self.label = self.base_label
+
+ self.used_labels = used_labels
+
+ self.output = []
+ self.labels = []
+ self.label_asm = (
+ self.start_address,
+ '%s: ; %x' % (self.label, self.start_address),
+ self.start_address
+ )
+ self.labels += [self.label_asm]
+ self.used_labels += [self.label_asm]
+
+ self.macros = macros
+
+ self.parse()
+
+ def parse(self):
+
+ done = False
+ while not done:
+ cmd = rom[self.address]
+ class_ = self.get_command_class(cmd)(address=self.address)
+ asm = class_.to_asm()
+
+ # label jumps/calls
+ for key, param in class_.param_types.items():
+ if param['class'] == PointerLabelParam:
+ label_address = class_.params[key].parsed_address
+ label = '%s_branch_%x' % (self.base_label, label_address)
+ label_def = '%s: ; %x' % (label, label_address)
+ label_asm = (label_address, label_def, label_address)
+ if label_asm not in self.used_labels:
+ self.labels += [label_asm]
+ asm = asm.replace('$%x' % get_local_address(label_address), label)
+
+ self.output += [(self.address, '\t' + asm, self.address + class_.size)]
+ self.address += class_.size
+
+ done = class_.end
+ # infinite loops are enders
+ if class_.macro_name == 'anim_loop':
+ if class_.params[0].byte == 0:
+ done = True
+
+ # last_address comment
+ self.output += [(self.address, '; %x\n' % self.address, self.address)]
+
+ # parse any other branches too
+ self.labels = list(set(self.labels))
+ for address, asm, last_address in self.labels:
+ if not (self.start_address <= address < self.address) and (address, asm, last_address) not in self.used_labels:
+ self.used_labels += [(address, asm, last_address)]
+ sub = BattleAnim(
+ address=address,
+ base_label=self.base_label,
+ label=asm.split(':')[0],
+ used_labels=self.used_labels,
+ macros=self.macros
+ )
+ self.output += sub.output
+ self.labels += sub.labels
+
+ self.output = list(set(self.output))
+ self.labels = list(set(self.labels))
+
+ def to_asm(self):
+ output = sort_asms(self.output + self.labels)
+ text = ''
+ for (address, asm, last_address) in output:
+ text += asm + '\n'
+ return text
+
+ def get_command_class(self, cmd):
+ if cmd < 0xd0:
+ return BattleAnimWait
+ for class_ in self.macros:
+ if class_.id == cmd:
+ return class_
+ return None
+
+
+def battle_anim_label(i):
+ """
+ Return a label matching the name of a battle animation by id.
+ """
+ if i in anims.keys():
+ base_label = 'BattleAnim_%s' % anims[i].title().replace('_','')
+ else:
+ base_label = 'BattleAnim_%d' % i
+ return base_label
+
+def dump_battle_anims(table_address=0xc906f, num_anims=278, macros=battle_animation_classes):
+ """
+ Dump each battle animation from a pointer table.
+ """
+
+ asms = []
+
+ asms += [(table_address, 'BattleAnimations: ; %x' % table_address, table_address)]
+
+ address = table_address
+ bank = address / 0x4000
+
+ for i in xrange(num_anims):
+ pointer_address = address
+ anim_address = rom[pointer_address] + rom[pointer_address + 1] * 0x100
+ anim_address = get_global_address(anim_address, bank)
+ base_label = battle_anim_label(i)
+ address += 2
+
+ # anim pointer
+ asms += [(pointer_address, '\tdw %s' % base_label, address)]
+
+ # anim script
+ anim = BattleAnim(
+ address=anim_address,
+ base_label=base_label,
+ macros=macros
+ )
+ asms += anim.output + anim.labels
+
+ asms += [(address, '; %x\n' % address, address)]
+
+ # jp sonicboom
+ anim = BattleAnim(
+ address=0xc9c00,
+ base_label='BattleAnim_Sonicboom_JP',
+ macros=macros
+ )
+ asms += anim.output + anim.labels
+
+ asms = sort_asms(asms)
+ return asms
+
+def asm_list_to_text(asms):
+ output = ''
+ last = asms[0][0]
+ for addr, asm, last_addr in asms:
+ if addr > last:
+ # incbin any unknown areas
+ output += '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n\n\n' % (last, addr, last)
+ if addr >= last:
+ output += asm + '\n'
+ last = last_addr
+ return output
+
+if __name__ == '__main__':
+ asms = dump_battle_anims()
+ print asm_list_to_text(asms)
+