Guy-L / parakit

Scripting framework to extract and analyze Touhou game data for score players and anyone looking for insight!
7 stars 2 forks source link

ParaKit - Touhou Data Analysis

ParaKit is a customizable Python tool and scripting framework capable of extracting nearly all gameplay relevant data from Touhou games for the purpose of helping players gain insight. Extracting game states can either be done frame-by-frame or over a span of time by specifying a duration. Once extraction is complete, the results of the specified analyzer will be displayed.

Many analyzers come pre-built to help you get started, such as "graph bullet count over time", "plot all game entities" or "find the biggest bullet cluster of a given size" (see below screenshots). All the Touhou data you could need is given to you, and it's your turn to find the best ways to use it!

If you have feature requests or need help making your own custom analyzer, feel free to ask Guy for support. ParaKit is meant to be useful even for those with little to no Python experience.

Settings Documentation

Supported games:

❌ EoSD StB DS ISC WBaWC
❌ PCB ❌ MoF GFW LoLK UM
❌ IN ❌ SA TD HSiFS HBM
❌ PoFV ❌ UFO DDC VD UDoALG

Goals:

Installation & Setup

Requirements:

You can check that these are correctly installed on your machine by running in the terminal:

> python --version    #any version above 3.7.4 is fine
> git --version       #any version is fine

Since ParaKit is not currently able to update itself but is constantly improving, we highly recommend installing it through Git. Instead of manually swapping out old files with new ones, you'll be able to update by simply running a command (git pull) when prompted to.

To install, open the terminal in the folder you'd like ParaKit to be installed in and run either:

The rest of the setup process will be handled for you when running ParaKit for the first time. Please report to the developers if any issue comes up during this process.

Running ParaKit

You can simply run ParaKit by opening parakit.py.

Doing so by double-clicking the script file will work. However, if you'd like to keep working in the same window, you should instead run it by opening the terminal in the ParaKit folder and running:

> python parakit.py

This comes with the added benefit of being able to specify three important parameters for the extraction as command-line arguments. If arguments are supplied, they will override the associated settings in settings.py. Conversely, this also means you should set those settings in settings.py if you intend to keep working with the same parameters.

Setting Name Defaults to Details
analyzer AnalysisTemplate The name of the analyzer you'd like to run (case-sensitive).
For a list of built-in analyzers you can use out of the box, see below section.
ingame_duration single-frame extraction The in-game duration for sequence extraction. Can be:
Frames (integer+f): 100f, 5000f, etc.
Seconds (decimal+s): 25s, 9.5s, etc.
Infinite: infinite, inf, endless, forever
Sequence extraction can be terminted manually by pressing the termination key (which defaults to F6) or automatically by the running analyzer. A duration of 1 or 0 frames causes single-frame extraction, and a negative duration causes infinite sequence extraction.
exact False Whether ParaKit is allowed to slow down the game to ensure extraction of state data for every single unique frame in sequence extraction. When given as a command-line argument, can be set by specifying exact or unset by specifying inexact.

Example command:

> python parakit.py AnalysisMostBulletsFrame 100f exact

Note: If the same parameter is specified multiple times, the last value will be used.

ParaKit will wait while in menus, endings, and stage transitions.
You can start ParaKit while in the main menu, and it will start extracting as soon as the game screen loads.

You can disable extraction of various game entities (i.e., bullets, enemies, items, lasers, player shots & the other side of the screen in PvP games) in settings.py to improve ParaKit's performance if the analysis you're doing doesn't require some of them. You may also enable adding game screenshots to the extracted states, though this has a significant performance and memory impact.

Documentation explaining every available setting in settings.py can be found here.

Custom Analyzers

Temporary note:
We higly recommend that you get started by looking at the GameState object specification in game_entities.py and simple analyzers in analysis_examples.py. This section will be expanded and improved for clarity at a later point. Feel free to ask if you have any questions.

A template analyzer called AnalysisTemplate can be found in analysis.py. To make your own analyzer, copy this template, give it a unique class name, and implement the __init__(), step() and done() methods. It'll then instantly be added to the analyzers you can select in settings.py.

Initialize in __init__() any variables you need to track during the extraction (a common property, for instance, is the "best frame" seen so far). Every time a game state is extracted (i.e. only once for single-state extraction), the step() method is called and passed a GameState object. done() then runs once extraction is complete.

The full specification of the GameState object is found in game_entities.py.

Getting the information you need should be intuitive even for novice programmers. If you're not sure how to get something done programatically, you can try to give game_entities.py and AnalysisTemplate to a language model like ChatGPT.

If the result of your analyzer includes a plot of the game world, you'll want to extend AnalysisPlot instead of Analysis and implement plot(). There's many examples of plotting analyzers for each type of game entity. You can add any of these to your plot by calling their plot() method inside of your own (see AnalysisPlotAll). The latest recorded frame is stored in lastframe (though you can store any frame you want to have plotted there instead).

If you'd like to forcefully terminate sequence extraction when a certain condition is met, you can call terminate() in your step() method. done() will still be ran when this occurs.

You shouldn't need to edit any file other than settings.py and analysis.py.
If you do, feel free to send a feature request to the developers.

Sample Outputs

