Skip to content

Add Battle Animations module - #352

Open
Supremekirb wants to merge 15 commits into
pk-hack:masterfrom
Supremekirb:battle-animations
Open

Add Battle Animations module#352
Supremekirb wants to merge 15 commits into
pk-hack:masterfrom
Supremekirb:battle-animations

Conversation

@Supremekirb

@Supremekirb Supremekirb commented Aug 4, 2026

Copy link
Copy Markdown

This introduces the Battle Animations module, which is responsible for decompiling+recompiling data related to battle animations (aka PSI animations). To support dumping edited data from the current usual method of editing battle animations, which requires direct edits to a project's base ROM plus a CCScript repointing of the data, the project upgrade process now also optionally accepts an existing compiled ROM either as a prompt in the GUI or the new CLI flag --existing_rom / -e.

Battle animations are dumped into the new BattleAnimations directory, which includes the battle_animations.yml configuration file, the Tilesets subdirectory which includes grayscale PNGs of each of the tilesets, and the Arrangements subdirectory which includes the individual frame tilemaps for every animation. Like the vanilla game, tilesets compiled into the ROM do not contain extraneous tiles.

Brand-new animations can be added by creating a new .map arrangement file with the appropriate number filename and adding a corresponding entry to battle_animations.yml. If there are more animations than vanilla, then a small code patch will be applied so that the function at C3F981 can process them. Additional battle animation IDs start at 55 (56 in CCScript) due to battle animations and the HDMA-based enemy animations sharing the same ID space.

@Supremekirb

Copy link
Copy Markdown
Author

Still missing one feature: patching the game code to accept higher battle animation IDs than usual. Oops

@tolmar tolmar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!
I read through everything and only had a few comments.
Note that I just read through everything, I did not build or test it myself.

Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
Comment thread coilsnake/ui/common.py
Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
Comment thread coilsnake/modules/eb/BattleAnimationModule.py
Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
Comment thread coilsnake/modules/eb/BattleAnimationModule.py
Comment thread coilsnake/modules/eb/BattleAnimationModule.py
Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
@Supremekirb

Copy link
Copy Markdown
Author

OK, after some discussion on the discord server, I've changed the way we're dumping so that tilesets are no longer duplicated when decompiling and deduplicated when compiling. This makes it easier for external editors to handle sharing tilesets across animations. I'll update the PR description to reflect this

@PhoenixBound PhoenixBound left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the flood, some of these are nitpicky. I also haven't tested anything out in its current form, this is just from reading the code

The existing/old ROM thing being optional is an appreciated change already. Re frame count being hidden: as discussed on Discord, I can see the case for restricting dumping of unused frames to a tool outside of CoilSnake, so 👍 to that.

Thanks for working on this!

Comment thread coilsnake/util/eb/pointer.py Outdated
Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
Comment on lines +100 to +106
text_frames = raw.split("\n\n")[:-1] # Last split is the trailing gap

for text_frame in text_frames:
arrangement = EbOneByteTileArrangement(32, 32)
arrangement.arrangement = [[EbOneByteTileArrangementItem(int(x, 16)) for x in y.split()] for y in text_frame.split("\n")]
self.arrangements.append(arrangement)
self.frame_count += 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is reading from a file provided by the user, this should probably have a bit of up-front validation to limit surprises:

Suggested change
text_frames = raw.split("\n\n")[:-1] # Last split is the trailing gap
for text_frame in text_frames:
arrangement = EbOneByteTileArrangement(32, 32)
arrangement.arrangement = [[EbOneByteTileArrangementItem(int(x, 16)) for x in y.split()] for y in text_frame.split("\n")]
self.arrangements.append(arrangement)
self.frame_count += 1
text_frames = raw.split("\n\n")
if text_frames and not text_frames[-1]:
# The last split is from the trailing \n\n gap
text_frames.pop()
if not text_frames:
raise CoilSnakeUserError("Battle animation has no frames")
for i, text_frame in enumerate(text_frames):
arrangement = EbOneByteTileArrangement(32, 32)
rows = text_frame.split("\n")
if len(rows) != arrangement.height:
raise CoilSnakeUserError("Frame {} has {} rows of tiles (expected {})".format(i, len(rows), arrangement.height)
for j, row in enumerate(rows):
tiles = row.split()
if len(tiles) != arrangement.width:
raise CoilSnakeUserError("Frame {} row {} has {} tiles (expected {})".format(i, j, len(tiles), arrangement.width)
for k, tile in enumerate(tiles):
arrangement.arrangement[j][k] = EbOneByteTileArrangementItem(int(tile, 16))
self.arrangements.append(arrangement)
self.frame_count += 1

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied though I haven't tested messing with the file to see if these errors get fielded correctly

Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
Comment thread coilsnake/modules/eb/BattleAnimationModule.py Outdated
Comment thread coilsnake/ui/gui.py
if confirm != "yes":
return

has_old = tkinter.messagebox.askyesnocancel("Do you have expanded battle animations?",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not going to request a specific change with the "old ROM" stuff, this is all fine for now. But in the future, maybe it would be neat if the logic of asking for an existing compiled ROM was behind a callback in upgrade_project, because upgrade_project can load the project and check its version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants