Giter Club home page Giter Club logo

parakit's Introduction

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.

Supported games:

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

Goals:

  • Faster inter-process reads
  • Built-in analyzers for dynamic entity plotting
  • All-game support
  • Bomb data (more)
  • Damage sources
  • Cancel sources
  • API collision check methods
  • Better UX

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:

  • via HTTPS (no setup needed): git clone https://github.com/Guy-L/parakit.git
  • via SSH (requires setup): git clone [email protected]:Guy-L/parakit.git

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

parakit's People

Contributors

guy-l avatar zero318 avatar

Stargazers

Invex avatar n0099 avatar Nylilsa avatar Hoàng Cao Minh avatar Wicky avatar Reisensei avatar wearr avatar

Watchers

 avatar  avatar

Forkers

khang06 nylilsa

parakit's Issues

Easy to understand / modify entity scatterplot analyzers

AnalysisPlot is good for demoing PK, but quickly becomes a problem once users want to properties of the graph (hard to understand/copy, modifying the original leads to merge conflicts, etc.). Should make a simple entity analyzer from the ground up as an example.

Refining user experience -> GUI

Continually take input from ParaKit users to see how the on-boarding and general user experience can be improved.
Consider developing a GUI.

Potentially useful new built-in analyzers

  • Plotting all the paths that survived a pattern(?)
  • Heatmap plots for all types of collision (see #27)
  • Demo of how to detect section change in games other than LoLK
  • Live split data (surv/scoring/speedrun)

[All] Ensure boss timer indicators are logical & consistent

In all games except UDoALG, the address at offset +0x8 from the zGui_bosstimer_ms offset seems to be a reliable indicator of whether the boss timer is drawn. The boss timer draw logic seems intrinsically linked to that of the boss health-bar, so it has happened that our "boss timer drawn indicator" is actually a "boss health bar drawn indicator". A good test case (where the health-bar is not displayed but the timer is) would be the death animation of the Extra boss on their last spell.

Check drawn indicator is for boss timer and not health bar:

  • TD
  • DDC
  • LoLK
  • UM
  • UDoALG

Check drawn indicator is accurate when boss has its "invincible" flag enabled:

  • TD
  • DDC
  • LoLK
  • UM
  • UDoALG

Add keybind to trigger event in analyzer

Probably another F key that can be set.
One potential use is to run a 1-state plotting analyzer and press a key to have it pop up with the current state's plot.

Re-work and improve documentation around analyzers

How to use the data from GameStates, what other API methods are available and how to extend sample analyzers meant to be extended is not well-documented. README should clearly push new users to get inspiration from basic examples first, and link to a new markdown file with thorough guidelines and documentation. Related to #13.

Full series support

Modern main-games

  • MoF
  • SA
  • UFO
  • TD
  • DDC
  • LoLK
  • HSiFS
  • WBaWC
  • UM
  • UDoALG

Modern side-games

  • DS
  • GFW
  • ISC
  • VD
  • HBM

Classic main-games

  • EoSD
  • PCB
  • IN
  • PoFV

Classic side-games

  • StB

Consider adding bullet spawn graze/collision delay

Apparently present in some games before UFO (which ones? Not in SA onwards according to RB)
May be a property the game stores per bullet, or simply a hard-coded check against the bullet's timer. If the former, add it to states. If the latter, consider whether to add it to states.

On UFO spawn graze delay (RB)

iirc it was something like: 1 active, 2 spawn animation, 3 despawn animation
no no
theres no graze delay
but it's more like, I can't tell when a bullet becomes active based off the state param alone
in SA it corresponded perfectly, it turns from 2 to 1 on the exact frame it's active
in UFO it turns from 2 to 1 like 15 frames or something in
but bullets become active 8 frames after spawn
(both cases assume etEx 2 with intensity 1)

Split and re-organize project files

Certain files (interface.py, state_reader.py...) are getting too big and hard to read; may benefit from internal refactors and splitting (could simplify some imports). analysis_examples.py should be split into sub-folders.

  • Evaluate and list goals for refactors/splits
  • Split off Ability Card extraction into its own method in state_reader.py
  • Re-organize analyzers
  • Ensure analysis examples are available in places users are expected to code

[All] Add PoC line height

Constant in some games (usually at y=128), not in others (UM, HBM, different for Marisa in PCB(?), maybe others).
Probably should be handled like difficulty: extracted once on first frame, not tracked in states.
To be used by default Item entity scatter-plot (draw the line faintly).

  • TD
  • DDC
  • LoLK
  • HSiFS
  • WBaWC
  • UM
  • UDoALG

Consider rethinking magic number + map system

Many things are currently provided to the user as a magic number (item/enemy/bullet type, sprite, color, game specifics like TD spirit item types or UM card ids), and the user is expected to use a separately provided association array/map to get its meaning. Consider rethinking: keep as is? switch to providing strings directly instead? there ARE some uses for having numbers (i.e. checking that an item type is in a certain range): use enums to provide both? note: enum names are limited since they need to be valid identifiers, so they'd also require some fancy formatting methods. may be unwieldy... alternatively: providing a key value pair, etc.

Complete collision research for analyzer API

The wiki should have a page detailing research of the collision methods used by each supported game. There should be a table in which the columns list all the different types of collisions in the games (player/bullet, player/laser, enemy/projectile, etc. - potentially two columns for "hit" and "graze" when applicable) and the rows list every game supported. The items in each cell should be the name of a function, and the detailed behavior of each unique function with their inputs should be explained in the document.

This knowledge should be used to make available a set of API functions (defined on a per-game basis in Offset objects) that analyzers can use to perform accurate collision checks. This is particularly helpful for "heatmap" analyzers, to better understand which positions see the most hits/graze/etc.

The following questions should be answered by the above task and highlight things the built-in AnalysisPlotEnemies may do wrong:

  1. Do enemy hurtboxes take into account enemy rotation and the "rectangular hitbox" flag?
  2. If the "rectangular hitbox flag" is disabled, are hitboxes that should behave like ellipses (e.g. Taiko drums) actually treated as circles, with only one of the two hitbox dimensions being used for radius (as suggested by enemy collision code)?

Attempt at an exhaustive list of types of collisions (some may be unnecessary, some may be missing):

  • Player/Bullet graze
  • Player/Bullet hit
  • Player/Line Laser graze
  • Player/Line Laser hit
  • Player/Infinite Laser graze
  • Player/Infinite Laser hit
  • Player/Curve Laser graze
  • Player/Curve Laser hit
  • Player/Enemy graze
  • Player/Enemy hit
  • Enemy/Player projectile hit

[All] Add identifiers for all entities

To be able to track specific entities over time in analyzers, they should have an identifier.
The simplest way to do this is to use their offset in memory.
In order of usefulness:

  • Items
  • Enemies
  • Bullets
  • Lasers
  • Curve Laser Nodes
  • TD Spirit Items

Minimizing extraction overhead

The many inter-process memory reads involved in extraction are currently the project's biggest bottleneck, which prevents PK from being frame-accurate unless the game is slowed down via the exact mode. Extraction currently relies on Python's slow inter-process API.
Khangaroo has done some work to integrate ParaKit into the game's address space by linking a portable Python install using thcrap, but I have concerns about how this will change the UX.
Among other solutions, I would be ready (though reluctant) to go as far as porting the project completely to a compiled language if it allowed PK to be frame-accurate without slowing down games. The ability to run analyzers written in Python is important for this project and would be preserved.

  • Evaluate the performance benefit of splitting _read_memory into _read_memory and _read_memory_rel (removing the conditional)
  • Talk about the Python linking approach with Khangaroo

[All] Switch from "spellcard indicator" heuristic to spell flags

Spellcard indicators work in all games, but they're heuristics. zSpellcard seems to always have a "flag" property (offset 0x78 in HSiFS & UM), and one of its bits (i.e., in UM, the last one (flags & 1)) would work as a reliable "spell active" indicator.

  • UM (check change works, rework logic)
  • Verify in TD
  • Verify in DDC
  • Verify in LoLK
  • Verify in UDoALG

Add release-bomb data to extraction

HSiFS releases are implemented similarly to bombs. Come back and extract more release data once bombs are properly supported.
Notes about remaining HSiFS release data:

            #release data:
                #level that was released: found (SeasonBombPTR -> 0xd8)
                #position: ???,
                #radius: ???,
                    #found in tables around 0x491ed4 but all values MAY BE lower by some amount (to test), also maybe just visual
                #timer:  found (SeasonBombPTR -> 0x38, also used for post-use delay)
                #total duration: ???,
                    #found in tables around 0x491ed4 but all values are lower by 30f
                    #51(?) for max spring, 61(?) maxsummer, 161(?) maxfall, 221(??) maxwinter, 71(?) maxex
                #autumn speed boost: found (PlayerPTR -> 0x16688),
                #winter dps boost: found (PlayerPTR -> 0x2c7cc) BUT WEIRD,
                #should we display the info of how much season power will drop afte release? (i THINK it's to 0 for spring/fall/winter and -delta for summer/ex)

Referencing current Offset object directly to get offsets

Using properties of the current games' offset object directly instead of using it to define hundreds of variables in interface.py would drastically improve readability. The only reason this hasn't been done is due to performance concerns, so performance should be evaluated before and after this change.

Dynamic (real-time) graphs

Currently very primitive.

  • Use most appropriate pyqtgraph classes for abstract parents
  • Styling: make it look closer to pyplot graphs
  • Styling: PvP games & Seija flip
  • Styling: making plot fit window & scale with window size
  • Styling: evaluate enabling antialiasing
  • Evaluate: separate entity draw methods vs separate analyzers with All analyzer
  • Evaluate: separating entity draw methods in pyplot analyzers and only having one
  • Investigate spikes in non-exact mode for dynamic line-graphs
  • Fix issue where plot window can't be properly closed (via termination key) when focused in either exact or non-exact mode
  • Draw enemies
  • Draw items
  • Draw line lasers
  • Draw infinite lasers
  • Draw curve lasers
  • Draw game specific
  • Document properly

May completely replace pyplot analyzers if base functionality is equivalent (scatterplots look roughly the same with the same behavior, can render a single frame, can be integrated into other analyzers...).

Remove game selection option

I believe it's pretty much impossible for someone to have two Touhou games open at the same time unless they're really trying hard to do so. Investigate that claim, and consider removing the option to select the game (and instead auto-detect it)

Heatmap equivalent to AnalysisMostBulletsCircleFrame

AnalysisMostBulletsCircleFrame, which can be used to calculate the best locations for grazing or cancelling bullets, only shows the best result and discards any other. A heatmap version could keep and display all that information.

Cross-stage and cross-run sequence extraction

Some use cases would benefit from being able to keep sequence extraction going between stages or between runs (for instance, a user might want to retry a thprac section over and over while changing one variable in their set of inputs to find the best outcome). This would require heavy refactoring; for instance, lots of initialization definitions meant to only run on startup would need to be re-ran.

[UM/UDoALG] Modern speedkill item drop extraction

With item drops (and TD time-based drops) being added, time-based drops in UM should also be extracted.
The EnemyDrop class seems to be the same between UM and UDoALG, so this should be implemented with a game-group (even if UDoALG doesn't use item drops in practice). If this is cumbersome and the EnemyDrop class is reverted in Touhou 20, could change this to be UM-only.

Bonus: Integrate into AnalysisPlotEnemiesSpeedkillDrops. Make new enemy structs inherit speedkill_cur_drop_amt and speedkill_time_left_for_amt from common parent

Consider changing draw order in AnalysisPlotAll and removing unnecessary zorder uses

The Touhou render order is: (TD spirit items/WBaWC otters/UM fang) -> player projectiles -> items -> enemies -> player -> (UFO/WBaWC tokens) -> lasers (curves -> lines -> telegraphs) -> bullets

Consider changes required to make AnalysisPlotAll strictly respect this render order. Also consider if not respecting the order gives more valuable information (i.e. seeing items on top of enemies)

Pattern cancel score gain potential analyzer

Calcules resulting gains from killing/cancelling pattern on a given frame and plots it as a graph (probably dynamic). Would be useful for EoSD/MoF
Probably have a separate line for SCB, and a third line that adds the two to find "sweet spot"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.