Single State Extraction A single frame's state is extracted and supplied to the selected analyzer to draw results from. The terminal output will display some information from the extracted game state, including basic state data, game-specific data, and data about the active entities in the game world. Note that the information presented in this mode is but an arbitrary sample, and that extracted states contain much more data not displayed here (see game_entities.py).

single state extraction in wbawc

Sequence Extraction The analyzer will be supplied the game state extracted from each frame and will present its results once the extraction process is complete. The terminal output simply displays the extraction's progress. Note that extraction is paused while the game is paused, and that its duration can be infinite (in this case, the user should terminate it by pressing the termination key which defaults to F6; some analyzers may also terminate it automatically).

100f & inf sequence extraction

Built-In Analyzers

Name / Description Screenshot(s)
AnalysisTemplate
See Custom Analyzers.
template
AnalysisBulletsOverTime
Tracks the amount of bullets across time and plots that as a graph. Simple example of how to make an analyzer.
Uses bullets.
9head bullet count over time
AnalysisCloseBulletsOverTime
Tracks the amount of bullets in a radius around the player across time and plots that as a graph.
Uses bullets.
FMH close bullets over time
AnalysisMostBulletsFrame
Finds the recorded frame which had the most bullets; saves the frame as most_bullets.png if screenshots are on.
Uses bullets & optionally screenshots.
most bullets
AnalysisMostBulletsCircleFrame
Finds the time and position of the circle covering the most bullets.
Uses bullets.
TD Yahoo easy most bullets circle
AnalysisDynamic
Abstract base class to factorize common code for real-time auto-updating graphs using PyQt5.
AnalysisBulletsOverTimeDynamic
Tracks the amount of bullets across time and plots that as a dynamic graph. Simple example of how to make a dynamic analyzer.
Uses bullets.
Bullets plot (UM Mike)
AnalysisItemCollectionDynamic
Tracks item collection events, counts items auto-collected vs attracted manually and plots that as a dynamic graph.
Uses items.
Item collection plot
AnalysisPlot
Abstract base class to factorize common plotting code.
See Custom Analyzers.
AnalysisPlotBullets
Plots the bullet positions of the last frame.
Uses bullets.
Kudoku Gourmet
AnalysisPlotEnemies
Plots the enemy positions of the last frame.
Uses enemies.
TD s5c1
AnalysisPlotItems
Plots the item positions of the last frame.
Uses items.
UM st2 woozy yy
AnalysisPlotLineLasers
Plots the line laser positions of the last frame.
Uses lasers.
Shimmy non 4
AnalysisPlotInfiniteLasers
Plots the telegraphed laser positions of the last frame.
Uses lasers.
Megu Final
AnalysisPlotCurveLasers
Plots the curvy laser positions of the last frame.
Uses lasers.
Score Desire Eater
AnalysisPlotPlayerShots
Plots the player shot positions of the last frame.
Uses player shots.
Yatsuhashi midnon 1 w/ SakuyaA shots
AnalysisPlotAll
Runs all the above plotting analyzers.
UDoALG Sanae vs Hisami
AnalysisPlotBulletHeatmap
Creates and plots a heatmap of bullet positions across time.
Uses bullets.
UM st5 fireballs enemies
AnalysisPrintBulletsASCII
Renders the bullet positions as ASCII art in the terminal.
Uses bullets.
Seki Ascii
AnalysisPatternTurbulence
Calculates the ratio of codirectional to non-codirectional pattern projectiles and plots that as a dynamic graph.
Uses bullets, lasers and enemies.
UM s1 Turbulence
AnalysisPlotGrazeableBullets
Plots the bullet positions with ungrazeable bullets obscured. Also obscures unscopeable bullets in UDoALG, as the two are equivalent.
Uses bullets.
Narumi spell 1
AnalysisPlotTD
Plots the spirit item positions and Kyouko echo bounds of the last frame. Included in AnalysisPlotAll.
Uses items & enemies.
Kyouko non 2
AnalysisPlotEnemiesSpeedkillDrops
Plots enemies with color intensity based on time-based item drops, shows the current amount of speedkill drops and the remaining time to get that amount. Works with blue spirits in TD and season items in HSiFS.
Uses enemies.
TD s4 post midboss HSiFS s1c2
AnalysisHookChapterTransition
Example showing how to programatically detect chapter transitions in LoLK.
Log of chapter detected transitions
AnalysisPlotBulletGraze
Plots bullets with color intensity based on graze timer.
Uses bullets.
EX Doremy final
AnalysisPlotWBaWC
Plots the animal token and shield otter positions of the last frame. Included in AnalysisPlotAll.
Uses items.
Keiki penult with otter hyper
AnalysisBestMallet
Finds the best timing and position to convert bullets to items via the Miracle Mallet in UM, plots Mallet circle and prints relevant data.
Uses bullets.
S4 Casino

For Contributors

Add the following pre-commit hook (pre-commit, no extension) to .git/hooks/ to avoid accidentally breaking the commit-based automatic version checker:

pre-commit ``` #!/bin/sh adjustedDate=$(date -u -d '+2 minutes' +"%Y, %-m, %-d, %-H, %-M, %-S") sed -i "s/VERSION_DATE = datetime(.*)/VERSION_DATE = datetime($adjustedDate, tzinfo=timezone.utc)/" "version.py" git add version.py ```