├── .gitignore ├── 3rd_spectator.lua ├── 3rd_training.lua ├── README.md ├── data ├── sfiii3nr1 │ ├── framedata │ │ ├── alex_framedata.json │ │ ├── chunli_framedata.json │ │ ├── dudley_framedata.json │ │ ├── elena_framedata.json │ │ ├── gouki_framedata.json │ │ ├── hugo_framedata.json │ │ ├── ibuki_framedata.json │ │ ├── ken_framedata.json │ │ ├── makoto_framedata.json │ │ ├── necro_framedata.json │ │ ├── oro_framedata.json │ │ ├── q_framedata.json │ │ ├── remy_framedata.json │ │ ├── ryu_framedata.json │ │ ├── sean_framedata.json │ │ ├── twelve_framedata.json │ │ ├── urien_framedata.json │ │ ├── yang_framedata.json │ │ └── yun_framedata.json │ ├── framedata_meta.lua │ └── savestates │ │ └── character_select.fs └── sfiii4n │ ├── framedata │ ├── alex_framedata.json │ ├── chunli_framedata.json │ ├── dudley_framedata.json │ ├── elena_framedata.json │ ├── gill_framedata.json │ ├── gouki_framedata.json │ ├── hugo_framedata.json │ ├── ibuki_framedata.json │ ├── ken_framedata.json │ ├── makoto_framedata.json │ ├── necro_framedata.json │ ├── oro_framedata.json │ ├── q_framedata.json │ ├── remy_framedata.json │ ├── ryu_framedata.json │ ├── sean_framedata.json │ ├── shinakuma_framedata.json │ ├── twelve_framedata.json │ ├── urien_framedata.json │ ├── yang_framedata.json │ └── yun_framedata.json │ ├── framedata_meta.lua │ └── savestates │ └── character_select.fs ├── images ├── big │ ├── 1_dir.png │ ├── 2_dir.png │ ├── 3_dir.png │ ├── 4_dir.png │ ├── 5_dir.png │ ├── 6_dir.png │ ├── 7_dir.png │ ├── 8_dir.png │ ├── 9_dir.png │ ├── H_button.png │ ├── L_button.png │ ├── M_button.png │ └── no_button.png └── small │ ├── 1_dir.png │ ├── 2_dir.png │ ├── 3_dir.png │ ├── 4_dir.png │ ├── 5_dir.png │ ├── 6_dir.png │ ├── 7_dir.png │ ├── 8_dir.png │ ├── 9_dir.png │ ├── HK_button.png │ ├── HP_button.png │ ├── LK_button.png │ ├── LP_button.png │ ├── MK_button.png │ └── MP_button.png ├── saved ├── _ └── recordings │ ├── A_Guard_Jump_Back.json │ ├── A_Guard_Jump_Forward.json │ ├── A_Guard_Jump_Neutral.json │ └── _ └── src ├── attack_data.lua ├── character_select.lua ├── display.lua ├── draw.lua ├── frame_advantage.lua ├── framedata.lua ├── gamestate.lua ├── input_history.lua ├── libs └── dkjson.lua ├── memory_adresses.lua ├── menu_widgets.lua ├── startup.lua └── tools.lua /.gitignore: -------------------------------------------------------------------------------- 1 | saved/*.* 2 | saved/recordings/*.* 3 | !saved/recordings/A_Guard_Jump_Back.json 4 | !saved/recordings/A_Guard_Jump_Forward.json 5 | !saved/recordings/A_Guard_Jump_Neutral.json 6 | *.sublime-* 7 | -------------------------------------------------------------------------------- /3rd_spectator.lua: -------------------------------------------------------------------------------- 1 | require("src/startup") 2 | 3 | print("-----------------------------") 4 | print(" 3rd_spectator.lua - "..script_version.."") 5 | print(" Spectator script for "..game_name.."") 6 | print(" Last tested Fightcade version: "..fc_version.."") 7 | print(" project url: https://github.com/Grouflon/3rd_training_lua") 8 | print("-----------------------------") 9 | print("") 10 | print("Command List:") 11 | print("- Lua Hotkey 1 (alt+1) to open the settings menu") 12 | print("- Lua Hotkey 2 (alt+2) to go up the settings list") 13 | print("- Lua Hotkey 3 (alt+3) to go down the settings list") 14 | print("- Lua Hotkey 4 (alt+4) to toggle the selected setting") 15 | print("") 16 | print("* settings are saved and kept between sessions") 17 | print("") 18 | 19 | require("src/tools") 20 | require("src/memory_adresses") 21 | require("src/draw") 22 | require("src/display") 23 | require("src/framedata") 24 | require("src/gamestate") 25 | require("src/input_history") 26 | require("src/menu_widgets") 27 | 28 | developer_mode = false 29 | 30 | -- settings 31 | settings_version = 2 32 | spectator_settings_file = "spectator_settings.json" 33 | spectator_settings = 34 | { 35 | version = settings_version, 36 | 37 | -- 0 is nothing, 1 is both players, 2 is P1 only, 3 is P2 only 38 | display_controllers = 1, 39 | display_input_history = 1, 40 | display_hitboxes = 1, 41 | display_gauges = 1, 42 | display_distances = false, 43 | } 44 | 45 | hotkey1_pressed = false 46 | hotkey2_pressed = false 47 | hotkey3_pressed = false 48 | hotkey4_pressed = false 49 | 50 | is_menu_open = false 51 | 52 | function save_spectator_settings() 53 | if not write_object_to_json_file(spectator_settings, saved_path..spectator_settings_file) then 54 | print(string.format("Error: Failed to save spectator settings to \"%s\"", spectator_settings_file)) 55 | end 56 | end 57 | 58 | 59 | function load_spectator_settings() 60 | local _spectator_settings = read_object_from_json_file(saved_path..spectator_settings_file) 61 | if _spectator_settings == nil then 62 | _spectator_settings = {} 63 | end 64 | 65 | if _spectator_settings.version == spectator_settings.version then 66 | for _key, _value in pairs(_spectator_settings) do 67 | if type(_value) == "number" then 68 | spectator_settings[_key] = _value 69 | end 70 | end 71 | end 72 | end 73 | -- !settings 74 | 75 | display_mode = { 76 | "none", 77 | "P1+P2", 78 | "P1", 79 | "P2", 80 | } 81 | 82 | -- menu 83 | settings_menu = make_menu(71, 40, 312, 105, -- screen size 383,223 84 | { 85 | list_menu_item("Display Controllers", spectator_settings, "display_controllers", display_mode), 86 | list_menu_item("Display Input History", spectator_settings, "display_input_history", display_mode), 87 | list_menu_item("Display Hitboxes", spectator_settings, "display_hitboxes", display_mode), 88 | list_menu_item("Display Gauges", spectator_settings, "display_gauges", display_mode), 89 | checkbox_menu_item("Display Distances", spectator_settings, "display_distances") 90 | }, 91 | save_spectator_settings, 92 | false) 93 | -- !menu 94 | 95 | function on_start() 96 | load_spectator_settings() 97 | end 98 | 99 | function on_load_state() 100 | end 101 | 102 | function before_frame() 103 | 104 | draw_read() 105 | gamestate_read() 106 | 107 | if developer_mode then 108 | local _write_game_vars_settings = 109 | { 110 | infinite_time = true, 111 | } 112 | write_game_vars(_write_game_vars_settings) 113 | end 114 | 115 | local _input = joypad.get() 116 | input_history_update(input_history[1], "P1", _input) 117 | input_history_update(input_history[2], "P2", _input) 118 | 119 | end 120 | 121 | function on_gui() 122 | 123 | if hotkey1_pressed then 124 | is_menu_open = not is_menu_open 125 | 126 | if is_menu_open then 127 | menu_stack_push(settings_menu) 128 | else 129 | menu_stack_clear() 130 | end 131 | end 132 | 133 | local _input = 134 | { 135 | down = hotkey3_pressed, 136 | up = hotkey2_pressed, 137 | left = false, 138 | right = hotkey4_pressed, 139 | validate = false, 140 | reset = false, 141 | cancel = false, 142 | } 143 | menu_stack_update(_input) 144 | menu_stack_draw() 145 | 146 | -- input history 147 | if spectator_settings.display_input_history == 2 or spectator_settings.display_input_history == 3 then 148 | input_history_draw(input_history[1], 4, 49, false) 149 | end 150 | 151 | if spectator_settings.display_input_history == 2 or spectator_settings.display_input_history == 4 then 152 | input_history_draw(input_history[2], screen_width - 4, 49, true) 153 | end 154 | 155 | -- controllers 156 | local _i = joypad.get() 157 | if spectator_settings.display_controllers == 2 or spectator_settings.display_controllers == 3 then 158 | local _p1 = make_input_history_entry("P1", _i) 159 | draw_controller_big(_p1, 44, 34) 160 | end 161 | 162 | if spectator_settings.display_controllers == 2 or spectator_settings.display_controllers == 4 then 163 | local _p2 = make_input_history_entry("P2", _i) 164 | draw_controller_big(_p2, 310, 34) 165 | end 166 | 167 | if is_in_match then 168 | 169 | -- hitboxes 170 | if spectator_settings.display_hitboxes == 2 or spectator_settings.display_hitboxes == 3 then 171 | draw_hitboxes(player_objects[1].pos_x, player_objects[1].pos_y, player_objects[1].flip_x, player_objects[1].boxes) 172 | 173 | -- projectiles 174 | for _id, _obj in pairs(projectiles) do 175 | if _obj.emitter_id == 1 then 176 | draw_hitboxes(_obj.pos_x, _obj.pos_y, _obj.flip_x, _obj.boxes) 177 | end 178 | end 179 | end 180 | 181 | if spectator_settings.display_hitboxes == 2 or spectator_settings.display_hitboxes == 4 then 182 | draw_hitboxes(player_objects[2].pos_x, player_objects[2].pos_y, player_objects[2].flip_x, player_objects[2].boxes) 183 | 184 | -- projectiles 185 | for _id, _obj in pairs(projectiles) do 186 | if _obj.emitter_id == 2 then 187 | draw_hitboxes(_obj.pos_x, _obj.pos_y, _obj.flip_x, _obj.boxes) 188 | end 189 | end 190 | end 191 | 192 | -- gauges 193 | if spectator_settings.display_gauges == 2 or spectator_settings.display_gauges == 3 then 194 | display_draw_life(player_objects[1]) 195 | display_draw_meter(player_objects[1]) 196 | display_draw_stun_gauge(player_objects[1]) 197 | display_draw_bonuses(player_objects[1]) 198 | end 199 | 200 | if spectator_settings.display_gauges == 2 or spectator_settings.display_gauges == 4 then 201 | display_draw_life(player_objects[2]) 202 | display_draw_meter(player_objects[2]) 203 | display_draw_stun_gauge(player_objects[2]) 204 | display_draw_bonuses(player_objects[2]) 205 | end 206 | 207 | if spectator_settings.display_distances then 208 | display_draw_distances(player_objects[1], player_objects[2], 70, 2, 2) 209 | end 210 | 211 | end 212 | 213 | gui.box(0,0,0,0,0,0) -- if we don't draw something, what we drawed from last frame won't be cleared 214 | 215 | hotkey1_pressed = false 216 | hotkey2_pressed = false 217 | hotkey3_pressed = false 218 | hotkey4_pressed = false 219 | end 220 | 221 | emu.registerstart(on_start) 222 | emu.registerbefore(before_frame) 223 | gui.register(on_gui) 224 | savestate.registerload(on_load_state) 225 | 226 | 227 | function hotkey1() 228 | hotkey1_pressed = true 229 | end 230 | function hotkey2() 231 | hotkey2_pressed = true 232 | end 233 | function hotkey3() 234 | hotkey3_pressed = true 235 | end 236 | function hotkey4() 237 | hotkey4_pressed = true 238 | end 239 | 240 | input.registerhotkey(1, hotkey1) 241 | input.registerhotkey(2, hotkey2) 242 | input.registerhotkey(3, hotkey3) 243 | input.registerhotkey(4, hotkey4) 244 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3rd_training_lua 2 | Training mode for Street Fighter III 3rd Strike (Japan 990512), on Fightcade v2.0.91 3 | 4 | The right version of Fightcade can be downloaded [here](https://www.fightcade.com/) 5 | 6 | ## Main features 7 | - Can set dummy to counter-attack with any move on frame 1 after any hit / block / parry / wake-up 8 | - Can record and replay sequences into 8 different slots 9 | - Can replay sequences randomly and as counter-attack 10 | - Can save/load recorded sequences to/from files 11 | - Can display hit/hurt/throwboxes 12 | - Can display input history for both players 13 | - Special training mode to train parries and red parries 14 | 15 | ## How to use 16 | * Download emulator from [here](https://www.fightcade.com/) and find the proper roms 17 | * Download the archive from [here](https://github.com/Grouflon/3rd_training_lua/archive/master.zip) or clone repository 18 | * Extract the archive anywhere on your computer 19 | * Start the emulator, load the rom, start a match with P1 and P2 (you will need to map input for both players) 20 | * Go to Game->Lua Scripting->New Lua Script Window and run the script **3rd_training.lua** from here 21 | * Follow instructions from the Output Console 22 | 23 | ## Bug reporting / Contribute 24 | If you want to be informed when a new version come out and/or discuss the current bugs and features, you can join the [Discord server](https://discord.gg/CDXQyFmcSe) of the project. 25 | 26 | This training mode is still in development and you may encounter bugs or missing features while using it. Please report any bug on the **#bugs** channel, and any feature request on the **#features** channel of the discord server. 27 | 28 | If you wish to contribute or give any feedback, feel free to get in touch or submit pull requests. 29 | 30 | ## Troubleshooting 31 | **Q: Missing rom, zip file not found** 32 | 33 | A: Make sure you have the proper roms. You must have at least 2 roms: _sfiii3.zip_ and _sfiii3a.zip_. sfiii3 is the japanese version and the zip contains _sfiii3_japan_nocd.29f400.u2_. sfiiia is the american version and contains _sfiii3_usa.29f400.u2_. 34 | 35 | You may need to rename zip files so they match exactly what the emulator expect for. 36 | 37 | **Q: When I run the script, the characters can no longer move** 38 | 39 | A: You are probably using the script on FBA-RR which is not supported anymore, in order to benefit from the last features and improvement you must run the script on Fightcade2's FBNeo emulator. However if you still want to use FBA-RR, you can go back to v0.6 which was the last version supported on FBA-RR. 40 | 41 | **Q: Emulator crash when I run lua script** 42 | 43 | A: Check video settings, you musn't use "Enhanced" blitter option. 44 | 45 | **Q: UI looks weird and hitboxes are misplaced** 46 | 47 | A: Check video settings, you must use "Basic" blitter option with no scanlines if you want the UI to work properly. 48 | 49 | **Q: Emulator doesn't run at all, there's a missing dll** 50 | 51 | A: Install prerequires from [here](https://github.com/TASVideos/BizHawk-Prereqs/releases/latest/) 52 | 53 | ## Guard Jump 54 | The way guard jump works is that there are situations where you are throw invulnerable (cannot be thrown). These situations are after a reset, knockdown and blocking or being hit by an attack. This unthrowable state lasts for 6 frames. Guard jump should be input to block for this duration, jumping in a direction and then blocking again. 55 | 56 | The current implementation of Counter-Attack Move does not work with how guard jump functions and needs to be input for all of these situations. 57 | 58 | This is because it requires pre-buffered frames of input (the assumption of holding block beforehand in this case). 59 | 60 | Because of its current implementation it will only work properly when knocked down. So in the other listed situations such as being reset or your opponent going for a tick throw, the Counter-Attack Move version in the dummy menu will get thrown if timed correctly. 61 | 62 | To properly test guard jump in these situations you must use provided replays. 63 | 64 | To use these replays go to the recording menu and follow these steps; 65 | 66 | 1. Pick a slot you wish to load the replay into 67 | 2. Navigate to "Load slot from file" and hit Light Punch 68 | 3. Use left and right on your lever or keyboard to navigate the files in your recordings folder and find the Guard Jump you wish to use. 69 | 4. Make sure the replay slot where this is loaded is active and replay mode is set to normal. 70 | 5. Set "Counter Attack - Delay" in the recording menu to -4 (NEGATIVE 4) for each Guard Jump replay used. 71 | 6. Navigate to the "Dummy" menu and set "Counter Attack - Move" to none, and "Counter Attack - Action" to recording. 72 | 73 | Now every time your opponent is knocked down, is reset, blocks or gets hit it will attempt to guard jump in the direction of the replay used. 74 | 75 | There are three files provided; 76 | 1. Neutral Guard jump 77 | 2. Guard Jump Back (most commonly used and is what Guard Jump in the Dummy menu currently uses) 78 | 3. Guard Jump Forward (To get out of the corner or just try to jump over you) 79 | 80 | 81 | Providing these replays prevents headache for users figuring out how to properly generate these replays and make them function in any given situation where it is applicable. 82 | 83 | Hopefully these replays helps users practice against this technique while the "Counter Attack" functionality is being re-written. These replays won't be required forever unless you want to use them in some advanced use case. 84 | 85 | But for now its best to add this feature so people can know of it's existence and provide replays for advanced players that wish to practice against it in non knockdown scenarios. 86 | 87 | These replays are also provided for the purpose of randomized reaction or ordered reaction training for advanced users. 88 | 89 | If you want randomization between the three replays then simply load each one into a different replay slot and use the "Random" replay mode with no other replay slots populated. 90 | 91 | One such advanced use case example would be a Makoto player using the ability use replay weighting to simulate weighted decisions in order to practice post hayate mixups against an opponent that favors specific types of defensive options. 92 | 93 | Thank you for your support in this matter and please enjoy! 94 | 95 | ## Roadmap 96 | [Trello board](https://trello.com/b/UQ8ey2rQ/3rdtraining) 97 | 98 | ## Changelog 99 | ### v0.10 (29/05/2022) 100 | - [Feature] Charge special training (contribution of @ProfessorAnon) 101 | - [Feature] Hyakuretsu Kyaku special training (contribution of @ProfessorAnon) 102 | - [Feature] Dynamic input display (switch sides to avoid overlapping action) (contribution of @ProfessorAnon) 103 | - [Feature] Damage data display (contribution of @sammygutierrez) 104 | - [Feature] New 3rd_spectator.lua script for displaying info during replays without messing with input 105 | - [Feature] Number display for all gauges and bonuses 106 | - [Feature] Frame advantage display 107 | - [Feature] Character switch is now a lot easier: 108 | - Initial loading puts you right in the character select screen 109 | - You can go back to the character select screen by hitting alt-1 or from the entry in the training menu 110 | - Both characters and SA can be selected directly from P1 controller 111 | - Game intro animation is sped up by default, but this can be disabled in the options 112 | - [Feature] Gill and Shin Gouki can be selected from the character select screen 113 | - [Feature] Added back jump, forward jump, super jump, super forward jump, super back jump counter-attack options 114 | - [Feature] Added auto-crop last frames option 115 | - [Feature] Added guard jump first basic implementation + replays for advanced scenarios (courtesy of @Shodokan) 116 | - [Feature] Added "ordered" and "repeat ordered" replay modes 117 | - [Feature] Blocking system is now working in 4rd Strike (thanks to @speedmccool25 frame data recording) 118 | - [Bugfix] Fixed random parry not behaving properly 119 | - [Bugfix] Fixed self-cancellable LP/LK not correctly blocked on various characters 120 | - [FrameData][Q] added missing back mp + SA2 121 | 122 | ### v0.9 (04/04/2021) 123 | - [Feature] Projectiles are now blocked/parried 124 | - [Feature] The dummy will now counter-attack on landing after an air recovery 125 | - [Feature] Yun's Genei Jin is now fully blocked/parried by the dummy 126 | - [Feature] Added 4rd Strike rom support in collaboration with @speedmccool25, but no frame data recorded yet. 127 | - [Improvement] When loading a save state, the recording state is reset to a useful state depending on the state you were before 128 | - [Bugfix/Improvement] All characters can now block/parry meaties and all first frame wake up hits 129 | - [Bugfix/Improvement] Fixed a lot of bugs in the overall blocking/parrying/counter-attack system 130 | - [Bugfix/Improvement] Revamped the wake-up / fast wake-up triggering and counter-attack system to be more reliable and maintainable 131 | - [Bugfix] Fixed recordings not loading correctly on US-regioned machines 132 | 133 | ### v0.8 (23/12/2020) 134 | - [Feature] Special trainings section + parry special training 135 | - [Feature] Stun delayed reset mode 136 | - [Improvement] Added new menu categories and made a better split of options between them 137 | - [Improvement] Changed counter-attack random deviation cap from 40 to 600 138 | - [Bugfix] Fixed incorrect index causing errors when using random replay and weights 139 | - [Bugfix] [issue#21](https://github.com/Grouflon/3rd_training_lua/issues/21) When the game is paused and hitboxes are enabled, an error occurs when loading a savestate 140 | - [Bugfix] [issue#29](https://github.com/Grouflon/3rd_training_lua/issues/29) If you make a recording and rename it with lower case or space in its name, it won't launch 141 | - [Bugfix] [issue#22](https://github.com/Grouflon/3rd_training_lua/issues/22) Input flipping is now decided upon character position diff instead of sprite flip (should fix wrong manipulations occuring after some moves) 142 | - [Bufix] [Fixed meter gauges not updating after loading a save state](https://trello.com/c/7eMUwOHg/76-meter-refill-does-not-update-max-values-correctly-when-coming-back-to-select-screen-or-using-save-states) 143 | - [FrameData] Added some missing Makoto wake up data 144 | - [FrameData] Added some missing Ken wake up data 145 | - [FrameData] Added some missing Ibuki frame data 146 | 147 | ### v0.7 (12/11/2020) 148 | - Changed main supported emulator from FBA-rr to Fightcade's FBNeo fork 149 | - [Feature] Main player now acts as the training dummy during recording and pre-recording 150 | - [Feature] Added input history display for both players 151 | - [Feature] Added a weight to each replay slot to control randomness (Contribution of @BoredKittenz) 152 | - Redesigned controller display 153 | - [Bugfix] [issue#8](https://github.com/Grouflon/3rd_training_lua/issues/8) Cannot Link moves into super 154 | - [Bugfix] [issue#15](https://github.com/Grouflon/3rd_training_lua/issues/15) Time based super like geneijin not consistent with their meter usage 155 | - [Bugfix] [issue#19](https://github.com/Grouflon/3rd_training_lua/issues/19) Error: Failed to save training settings to training settings.json 156 | - [Bugfix] [issue#18](https://github.com/Grouflon/3rd_training_lua/issues/18) Another Big Issue: Constant Negative Edge While Recording 157 | - [Bugfix] [issue#17](https://github.com/Grouflon/3rd_training_lua/issues/17) Large issue : P2 cannot do EX moves even if they have meter 158 | 159 | ### v0.6 (04/04/2020) 160 | - Can save/load recorded sequences to/from files 161 | - Keep recordings between sessions (saved per character inside training_settings.json) 162 | - Added counter-attack delay and maximum random deviation to recording slots 163 | - Random blocking mode won't stop blocking in the middle of a true blockstring 164 | - Added First Hit blocking mode 165 | - Added refill delay for life and meter into training settings 166 | - [Bugfix] Fixed dummy bricking when triggering a recording counter attack with nothing recorded 167 | - [Frame Data] Elena 168 | - [Frame Data] Q 169 | - [Frame Data] Ryu 170 | - [Frame Data] Remy 171 | - [Frame Data] Twelve 172 | - [Frame Data] Chun-Li 173 | - [Frame Data] Sean 174 | - [Frame Data] Necro 175 | - [Frame Data] Dudley 176 | - [Frame Data] Yang 177 | - [Frame Data] Yun 178 | 179 | ### v0.5 (23/03/2020) 180 | - Auto refill life mode 181 | - Auto refill meter mode + ability to set a precise meter amount from the menu 182 | - Infinite Super Art Timer mode 183 | - input autofire (rapid movement when holding key) in menus 184 | - Frame data prediction can resync itself to the actual animation frame, and thus handle a lot more blocking situations 185 | - All 2 hits blocking / parying Fixed 186 | - Blocking / parying of self cancellable moves supported 187 | - Improved wording of some menu elements 188 | - [Bugfix] Fixed infinite meter not working for player 2 189 | - [Bugfix] Fixed recording counterattack triggering in the middle of a blockstring 190 | - [Bugfix] Fixed recording counterattack restarting on hit 191 | - [Frame Data] Oro 192 | - [Frame Data] Ken 193 | 194 | ### v0.4 (13/02/2020) 195 | - Urien frame data 196 | - Gouki frame data 197 | - Makoto frame data 198 | - Random fast wake up 199 | - Random blocking 200 | - Throws teching 201 | - Added music volume control 202 | - [Bugfix] Fixed Dudley not crouching correctly 203 | - [Bugfix] Fixed Oro not crouching correctly 204 | - [Bugfix] Do not counter attack on state load anymore 205 | 206 | ### v0.3 (28/01/2020) 207 | - Can now record sequences within 8 different slots 208 | - Can play recorded sequences repeatedly and on random 209 | - Recorded sequences can by triggered as a counter-attack 210 | 211 | ### v0.2 (26/01/2020) 212 | - New blocking system: Now works by recording hitboxes characteristics to a file for every move and predict hitbox collisions with actual frame data. 213 | - Can switch main player between P1 and P2 214 | - Removed all old frame data 215 | - Entered frame data for Ibuki, Alex and Hugo 216 | 217 | ### v0.1 (25/11/2019) 218 | - Basic blocking and training options 219 | - Can set dummy to block, parry and red parry after x hits 220 | - Can set dummy to counter-attack with any move after hit, block parry or wake up 221 | - Entered frame data by hand for Ibuki and Urien 222 | 223 | ## References & Inspirations 224 | - [Wonderful 3S frame data reference](http://baston.esn3s.com/) 225 | - [Hitbox display script by dammit](https://dammit.typepad.com/blog/2011/10/improved-3rd-strike-hitboxes.html) 226 | - [Trials mode script by c_cube](https://ameblo.jp/3fv/entry-12429961069.html) 227 | - [External C# training mode by furitiem](https://www.youtube.com/watch?v=vE27xe0QM64) 228 | - [3S InGame addresses spreadsheet](https://docs.google.com/spreadsheets/d/1eLi9phXMj18QGLfugrHhEQEjIVvSI2zbbUmDgPuLSf0/edit#gid=706955060) 229 | -------------------------------------------------------------------------------- /data/sfiii3nr1/savestates/character_select.fs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/data/sfiii3nr1/savestates/character_select.fs -------------------------------------------------------------------------------- /data/sfiii4n/framedata_meta.lua: -------------------------------------------------------------------------------- 1 | -- GILL 2 | frame_data_meta["gill"].moves["13c8"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 3 | frame_data_meta["gill"].moves["1498"] = { hits = {{ type = 2 }} } -- Cr MK 4 | frame_data_meta["gill"].moves["1578"] = { hits = {{ type = 2 }} } -- Cr HK 5 | 6 | frame_data_meta["gill"].moves["0ac8"] = { hits = {{ type = 3 }, { type = 3 }} } -- St.HK 7 | 8 | frame_data_meta["gill"].moves["17c8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 9 | frame_data_meta["gill"].moves["18a8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 10 | frame_data_meta["gill"].moves["1708"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 11 | frame_data_meta["gill"].moves["1ea8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 12 | frame_data_meta["gill"].moves["1f58"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 13 | frame_data_meta["gill"].moves["1988"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 14 | frame_data_meta["gill"].moves["1a28"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 15 | frame_data_meta["gill"].moves["1b08"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 16 | frame_data_meta["gill"].moves["2d70"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Diagonal Jump Down MK 17 | 18 | frame_data_meta["gill"].moves["2818"] = { hits = {{ type = 3 }} } -- LK Moonsault Drop 19 | frame_data_meta["gill"].moves["2908"] = { hits = {{ type = 3 }} } -- MK Moonsault Drop 20 | frame_data_meta["gill"].moves["29f8"] = { hits = {{ type = 3 }} } -- HK Moonsault Drop 21 | frame_data_meta["gill"].moves["2ae8"] = { hits = {{ type = 3 }} } -- EX Moonsault Drop 22 | 23 | frame_data_meta["gill"].moves["2e30"] = { hits = {{ type = 2 }, { type = 1 }, { type = 1 }, { type = 1 }, { type = 3 }, { type = 1 }} } -- SA1 Cyber Raid 24 | 25 | 26 | 27 | -- ALEX 28 | frame_data_meta["alex"].moves["b948"] = { hits = {{ type = 2 }} } -- Cr LK 29 | frame_data_meta["alex"].moves["bae8"] = { hits = {{ type = 2 }} } -- Cr MK 30 | frame_data_meta["alex"].moves["bc08"] = { hits = {{ type = 2 }} } -- Cr HK 31 | 32 | frame_data_meta["alex"].moves["2178"] = { hits = {{ type = 2 }} } -- Target Combo Cr HK 33 | 34 | frame_data_meta["alex"].moves["a590"] = { force_recording = true } -- LP 35 | 36 | frame_data_meta["alex"].moves["a7dc"] = { hits = {{ type = 3 }} } -- HP 37 | 38 | frame_data_meta["alex"].moves["72d4"] = { hits = {{ type = 3 }} } -- UOH 39 | 40 | frame_data_meta["alex"].moves["bd58"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 41 | frame_data_meta["alex"].moves["beb8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 42 | frame_data_meta["alex"].moves["bfc8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 43 | 44 | frame_data_meta["alex"].moves["c0e0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 45 | frame_data_meta["alex"].moves["c230"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 46 | frame_data_meta["alex"].moves["c310"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 47 | 48 | frame_data_meta["alex"].moves["6e70"] = { proxy = { offset = -23, id = "7044" } } -- VCharge LK 49 | frame_data_meta["alex"].moves["7190"] = { hits = {{ type = 3 }} } -- VCharge LK 50 | 51 | frame_data_meta["alex"].moves["6df4"] = { proxy = { offset = -25, id = "7094" } } -- VCharge MK 52 | frame_data_meta["alex"].moves["7094"] = { hits = {{ type = 3 }} } -- VCharge MK 53 | 54 | frame_data_meta["alex"].moves["6ec4"] = { proxy = { offset = -26, id = "70e4" } } -- VCharge HK 55 | frame_data_meta["alex"].moves["70e4"] = { hits = {{ type = 3 }} } -- VCharge HK 56 | 57 | frame_data_meta["alex"].moves["6f94"] = { proxy = { offset = -26, id = "70e4" } } -- VCharge EXK 58 | 59 | frame_data_meta["alex"].moves["ed90"] = { hit_throw = true } -- Back HP 60 | 61 | frame_data_meta["alex"].moves["5468"] = { hit_throw = true } -- LK Air Knee Smash 62 | frame_data_meta["alex"].moves["55f8"] = { hit_throw = true } -- MK Air Knee Smash 63 | frame_data_meta["alex"].moves["5770"] = { hit_throw = true } -- HK Air Knee Smash 64 | frame_data_meta["alex"].moves["58e8"] = { hit_throw = true } -- EX Air Knee Smash 65 | 66 | frame_data_meta["alex"].moves["0e08"] = { hit_throw = true } -- 3rd HP Chop Rekka QCF+P 67 | frame_data_meta["alex"].moves["10c8"] = { hit_throw = true } -- 3rd HP Chop Rekka HCB+P 68 | frame_data_meta["alex"].moves["6d98"] = { hit_throw = true } -- Hit Grab of Boomerang Raid 69 | 70 | -- IBUKI 71 | frame_data_meta["ibuki"].moves["1a88"] = { hits = {{ type = 2 }} } -- Cr LK 72 | frame_data_meta["ibuki"].moves["173c"] = { hits = {{ type = 2 }} } -- Cr MK 73 | frame_data_meta["ibuki"].moves["188c"] = { hits = {{ type = 2 }} } -- Cr Forward MK 74 | frame_data_meta["ibuki"].moves["1b0c"] = { hits = {{ type = 2 }} } -- Cr HK 75 | 76 | frame_data_meta["ibuki"].moves["33dc"] = { hits = {{ type = 2 }} } -- Target Combo Cr HK 77 | 78 | frame_data_meta["ibuki"].moves["a8b4"] = { hits = {{ type = 2 }} } -- L Tsumuji rekka 79 | frame_data_meta["ibuki"].moves["fdac"] = { hits = {{ type = 2 }} } -- H Kazekiri rekka 80 | frame_data_meta["ibuki"].moves["e95c"] = { hits = {{ type = 2 }, { type = 2 }} } -- EX Kazekiri rekka 81 | frame_data_meta["ibuki"].moves["ecac"] = { hits = {{ type = 2 }, { type = 2 }} } -- EX Kazekiri rekka 82 | 83 | frame_data_meta["ibuki"].moves["0894"] = { hits = {{ type = 3 }} } -- Forward MK 84 | frame_data_meta["ibuki"].moves["31ec"] = { hits = {{ type = 3 }} } -- Target MK 85 | frame_data_meta["ibuki"].moves["e00c"] = { hits = {{ type = 3 }} } -- UOH 86 | frame_data_meta["ibuki"].moves["259c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 87 | frame_data_meta["ibuki"].moves["26fc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 88 | frame_data_meta["ibuki"].moves["2034"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 89 | frame_data_meta["ibuki"].moves["2894"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 90 | frame_data_meta["ibuki"].moves["2964"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 91 | frame_data_meta["ibuki"].moves["2af4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 92 | frame_data_meta["ibuki"].moves["1d5c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 93 | frame_data_meta["ibuki"].moves["1e5c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 94 | frame_data_meta["ibuki"].moves["223c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 95 | frame_data_meta["ibuki"].moves["235c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 96 | frame_data_meta["ibuki"].moves["247c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 97 | 98 | frame_data_meta["ibuki"].moves["9344"] = { hit_throw = true, hits = {{ type = 2 }} } -- L Neck Breaker 99 | frame_data_meta["ibuki"].moves["9504"] = { hit_throw = true, hits = {{ type = 2 }} } -- M Neck Breaker 100 | frame_data_meta["ibuki"].moves["96c4"] = { hit_throw = true, hits = {{ type = 2 }} } -- H Neck Breaker 101 | frame_data_meta["ibuki"].moves["989c"] = { hit_throw = true, hits = {{ type = 2 }} } -- EX Neck Breaker 102 | 103 | frame_data_meta["ibuki"].moves["8f6c"] = { hit_throw = true } -- L Raida 104 | frame_data_meta["ibuki"].moves["90b4"] = { hit_throw = true } -- M Raida 105 | frame_data_meta["ibuki"].moves["91fc"] = { hit_throw = true } -- H Raida 106 | 107 | frame_data_meta["ibuki"].moves["7dec"] = { hits = {{ type = 3 }, { type = 3 }}, force_recording = true } -- L Hien 108 | frame_data_meta["ibuki"].moves["824c"] = { hits = {{ type = 3 }, { type = 3 }}, force_recording = true } -- M Hien 109 | frame_data_meta["ibuki"].moves["86ac"] = { hits = {{ type = 3 }, { type = 3 }}, force_recording = true } -- H Hien 110 | frame_data_meta["ibuki"].moves["8b0c"] = { hits = {{ type = 3 }, { type = 3 }}, movement_type = 2 , force_recording = true } -- Ex Hien 111 | 112 | -- HUGO 113 | frame_data_meta["hugo"].moves["51ac"] = { hits = {{ type = 2 }} } -- Cr LK 114 | frame_data_meta["hugo"].moves["525c"] = { hits = {{ type = 2 }} } -- Cr MK 115 | frame_data_meta["hugo"].moves["1d38"] = { hits = {{ type = 3 }} } -- Cr HK 116 | 117 | frame_data_meta["hugo"].moves["4f4c"] = { force_recording = true } -- Cr LP 118 | frame_data_meta["hugo"].moves["412c"] = { force_recording = true } -- LP 119 | 120 | frame_data_meta["hugo"].moves["1e20"] = { hits = {{ type = 3 }} } -- UOH 121 | frame_data_meta["hugo"].moves["434c"] = { hits = {{ type = 3 }} } -- HP 122 | frame_data_meta["hugo"].moves["48d0"] = { hits = {{ type = 1 }, { type = 3 }} } -- MK 123 | frame_data_meta["hugo"].moves["4d5c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- HK 124 | 125 | frame_data_meta["hugo"].moves["53ec"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 126 | frame_data_meta["hugo"].moves["54bc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 127 | frame_data_meta["hugo"].moves["558c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 128 | frame_data_meta["hugo"].moves["568c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air Down HP 129 | frame_data_meta["hugo"].moves["573c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 130 | frame_data_meta["hugo"].moves["580c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 131 | frame_data_meta["hugo"].moves["58dc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 132 | 133 | -- URIEN 134 | frame_data_meta["urien"].moves["ec40"] = { hits = {{ type = 2 }} } -- Cr LK 135 | frame_data_meta["urien"].moves["ed10"] = { hits = {{ type = 2 }} } -- Cr MK 136 | frame_data_meta["urien"].moves["edd0"] = { hits = {{ type = 2 }} } -- Cr HK 137 | 138 | frame_data_meta["urien"].moves["d8c0"] = { force_recording = true } -- LP 139 | frame_data_meta["urien"].moves["e5f8"] = { force_recording = true } -- Cr LP 140 | 141 | frame_data_meta["urien"].moves["68d0"] = { hits = {{ type = 3 }} } -- UOH 142 | frame_data_meta["urien"].moves["dd68"] = { hits = {{ type = 3 }} } -- Forward HP 143 | frame_data_meta["urien"].moves["e200"] = { hits = {{ type = 3 }, { type = 3 }} } -- HK 144 | 145 | frame_data_meta["urien"].moves["4e08"] = { hits = {{ type = 3 }} } -- L Knee Drop 146 | frame_data_meta["urien"].moves["4f98"] = { hits = {{ type = 3 }} } -- M Knee Drop 147 | frame_data_meta["urien"].moves["5128"] = { hits = {{ type = 3 }} } -- H Knee Drop 148 | frame_data_meta["urien"].moves["52b8"] = { hits = {{ type = 3 }, { type = 3 }}, movement_type = 2, force_recording = true } -- EX Knee Drop 149 | 150 | frame_data_meta["urien"].moves["ef60"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 151 | frame_data_meta["urien"].moves["f000"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 152 | frame_data_meta["urien"].moves["f0e0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 153 | frame_data_meta["urien"].moves["f1c0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 154 | frame_data_meta["urien"].moves["f260"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 155 | frame_data_meta["urien"].moves["f340"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 156 | 157 | -- GOUKI 158 | frame_data_meta["gouki"].moves["20b4"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }, { type = 2 }}, force_recording = true } -- Cr LK 159 | frame_data_meta["gouki"].moves["2154"] = { hits = {{ type = 2 }} } -- Cr MK 160 | frame_data_meta["gouki"].moves["2224"] = { hits = {{ type = 2 }} } -- Cr HK 161 | 162 | frame_data_meta["gouki"].moves["1584"] = { force_recording = true } -- LP 163 | frame_data_meta["gouki"].moves["1e74"] = { force_recording = true } -- Cr LP 164 | 165 | frame_data_meta["gouki"].moves["2790"] = { hits = {{ type = 3 }, { type = 3 }}, force_recording = true } -- Forward MP 166 | frame_data_meta["gouki"].moves["9a44"] = { hits = {{ type = 3 }, { type = 3 }} } -- UOH 167 | frame_data_meta["gouki"].moves["1c54"] = { hits = {{ type = 3 }, { type = 3 }} } -- Close HK 168 | 169 | frame_data_meta["gouki"].moves["3850"] = { proxy = { offset = -2, id = "1818" } } -- Target HP 170 | 171 | frame_data_meta["gouki"].moves["2314"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 172 | frame_data_meta["gouki"].moves["2854"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 173 | frame_data_meta["gouki"].moves["23f4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 174 | frame_data_meta["gouki"].moves["24d4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 175 | frame_data_meta["gouki"].moves["294c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 176 | frame_data_meta["gouki"].moves["2594"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 177 | frame_data_meta["gouki"].moves["2a2c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 178 | frame_data_meta["gouki"].moves["26a4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 179 | frame_data_meta["gouki"].moves["2b0c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 180 | frame_data_meta["gouki"].moves["2774"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 181 | frame_data_meta["gouki"].moves["2c7c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 182 | frame_data_meta["gouki"].moves["2bec"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air Down MK 183 | 184 | frame_data_meta["gouki"].moves["b054"] = { hits = {{ type = 2 }}, movement_type = 2 } -- Demon flip 185 | frame_data_meta["gouki"].moves["b364"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Demon flip K cancel 186 | frame_data_meta["gouki"].moves["b264"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Demon flip P cancel 187 | 188 | -- MAKOTO 189 | frame_data_meta["makoto"].moves["305c"] = { hits = {{ type = 2 }} } -- Cr LK 190 | frame_data_meta["makoto"].moves["2f2c"] = { hits = {{ type = 2 }} } -- Cr HP 191 | frame_data_meta["makoto"].moves["2b6c"] = { hits = {{ type = 2 }} } -- Forward HK 192 | 193 | frame_data_meta["makoto"].moves["dc5c"] = { hits = {{ type = 3 }} } -- UOH 194 | frame_data_meta["makoto"].moves["ed04"] = { hits = {{ type = 3 }} } -- L Oroshi 195 | frame_data_meta["makoto"].moves["eee4"] = { hits = {{ type = 3 }} } -- M Oroshi 196 | frame_data_meta["makoto"].moves["efe4"] = { hits = {{ type = 3 }} } -- H Oroshi 197 | frame_data_meta["makoto"].moves["f0e4"] = { hits = {{ type = 3 }} } -- EX Oroshi 198 | 199 | frame_data_meta["makoto"].moves["332c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 200 | frame_data_meta["makoto"].moves["340c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 201 | frame_data_meta["makoto"].moves["34cc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 202 | frame_data_meta["makoto"].moves["35ac"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 203 | frame_data_meta["makoto"].moves["366c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 204 | frame_data_meta["makoto"].moves["375c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 205 | 206 | frame_data_meta["makoto"].moves["3e5c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 207 | frame_data_meta["makoto"].moves["3f1c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 208 | frame_data_meta["makoto"].moves["401c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 209 | frame_data_meta["makoto"].moves["418c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 210 | frame_data_meta["makoto"].moves["424c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 211 | frame_data_meta["makoto"].moves["433c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 212 | 213 | frame_data_meta["makoto"].moves["22dc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- L Tsurugi 214 | frame_data_meta["makoto"].moves["245c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- M Tsurugi 215 | frame_data_meta["makoto"].moves["255c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- H Tsurugi 216 | frame_data_meta["makoto"].moves["265c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- EX Tsurugi 217 | 218 | frame_data_meta["makoto"].moves["ff34"] = { hits = {{ type = 3 }}, force_recording = true } -- LK SA 2 Initial Hit 219 | frame_data_meta["makoto"].moves["0014"] = { hits = {{ type = 3 }}, force_recording = true } -- MK SA 2 Initial Hit 220 | frame_data_meta["makoto"].moves["00f4"] = { hits = {{ type = 3 }}, force_recording = true } -- HK SA 2 Initial Hit 221 | frame_data_meta["makoto"].moves["03dc"] = { hits = {{ type = 3 }}, force_recording = true } -- SA 2 3 Grounded Hits 222 | 223 | 224 | -- ORO 225 | frame_data_meta["oro"].moves["5d5c"] = { hits = {{ type = 2 }} } -- Cr LK 226 | frame_data_meta["oro"].moves["5eec"] = { hits = {{ type = 2 }} } -- Cr MK 227 | frame_data_meta["oro"].moves["601c"] = { hits = {{ type = 2 }} } -- Cr HK 228 | 229 | frame_data_meta["oro"].moves["5054"] = { hits = {{ type = 3 }, { type = 3 }} } -- HP 230 | frame_data_meta["oro"].moves["1108"] = { hits = {{ type = 3}} } -- UOH 231 | 232 | frame_data_meta["oro"].moves["610c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 233 | frame_data_meta["oro"].moves["621c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 234 | frame_data_meta["oro"].moves["634c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 235 | frame_data_meta["oro"].moves["644c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 236 | frame_data_meta["oro"].moves["65ac"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 237 | frame_data_meta["oro"].moves["66dc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 238 | 239 | frame_data_meta["oro"].moves["6854"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 240 | frame_data_meta["oro"].moves["69d4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 241 | frame_data_meta["oro"].moves["6b54"] = { hits = {{ type = 3 }, { type = 3 }}, movement_type = 2 } -- Air HP 242 | frame_data_meta["oro"].moves["6d44"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 243 | frame_data_meta["oro"].moves["6e54"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 244 | frame_data_meta["oro"].moves["7044"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 245 | 246 | frame_data_meta["oro"].moves["d868"] = { hit_throw = true } -- HCB LP 247 | frame_data_meta["oro"].moves["d9e8"] = { hit_throw = true } -- HCB MP 248 | frame_data_meta["oro"].moves["dab8"] = { hit_throw = true } -- HCB HP 249 | 250 | frame_data_meta["oro"].moves["0a08"] = { hits = {{ type = 3 }, { type = 3 }} } -- QCF LK 251 | frame_data_meta["oro"].moves["0c78"] = { hits = {{ type = 3 }, { type = 3 }} } -- QCF MK 252 | frame_data_meta["oro"].moves["0de8"] = { hits = {{ type = 3 }, { type = 3 }} } -- QCF HK 253 | frame_data_meta["oro"].moves["0f58"] = { force_recording = true, hits = {{ type = 3 }, { type = 3 }, { type = 3 }}, movement_type = 2 } -- QCF EXK 254 | 255 | frame_data_meta["oro"].moves["0278"] = { hits = {{ type = 3 }, { type = 3 }, { type = 3 }}} -- Air QCF K 256 | frame_data_meta["oro"].moves["0568"] = { hits = {{ type = 3 }, { type = 3 }, { type = 3 }}} -- Air QCF EXK 257 | 258 | frame_data_meta["oro"].moves["fe50"] = { hit_throw = true } -- Grounded SA 1 259 | frame_data_meta["oro"].moves["1860"] = { hit_throw = true } -- Air SA 1 260 | 261 | -- KEN 262 | frame_data_meta["ken"].moves["b194"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 263 | frame_data_meta["ken"].moves["b234"] = { hits = {{ type = 2 }} } -- Cr MK 264 | frame_data_meta["ken"].moves["b304"] = { hits = {{ type = 2 }} } -- Cr HK 265 | 266 | frame_data_meta["ken"].moves["2538"] = { hits = {{ type = 3 }} } -- UOH 267 | 268 | frame_data_meta["ken"].moves["aacc"] = { hits = {{ type = 3 }, { type = 3 }} } -- Back MK 269 | frame_data_meta["ken"].moves["a9bc"] = { hits = {{ type = 3 }} } -- Forward HK 270 | 271 | frame_data_meta["ken"].moves["b674"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 272 | frame_data_meta["ken"].moves["b784"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 273 | frame_data_meta["ken"].moves["b854"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 274 | frame_data_meta["ken"].moves["b3f4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 275 | frame_data_meta["ken"].moves["b4d4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 276 | frame_data_meta["ken"].moves["b5b4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 277 | 278 | frame_data_meta["ken"].moves["bbd4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 279 | frame_data_meta["ken"].moves["bcb4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 280 | frame_data_meta["ken"].moves["bd94"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 281 | frame_data_meta["ken"].moves["b934"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 282 | frame_data_meta["ken"].moves["ba14"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 283 | frame_data_meta["ken"].moves["baf4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 284 | 285 | frame_data_meta["ken"].moves["1dc0"] = { hits = {{ type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }} } -- Air Tatsu L 286 | frame_data_meta["ken"].moves["1f40"] = { hits = {{ type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }} } -- Air Tatsu M 287 | frame_data_meta["ken"].moves["2040"] = { hits = {{ type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }} } -- Air Tatsu H 288 | frame_data_meta["ken"].moves["2140"] = { hits = {{ type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }, { type = 3 }} } -- Air Tatsu Ex 289 | 290 | frame_data_meta["ken"].moves["1588"] = { hits = {{ type = 3 }} } -- HCF MK 291 | 292 | 293 | frame_data_meta["ken"].moves["1214"] = { force_recording = true } -- SA 1 294 | frame_data_meta["ken"].moves["15b4"] = { force_recording = true } -- SA 2 295 | frame_data_meta["ken"].moves["1834"] = { force_recording = true } -- SA 3 296 | frame_data_meta["ken"].moves["1d24"] = { force_recording = true } -- SA 3 297 | 298 | -- ELENA 299 | frame_data_meta["elena"].moves["bf2c"] = { hits = {{ type = 2 }} } -- Cr LK 300 | frame_data_meta["elena"].moves["c0d4"] = { hits = {{ type = 2 }} } -- Cr MK 301 | frame_data_meta["elena"].moves["c324"] = { hits = {{ type = 2 }} } -- Cr HK 302 | frame_data_meta["elena"].moves["03a0"] = { hits = {{ type = 2 }} } -- Cr Forward MK 303 | frame_data_meta["elena"].moves["c58c"] = { hits = {{ type = 2 }} } -- Cr Forward HK 304 | frame_data_meta["elena"].moves["6520"] = { hits = {{ type = 2 }, { type = 1 }} } -- Taunt 305 | 306 | frame_data_meta["elena"].moves["64a0"] = { hits = {{ type = 3 }} } -- UOH 307 | frame_data_meta["elena"].moves["ace4"] = { hits = {{ type = 3 }} } -- Forward MP 308 | frame_data_meta["elena"].moves["b57c"] = { hits = {{ type = 3 }} } -- Forward MK 309 | 310 | frame_data_meta["elena"].moves["e370"] = { proxy = { id = "af64", offset = 0 }} -- Target HK 311 | frame_data_meta["elena"].moves["e068"] = { proxy = { id = "d8e4", offset = 0 }} -- Target Air MK 312 | frame_data_meta["elena"].moves["e1f8"] = { proxy = { id = "d448", offset = 6 }} -- Target Air HP 313 | 314 | frame_data_meta["elena"].moves["ccec"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 315 | frame_data_meta["elena"].moves["ce7c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 316 | frame_data_meta["elena"].moves["d03c"] = { hits = {{ type = 3 }, { type = 3 }}, movement_type = 2 } -- Straight Air HK 317 | frame_data_meta["elena"].moves["d754"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 318 | frame_data_meta["elena"].moves["de84"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 319 | frame_data_meta["elena"].moves["daa4"] = { hits = {{ type = 3 }, { type = 3 }}, movement_type = 2 } -- Air HK 320 | 321 | frame_data_meta["elena"].moves["c7dc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 322 | frame_data_meta["elena"].moves["c96c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 323 | frame_data_meta["elena"].moves["cb2c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 324 | frame_data_meta["elena"].moves["d244"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 325 | frame_data_meta["elena"].moves["d3d4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 326 | frame_data_meta["elena"].moves["daa4"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 327 | 328 | frame_data_meta["elena"].moves["0a98"] = { hits = {{ type = 3 }, { type = 3 }} } -- Mallet Smash L 329 | frame_data_meta["elena"].moves["0e38"] = { hits = {{ type = 3 }, { type = 3 }} } -- Mallet Smash M 330 | frame_data_meta["elena"].moves["0ff8"] = { hits = {{ type = 3 }, { type = 3 }} } -- Mallet Smash H 331 | frame_data_meta["elena"].moves["ff30"] = { hits = {{ type = 3 }, { type = 3 }} } -- Mallet Smash EX 332 | 333 | frame_data_meta["elena"].moves["3368"] = { hits = {{ type = 2 }, { type = 2 }} } -- Scratch Wheel L 334 | frame_data_meta["elena"].moves["3448"] = { hits = {{ type = 2 }, { type = 2 }} } -- Scratch Wheel M 335 | frame_data_meta["elena"].moves["35a8"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }, { type = 2 }} } -- Scratch Wheel H 336 | frame_data_meta["elena"].moves["36d8"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }, { type = 2 }, { type = 1 }} } -- Scratch Wheel EX 337 | 338 | frame_data_meta["elena"].moves["8518"] = { hits = {{ type = 2 }, { type = 2 }} } -- LK Lynx Tail 339 | frame_data_meta["elena"].moves["86d8"] = { hits = {{ type = 2 }, { type = 2 }} } -- MK Lynx Tail 340 | frame_data_meta["elena"].moves["8898"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }, { type = 2 }} } -- HK Lynx Tail 341 | frame_data_meta["elena"].moves["0968"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }, { type = 2 }} } -- EX Lynx Tail 342 | 343 | 344 | frame_data_meta["elena"].moves["4f10"] = { force_recording = true, hits = {{ type = 1 }, { type = 1 }, { type = 2 }} } -- SA 2 345 | frame_data_meta["elena"].moves["51c0"] = { force_recording = true, hits = {{ type = 1 }, { type = 2 }, { type = 1 }, { type = 2 }, { type = 2 }} } -- SA 2 346 | 347 | -- Q 348 | frame_data_meta["q"].moves["e7d0"] = { hits = {{ type = 2 }} } -- Cr HP 349 | frame_data_meta["q"].moves["e930"] = { hits = {{ type = 2 }} } -- Cr LK 350 | frame_data_meta["q"].moves["ea00"] = { hits = {{ type = 2 }} } -- Cr MK 351 | frame_data_meta["q"].moves["eb60"] = { hits = {{ type = 2 }} } -- Cr HK 352 | 353 | frame_data_meta["q"].moves["91c0"] = { hits = {{ type = 3 }} } -- UOH 354 | 355 | frame_data_meta["q"].moves["eff0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 356 | frame_data_meta["q"].moves["f0e0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 357 | frame_data_meta["q"].moves["f1c0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 358 | frame_data_meta["q"].moves["ed50"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 359 | frame_data_meta["q"].moves["edf0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 360 | frame_data_meta["q"].moves["eef0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 361 | frame_data_meta["q"].moves["f2e0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 362 | frame_data_meta["q"].moves["f5e0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 363 | frame_data_meta["q"].moves["f6e0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Jump Back HP 364 | frame_data_meta["q"].moves["f480"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Jump Forward HP 365 | 366 | frame_data_meta["q"].moves["5e28"] = { hits = {{ type = 3 }} } -- OH Dash punch L 367 | frame_data_meta["q"].moves["6090"] = { hits = {{ type = 3 }} } -- OH Dash punch M 368 | frame_data_meta["q"].moves["62f8"] = { hits = {{ type = 3 }} } -- OH Dash punch H 369 | 370 | frame_data_meta["q"].moves["52d8"] = { hits = {{ type = 2 }} } -- Low Dash punch L 371 | frame_data_meta["q"].moves["55a0"] = { hits = {{ type = 2 }} } -- Low Dash punch M 372 | frame_data_meta["q"].moves["5880"] = { hits = {{ type = 2 }} } -- Low Dash punch H 373 | frame_data_meta["q"].moves["5b78"] = { hits = {{ type = 2 }, { type = 2 }} } -- Low Dash punch EX 374 | 375 | frame_data_meta["q"].moves["8450"] = { hits = {{ type = 2 }} } -- SA1 376 | frame_data_meta["q"].moves["85b0"] = { force_recording = true } -- SA2 377 | 378 | -- RYU 379 | frame_data_meta["ryu"].moves["2450"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 380 | frame_data_meta["ryu"].moves["24f0"] = { hits = {{ type = 2 }} } -- Cr MK 381 | frame_data_meta["ryu"].moves["25c0"] = { hits = {{ type = 2 }} } -- Cr HK 382 | 383 | frame_data_meta["ryu"].moves["8228"] = { hits = {{ type = 3 }} } -- UOH 384 | frame_data_meta["ryu"].moves["1ad0"] = { hits = {{ type = 3 }, { type = 3 }} } -- Forward MP 385 | 386 | frame_data_meta["ryu"].moves["2930"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 387 | frame_data_meta["ryu"].moves["2a40"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 388 | frame_data_meta["ryu"].moves["2b10"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 389 | frame_data_meta["ryu"].moves["2eb0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 390 | frame_data_meta["ryu"].moves["2f90"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 391 | frame_data_meta["ryu"].moves["3070"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 392 | frame_data_meta["ryu"].moves["26b0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 393 | frame_data_meta["ryu"].moves["2790"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 394 | frame_data_meta["ryu"].moves["2870"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 395 | frame_data_meta["ryu"].moves["2bf0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 396 | frame_data_meta["ryu"].moves["2cd0"] = { hits = {{ type = 3 }, { type = 3 }}, movement_type = 2 } -- Air MP 397 | frame_data_meta["ryu"].moves["2dd0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 398 | 399 | frame_data_meta["ryu"].moves["7e08"] = { movement_type = 2 } -- Air tatsu L 400 | frame_data_meta["ryu"].moves["7f48"] = { movement_type = 2 } -- Air tatsu M 401 | frame_data_meta["ryu"].moves["8028"] = { movement_type = 2 } -- Air tatsu H 402 | 403 | frame_data_meta["ryu"].moves["8a98"] = { force_recording = true } -- SA2 404 | frame_data_meta["ryu"].moves["8d30"] = { force_recording = true } -- SA2 405 | 406 | -- REMY 407 | frame_data_meta["remy"].moves["ac6c"] = { hits = {{ type = 2 }} } -- Cr LK 408 | frame_data_meta["remy"].moves["0b38"] = { hits = {{ type = 2 }} } -- Cr MK 409 | frame_data_meta["remy"].moves["0c08"] = { hits = {{ type = 2 }} } -- Cr HK 410 | frame_data_meta["remy"].moves["2008"] = { hits = {{ type = 2 }, { type = 2 }} } -- Cr Forward HK 411 | 412 | frame_data_meta["remy"].moves["0094"] = { hits = {{ type = 3 }} } -- UOH 413 | frame_data_meta["remy"].moves["a5fc"] = { hits = {{ type = 3 }} } -- Forward MK 414 | 415 | frame_data_meta["remy"].moves["b3bc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 416 | frame_data_meta["remy"].moves["b4bc"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 417 | frame_data_meta["remy"].moves["b59c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 418 | frame_data_meta["remy"].moves["b08c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 419 | frame_data_meta["remy"].moves["b18c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 420 | frame_data_meta["remy"].moves["b28c"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 421 | 422 | -- TWELVE 423 | frame_data_meta["twelve"].moves["4778"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 424 | frame_data_meta["twelve"].moves["4848"] = { hits = {{ type = 2 }} } -- Cr MK 425 | frame_data_meta["twelve"].moves["1408"] = { hits = {{ type = 2 }} } -- Cr HK 426 | 427 | frame_data_meta["twelve"].moves["e300"] = { hits = {{ type = 3 }} } -- UOH 428 | 429 | frame_data_meta["twelve"].moves["4e18"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 430 | frame_data_meta["twelve"].moves["4ee8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 431 | frame_data_meta["twelve"].moves["4fe8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 432 | frame_data_meta["twelve"].moves["5378"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 433 | frame_data_meta["twelve"].moves["5448"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 434 | frame_data_meta["twelve"].moves["5548"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 435 | frame_data_meta["twelve"].moves["4b78"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 436 | frame_data_meta["twelve"].moves["4c38"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 437 | frame_data_meta["twelve"].moves["4cf8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 438 | frame_data_meta["twelve"].moves["50d8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 439 | frame_data_meta["twelve"].moves["5198"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 440 | frame_data_meta["twelve"].moves["5258"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 441 | 442 | frame_data_meta["twelve"].moves["ab28"] = { hits = {{ type = 3 }} } -- Air QCB LK 443 | frame_data_meta["twelve"].moves["ae80"] = { hits = {{ type = 3 }} } -- Air QCB MK 444 | frame_data_meta["twelve"].moves["b0e0"] = { hits = {{ type = 3 }} } -- Air QCB HK 445 | frame_data_meta["twelve"].moves["b340"] = { hits = {{ type = 3 }} } -- Air QCB EX 446 | 447 | -- CHUNLI 448 | frame_data_meta["chunli"].moves["cc10"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 449 | frame_data_meta["chunli"].moves["cd00"] = { hits = {{ type = 2 }} } -- Cr MK 450 | frame_data_meta["chunli"].moves["ce30"] = { hits = {{ type = 2 }} } -- Cr HK 451 | frame_data_meta["chunli"].moves["c950"] = { hits = {{ type = 2 }} } -- Cr MP 452 | 453 | frame_data_meta["chunli"].moves["6b88"] = { hits = {{ type = 3 }} } -- UOH 454 | frame_data_meta["chunli"].moves["ce8c"] = { hits = {{ type = 3 }} } -- Cr Forward HK 455 | 456 | frame_data_meta["chunli"].moves["d4d8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 457 | frame_data_meta["chunli"].moves["d5e8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 458 | frame_data_meta["chunli"].moves["d6f8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 459 | frame_data_meta["chunli"].moves["dd08"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 460 | frame_data_meta["chunli"].moves["dda8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 461 | frame_data_meta["chunli"].moves["e008"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 462 | frame_data_meta["chunli"].moves["d128"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 463 | frame_data_meta["chunli"].moves["d238"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 464 | frame_data_meta["chunli"].moves["d348"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 465 | frame_data_meta["chunli"].moves["d7d8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 466 | frame_data_meta["chunli"].moves["d878"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 467 | frame_data_meta["chunli"].moves["d928"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 468 | frame_data_meta["chunli"].moves["de98"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air Down MK 469 | 470 | frame_data_meta["chunli"].moves["6c38"] = { hits = {{ type = 3 }} } -- HCB LK 471 | frame_data_meta["chunli"].moves["6fa8"] = { hits = {{ type = 3 }} } -- HCB MK 472 | frame_data_meta["chunli"].moves["7318"] = { hits = {{ type = 3 }} } -- HCB HK 473 | frame_data_meta["chunli"].moves["7688"] = { hits = {{ type = 3 }} } -- HCB EXK 474 | 475 | -- SEAN 476 | frame_data_meta["sean"].moves["cb88"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 477 | frame_data_meta["sean"].moves["cc28"] = { hits = {{ type = 2 }} } -- Cr MK 478 | frame_data_meta["sean"].moves["ccf8"] = { hits = {{ type = 2 }} } -- Cr HK 479 | frame_data_meta["sean"].moves["203c"] = { hits = {{ type = 2 }}, hit_throw = true } -- HCF LP 480 | frame_data_meta["sean"].moves["21ac"] = { hits = {{ type = 2 }}, hit_throw = true } -- HCF MP 481 | frame_data_meta["sean"].moves["227c"] = { hits = {{ type = 2 }}, hit_throw = true } -- HCF HP 482 | frame_data_meta["sean"].moves["234c"] = { hits = {{ type = 2 }}, hit_throw = true } -- HCF EXP 483 | 484 | frame_data_meta["sean"].moves["dad4"] = { force_recording = true } -- Target HK 485 | 486 | frame_data_meta["sean"].moves["3e50"] = { hits = {{ type = 3 }} } -- UOH 487 | frame_data_meta["sean"].moves["c3a8"] = { hits = {{ type = 3 }, { type = 3 }} } -- Forward HP 488 | frame_data_meta["sean"].moves["dc7c"] = { proxy = { id = "c25c", offset = 0 } } -- Target Forward HP 489 | 490 | frame_data_meta["sean"].moves["28c0"] = { hits = {{ type = 3 }} } -- QCF K 491 | frame_data_meta["sean"].moves["2a10"] = { hits = {{ type = 3 }, { type = 3 }, { type = 3 }} } -- QCF EXK 492 | 493 | frame_data_meta["sean"].moves["d068"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 494 | frame_data_meta["sean"].moves["d178"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 495 | frame_data_meta["sean"].moves["d248"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 496 | frame_data_meta["sean"].moves["d5c8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 497 | frame_data_meta["sean"].moves["d6a8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 498 | frame_data_meta["sean"].moves["d788"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 499 | frame_data_meta["sean"].moves["cde8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 500 | frame_data_meta["sean"].moves["cec8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 501 | frame_data_meta["sean"].moves["cfa8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 502 | frame_data_meta["sean"].moves["d328"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 503 | frame_data_meta["sean"].moves["d408"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 504 | frame_data_meta["sean"].moves["d4e8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 505 | 506 | -- NECRO 507 | frame_data_meta["necro"].moves["e048"] = { hits = {{ type = 2 }} } -- Cr HP 508 | frame_data_meta["necro"].moves["e2d8"] = { hits = {{ type = 2 }} } -- Cr LK 509 | frame_data_meta["necro"].moves["e3e8"] = { hits = {{ type = 2 }} } -- Cr MK 510 | frame_data_meta["necro"].moves["07e0"] = { hits = {{ type = 2 }} } -- Cr HK 511 | 512 | frame_data_meta["necro"].moves["73c0"] = { hits = {{ type = 2 }}, hit_throw = true } -- Snake Fang L 513 | frame_data_meta["necro"].moves["74c0"] = { hits = {{ type = 2 }}, hit_throw = true } -- Snake Fang M 514 | frame_data_meta["necro"].moves["75c0"] = { hits = {{ type = 2 }}, hit_throw = true } -- Snake Fang H 515 | 516 | frame_data_meta["necro"].moves["7e40"] = { hits = {{ type = 3 }} } -- UOH 517 | frame_data_meta["necro"].moves["7ee0"] = { hits = {{ type = 3 }} } -- Rising Cobra L 518 | frame_data_meta["necro"].moves["8070"] = { hits = {{ type = 3 }} } -- Rising Cobra M 519 | frame_data_meta["necro"].moves["8200"] = { hits = {{ type = 3 }} } -- Rising Cobra H 520 | frame_data_meta["necro"].moves["8390"] = { hits = {{ type = 3 }, { type = 3 }} } -- Rising Cobra EX 521 | 522 | frame_data_meta["necro"].moves["eaa0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 523 | frame_data_meta["necro"].moves["ed80"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 524 | frame_data_meta["necro"].moves["f370"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 525 | frame_data_meta["necro"].moves["ed80"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 526 | frame_data_meta["necro"].moves["eec0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 527 | frame_data_meta["necro"].moves["e730"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 528 | frame_data_meta["necro"].moves["e800"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 529 | frame_data_meta["necro"].moves["e8f0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 530 | frame_data_meta["necro"].moves["06a8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air Down LP 531 | frame_data_meta["necro"].moves["f0f0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air Down MP 532 | frame_data_meta["necro"].moves["f1d0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 533 | 534 | -- DUDLEY 535 | frame_data_meta["dudley"].moves["4a48"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 536 | frame_data_meta["dudley"].moves["4b38"] = { hits = {{ type = 2 }} } -- Cr MK 537 | frame_data_meta["dudley"].moves["4d40"] = { hits = {{ type = 2 }} } -- Cr HK 538 | 539 | frame_data_meta["dudley"].moves["0a50"] = { hits = {{ type = 3 }} } -- UOH 540 | frame_data_meta["dudley"].moves["4394"] = { hits = {{ type = 3 }} } -- Forward HK 541 | 542 | frame_data_meta["dudley"].moves["5320"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 543 | frame_data_meta["dudley"].moves["5460"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 544 | frame_data_meta["dudley"].moves["55a0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 545 | frame_data_meta["dudley"].moves["59d0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 546 | frame_data_meta["dudley"].moves["5b10"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 547 | frame_data_meta["dudley"].moves["5c50"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 548 | frame_data_meta["dudley"].moves["5020"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 549 | frame_data_meta["dudley"].moves["5100"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 550 | frame_data_meta["dudley"].moves["5200"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 551 | frame_data_meta["dudley"].moves["56d0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 552 | frame_data_meta["dudley"].moves["57b0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 553 | frame_data_meta["dudley"].moves["58b0"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 554 | 555 | frame_data_meta["dudley"].moves["656c"] = { proxy = { id = "3fd4", offset = 0 } } -- Target MK 556 | frame_data_meta["dudley"].moves["675c"] = { proxy = { id = "3914", offset = 0 } } -- Target MP 557 | 558 | -- YANG 559 | frame_data_meta["yang"].moves["d5a8"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 560 | frame_data_meta["yang"].moves["d678"] = { hits = {{ type = 2 }} } -- Cr MK 561 | frame_data_meta["yang"].moves["d7f0"] = { hits = {{ type = 2 }} } -- Cr HK 562 | frame_data_meta["yang"].moves["d310"] = { hits = {{ type = 2 }} } -- Cr MP 563 | 564 | frame_data_meta["yang"].moves["0ee8"] = { hits = {{ type = 3 }} } -- UOH 565 | frame_data_meta["yang"].moves["1d38"] = { hits = {{ type = 3 }} } -- Forward MK 566 | 567 | frame_data_meta["yang"].moves["dd48"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 568 | frame_data_meta["yang"].moves["de88"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 569 | frame_data_meta["yang"].moves["dfd8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 570 | frame_data_meta["yang"].moves["e3a8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 571 | frame_data_meta["yang"].moves["e598"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 572 | frame_data_meta["yang"].moves["e7a8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 573 | frame_data_meta["yang"].moves["d9f8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 574 | frame_data_meta["yang"].moves["dae8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 575 | frame_data_meta["yang"].moves["dbd8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 576 | frame_data_meta["yang"].moves["e0d8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 577 | frame_data_meta["yang"].moves["e1d8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 578 | frame_data_meta["yang"].moves["e2c8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 579 | frame_data_meta["yang"].moves["e4e8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Dive L 580 | frame_data_meta["yang"].moves["e6f8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Dive M 581 | frame_data_meta["yang"].moves["e8a8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Dive H 582 | 583 | -- YUN 584 | frame_data_meta["yun"].moves["5508"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 585 | frame_data_meta["yun"].moves["55d8"] = { hits = {{ type = 2 }} } -- Cr MK 586 | frame_data_meta["yun"].moves["a160"] = { hits = {{ type = 2 }} } -- Cr HK 587 | 588 | frame_data_meta["yun"].moves["5f9c"] = { hits = {{ type = 3 }} } -- UOH 589 | frame_data_meta["yun"].moves["4e78"] = { hits = {{ type = 3 }} } -- Forward MK 590 | 591 | frame_data_meta["yun"].moves["5cb8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 592 | frame_data_meta["yun"].moves["5df8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 593 | frame_data_meta["yun"].moves["5f48"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 594 | frame_data_meta["yun"].moves["6318"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 595 | frame_data_meta["yun"].moves["6508"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 596 | frame_data_meta["yun"].moves["6708"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 597 | frame_data_meta["yun"].moves["5958"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 598 | frame_data_meta["yun"].moves["5a58"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 599 | frame_data_meta["yun"].moves["5b48"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 600 | frame_data_meta["yun"].moves["6048"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 601 | frame_data_meta["yun"].moves["6148"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 602 | frame_data_meta["yun"].moves["6238"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 603 | frame_data_meta["yun"].moves["6458"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Dive L 604 | frame_data_meta["yun"].moves["6658"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Dive M 605 | frame_data_meta["yun"].moves["6808"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Dive H 606 | 607 | frame_data_meta["yun"].moves["6c60"] = { hits = {{ type = 3 }} } -- Target Air HP 608 | frame_data_meta["yun"].moves["49ec"] = { hits = {{ type = 3 }}, force_recording = true } -- SA2 first two hits 609 | frame_data_meta["yun"].moves["4d2c"] = { hits = {{ type = 3 }}, force_recording = true } -- SA2 rest of combo 610 | 611 | -- SHIN AKUMA (ULTRA SEAN) 612 | frame_data_meta["usean"].moves["cb88"] = { hits = {{ type = 2 }, { type = 2 }, { type = 2 }} } -- Cr LK 613 | frame_data_meta["usean"].moves["cc28"] = { hits = {{ type = 2 }} } -- Cr MK 614 | frame_data_meta["usean"].moves["ccf8"] = { hits = {{ type = 2 }} } -- Cr HK 615 | 616 | frame_data_meta["usean"].moves["cde8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LP 617 | frame_data_meta["usean"].moves["cec8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MP 618 | frame_data_meta["usean"].moves["cfa8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HP 619 | frame_data_meta["usean"].moves["d328"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LP 620 | frame_data_meta["usean"].moves["d408"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MP 621 | frame_data_meta["usean"].moves["d4e8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HP 622 | frame_data_meta["usean"].moves["d068"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air LK 623 | frame_data_meta["usean"].moves["d178"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air MK 624 | frame_data_meta["usean"].moves["d248"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Straight Air HK 625 | frame_data_meta["usean"].moves["d5c8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air LK 626 | frame_data_meta["usean"].moves["d6a8"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air MK 627 | frame_data_meta["usean"].moves["d788"] = { hits = {{ type = 3 }}, movement_type = 2 } -- Air HK 628 | 629 | frame_data_meta["usean"].moves["4538"] = { hits = {{ type = 3 }}} -- LK Air Wheel Kick 630 | frame_data_meta["usean"].moves["46e8"] = { hits = {{ type = 3 }}} -- MK Air Wheel Kick 631 | frame_data_meta["usean"].moves["4848"] = { hits = {{ type = 3 }}} -- HK Air Wheel Kick 632 | 633 | frame_data_meta["usean"].moves["5310"] = { hits = {{ type = 2 }}, hit_throw = true } -- SA 3 634 | -------------------------------------------------------------------------------- /data/sfiii4n/savestates/character_select.fs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/data/sfiii4n/savestates/character_select.fs -------------------------------------------------------------------------------- /images/big/1_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/1_dir.png -------------------------------------------------------------------------------- /images/big/2_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/2_dir.png -------------------------------------------------------------------------------- /images/big/3_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/3_dir.png -------------------------------------------------------------------------------- /images/big/4_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/4_dir.png -------------------------------------------------------------------------------- /images/big/5_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/5_dir.png -------------------------------------------------------------------------------- /images/big/6_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/6_dir.png -------------------------------------------------------------------------------- /images/big/7_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/7_dir.png -------------------------------------------------------------------------------- /images/big/8_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/8_dir.png -------------------------------------------------------------------------------- /images/big/9_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/9_dir.png -------------------------------------------------------------------------------- /images/big/H_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/H_button.png -------------------------------------------------------------------------------- /images/big/L_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/L_button.png -------------------------------------------------------------------------------- /images/big/M_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/M_button.png -------------------------------------------------------------------------------- /images/big/no_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/big/no_button.png -------------------------------------------------------------------------------- /images/small/1_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/1_dir.png -------------------------------------------------------------------------------- /images/small/2_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/2_dir.png -------------------------------------------------------------------------------- /images/small/3_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/3_dir.png -------------------------------------------------------------------------------- /images/small/4_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/4_dir.png -------------------------------------------------------------------------------- /images/small/5_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/5_dir.png -------------------------------------------------------------------------------- /images/small/6_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/6_dir.png -------------------------------------------------------------------------------- /images/small/7_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/7_dir.png -------------------------------------------------------------------------------- /images/small/8_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/8_dir.png -------------------------------------------------------------------------------- /images/small/9_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/9_dir.png -------------------------------------------------------------------------------- /images/small/HK_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/HK_button.png -------------------------------------------------------------------------------- /images/small/HP_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/HP_button.png -------------------------------------------------------------------------------- /images/small/LK_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/LK_button.png -------------------------------------------------------------------------------- /images/small/LP_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/LP_button.png -------------------------------------------------------------------------------- /images/small/MK_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/MK_button.png -------------------------------------------------------------------------------- /images/small/MP_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/images/small/MP_button.png -------------------------------------------------------------------------------- /saved/_: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/saved/_ -------------------------------------------------------------------------------- /saved/recordings/A_Guard_Jump_Back.json: -------------------------------------------------------------------------------- 1 | [["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["back","up"],["back","up"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"]] -------------------------------------------------------------------------------- /saved/recordings/A_Guard_Jump_Forward.json: -------------------------------------------------------------------------------- 1 | [["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["forward","up"],["forward","up"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"]] -------------------------------------------------------------------------------- /saved/recordings/A_Guard_Jump_Neutral.json: -------------------------------------------------------------------------------- 1 | [["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["up"],["up"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"],["down","back"]] -------------------------------------------------------------------------------- /saved/recordings/_: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grouflon/3rd_training_lua/73ec4c062108fd3494c4fae6b81a61f9cf518b81/saved/recordings/_ -------------------------------------------------------------------------------- /src/attack_data.lua: -------------------------------------------------------------------------------- 1 | attack_data = {} 2 | 3 | function attack_data_update(_attacker, _defender) 4 | attack_data.player_id = _attacker.id 5 | 6 | if _attacker.combo == nil then 7 | _attacker.combo = 0 8 | end 9 | 10 | if _attacker.combo == 0 then 11 | attack_data.last_hit_combo = 0 12 | end 13 | 14 | if _attacker.damage_of_next_hit ~= 0 then 15 | attack_data.damage = _attacker.damage_of_next_hit 16 | attack_data.stun = _attacker.stun_of_next_hit 17 | 18 | if _attacker.combo > attack_data.last_hit_combo and attack_data.last_hit_combo ~= 0 then 19 | attack_data.total_damage = attack_data.total_damage + _attacker.damage_of_next_hit 20 | attack_data.total_stun = attack_data.total_stun + _attacker.stun_of_next_hit 21 | elseif _attacker.combo == attack_data.last_hit_combo then 22 | -- Repeated hit, skip 23 | else 24 | attack_data.total_damage = _attacker.damage_of_next_hit 25 | attack_data.total_stun = _attacker.stun_of_next_hit 26 | end 27 | 28 | attack_data.last_hit_combo = _attacker.combo 29 | end 30 | 31 | if _attacker.combo ~= 0 then 32 | attack_data.combo = _attacker.combo 33 | end 34 | if _attacker.combo > attack_data.max_combo then 35 | attack_data.max_combo = _attacker.combo 36 | end 37 | end 38 | 39 | function attack_data_display() 40 | local _text_width1 = get_text_width("damage: ") 41 | local _text_width2 = get_text_width("stun: ") 42 | local _text_width3 = get_text_width("combo: ") 43 | local _text_width4 = get_text_width("total damage: ") 44 | local _text_width5 = get_text_width("total stun: ") 45 | local _text_width6 = get_text_width("max combo: ") 46 | 47 | local _x1 = 0 48 | local _x2 = 0 49 | local _x3 = 0 50 | local _x4 = 0 51 | local _x5 = 0 52 | local _x6 = 0 53 | local _y = 49 54 | 55 | local _x_spacing = 80 56 | 57 | if attack_data.player_id == 1 then 58 | local _base = screen_width - 138 59 | _x1 = _base - _text_width1 60 | _x2 = _base - _text_width2 61 | _x3 = _base - _text_width3 62 | local _base2 = _base + _x_spacing 63 | _x4 = _base2 - _text_width4 64 | _x5 = _base2 - _text_width5 65 | _x6 = _base2 - _text_width6 66 | elseif attack_data.player_id == 2 then 67 | local _base = 82 68 | _x1 = _base - _text_width1 69 | _x2 = _base - _text_width2 70 | _x3 = _base - _text_width3 71 | local _base2 = _base + _x_spacing 72 | _x4 = _base2 - _text_width4 73 | _x5 = _base2 - _text_width5 74 | _x6 = _base2 - _text_width6 75 | end 76 | 77 | gui.text(_x1, _y, string.format("damage: ")) 78 | gui.text(_x1 + _text_width1, _y, string.format("%d", attack_data.damage)) 79 | 80 | gui.text(_x2, _y + 10, string.format("stun: ")) 81 | gui.text(_x2 + _text_width2, _y + 10, string.format("%d", attack_data.stun)) 82 | 83 | gui.text(_x3, _y + 20, string.format("combo: ")) 84 | gui.text(_x3 + _text_width3, _y + 20, string.format("%d", attack_data.combo)) 85 | 86 | gui.text(_x4, _y, string.format("total damage: ")) 87 | gui.text(_x4 + _text_width4, _y, string.format("%d", attack_data.total_damage)) 88 | 89 | gui.text(_x5, _y + 10, string.format("total stun: ")) 90 | gui.text(_x5 + _text_width5, _y + 10, string.format("%d", attack_data.total_stun)) 91 | 92 | gui.text(_x6, _y + 20, string.format("max combo: ")) 93 | gui.text(_x6 + _text_width6, _y + 20, string.format("%d", attack_data.max_combo)) 94 | end 95 | 96 | function attack_data_reset() 97 | attack_data = { 98 | player_id = nil, 99 | last_hit_combo = 0, 100 | 101 | damage = 0, 102 | stun = 0, 103 | combo = 0, 104 | total_damage = 0, 105 | total_stun = 0, 106 | max_combo = 0, 107 | } 108 | end 109 | attack_data_reset() -------------------------------------------------------------------------------- /src/character_select.lua: -------------------------------------------------------------------------------- 1 | 2 | character_select_coroutine = nil 3 | 4 | -- 0 is out 5 | -- 1 is waiting for input release for p1 6 | -- 2 is selecting p1 7 | -- 3 is waiting for input release for p2 8 | -- 4 is selecting p2 9 | character_select_sequence_state = 0 10 | 11 | function co_wait_x_frames(_frame_count) 12 | local _start_frame = frame_number 13 | while frame_number < _start_frame + _frame_count do 14 | coroutine.yield() 15 | end 16 | end 17 | 18 | function start_character_select_sequence() 19 | savestate.load(savestate.create("data/"..rom_name.."/savestates/character_select.fs")) 20 | character_select_sequence_state = 1 21 | end 22 | 23 | function select_gill() 24 | character_select_coroutine = coroutine.create(co_select_gill) 25 | end 26 | 27 | function co_select_gill(_input) 28 | local _player_id = 0 29 | 30 | local _p1_character_select_state = memory.readbyte(adresses.players[1].character_select_state) 31 | local _p2_character_select_state = memory.readbyte(adresses.players[2].character_select_state) 32 | 33 | if _p1_character_select_state > 2 and _p2_character_select_state > 2 then 34 | return 35 | end 36 | 37 | if _p1_character_select_state <= 2 then 38 | _player_id = 1 39 | else 40 | _player_id = 2 41 | end 42 | 43 | memory.writebyte(adresses.players[_player_id].character_select_col, 3) 44 | memory.writebyte(adresses.players[_player_id].character_select_row, 1) 45 | 46 | make_input_empty(_input) 47 | _input[player_objects[_player_id].prefix.." Weak Punch"] = true 48 | end 49 | 50 | function select_shingouki() 51 | character_select_coroutine = coroutine.create(co_select_shingouki) 52 | end 53 | 54 | function co_select_shingouki(_input) 55 | local _player_id = 0 56 | 57 | local _p1_character_select_state = memory.readbyte(adresses.players[1].character_select_state) 58 | local _p2_character_select_state = memory.readbyte(adresses.players[2].character_select_state) 59 | 60 | if _p1_character_select_state > 2 and _p2_character_select_state > 2 then 61 | return 62 | end 63 | 64 | if _p1_character_select_state <= 2 then 65 | _player_id = 1 66 | else 67 | _player_id = 2 68 | end 69 | 70 | memory.writebyte(adresses.players[_player_id].character_select_col, 0) 71 | memory.writebyte(adresses.players[_player_id].character_select_row, 6) 72 | 73 | make_input_empty(_input) 74 | _input[player_objects[_player_id].prefix.." Weak Punch"] = true 75 | 76 | co_wait_x_frames(20) 77 | 78 | memory.writebyte(adresses.players[_player_id].character_select_id, 0x0F) 79 | end 80 | 81 | function update_character_select(_input, _do_fast_forward) 82 | 83 | if not character_select_sequence_state == 0 then 84 | return 85 | end 86 | 87 | -- Infinite select time 88 | --memory.writebyte(adresses.global.character_select_timer, 0x30) 89 | 90 | if (character_select_coroutine ~= nil) then 91 | make_input_empty(_input) 92 | local _status = coroutine.status(character_select_coroutine) 93 | if _status == "suspended" then 94 | local _r, _error = coroutine.resume(character_select_coroutine, _input) 95 | if not _r then 96 | print(_error) 97 | end 98 | elseif _status == "dead" then 99 | character_select_coroutine = nil 100 | end 101 | return 102 | end 103 | 104 | local _p1_character_select_state = memory.readbyte(adresses.players[1].character_select_state) 105 | local _p2_character_select_state = memory.readbyte(adresses.players[2].character_select_state) 106 | 107 | --print(string.format("%d, %d, %d", character_select_sequence_state, _p1_character_select_state, _p2_character_select_state)) 108 | 109 | if _p1_character_select_state > 4 and not is_in_match then 110 | if character_select_sequence_state == 2 then 111 | character_select_sequence_state = 3 112 | end 113 | swap_inputs(_input) 114 | end 115 | 116 | -- wait for all inputs to be released 117 | if character_select_sequence_state == 1 or character_select_sequence_state == 3 then 118 | for _key, _state in pairs(_input) do 119 | if _state == true then 120 | make_input_empty(_input) 121 | return 122 | end 123 | end 124 | character_select_sequence_state = character_select_sequence_state + 1 125 | end 126 | 127 | if has_match_just_started then 128 | emu.speedmode("normal") 129 | character_select_sequence_state = 0 130 | elseif not is_in_match then 131 | if _do_fast_forward and _p1_character_select_state > 4 and _p2_character_select_state > 4 then 132 | emu.speedmode("turbo") 133 | elseif character_select_sequence_state == 0 and (_p1_character_select_state < 5 or _p2_character_select_state < 5) then 134 | emu.speedmode("normal") 135 | character_select_sequence_state = 1 136 | end 137 | else 138 | character_select_sequence_state = 0 139 | end 140 | 141 | end 142 | 143 | function draw_character_select() 144 | local _p1_character_select_state = memory.readbyte(adresses.players[1].character_select_state) 145 | local _p2_character_select_state = memory.readbyte(adresses.players[2].character_select_state) 146 | 147 | if _p1_character_select_state <= 2 or _p2_character_select_state <= 2 then 148 | gui.text(10, 10, "Alt+1 -> Return To Character Select Screen", text_default_color, text_default_border_color) 149 | if rom_name == "sfiii3nr1" then 150 | gui.text(10, 20, "Alt+2 -> Gill", text_default_color, text_default_border_color) 151 | gui.text(10, 30, "Alt+3 -> Shin Gouki", text_default_color, text_default_border_color) 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /src/display.lua: -------------------------------------------------------------------------------- 1 | -- # enums 2 | distance_display_mode = 3 | { 4 | "none", 5 | "simple", 6 | "advanced", 7 | } 8 | 9 | distance_display_reference_point = 10 | { 11 | "origin", 12 | "hurtbox", 13 | } 14 | 15 | -- # api 16 | 17 | -- push a persistent set of hitboxes to be drawn on the screen each frame 18 | function print_hitboxes(_pos_x, _pos_y, _flip_x, _boxes, _filter, _dilation) 19 | local _g = { 20 | type = "hitboxes", 21 | x = _pos_x, 22 | y = _pos_y, 23 | flip_x = _flip_x, 24 | boxes = _boxes, 25 | filter = _filter, 26 | dilation = _dilation 27 | } 28 | table.insert(printed_geometry, _g) 29 | end 30 | 31 | -- push a persistent point to be drawn on the screen each frame 32 | function print_point(_pos_x, _pos_y, _color) 33 | local _g = { 34 | type = "point", 35 | x = _pos_x, 36 | y = _pos_y, 37 | color = _color 38 | } 39 | table.insert(printed_geometry, _g) 40 | end 41 | 42 | function clear_printed_geometry() 43 | printed_geometry = {} 44 | end 45 | 46 | -- # system 47 | printed_geometry = {} 48 | 49 | function display_draw_printed_geometry() 50 | -- printed geometry 51 | for _i, _geometry in ipairs(printed_geometry) do 52 | if _geometry.type == "hitboxes" then 53 | draw_hitboxes(_geometry.x, _geometry.y, _geometry.flip_x, _geometry.boxes, _geometry.filter, _geometry.dilation) 54 | elseif _geometry.type == "point" then 55 | draw_point(_geometry.x, _geometry.y, _geometry.color) 56 | end 57 | end 58 | end 59 | 60 | function display_draw_hitboxes() 61 | -- players 62 | for _id, _obj in pairs(player_objects) do 63 | draw_hitboxes(_obj.pos_x, _obj.pos_y, _obj.flip_x, _obj.boxes) 64 | end 65 | -- projectiles 66 | for _id, _obj in pairs(projectiles) do 67 | draw_hitboxes(_obj.pos_x, _obj.pos_y, _obj.flip_x, _obj.boxes) 68 | end 69 | end 70 | 71 | 72 | function display_draw_life(_player_object) 73 | local _x = 0 74 | local _y = 20 75 | 76 | local _t = string.format("%d/160", _player_object.life) 77 | 78 | if _player_object.id == 1 then 79 | _x = 13 80 | elseif _player_object.id == 2 then 81 | _x = screen_width - 11 - get_text_width(_t) 82 | end 83 | 84 | gui.text(_x, _y, _t, 0xFFFB63FF) 85 | end 86 | 87 | 88 | function display_draw_meter(_player_object) 89 | local _x = 0 90 | local _y = 214 91 | 92 | local _gauge = _player_object.meter_gauge 93 | 94 | if _player_object.meter_count == _player_object.max_meter_count then 95 | _gauge = _player_object.max_meter_gauge 96 | end 97 | 98 | local _t = string.format("%d/%d", _gauge, _player_object.max_meter_gauge) 99 | 100 | if _player_object.id == 1 then 101 | _x = 53 102 | elseif _player_object.id == 2 then 103 | _x = screen_width - 51 - get_text_width(_t) 104 | end 105 | 106 | gui.text(_x, _y, _t, 0x00FFCEFF, 0x001433FF) 107 | end 108 | 109 | 110 | function display_draw_stun_gauge(_player_object) 111 | local _x = 0 112 | local _y = 29 113 | 114 | local _t = string.format("%d/%d", _player_object.stun_bar, _player_object.stun_max) 115 | 116 | if _player_object.id == 1 then 117 | _x = 118 118 | elseif _player_object.id == 2 then 119 | _x = screen_width - 116 - get_text_width(_t) 120 | end 121 | 122 | gui.text(_x, _y, _t, 0xE70000FF, 0x001433FF) 123 | end 124 | 125 | function display_draw_bonuses(_player_object) 126 | 127 | if _player_object.damage_bonus > 0 then 128 | local _x = 0 129 | local _y = 7 130 | 131 | local _t = string.format("+%d dmg", _player_object.damage_bonus) 132 | 133 | if _player_object.id == 1 then 134 | _x = 43 135 | elseif _player_object.id == 2 then 136 | _x = screen_width - 40 - get_text_width(_t) 137 | end 138 | 139 | gui.text(_x, _y, _t, 0xFF7184FF, 0x392031FF) 140 | end 141 | 142 | if _player_object.defense_bonus > 0 then 143 | 144 | local _x = 0 145 | local _y = 7 146 | 147 | local _t = string.format("+%d def", _player_object.defense_bonus) 148 | 149 | if _player_object.id == 1 then 150 | _x = 10 151 | elseif _player_object.id == 2 then 152 | _x = screen_width - 7 - get_text_width(_t) 153 | end 154 | 155 | gui.text(_x, _y, _t, 0xD6E3EFFF, 0x000029FF) 156 | end 157 | 158 | if _player_object.stun_bonus > 0 then 159 | 160 | local _x = 0 161 | local _y = 33 162 | 163 | local _t = string.format("+%d stun", _player_object.stun_bonus) 164 | 165 | if _player_object.id == 1 then 166 | _x = 81 167 | elseif _player_object.id == 2 then 168 | _x = screen_width - 79 - get_text_width(_t) 169 | end 170 | 171 | gui.text(_x, _y, _t, 0xD6E3EFFF, 0x000029FF) 172 | end 173 | 174 | end 175 | 176 | function draw_horizontal_text_segment(_p1_x, _p2_x, _y, _text, _line_color, _edges_height) 177 | 178 | _edges_height = _edges_height or 3 179 | local _half_distance_str_width = get_text_width(_text) * 0.5 180 | 181 | local _center_x = (_p1_x + _p2_x) * 0.5 182 | draw_horizontal_line(math.min(_p1_x, _p2_x), _center_x - _half_distance_str_width - 3, _y, _line_color, 1) 183 | draw_horizontal_line(_center_x + _half_distance_str_width + 3, math.max(_p1_x, _p2_x), _y, _line_color, 1) 184 | gui.text(_center_x - _half_distance_str_width, _y - 3, _text, text_default_color, text_default_border_color) 185 | 186 | if _edges_height > 0 then 187 | draw_vertical_line(_p1_x, _y - _edges_height, _y + _edges_height, _line_color, 1) 188 | draw_vertical_line(_p2_x, _y - _edges_height, _y + _edges_height, _line_color, 1) 189 | end 190 | end 191 | 192 | function display_draw_distances(_p1_object, _p2_object, _mid_distance_height, _p1_reference_point, _p2_reference_point) 193 | 194 | function _find_closest_box_at_height(_player_obj, _height, _box_types) 195 | 196 | local _px = _player_obj.pos_x 197 | local _py = _player_obj.pos_y 198 | 199 | local _left, _right = _px, _px 200 | 201 | if _box_types == nil then 202 | return false, _left, _right 203 | end 204 | 205 | local _has_boxes = false 206 | for __, _box in ipairs(_player_obj.boxes) do 207 | 208 | if _box_types[_box.type] then 209 | local _l, _r 210 | if _player_obj.flip_x == 0 then 211 | _l = _px + _box.left 212 | else 213 | _l = _px - _box.left - _box.width 214 | end 215 | local _r = _l + _box.width 216 | local _b = _py + _box.bottom 217 | local _t = _b + _box.height 218 | 219 | if _height >= _b and _height <= _t then 220 | _has_boxes = true 221 | _left = math.min(_left, _l) 222 | _right = math.max(_right, _r) 223 | end 224 | end 225 | end 226 | 227 | return _has_boxes, _left, _right 228 | end 229 | 230 | function _get_screen_line_between_boxes(_box1_l, _box1_r, _box2_l, _box2_r) 231 | if not ( 232 | (_box1_l >= _box2_r) or 233 | (_box1_r <= _box2_l) 234 | ) then 235 | return false 236 | end 237 | 238 | if _box1_l < _box2_l then 239 | return true, game_to_screen_space_x(_box1_r), game_to_screen_space_x(_box2_l) 240 | else 241 | return true, game_to_screen_space_x(_box2_r), game_to_screen_space_x(_box1_l) 242 | end 243 | end 244 | 245 | function _display_distance(_p1_object, _p2_object, _height, _box_types, _p1_reference_point, _p2_reference_point, _color) 246 | local _y = math.min(_p1_object.pos_y + _height, _p2_object.pos_y + _height) 247 | local _p1_l, _p1_r, _p2_l, _p2_r 248 | local _p1_result, _p2_result = false, false 249 | if _p1_reference_point == 2 then 250 | _p1_result, _p1_l, _p1_r = _find_closest_box_at_height(_p1_object, _y, _box_types) 251 | end 252 | if not _p1_result then 253 | _p1_l, _p1_r = _p1_object.pos_x, _p1_object.pos_x 254 | end 255 | if _p2_reference_point == 2 then 256 | _p2_result, _p2_l, _p2_r = _find_closest_box_at_height(_p2_object, _y, _box_types) 257 | end 258 | if not _p2_result then 259 | _p2_l, _p2_r = _p2_object.pos_x, _p2_object.pos_x 260 | end 261 | 262 | local _line_result, _screen_l, _screen_r = _get_screen_line_between_boxes(_p1_l, _p1_r, _p2_l, _p2_r) 263 | 264 | if _line_result then 265 | local _screen_y = game_to_screen_space_y(_y) 266 | local _str = string.format("%d", math.abs(_screen_r - _screen_l)) 267 | draw_horizontal_text_segment(_screen_l, _screen_r, _screen_y, _str, _color) 268 | end 269 | end 270 | 271 | -- throw 272 | _display_distance(_p1_object, _p2_object, 2, { throwable = true }, _p1_reference_point, _p2_reference_point, 0x08CF00FF) 273 | 274 | -- low and mid 275 | local _hurtbox_types = {} 276 | _hurtbox_types["vulnerability"] = true 277 | _hurtbox_types["ext. vulnerability"] = true 278 | _display_distance(_p1_object, _p2_object, 10, _hurtbox_types, _p1_reference_point, _p2_reference_point, 0x00E7FFFF) 279 | _display_distance(_p1_object, _p2_object, _mid_distance_height, _hurtbox_types, _p1_reference_point, _p2_reference_point, 0x00E7FFFF) 280 | 281 | -- player positions 282 | local _line_color = 0xFFFF63FF 283 | local _p1_screen_x, _p1_screen_y = game_to_screen_space(_p1_object.pos_x, _p1_object.pos_y) 284 | local _p2_screen_x, _p2_screen_y = game_to_screen_space(_p2_object.pos_x, _p2_object.pos_y) 285 | draw_point(_p1_screen_x, _p1_screen_y, _line_color) 286 | draw_point(_p2_screen_x, _p2_screen_y, _line_color) 287 | gui.text(_p1_screen_x + 3, _p1_screen_y + 2, string.format("%d:%d", _p1_object.pos_x, _p1_object.pos_y), text_default_color, text_default_border_color) 288 | gui.text(_p2_screen_x + 3, _p2_screen_y + 2, string.format("%d:%d", _p2_object.pos_x, _p2_object.pos_y), text_default_color, text_default_border_color) 289 | end 290 | -------------------------------------------------------------------------------- /src/draw.lua: -------------------------------------------------------------------------------- 1 | require "gd" 2 | 3 | -- # Constants 4 | screen_width = 383 5 | screen_height = 223 6 | ground_offset = 23 7 | 8 | -- # Global variables 9 | screen_x = 0 10 | screen_y = 0 11 | scale = 1 12 | 13 | -- # Images 14 | 15 | img_1_dir_big = gd.createFromPng("images/big/1_dir.png"):gdStr() 16 | img_2_dir_big = gd.createFromPng("images/big/2_dir.png"):gdStr() 17 | img_3_dir_big = gd.createFromPng("images/big/3_dir.png"):gdStr() 18 | img_4_dir_big = gd.createFromPng("images/big/4_dir.png"):gdStr() 19 | img_5_dir_big = gd.createFromPng("images/big/5_dir.png"):gdStr() 20 | img_6_dir_big = gd.createFromPng("images/big/6_dir.png"):gdStr() 21 | img_7_dir_big = gd.createFromPng("images/big/7_dir.png"):gdStr() 22 | img_8_dir_big = gd.createFromPng("images/big/8_dir.png"):gdStr() 23 | img_9_dir_big = gd.createFromPng("images/big/9_dir.png"):gdStr() 24 | img_no_button_big = gd.createFromPng("images/big/no_button.png"):gdStr() 25 | img_L_button_big = gd.createFromPng("images/big/L_button.png"):gdStr() 26 | img_M_button_big = gd.createFromPng("images/big/M_button.png"):gdStr() 27 | img_H_button_big = gd.createFromPng("images/big/H_button.png"):gdStr() 28 | img_dir_big = { 29 | img_1_dir_big, 30 | img_2_dir_big, 31 | img_3_dir_big, 32 | img_4_dir_big, 33 | img_5_dir_big, 34 | img_6_dir_big, 35 | img_7_dir_big, 36 | img_8_dir_big, 37 | img_9_dir_big 38 | } 39 | 40 | img_1_dir_small = gd.createFromPng("images/small/1_dir.png"):gdStr() 41 | img_2_dir_small = gd.createFromPng("images/small/2_dir.png"):gdStr() 42 | img_3_dir_small = gd.createFromPng("images/small/3_dir.png"):gdStr() 43 | img_4_dir_small = gd.createFromPng("images/small/4_dir.png"):gdStr() 44 | img_5_dir_small = gd.createFromPng("images/small/5_dir.png"):gdStr() 45 | img_6_dir_small = gd.createFromPng("images/small/6_dir.png"):gdStr() 46 | img_7_dir_small = gd.createFromPng("images/small/7_dir.png"):gdStr() 47 | img_8_dir_small = gd.createFromPng("images/small/8_dir.png"):gdStr() 48 | img_9_dir_small = gd.createFromPng("images/small/9_dir.png"):gdStr() 49 | img_LP_button_small = gd.createFromPng("images/small/LP_button.png"):gdStr() 50 | img_MP_button_small = gd.createFromPng("images/small/MP_button.png"):gdStr() 51 | img_HP_button_small = gd.createFromPng("images/small/HP_button.png"):gdStr() 52 | img_LK_button_small = gd.createFromPng("images/small/LK_button.png"):gdStr() 53 | img_MK_button_small = gd.createFromPng("images/small/MK_button.png"):gdStr() 54 | img_HK_button_small = gd.createFromPng("images/small/HK_button.png"):gdStr() 55 | img_dir_small = { 56 | img_1_dir_small, 57 | img_2_dir_small, 58 | img_3_dir_small, 59 | img_4_dir_small, 60 | img_5_dir_small, 61 | img_6_dir_small, 62 | img_7_dir_small, 63 | img_8_dir_small, 64 | img_9_dir_small 65 | } 66 | 67 | -- # System 68 | 69 | function draw_read() 70 | -- screen stuff 71 | screen_x = memory.readwordsigned(0x02026CB0) 72 | screen_y = memory.readwordsigned(0x02026CB4) 73 | scale = memory.readwordsigned(0x0200DCBA) --FBA can't read from 04xxxxxx 74 | scale = 0x40/(scale > 0 and scale or 1) 75 | end 76 | 77 | -- # Tools 78 | function game_to_screen_space_x(_x) 79 | return _x - screen_x + emu.screenwidth()/2 80 | end 81 | function game_to_screen_space_y(_y) 82 | return emu.screenheight() - (_y - screen_y) - ground_offset 83 | end 84 | function game_to_screen_space(_x, _y) 85 | return game_to_screen_space_x(_x), game_to_screen_space_y(_y) 86 | end 87 | 88 | 89 | function get_text_width(_text) 90 | if #_text == 0 then 91 | return 0 92 | end 93 | 94 | return #_text * 4 95 | end 96 | 97 | -- # Draw functions 98 | 99 | -- draws a set of hitboxes 100 | function draw_hitboxes(_pos_x, _pos_y, _flip_x, _boxes, _filter, _dilation) 101 | _dilation = _dilation or 0 102 | local _px, _py = game_to_screen_space(_pos_x, _pos_y) 103 | 104 | for __, _box in ipairs(_boxes) do 105 | if _filter == nil or _filter[_box.type] == true then 106 | local _c = 0x0000FFFF 107 | if (_box.type == "attack") then 108 | _c = 0xFF0000FF 109 | elseif (_box.type == "throwable") then 110 | _c = 0x00FF00FF 111 | elseif (_box.type == "throw") then 112 | _c = 0xFFFF00FF 113 | elseif (_box.type == "push") then 114 | _c = 0xFF00FFFF 115 | elseif (_box.type == "ext. vulnerability") then 116 | _c = 0x00FFFFFF 117 | end 118 | 119 | local _l, _r 120 | if _flip_x == 0 then 121 | _l = _px + _box.left 122 | else 123 | _l = _px - _box.left - _box.width 124 | end 125 | local _r = _l + _box.width 126 | local _b = _py - _box.bottom 127 | local _t = _b - _box.height 128 | 129 | _l = _l - _dilation 130 | _r = _r + _dilation 131 | _b = _b + _dilation 132 | _t = _t - _dilation 133 | 134 | gui.box(_l, _b, _r, _t, 0x00000000, _c) 135 | end 136 | end 137 | end 138 | 139 | -- draws a point 140 | function draw_point(_x, _y, _color) 141 | local _cross_half_size = 4 142 | local _l = _x - _cross_half_size 143 | local _r = _x + _cross_half_size 144 | local _t = _y - _cross_half_size 145 | local _b = _y + _cross_half_size 146 | 147 | gui.box(_l, _y, _r, _y, 0x00000000, _color) 148 | gui.box(_x, _t, _x, _b, 0x00000000, _color) 149 | end 150 | 151 | -- draws a controller representation 152 | function draw_controller_big(_entry, _x, _y) 153 | gui.image(_x, _y, img_dir_big[_entry.direction]) 154 | 155 | local _img_LP = img_no_button_big 156 | local _img_MP = img_no_button_big 157 | local _img_HP = img_no_button_big 158 | local _img_LK = img_no_button_big 159 | local _img_MK = img_no_button_big 160 | local _img_HK = img_no_button_big 161 | if _entry.buttons[1] then _img_LP = img_L_button_big end 162 | if _entry.buttons[2] then _img_MP = img_M_button_big end 163 | if _entry.buttons[3] then _img_HP = img_H_button_big end 164 | if _entry.buttons[4] then _img_LK = img_L_button_big end 165 | if _entry.buttons[5] then _img_MK = img_M_button_big end 166 | if _entry.buttons[6] then _img_HK = img_H_button_big end 167 | 168 | gui.image(_x + 13, _y, _img_LP) 169 | gui.image(_x + 18, _y, _img_MP) 170 | gui.image(_x + 23, _y, _img_HP) 171 | gui.image(_x + 13, _y + 5, _img_LK) 172 | gui.image(_x + 18, _y + 5, _img_MK) 173 | gui.image(_x + 23, _y + 5, _img_HK) 174 | end 175 | 176 | -- draws a controller representation 177 | function draw_controller_small(_entry, _x, _y, _is_right) 178 | local _x_offset = 0 179 | local _sign = 1 180 | if _is_right then 181 | _x_offset = _x_offset - 9 182 | _sign = -1 183 | end 184 | 185 | gui.image(_x + _x_offset, _y, img_dir_small[_entry.direction]) 186 | _x_offset = _x_offset + _sign * 2 187 | 188 | 189 | local _interval = 8 190 | _x_offset = _x_offset + _sign * _interval 191 | 192 | if _entry.buttons[1] then 193 | gui.image(_x + _x_offset, _y, img_LP_button_small) 194 | _x_offset = _x_offset + _sign * _interval 195 | end 196 | 197 | if _entry.buttons[2] then 198 | gui.image(_x + _x_offset, _y, img_MP_button_small) 199 | _x_offset = _x_offset + _sign * _interval 200 | end 201 | 202 | if _entry.buttons[3] then 203 | gui.image(_x + _x_offset, _y, img_HP_button_small) 204 | _x_offset = _x_offset + _sign * _interval 205 | end 206 | 207 | if _entry.buttons[4] then 208 | gui.image(_x + _x_offset, _y, img_LK_button_small) 209 | _x_offset = _x_offset + _sign * _interval 210 | end 211 | 212 | if _entry.buttons[5] then 213 | gui.image(_x + _x_offset, _y, img_MK_button_small) 214 | _x_offset = _x_offset + _sign * _interval 215 | end 216 | 217 | if _entry.buttons[6] then 218 | gui.image(_x + _x_offset, _y, img_HK_button_small) 219 | _x_offset = _x_offset + _sign * _interval 220 | end 221 | 222 | end 223 | 224 | -- draws a gauge 225 | function draw_gauge(_x, _y, _width, _height, _fill_ratio, _fill_color, _bg_color, _border_color, _reverse_fill) 226 | _bg_color = _bg_color or 0x00000000 227 | _border_color = _border_color or 0xFFFFFFFF 228 | _reverse_fill = _reverse_fill or false 229 | 230 | _width = _width + 1 231 | _height = _height + 1 232 | 233 | gui.box(_x, _y, _x + _width, _y + _height, _bg_color, _border_color) 234 | if _reverse_fill then 235 | gui.box(_x + _width, _y, _x + _width - _width * clamp01(_fill_ratio), _y + _height, _fill_color, 0x00000000) 236 | else 237 | gui.box(_x, _y, _x + _width * clamp01(_fill_ratio), _y + _height, _fill_color, 0x00000000) 238 | end 239 | end 240 | 241 | -- draws an horizontal line 242 | function draw_horizontal_line(_x_start, _x_end, _y, _color, _thickness) 243 | _thickness = _thickness or 1.0 244 | local _l = _x_start - 1 245 | local _b = _y + math.ceil(_thickness * 0.5) 246 | local _r = _x_end + 1 247 | local _t = _y - math.floor(_thickness * 0.5) - 1 248 | gui.box(_l, _b, _r, _t, _color, 0x00000000) 249 | end 250 | 251 | -- draws a vertical line 252 | function draw_vertical_line(_x, _y_start, _y_end, _color, _thickness) 253 | _thickness = _thickness or 1.0 254 | local _l = _x - math.floor(_thickness * 0.5) - 1 255 | local _b = _y_end + 1 256 | local _r = _x + math.ceil(_thickness * 0.5) 257 | local _t = _y_start - 1 258 | gui.box(_l, _b, _r, _t, _color, 0x00000000) 259 | end 260 | -------------------------------------------------------------------------------- /src/frame_advantage.lua: -------------------------------------------------------------------------------- 1 | move_advantage = {} 2 | 3 | function frame_advantage_update(_attacker, _defender) 4 | 5 | function has_just_attacked(_player_obj) 6 | return _player_obj.has_just_attacked or _player_obj.has_just_thrown or (_player_obj.recovery_time == 0 and _player_obj.freeze_frames == 0 and _player_obj.input_capacity == 0 and _player_obj.previous_input_capacity ~= 0) or (_player_obj.movement_type == 4 and _player_obj.last_movement_type_change_frame == 0) 7 | end 8 | 9 | function has_ended_attack(_player_obj) 10 | return (_player_obj.busy_flag == 0 or _player_obj.is_in_jump_startup or _player_obj.is_idle) 11 | end 12 | 13 | function has_ended_recovery(_player_obj) 14 | return (_player_obj.is_idle or has_just_attacked(_player_obj) or _player_obj.is_in_jump_startup) 15 | end 16 | 17 | -- reset end frame if attack occurs again 18 | if move_advantage.armed and has_just_attacked(_attacker) then 19 | move_advantage.end_frame = nil 20 | end 21 | 22 | -- arm the move observation at first player attack 23 | if not move_advantage.armed and has_just_attacked(_attacker) then 24 | move_advantage = { 25 | armed = true, 26 | player_id = _attacker.id, 27 | start_frame = frame_number, 28 | hitbox_start_frame = nil, 29 | hitbox_end_frame = nil, 30 | hit_frame = nil, 31 | end_frame = nil, 32 | opponent_end_frame = nil, 33 | } 34 | 35 | if _attacker.is_throwing then 36 | move_advantage.start_frame = move_advantage.start_frame - 1 37 | end 38 | 39 | log(_attacker.prefix, "frame_advantage", string.format("armed")) 40 | end 41 | 42 | if move_advantage.armed then 43 | 44 | if _attacker.superfreeze_decount > 0 then 45 | move_advantage.start_frame = move_advantage.start_frame + 1 46 | end 47 | 48 | local _has_hitbox = false 49 | local _is_projectile = #projectiles > 0 50 | for _, _box in ipairs(_attacker.boxes) do 51 | if _box.type == "attack" or _box.type == "throw" then 52 | _has_hitbox = true 53 | break 54 | end 55 | end 56 | for _, _projectile in pairs(projectiles) do 57 | if _projectile.emitter_id == _attacker.id and _projectile.has_activated then 58 | _has_hitbox = true 59 | break 60 | end 61 | end 62 | 63 | if move_advantage.hitbox_start_frame == nil then 64 | -- Hitbox start 65 | if _has_hitbox then 66 | if _is_projectile then 67 | move_advantage.hitbox_start_frame = frame_number + 1 68 | log(_attacker.prefix, "frame_advantage", string.format("proj hitbox(+1)")) 69 | else 70 | move_advantage.hitbox_start_frame = frame_number 71 | log(_attacker.prefix, "frame_advantage", string.format("hitbox")) 72 | end 73 | move_advantage.end_frame = nil 74 | end 75 | elseif move_advantage.hitbox_end_frame == nil then 76 | -- Hitbox end (does not make a lot of sense for projectiles I guess) 77 | if not _is_projectile and not _has_hitbox then 78 | move_advantage.hitbox_end_frame = frame_number 79 | end 80 | end 81 | 82 | if (_attacker.has_just_hit or _attacker.has_just_been_blocked or _defender.has_just_been_hit or _defender.has_just_blocked) then 83 | move_advantage.hit_frame = frame_number 84 | move_advantage.opponent_end_frame = nil 85 | if move_advantage.hitbox_start_frame == nil then 86 | move_advantage.hitbox_start_frame = move_advantage.hit_frame 87 | end 88 | if _attacker.busy_flag ~= 0 then 89 | move_advantage.end_frame = nil 90 | end 91 | 92 | log(_defender.prefix, "frame_advantage", string.format("hit")) 93 | end 94 | 95 | if move_advantage.hit_frame ~= nil then 96 | if move_advantage.hitbox_start_frame ~= nil and frame_number > move_advantage.hit_frame then 97 | if move_advantage.end_frame == nil and has_ended_attack(_attacker) then 98 | move_advantage.end_frame = frame_number 99 | 100 | log(_attacker.prefix, "frame_advantage", string.format("end bf:%d js:%d", _attacker.busy_flag, to_bit(_attacker.is_in_jump_startup))) 101 | end 102 | 103 | if move_advantage.opponent_end_frame == nil and frame_number > move_advantage.hit_frame and has_ended_recovery(_defender) then 104 | log(_defender.prefix, "frame_advantage", string.format("end")) 105 | move_advantage.opponent_end_frame = frame_number 106 | end 107 | end 108 | end 109 | 110 | if (move_advantage.end_frame ~= nil and move_advantage.opponent_end_frame ~= nil) or (has_ended_attack(_attacker) and has_ended_recovery(_defender)) then 111 | if move_advantage.end_frame == nil then 112 | move_advantage.end_frame = frame_number 113 | end 114 | move_advantage.armed = false 115 | log(_defender.prefix, "frame_advantage", string.format("unarmed")) 116 | end 117 | end 118 | end 119 | 120 | function frame_advantage_display() 121 | if 122 | move_advantage.armed == true or 123 | move_advantage.player_id == nil or 124 | move_advantage.start_frame == nil or 125 | move_advantage.hitbox_start_frame == nil 126 | then 127 | return 128 | end 129 | 130 | local _y = 49 131 | function display_line(_text, _value, _color) 132 | _color = _color or text_default_color 133 | local _text_width = get_text_width(_text) 134 | local _x = 0 135 | if move_advantage.player_id == 1 then 136 | _x = 51 137 | elseif move_advantage.player_id == 2 then 138 | _x = screen_width - 65 - _text_width 139 | end 140 | 141 | gui.text(_x, _y, string.format(_text)) 142 | gui.text(_x + _text_width, _y, string.format("%d", _value), _color, text_default_border_color) 143 | _y = _y + 10 144 | end 145 | 146 | local _startup = move_advantage.hitbox_start_frame - move_advantage.start_frame 147 | 148 | display_line("startup: ", string.format("%d", _startup)) 149 | 150 | if move_advantage.hit_frame ~= nil then 151 | local _hit_frame = move_advantage.hit_frame - move_advantage.start_frame + 1 152 | display_line("hit frame: ", string.format("%d", _hit_frame)) 153 | end 154 | 155 | if move_advantage.hit_frame ~= nil and move_advantage.end_frame ~= nil and move_advantage.opponent_end_frame ~= nil then 156 | local _advantage = move_advantage.opponent_end_frame - (move_advantage.end_frame) 157 | 158 | local _sign = "" 159 | if _advantage > 0 then _sign = "+" end 160 | 161 | local _color = 0xFFFB63FF 162 | if _advantage < 0 then 163 | _color = 0xE70000FF 164 | elseif _advantage > 0 then 165 | _color = 0x10FB00FF 166 | end 167 | 168 | display_line("advantage: ", string.format("%s%d", _sign, _advantage), _color) 169 | else 170 | if move_advantage.hitbox_start_frame ~= nil and move_advantage.hitbox_end_frame ~= nil then 171 | display_line("active: ", string.format("%d", move_advantage.hitbox_end_frame - move_advantage.hitbox_start_frame)) 172 | end 173 | display_line("duration: ", string.format("%d", move_advantage.end_frame - move_advantage.start_frame)) 174 | end 175 | end 176 | 177 | function frame_advantage_reset() 178 | move_advantage = 179 | { 180 | armed = false 181 | } 182 | end 183 | frame_advantage_reset() -------------------------------------------------------------------------------- /src/framedata.lua: -------------------------------------------------------------------------------- 1 | data_path = "data/"..rom_name.."/" 2 | framedata_path = data_path.."framedata/" 3 | frame_data_file_ext = "_framedata.json" 4 | 5 | characters = 6 | { 7 | "gill", 8 | "alex", 9 | "ryu", 10 | "yun", 11 | "dudley", 12 | "necro", 13 | "hugo", 14 | "ibuki", 15 | "elena", 16 | "oro", 17 | "yang", 18 | "ken", 19 | "sean", 20 | "urien", 21 | "gouki", 22 | "gill", 23 | "chunli", 24 | "makoto", 25 | "q", 26 | "twelve", 27 | "remy", 28 | } 29 | if is_4rd_strike then 30 | characters[1] = "gill" 31 | characters[16] = "usean" 32 | end 33 | 34 | -- # Character specific stuff 35 | character_specific = {} 36 | for i = 1, #characters do 37 | character_specific[characters[i]] = { timed_sa = {false, false, false} } 38 | end 39 | 40 | -- ## Character approximate dimensions 41 | character_specific.alex.half_width = 45 42 | character_specific.chunli.half_width = 39 43 | character_specific.dudley.half_width = 29 44 | character_specific.elena.half_width = 44 45 | character_specific.gouki.half_width = 33 46 | character_specific.hugo.half_width = 43 47 | character_specific.ibuki.half_width = 34 48 | character_specific.ken.half_width = 30 49 | character_specific.makoto.half_width = 42 50 | character_specific.necro.half_width = 26 51 | character_specific.oro.half_width = 40 52 | character_specific.q.half_width = 25 53 | character_specific.remy.half_width = 32 54 | character_specific.ryu.half_width = 31 55 | character_specific.sean.half_width = 29 56 | character_specific.twelve.half_width = 33 57 | character_specific.urien.half_width = 36 58 | character_specific.yang.half_width = 41 59 | character_specific.yun.half_width = 37 60 | 61 | character_specific.alex.height = 104 62 | character_specific.chunli.height = 97 63 | character_specific.dudley.height = 109 64 | character_specific.elena.height = 88 65 | character_specific.gouki.height = 107 66 | character_specific.hugo.height = 137 67 | character_specific.ibuki.height = 92 68 | character_specific.ken.height = 107 69 | character_specific.makoto.height = 90 70 | character_specific.necro.height = 89 71 | character_specific.oro.height = 88 72 | character_specific.q.height = 130 73 | character_specific.remy.height = 114 74 | character_specific.ryu.height = 101 75 | character_specific.sean.height = 103 76 | character_specific.twelve.height = 91 77 | character_specific.urien.height = 121 78 | character_specific.yang.height = 89 79 | character_specific.yun.height = 89 80 | 81 | -- ## Characters standing states 82 | character_specific.oro.additional_standing_states = { 3 } -- 3 is crouching 83 | character_specific.dudley.additional_standing_states = { 6 } -- 6 is crouching 84 | character_specific.makoto.additional_standing_states = { 7 } -- 7 happens during Oroshi 85 | character_specific.necro.additional_standing_states = { 13 } -- 13 happens during CrLK 86 | 87 | -- ## Characters timed SA 88 | character_specific.oro.timed_sa[1] = true; 89 | character_specific.oro.timed_sa[3] = true; 90 | character_specific.q.timed_sa[3] = true; 91 | character_specific.makoto.timed_sa[3] = true; 92 | character_specific.twelve.timed_sa[3] = true; 93 | character_specific.yang.timed_sa[3] = true; 94 | character_specific.yun.timed_sa[3] = true; 95 | 96 | -- ## Frame data meta 97 | frame_data_meta = {} 98 | for i = 1, #characters do 99 | frame_data_meta[characters[i]] = { 100 | moves = {}, 101 | projectiles = {}, 102 | } 103 | end 104 | framedata_meta_file_path = data_path.."framedata_meta" 105 | require(framedata_meta_file_path) 106 | 107 | -- # Frame data 108 | frame_data = {} 109 | 110 | function save_frame_data() 111 | for _key, _value in ipairs(characters) do 112 | if frame_data[_value].dirty then 113 | frame_data[_value].dirty = nil 114 | local _file_path = framedata_path.._value..frame_data_file_ext 115 | if not write_object_to_json_file(frame_data[_value], _file_path) then 116 | print(string.format("Error: Failed to write frame data to \"%s\"", _file_path)) 117 | else 118 | print(string.format("Saved frame data to \"%s\"", _file_path)) 119 | end 120 | end 121 | end 122 | end 123 | 124 | function load_frame_data() 125 | for _key, _value in ipairs(characters) do 126 | local _file_path = framedata_path.._value..frame_data_file_ext 127 | frame_data[_value] = read_object_from_json_file(_file_path) or {} 128 | frame_data[_value].wakeups = frame_data[_value].wakeups or {} 129 | end 130 | end 131 | 132 | 133 | -- # Frame data recording 134 | function reset_current_recording_animation() 135 | current_recording_animation_previous_pos = {0, 0} 136 | current_recording_animation = nil 137 | end 138 | reset_current_recording_animation() 139 | 140 | function record_framedata(_player_obj, _projectiles) 141 | local _debug = true 142 | 143 | local _force_recording = current_recording_animation and frame_data_meta[_player_obj.char_str].moves[current_recording_animation.id] ~= nil and frame_data_meta[_player_obj.char_str].moves[current_recording_animation.id].force_recording 144 | -- any connecting attack frame data may be ill formed. We discard it immediately to avoid data loss (except for moves tagged as "force_recording" that are difficult to record otherwise) 145 | if (_player_obj.has_just_hit or _player_obj.has_just_been_blocked or _player_obj.has_just_been_parried) then 146 | if not _force_recording then 147 | if current_recording_animation and _debug then 148 | print(string.format("dropped animation because it connected: %s", _player_obj.animation)) 149 | end 150 | reset_current_recording_animation() 151 | end 152 | end 153 | 154 | if (_player_obj.has_animation_just_changed) then 155 | local _id 156 | if current_recording_animation then _id = current_recording_animation.id end 157 | 158 | if current_recording_animation and (current_recording_animation.attack_box_count > 0 or _force_recording) then 159 | current_recording_animation.attack_box_count = nil -- don't save that 160 | current_recording_animation.id = nil -- don't save that 161 | 162 | -- compute hit frames range 163 | for _i, _hit_frame in ipairs(current_recording_animation.hit_frames) do 164 | local _range_limit_frame = #current_recording_animation.frames - 1 165 | if _i < #current_recording_animation.hit_frames then 166 | _range_limit_frame = current_recording_animation.hit_frames[_i + 1] - 1 167 | end 168 | local _range_end_frame = _hit_frame 169 | if _hit_frame < _range_limit_frame then 170 | for _j = (_hit_frame + 1), _range_limit_frame do 171 | if #current_recording_animation.frames[_j].boxes > 0 then 172 | _range_end_frame = _j - 1 173 | else 174 | break 175 | end 176 | end 177 | end 178 | 179 | current_recording_animation.hit_frames[_i] = { min = _hit_frame, max = _range_end_frame } 180 | end 181 | 182 | if (frame_data[_player_obj.char_str] == nil) then 183 | frame_data[_player_obj.char_str] = {} 184 | end 185 | frame_data[_player_obj.char_str].dirty = true 186 | frame_data[_player_obj.char_str][_id] = current_recording_animation 187 | 188 | if _debug then 189 | print(string.format("recorded animation: %s", _id)) 190 | end 191 | elseif current_recording_animation then 192 | if _debug then 193 | print(string.format("dropped animation recording: %s", _id)) 194 | end 195 | end 196 | 197 | current_recording_animation_previous_pos = {_player_obj.pos_x, _player_obj.pos_y} 198 | current_recording_animation = { frames = {}, hit_frames = {}, attack_box_count = 0, id = _player_obj.animation } 199 | end 200 | 201 | if (current_recording_animation) then 202 | 203 | local _frame = frame_number - _player_obj.current_animation_freeze_frames - _player_obj.current_animation_start_frame 204 | if _player_obj.has_just_acted then 205 | table.insert(current_recording_animation.hit_frames, _frame) 206 | end 207 | 208 | if _player_obj.remaining_freeze_frames == 0 then 209 | --print(string.format("recording frame %d (%d - %d - %d)", _frame, frame_number, _player_obj.current_animation_freeze_frames, _player_obj.current_animation_start_frame)) 210 | 211 | local _sign = 1 212 | if _player_obj.flip_input then _sign = -1 end 213 | 214 | current_recording_animation.frames[_frame + 1] = { 215 | boxes = {}, 216 | movement = { 217 | (_player_obj.pos_x - current_recording_animation_previous_pos[1]) * _sign, 218 | (_player_obj.pos_y - current_recording_animation_previous_pos[2]), 219 | }, 220 | frame_id = _player_obj.animation_frame_id, 221 | hash = _player_obj.animation_frame_hash, 222 | } 223 | current_recording_animation_previous_pos = { _player_obj.pos_x, _player_obj.pos_y } 224 | 225 | for __, _box in ipairs(_player_obj.boxes) do 226 | if (_box.type == "attack") or (_box.type == "throw") then 227 | table.insert(current_recording_animation.frames[_frame + 1].boxes, copytable(_box)) 228 | current_recording_animation.attack_box_count = current_recording_animation.attack_box_count + 1 229 | end 230 | end 231 | 232 | local _move_framedata_meta = frame_data_meta[_player_obj.char_str].moves[current_recording_animation.id] 233 | if _move_framedata_meta and _move_framedata_meta.record_projectile then 234 | local _inserted_projectile = false 235 | for _id, _obj in pairs(_projectiles) do 236 | if _obj.emitter_animation == current_recording_animation.id then 237 | local _dx, _dy = _player_obj.pos_x - _obj.pos_x, _player_obj.pos_y - _obj.pos_y 238 | if _player_obj.flip_x ~= 0 then _dx = _dx * -1 end 239 | for __, _box in ipairs(_obj.boxes) do 240 | if (_box.type == "attack") or (_box.type == "throw") then 241 | local _temp_box = copytable(_box) 242 | _temp_box.bottom = _temp_box.bottom - _dy 243 | _temp_box.left = _temp_box.left + _dx 244 | table.insert(current_recording_animation.frames[_frame + 1].boxes, _temp_box) 245 | current_recording_animation.attack_box_count = current_recording_animation.attack_box_count + 1 246 | _inserted_projectile = true 247 | end 248 | end 249 | end 250 | end 251 | 252 | if _inserted_projectile and #current_recording_animation.hit_frames == 0 then 253 | table.insert(current_recording_animation.hit_frames, _frame) 254 | end 255 | end 256 | end 257 | end 258 | end 259 | 260 | projectiles_recording = {} 261 | function reset_current_projectiles_recording() 262 | for _id, _obj in pairs(projectiles_recording) do 263 | print(string.format("Dropped recording projectile %s", _obj.type)) 264 | end 265 | projectiles_recording = {} 266 | end 267 | 268 | function record_projectiles(_projectiles) 269 | for _id, _obj in pairs(_projectiles) do 270 | local _recording = projectiles_recording[_id] or { type = "", start_lifetime = 0, boxes = {}, recorded = false } 271 | _recording.type = _obj.projectile_start_type 272 | _recording.char_str = player_objects[_obj.emitter_id].char_str 273 | 274 | if not _recording.recorded then 275 | if #_recording.boxes == 0 and #_obj.boxes > 0 then 276 | _recording.recorded = true 277 | _recording.start_lifetime = _obj.lifetime 278 | _recording.boxes = _obj.boxes 279 | end 280 | projectiles_recording[_id] = _recording 281 | end 282 | end 283 | 284 | for _id, _obj in pairs(projectiles_recording) do 285 | if _projectiles[_id] == nil then 286 | local _recording = projectiles_recording[_id] 287 | projectiles_recording[_id] = nil 288 | 289 | frame_data[_recording.char_str][_recording.type] = { start_lifetime = _recording.start_lifetime, boxes = _recording.boxes } 290 | frame_data[_recording.char_str].dirty = true 291 | print(string.format("Recorded projectile %s", _obj.type)) 292 | end 293 | end 294 | end 295 | 296 | function reset_current_recording_idle_animation() 297 | if is_recording_idle_animation then 298 | print(string.format("Dropped recording idle animation")) 299 | end 300 | is_recording_idle_animation = false 301 | current_recording_idle_startup_animation = nil 302 | current_recording_idle_animation = nil 303 | wait_for_idle_start = false 304 | end 305 | 306 | function record_idle_framedata(_player_obj) 307 | -- arm recording 308 | if _player_obj.is_wakingup then 309 | wait_for_idle_start = true 310 | end 311 | 312 | -- start recording 313 | if wait_for_idle_start and _player_obj.is_idle then 314 | current_recording_idle_startup_animation = { 315 | frames = {} 316 | } 317 | current_recording_idle_animation = { 318 | id = _player_obj.animation, 319 | frames = {} 320 | } 321 | wait_for_idle_start = false 322 | is_recording_idle_animation = true 323 | print(string.format("Started recording idle animation")) 324 | end 325 | 326 | if is_recording_idle_animation and not _player_obj.is_idle then 327 | reset_current_recording_idle_animation() 328 | elseif is_recording_idle_animation then 329 | 330 | -- if animation has changed, transfer already recorded frame to the startup animation 331 | if _player_obj.has_animation_just_changed then 332 | for __, _frame in ipairs(current_recording_idle_animation.frames) do 333 | table.insert(current_recording_idle_startup_animation.frames, _frame) 334 | end 335 | current_recording_idle_animation.id = _player_obj.animation 336 | current_recording_idle_animation.frames = {} 337 | end 338 | 339 | -- record frame 340 | local _frame = { 341 | id = _player_obj.animation_frame_id, 342 | boxes = {} 343 | } 344 | for __, _box in ipairs(_player_obj.boxes) do 345 | table.insert(_frame.boxes, copytable(_box)) 346 | end 347 | table.insert(current_recording_idle_animation.frames, _frame) 348 | 349 | -- detect loop 350 | local _minimum_loop_size = 5 351 | local _loop_size = 0 352 | local _frames = current_recording_idle_animation.frames 353 | for _i = 2, #_frames do 354 | if _frames[_i].id == _frames[1].id then 355 | for _j = 1, #_frames do 356 | local _looped_index = _i + _j - 1 357 | if _looped_index > #_frames then 358 | break 359 | end 360 | if _frames[_j].id ~= _frames[_looped_index].id then 361 | break 362 | end 363 | 364 | if _j == _i then 365 | _loop_size = _i - 1 366 | break 367 | end 368 | end 369 | end 370 | if _loop_size > _minimum_loop_size then 371 | break 372 | end 373 | end 374 | 375 | -- write into frame data 376 | -- exceptions 377 | if #current_recording_idle_animation.frames > 30 and _player_obj.char_str == "makoto" then 378 | _loop_size = #current_recording_idle_animation.frames 379 | end 380 | if #current_recording_idle_animation.frames > 30 and _player_obj.char_str == "hugo" then 381 | _loop_size = #current_recording_idle_animation.frames 382 | end 383 | if _loop_size > _minimum_loop_size then 384 | while #current_recording_idle_animation.frames > _loop_size do 385 | table.remove(current_recording_idle_animation.frames) 386 | end 387 | frame_data[_player_obj.char_str].wakeup_to_idle = current_recording_idle_startup_animation 388 | frame_data[_player_obj.char_str].idle = current_recording_idle_animation 389 | frame_data[_player_obj.char_str].dirty = true 390 | print(string.format("Recorded idle animation \"%s\" of size %d/%d", current_recording_idle_animation.id, #current_recording_idle_startup_animation.frames, _loop_size)) 391 | is_recording_idle_animation = false 392 | reset_current_recording_idle_animation() 393 | end 394 | end 395 | end 396 | 397 | function update_framedata_recording(_player_obj, _projectiles) 398 | if debug_settings.record_framedata and is_in_match and not is_menu_open then 399 | record_framedata(_player_obj, _projectiles) 400 | else 401 | reset_current_recording_animation() 402 | end 403 | end 404 | 405 | function update_projectiles_recording(_projectiles) 406 | if debug_settings.record_framedata and is_in_match and not is_menu_open then 407 | record_projectiles(_projectiles) 408 | else 409 | reset_current_projectiles_recording() 410 | end 411 | end 412 | 413 | function update_idle_framedata_recording(_player_obj) 414 | if debug_settings.record_idle_framedata and is_in_match and not is_menu_open then 415 | record_idle_framedata(_player_obj) 416 | else 417 | reset_current_recording_idle_animation() 418 | end 419 | end 420 | 421 | function find_wake_up(_char_str, _wakeup_animation, _last_act_animation) 422 | local _frame_data = frame_data[_char_str] 423 | if _frame_data == nil then 424 | return nil 425 | end 426 | local _wakeup = _frame_data.wakeups[_wakeup_animation] 427 | if _wakeup == nil then 428 | return nil 429 | end 430 | 431 | if _wakeup.exceptions ~= nil then 432 | local _exception = _wakeup.exceptions[_last_act_animation] 433 | if _exception then 434 | return _exception.duration 435 | end 436 | end 437 | 438 | return _wakeup.duration 439 | end 440 | 441 | function insert_wake_up(_char_str, _wakeup_animation, _last_act_animation, _duration) 442 | local _debug = true 443 | local _char_frame_data = frame_data[_char_str] 444 | local _wakeup = _char_frame_data.wakeups[_wakeup_animation] 445 | 446 | if _wakeup == nil then 447 | _char_frame_data.dirty = true 448 | _char_frame_data.wakeups[_wakeup_animation] = { duration = _duration, exceptions = {} } 449 | _char_frame_data.wakeups[_wakeup_animation].exceptions[_last_act_animation] = { duration = _duration } 450 | if _debug then 451 | print(string.format("Inserted new wakeup \"%s\", %d", _wakeup_animation, _duration)) 452 | end 453 | return true 454 | else 455 | _wakeup.exceptions = _wakeup.exceptions or {} 456 | if _wakeup.exceptions[_last_act_animation] == nil or _wakeup.exceptions[_last_act_animation].duration ~= _duration then 457 | if _wakeup.exceptions[_last_act_animation] == nil and _wakeup.duration == _duration then 458 | return false 459 | end 460 | 461 | _char_frame_data.dirty = true 462 | _wakeup.exceptions[_last_act_animation] = { duration = _duration } 463 | 464 | -- recompute default value 465 | local _durations = {} 466 | local _max_occurence = 0 467 | local _exception_count = 0 468 | for _i, _o in pairs(_wakeup.exceptions) do 469 | _exception_count = _exception_count + 1 470 | local _id = tostring(_o.duration) 471 | _durations[_id] = (_durations[_id] or 0) + 1 472 | _max_occurence = math.max(_max_occurence, _durations[_id]) 473 | end 474 | local _final_duration = 0 475 | for _i, _occurences in pairs(_durations) do 476 | if _occurences == _max_occurence then 477 | _final_duration = tonumber(_i) 478 | end 479 | if _final_duration == _wakeup.duration then -- if default duration is ex-aequo, it wins 480 | break 481 | end 482 | end 483 | _wakeup.duration = _final_duration 484 | 485 | if _debug then 486 | print(string.format("Inserted new exception \"%s\" for wakeup \"%s\", %d. Default is %d",_last_act_animation, _wakeup_animation, _duration, _wakeup.duration)) 487 | end 488 | return true 489 | end 490 | end 491 | return false 492 | end 493 | 494 | function update_wakeupdata_recording(_player_obj, _dummy_obj) 495 | if not is_in_match then 496 | return 497 | end 498 | -- moves to record to produce a complete set of wake ups: 499 | -- fast wake ups: 500 | -- Alex throw 501 | -- Alex HCB P 502 | -- Alex HCB K 503 | -- Oro back throw 504 | -- Gouki Back throw 505 | -- Gouki Demon flip P 506 | -- normal wake ups: 507 | -- Alex slash Ex 508 | -- Alex sweep 509 | -- Alex HCB K 510 | -- Alex HCB P 511 | -- Alex stomp 512 | -- Alex DPF K 513 | -- Alex HCharge ExK 514 | -- Ibuki raida 515 | -- Ibuki air throw 516 | -- Ibuki neck breaker 517 | -- Hugo 360 P 518 | -- Hugo neutral grab 519 | -- Hugo back breaker 520 | -- Oro back throw 521 | -- Oro HCB Px3 522 | -- Gouki back throw 523 | -- Gouki demon flip P 524 | -- Twelve forward, neutral and back throw 525 | 526 | -- Report missing data 527 | if developer_mode and _dummy_obj.has_just_woke_up then 528 | local _wakeup_animation = _dummy_obj.wakeup_animation 529 | local _wakeup_time = _dummy_obj.wakeup_time 530 | local _wakeup = frame_data[_dummy_obj.char_str].wakeups[_wakeup_animation] 531 | local _duration = find_wake_up(_dummy_obj.char_str, _dummy_obj.wakeup_animation, _dummy_obj.wakeup_other_last_act_animation) 532 | if _duration == nil then 533 | print(string.format("Unknown wakeup animation: %s", _wakeup_animation)) 534 | elseif _duration ~= _wakeup_time then 535 | print(string.format("Mismatching %s wakeup animation time %s: %d against default %d. last %s act animation: \"%s\"", _dummy_obj.char_str, _wakeup_animation, _wakeup_time, _duration, _player_obj.char_str, _dummy_obj.wakeup_other_last_act_animation)) 536 | end 537 | end 538 | 539 | -- Record 540 | if debug_settings.record_wakeupdata then 541 | if _dummy_obj.has_just_woke_up then 542 | local _char_str = _dummy_obj.char_str 543 | local _animation = _dummy_obj.wakeup_animation 544 | local _last_act_animation = _dummy_obj.wakeup_other_last_act_animation 545 | local _duration = _dummy_obj.wakeup_time 546 | insert_wake_up(_char_str, _animation, _last_act_animation, _duration) 547 | end 548 | end 549 | end 550 | 551 | function test_collision(_defender_x, _defender_y, _defender_flip_x, _defender_boxes, _attacker_x, _attacker_y, _attacker_flip_x, _attacker_boxes, _box_type_matches, _defender_hurtbox_dilation_x, _defender_hurtbox_dilation_y, _attacker_hitbox_dilation_x, _attacker_hitbox_dilation_y) 552 | 553 | local _debug = false 554 | if (_defender_hurtbox_dilation_x == nil) then _defender_hurtbox_dilation_x = 0 end 555 | if (_defender_hurtbox_dilation_y == nil) then _defender_hurtbox_dilation_y = 0 end 556 | if (_attacker_hitbox_dilation_x == nil) then _attacker_hitbox_dilation_x = 0 end 557 | if (_attacker_hitbox_dilation_y == nil) then _attacker_hitbox_dilation_y = 0 end 558 | if (_test_throws == nil) then _test_throws = false end 559 | if (_box_type_matches == nil) then _box_type_matches = {{{"vulnerability", "ext. vulnerability"}, {"attack"}}} end 560 | 561 | if (#_box_type_matches == 0 ) then return false end 562 | if (#_defender_boxes == 0 ) then return false end 563 | if (#_attacker_boxes == 0 ) then return false end 564 | 565 | if _debug then print(string.format(" %d defender boxes, %d attacker boxes", #_defender_boxes, #_attacker_boxes)) end 566 | 567 | for k = 1, #_box_type_matches do 568 | local _box_type_match = _box_type_matches[k] 569 | for i = 1, #_defender_boxes do 570 | local _d_box = _defender_boxes[i] 571 | 572 | --print("d ".._d_box.type) 573 | 574 | local _defender_box_match = false 575 | for _key, _value in ipairs(_box_type_match[1]) do 576 | if _value == _d_box.type then 577 | _defender_box_match = true 578 | break 579 | end 580 | end 581 | 582 | if _defender_box_match then 583 | -- compute defender box bounds 584 | local _d_l 585 | if _defender_flip_x == 0 then 586 | _d_l = _defender_x + _d_box.left 587 | else 588 | _d_l = _defender_x - _d_box.left - _d_box.width 589 | end 590 | local _d_r = _d_l + _d_box.width 591 | local _d_b = _defender_y + _d_box.bottom 592 | local _d_t = _d_b + _d_box.height 593 | 594 | _d_l = _d_l - _defender_hurtbox_dilation_x 595 | _d_r = _d_r + _defender_hurtbox_dilation_x 596 | _d_b = _d_b - _defender_hurtbox_dilation_y 597 | _d_t = _d_t + _defender_hurtbox_dilation_y 598 | 599 | for j = 1, #_attacker_boxes do 600 | local _a_box = _attacker_boxes[j] 601 | 602 | --print("a ".._a_box.type) 603 | 604 | local _attacker_box_match = false 605 | for _key, _value in ipairs(_box_type_match[2]) do 606 | if _value == _a_box.type then 607 | _attacker_box_match = true 608 | break 609 | end 610 | end 611 | if _attacker_box_match then 612 | -- compute attacker box bounds 613 | local _a_l 614 | if _attacker_flip_x == 0 then 615 | _a_l = _attacker_x + _a_box.left 616 | else 617 | _a_l = _attacker_x - _a_box.left - _a_box.width 618 | end 619 | local _a_r = _a_l + _a_box.width 620 | local _a_b = _attacker_y + _a_box.bottom 621 | local _a_t = _a_b + _a_box.height 622 | 623 | _a_l = _a_l - _attacker_hitbox_dilation_x 624 | _a_r = _a_r + _attacker_hitbox_dilation_x 625 | _a_b = _a_b - _attacker_hitbox_dilation_y 626 | _a_t = _a_t + _attacker_hitbox_dilation_y 627 | 628 | if _debug then print(string.format(" testing (%d,%d,%d,%d)(%s) against (%d,%d,%d,%d)(%s)", _d_t, _d_r, _d_b, _d_l, _d_box.type, _a_t, _a_r, _a_b, _a_l, _a_box.type)) end 629 | 630 | -- check collision 631 | if not ( 632 | (_a_l >= _d_r) or 633 | (_a_r <= _d_l) or 634 | (_a_b >= _d_t) or 635 | (_a_t <= _d_b) 636 | ) then 637 | return true 638 | end 639 | end 640 | end 641 | end 642 | end 643 | end 644 | 645 | return false 646 | end 647 | -------------------------------------------------------------------------------- /src/input_history.lua: -------------------------------------------------------------------------------- 1 | input_history_size_max = 15 2 | input_history = { 3 | {}, 4 | {} 5 | } 6 | 7 | function make_input_history_entry(_prefix, _input) 8 | local _up = _input[_prefix.." Up"] 9 | local _down = _input[_prefix.." Down"] 10 | local _left = _input[_prefix.." Left"] 11 | local _right = _input[_prefix.." Right"] 12 | local _direction = 5 13 | if _down then 14 | if _left then _direction = 1 15 | elseif _right then _direction = 3 16 | else _direction = 2 end 17 | elseif _up then 18 | if _left then _direction = 7 19 | elseif _right then _direction = 9 20 | else _direction = 8 end 21 | else 22 | if _left then _direction = 4 23 | elseif _right then _direction = 6 24 | else _direction = 5 end 25 | end 26 | 27 | return { 28 | frame = frame_number, 29 | direction = _direction, 30 | buttons = { 31 | _input[_prefix.." Weak Punch"], 32 | _input[_prefix.." Medium Punch"], 33 | _input[_prefix.." Strong Punch"], 34 | _input[_prefix.." Weak Kick"], 35 | _input[_prefix.." Medium Kick"], 36 | _input[_prefix.." Strong Kick"] 37 | } 38 | } 39 | end 40 | 41 | function is_input_history_entry_equal(_a, _b) 42 | if (_a.direction ~= _b.direction) then return false end 43 | if (_a.buttons[1] ~= _b.buttons[1]) then return false end 44 | if (_a.buttons[2] ~= _b.buttons[2]) then return false end 45 | if (_a.buttons[3] ~= _b.buttons[3]) then return false end 46 | if (_a.buttons[4] ~= _b.buttons[4]) then return false end 47 | if (_a.buttons[5] ~= _b.buttons[5]) then return false end 48 | if (_a.buttons[6] ~= _b.buttons[6]) then return false end 49 | return true 50 | end 51 | 52 | function input_history_update(_history, _prefix, _input) 53 | local _entry = make_input_history_entry(_prefix, _input) 54 | 55 | if #_history == 0 then 56 | table.insert(_history, _entry) 57 | else 58 | local _last_entry = _history[#_history] 59 | if _last_entry.frame ~= frame_number and not is_input_history_entry_equal(_entry, _last_entry) then 60 | table.insert(_history, _entry) 61 | end 62 | end 63 | 64 | while #_history > input_history_size_max do 65 | table.remove(_history, 1) 66 | end 67 | end 68 | 69 | function input_history_draw(_history, _x, _y, _is_right) 70 | local _step_y = 10 71 | local _j = 0 72 | for _i = #_history, 1, -1 do 73 | local _current_y = _y + _j * _step_y 74 | local _entry = _history[_i] 75 | 76 | local _sign = 1 77 | if _is_right then 78 | _sign = -1 79 | end 80 | 81 | local _controller_offset = 14 * _sign 82 | draw_controller_small(_entry, _x + _controller_offset, _current_y, _is_right) 83 | 84 | local _next_frame = frame_number 85 | if _i < #_history then 86 | _next_frame = _history[_i + 1].frame 87 | end 88 | local _frame_diff = _next_frame - _entry.frame 89 | local _text = "-" 90 | if (_frame_diff < 999) then 91 | _text = string.format("%d", _frame_diff) 92 | end 93 | 94 | local _offset = -11 95 | if not _is_right then 96 | _offset = 8 97 | if (_frame_diff < 999) then 98 | if (_frame_diff >= 100) then _offset = 0 99 | elseif (_frame_diff >= 10) then _offset = 4 end 100 | end 101 | end 102 | 103 | gui.text(_x + _offset, _current_y + 1, _text, 0xd6e3efff, 0x101000ff) 104 | 105 | _j = _j + 1 106 | end 107 | end 108 | 109 | function clear_input_history() 110 | input_history[1] = {} 111 | input_history[2] = {} 112 | end 113 | -------------------------------------------------------------------------------- /src/libs/dkjson.lua: -------------------------------------------------------------------------------- 1 | -- Module options: 2 | local always_try_using_lpeg = true 3 | local register_global_module_table = false 4 | local global_module_name = 'json' 5 | 6 | --[==[ 7 | 8 | David Kolf's JSON module for Lua 5.1/5.2 9 | 10 | Version 2.5 11 | 12 | 13 | For the documentation see the corresponding readme.txt or visit 14 | . 15 | 16 | You can contact the author by sending an e-mail to 'david' at the 17 | domain 'dkolf.de'. 18 | 19 | 20 | Copyright (C) 2010-2014 David Heiko Kolf 21 | 22 | Permission is hereby granted, free of charge, to any person obtaining 23 | a copy of this software and associated documentation files (the 24 | "Software"), to deal in the Software without restriction, including 25 | without limitation the rights to use, copy, modify, merge, publish, 26 | distribute, sublicense, and/or sell copies of the Software, and to 27 | permit persons to whom the Software is furnished to do so, subject to 28 | the following conditions: 29 | 30 | The above copyright notice and this permission notice shall be 31 | included in all copies or substantial portions of the Software. 32 | 33 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 34 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 35 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 36 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 37 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 38 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 39 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | SOFTWARE. 41 | 42 | --]==] 43 | 44 | -- global dependencies: 45 | local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset = 46 | pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset 47 | local error, require, pcall, select = error, require, pcall, select 48 | local floor, huge = math.floor, math.huge 49 | local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat = 50 | string.rep, string.gsub, string.sub, string.byte, string.char, 51 | string.find, string.len, string.format 52 | local strmatch = string.match 53 | local concat = table.concat 54 | 55 | local json = { version = "dkjson 2.5" } 56 | 57 | if register_global_module_table then 58 | _G[global_module_name] = json 59 | end 60 | 61 | local _ENV = nil -- blocking globals in Lua 5.2 62 | 63 | pcall (function() 64 | -- Enable access to blocked metatables. 65 | -- Don't worry, this module doesn't change anything in them. 66 | local debmeta = require "debug".getmetatable 67 | if debmeta then getmetatable = debmeta end 68 | end) 69 | 70 | json.null = setmetatable ({}, { 71 | __tojson = function () return "null" end 72 | }) 73 | 74 | local function isarray (tbl) 75 | local max, n, arraylen = 0, 0, 0 76 | for k,v in pairs (tbl) do 77 | if k == 'n' and type(v) == 'number' then 78 | arraylen = v 79 | if v > max then 80 | max = v 81 | end 82 | else 83 | if type(k) ~= 'number' or k < 1 or floor(k) ~= k then 84 | return false 85 | end 86 | if k > max then 87 | max = k 88 | end 89 | n = n + 1 90 | end 91 | end 92 | if max > 10 and max > arraylen and max > n * 2 then 93 | return false -- don't create an array with too many holes 94 | end 95 | return true, max 96 | end 97 | 98 | local escapecodes = { 99 | ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", 100 | ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" 101 | } 102 | 103 | local function escapeutf8 (uchar) 104 | local value = escapecodes[uchar] 105 | if value then 106 | return value 107 | end 108 | local a, b, c, d = strbyte (uchar, 1, 4) 109 | a, b, c, d = a or 0, b or 0, c or 0, d or 0 110 | if a <= 0x7f then 111 | value = a 112 | elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then 113 | value = (a - 0xc0) * 0x40 + b - 0x80 114 | elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then 115 | value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 116 | elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then 117 | value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 118 | else 119 | return "" 120 | end 121 | if value <= 0xffff then 122 | return strformat ("\\u%.4x", value) 123 | elseif value <= 0x10ffff then 124 | -- encode as UTF-16 surrogate pair 125 | value = value - 0x10000 126 | local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) 127 | return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) 128 | else 129 | return "" 130 | end 131 | end 132 | 133 | local function fsub (str, pattern, repl) 134 | -- gsub always builds a new string in a buffer, even when no match 135 | -- exists. First using find should be more efficient when most strings 136 | -- don't contain the pattern. 137 | if strfind (str, pattern) then 138 | return gsub (str, pattern, repl) 139 | else 140 | return str 141 | end 142 | end 143 | 144 | local function quotestring (value) 145 | -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js 146 | value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) 147 | if strfind (value, "[\194\216\220\225\226\239]") then 148 | value = fsub (value, "\194[\128-\159\173]", escapeutf8) 149 | value = fsub (value, "\216[\128-\132]", escapeutf8) 150 | value = fsub (value, "\220\143", escapeutf8) 151 | value = fsub (value, "\225\158[\180\181]", escapeutf8) 152 | value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) 153 | value = fsub (value, "\226\129[\160-\175]", escapeutf8) 154 | value = fsub (value, "\239\187\191", escapeutf8) 155 | value = fsub (value, "\239\191[\176-\191]", escapeutf8) 156 | end 157 | return "\"" .. value .. "\"" 158 | end 159 | json.quotestring = quotestring 160 | 161 | local function replace(str, o, n) 162 | local i, j = strfind (str, o, 1, true) 163 | if i then 164 | return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1) 165 | else 166 | return str 167 | end 168 | end 169 | 170 | -- locale independent num2str and str2num functions 171 | local decpoint, numfilter 172 | 173 | local function updatedecpoint () 174 | decpoint = strmatch(tostring(0.5), "([^05+])") 175 | -- build a filter that can be used to remove group separators 176 | numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+" 177 | end 178 | 179 | updatedecpoint() 180 | 181 | local function num2str (num) 182 | return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") 183 | end 184 | 185 | local function str2num (str) 186 | local num = tonumber(replace(str, ".", decpoint)) 187 | if not num then 188 | updatedecpoint() 189 | num = tonumber(replace(str, ".", decpoint)) 190 | end 191 | return num 192 | end 193 | 194 | local function addnewline2 (level, buffer, buflen) 195 | buffer[buflen+1] = "\n" 196 | buffer[buflen+2] = strrep (" ", level) 197 | buflen = buflen + 2 198 | return buflen 199 | end 200 | 201 | function json.addnewline (state) 202 | if state.indent then 203 | state.bufferlen = addnewline2 (state.level or 0, 204 | state.buffer, state.bufferlen or #(state.buffer)) 205 | end 206 | end 207 | 208 | local encode2 -- forward declaration 209 | 210 | local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state) 211 | local kt = type (key) 212 | if kt ~= 'string' and kt ~= 'number' then 213 | return nil, "type '" .. kt .. "' is not supported as a key by JSON." 214 | end 215 | if prev then 216 | buflen = buflen + 1 217 | buffer[buflen] = "," 218 | end 219 | if indent then 220 | buflen = addnewline2 (level, buffer, buflen) 221 | end 222 | buffer[buflen+1] = quotestring (key) 223 | buffer[buflen+2] = ":" 224 | return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state) 225 | end 226 | 227 | local function appendcustom(res, buffer, state) 228 | local buflen = state.bufferlen 229 | if type (res) == 'string' then 230 | buflen = buflen + 1 231 | buffer[buflen] = res 232 | end 233 | return buflen 234 | end 235 | 236 | local function exception(reason, value, state, buffer, buflen, defaultmessage) 237 | defaultmessage = defaultmessage or reason 238 | local handler = state.exception 239 | if not handler then 240 | return nil, defaultmessage 241 | else 242 | state.bufferlen = buflen 243 | local ret, msg = handler (reason, value, state, defaultmessage) 244 | if not ret then return nil, msg or defaultmessage end 245 | return appendcustom(ret, buffer, state) 246 | end 247 | end 248 | 249 | function json.encodeexception(reason, value, state, defaultmessage) 250 | return quotestring("<" .. defaultmessage .. ">") 251 | end 252 | 253 | encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state) 254 | local valtype = type (value) 255 | local valmeta = getmetatable (value) 256 | valmeta = type (valmeta) == 'table' and valmeta -- only tables 257 | local valtojson = valmeta and valmeta.__tojson 258 | if valtojson then 259 | if tables[value] then 260 | return exception('reference cycle', value, state, buffer, buflen) 261 | end 262 | tables[value] = true 263 | state.bufferlen = buflen 264 | local ret, msg = valtojson (value, state) 265 | if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end 266 | tables[value] = nil 267 | buflen = appendcustom(ret, buffer, state) 268 | elseif value == nil then 269 | buflen = buflen + 1 270 | buffer[buflen] = "null" 271 | elseif valtype == 'number' then 272 | local s 273 | if value ~= value or value >= huge or -value >= huge then 274 | -- This is the behaviour of the original JSON implementation. 275 | s = "null" 276 | else 277 | s = num2str (value) 278 | end 279 | buflen = buflen + 1 280 | buffer[buflen] = s 281 | elseif valtype == 'boolean' then 282 | buflen = buflen + 1 283 | buffer[buflen] = value and "true" or "false" 284 | elseif valtype == 'string' then 285 | buflen = buflen + 1 286 | buffer[buflen] = quotestring (value) 287 | elseif valtype == 'table' then 288 | if tables[value] then 289 | return exception('reference cycle', value, state, buffer, buflen) 290 | end 291 | tables[value] = true 292 | level = level + 1 293 | local isa, n = isarray (value) 294 | if n == 0 and valmeta and valmeta.__jsontype == 'object' then 295 | isa = false 296 | end 297 | local msg 298 | if isa then -- JSON array 299 | buflen = buflen + 1 300 | buffer[buflen] = "[" 301 | for i = 1, n do 302 | buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state) 303 | if not buflen then return nil, msg end 304 | if i < n then 305 | buflen = buflen + 1 306 | buffer[buflen] = "," 307 | end 308 | end 309 | buflen = buflen + 1 310 | buffer[buflen] = "]" 311 | else -- JSON object 312 | local prev = false 313 | buflen = buflen + 1 314 | buffer[buflen] = "{" 315 | local order = valmeta and valmeta.__jsonorder or globalorder 316 | if order then 317 | local used = {} 318 | n = #order 319 | for i = 1, n do 320 | local k = order[i] 321 | local v = value[k] 322 | if v ~= nil then 323 | used[k] = true 324 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) 325 | prev = true -- add a seperator before the next element 326 | end 327 | end 328 | for k,v in pairs (value) do 329 | if not used[k] then 330 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) 331 | if not buflen then return nil, msg end 332 | prev = true -- add a seperator before the next element 333 | end 334 | end 335 | else -- unordered 336 | for k,v in pairs (value) do 337 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) 338 | if not buflen then return nil, msg end 339 | prev = true -- add a seperator before the next element 340 | end 341 | end 342 | if indent then 343 | buflen = addnewline2 (level - 1, buffer, buflen) 344 | end 345 | buflen = buflen + 1 346 | buffer[buflen] = "}" 347 | end 348 | tables[value] = nil 349 | else 350 | return exception ('unsupported type', value, state, buffer, buflen, 351 | "type '" .. valtype .. "' is not supported by JSON.") 352 | end 353 | return buflen 354 | end 355 | 356 | function json.encode (value, state) 357 | state = state or {} 358 | local oldbuffer = state.buffer 359 | local buffer = oldbuffer or {} 360 | state.buffer = buffer 361 | updatedecpoint() 362 | local ret, msg = encode2 (value, state.indent, state.level or 0, 363 | buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state) 364 | if not ret then 365 | error (msg, 2) 366 | elseif oldbuffer == buffer then 367 | state.bufferlen = ret 368 | return true 369 | else 370 | state.bufferlen = nil 371 | state.buffer = nil 372 | return concat (buffer) 373 | end 374 | end 375 | 376 | local function loc (str, where) 377 | local line, pos, linepos = 1, 1, 0 378 | while true do 379 | pos = strfind (str, "\n", pos, true) 380 | if pos and pos < where then 381 | line = line + 1 382 | linepos = pos 383 | pos = pos + 1 384 | else 385 | break 386 | end 387 | end 388 | return "line " .. line .. ", column " .. (where - linepos) 389 | end 390 | 391 | local function unterminated (str, what, where) 392 | return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) 393 | end 394 | 395 | local function scanwhite (str, pos) 396 | while true do 397 | pos = strfind (str, "%S", pos) 398 | if not pos then return nil end 399 | local sub2 = strsub (str, pos, pos + 1) 400 | if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then 401 | -- UTF-8 Byte Order Mark 402 | pos = pos + 3 403 | elseif sub2 == "//" then 404 | pos = strfind (str, "[\n\r]", pos + 2) 405 | if not pos then return nil end 406 | elseif sub2 == "/*" then 407 | pos = strfind (str, "*/", pos + 2) 408 | if not pos then return nil end 409 | pos = pos + 2 410 | else 411 | return pos 412 | end 413 | end 414 | end 415 | 416 | local escapechars = { 417 | ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", 418 | ["n"] = "\n", ["r"] = "\r", ["t"] = "\t" 419 | } 420 | 421 | local function unichar (value) 422 | if value < 0 then 423 | return nil 424 | elseif value <= 0x007f then 425 | return strchar (value) 426 | elseif value <= 0x07ff then 427 | return strchar (0xc0 + floor(value/0x40), 428 | 0x80 + (floor(value) % 0x40)) 429 | elseif value <= 0xffff then 430 | return strchar (0xe0 + floor(value/0x1000), 431 | 0x80 + (floor(value/0x40) % 0x40), 432 | 0x80 + (floor(value) % 0x40)) 433 | elseif value <= 0x10ffff then 434 | return strchar (0xf0 + floor(value/0x40000), 435 | 0x80 + (floor(value/0x1000) % 0x40), 436 | 0x80 + (floor(value/0x40) % 0x40), 437 | 0x80 + (floor(value) % 0x40)) 438 | else 439 | return nil 440 | end 441 | end 442 | 443 | local function scanstring (str, pos) 444 | local lastpos = pos + 1 445 | local buffer, n = {}, 0 446 | while true do 447 | local nextpos = strfind (str, "[\"\\]", lastpos) 448 | if not nextpos then 449 | return unterminated (str, "string", pos) 450 | end 451 | if nextpos > lastpos then 452 | n = n + 1 453 | buffer[n] = strsub (str, lastpos, nextpos - 1) 454 | end 455 | if strsub (str, nextpos, nextpos) == "\"" then 456 | lastpos = nextpos + 1 457 | break 458 | else 459 | local escchar = strsub (str, nextpos + 1, nextpos + 1) 460 | local value 461 | if escchar == "u" then 462 | value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16) 463 | if value then 464 | local value2 465 | if 0xD800 <= value and value <= 0xDBff then 466 | -- we have the high surrogate of UTF-16. Check if there is a 467 | -- low surrogate escaped nearby to combine them. 468 | if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then 469 | value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16) 470 | if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then 471 | value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000 472 | else 473 | value2 = nil -- in case it was out of range for a low surrogate 474 | end 475 | end 476 | end 477 | value = value and unichar (value) 478 | if value then 479 | if value2 then 480 | lastpos = nextpos + 12 481 | else 482 | lastpos = nextpos + 6 483 | end 484 | end 485 | end 486 | end 487 | if not value then 488 | value = escapechars[escchar] or escchar 489 | lastpos = nextpos + 2 490 | end 491 | n = n + 1 492 | buffer[n] = value 493 | end 494 | end 495 | if n == 1 then 496 | return buffer[1], lastpos 497 | elseif n > 1 then 498 | return concat (buffer), lastpos 499 | else 500 | return "", lastpos 501 | end 502 | end 503 | 504 | local scanvalue -- forward declaration 505 | 506 | local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta) 507 | local len = strlen (str) 508 | local tbl, n = {}, 0 509 | local pos = startpos + 1 510 | if what == 'object' then 511 | setmetatable (tbl, objectmeta) 512 | else 513 | setmetatable (tbl, arraymeta) 514 | end 515 | while true do 516 | pos = scanwhite (str, pos) 517 | if not pos then return unterminated (str, what, startpos) end 518 | local char = strsub (str, pos, pos) 519 | if char == closechar then 520 | return tbl, pos + 1 521 | end 522 | local val1, err 523 | val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) 524 | if err then return nil, pos, err end 525 | pos = scanwhite (str, pos) 526 | if not pos then return unterminated (str, what, startpos) end 527 | char = strsub (str, pos, pos) 528 | if char == ":" then 529 | if val1 == nil then 530 | return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")" 531 | end 532 | pos = scanwhite (str, pos + 1) 533 | if not pos then return unterminated (str, what, startpos) end 534 | local val2 535 | val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) 536 | if err then return nil, pos, err end 537 | tbl[val1] = val2 538 | pos = scanwhite (str, pos) 539 | if not pos then return unterminated (str, what, startpos) end 540 | char = strsub (str, pos, pos) 541 | else 542 | n = n + 1 543 | tbl[n] = val1 544 | end 545 | if char == "," then 546 | pos = pos + 1 547 | end 548 | end 549 | end 550 | 551 | scanvalue = function (str, pos, nullval, objectmeta, arraymeta) 552 | pos = pos or 1 553 | pos = scanwhite (str, pos) 554 | if not pos then 555 | return nil, strlen (str) + 1, "no valid JSON value (reached the end)" 556 | end 557 | local char = strsub (str, pos, pos) 558 | if char == "{" then 559 | return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta) 560 | elseif char == "[" then 561 | return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta) 562 | elseif char == "\"" then 563 | return scanstring (str, pos) 564 | else 565 | local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos) 566 | if pstart then 567 | local number = str2num (strsub (str, pstart, pend)) 568 | if number then 569 | return number, pend + 1 570 | end 571 | end 572 | pstart, pend = strfind (str, "^%a%w*", pos) 573 | if pstart then 574 | local name = strsub (str, pstart, pend) 575 | if name == "true" then 576 | return true, pend + 1 577 | elseif name == "false" then 578 | return false, pend + 1 579 | elseif name == "null" then 580 | return nullval, pend + 1 581 | end 582 | end 583 | return nil, pos, "no valid JSON value at " .. loc (str, pos) 584 | end 585 | end 586 | 587 | local function optionalmetatables(...) 588 | if select("#", ...) > 0 then 589 | return ... 590 | else 591 | return {__jsontype = 'object'}, {__jsontype = 'array'} 592 | end 593 | end 594 | 595 | function json.decode (str, pos, nullval, ...) 596 | local objectmeta, arraymeta = optionalmetatables(...) 597 | return scanvalue (str, pos, nullval, objectmeta, arraymeta) 598 | end 599 | 600 | function json.use_lpeg () 601 | local g = require ("lpeg") 602 | 603 | if g.version() == "0.11" then 604 | error "due to a bug in LPeg 0.11, it cannot be used for JSON matching" 605 | end 606 | 607 | local pegmatch = g.match 608 | local P, S, R = g.P, g.S, g.R 609 | 610 | local function ErrorCall (str, pos, msg, state) 611 | if not state.msg then 612 | state.msg = msg .. " at " .. loc (str, pos) 613 | state.pos = pos 614 | end 615 | return false 616 | end 617 | 618 | local function Err (msg) 619 | return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall) 620 | end 621 | 622 | local SingleLineComment = P"//" * (1 - S"\n\r")^0 623 | local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/" 624 | local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0 625 | 626 | local PlainChar = 1 - S"\"\\\n\r" 627 | local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars 628 | local HexDigit = R("09", "af", "AF") 629 | local function UTF16Surrogate (match, pos, high, low) 630 | high, low = tonumber (high, 16), tonumber (low, 16) 631 | if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then 632 | return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000) 633 | else 634 | return false 635 | end 636 | end 637 | local function UTF16BMP (hex) 638 | return unichar (tonumber (hex, 16)) 639 | end 640 | local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit)) 641 | local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP 642 | local Char = UnicodeEscape + EscapeSequence + PlainChar 643 | local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string") 644 | local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0)) 645 | local Fractal = P"." * R"09"^0 646 | local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1 647 | local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num 648 | local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1) 649 | local SimpleValue = Number + String + Constant 650 | local ArrayContent, ObjectContent 651 | 652 | -- The functions parsearray and parseobject parse only a single value/pair 653 | -- at a time and store them directly to avoid hitting the LPeg limits. 654 | local function parsearray (str, pos, nullval, state) 655 | local obj, cont 656 | local npos 657 | local t, nt = {}, 0 658 | repeat 659 | obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state) 660 | if not npos then break end 661 | pos = npos 662 | nt = nt + 1 663 | t[nt] = obj 664 | until cont == 'last' 665 | return pos, setmetatable (t, state.arraymeta) 666 | end 667 | 668 | local function parseobject (str, pos, nullval, state) 669 | local obj, key, cont 670 | local npos 671 | local t = {} 672 | repeat 673 | key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state) 674 | if not npos then break end 675 | pos = npos 676 | t[key] = obj 677 | until cont == 'last' 678 | return pos, setmetatable (t, state.objectmeta) 679 | end 680 | 681 | local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected") 682 | local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected") 683 | local Value = Space * (Array + Object + SimpleValue) 684 | local ExpectedValue = Value + Space * Err "value expected" 685 | ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() 686 | local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue) 687 | ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() 688 | local DecodeValue = ExpectedValue * g.Cp () 689 | 690 | function json.decode (str, pos, nullval, ...) 691 | local state = {} 692 | state.objectmeta, state.arraymeta = optionalmetatables(...) 693 | local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state) 694 | if state.msg then 695 | return nil, state.pos, state.msg 696 | else 697 | return obj, retpos 698 | end 699 | end 700 | 701 | -- use this function only once: 702 | json.use_lpeg = function () return json end 703 | 704 | json.using_lpeg = true 705 | 706 | return json -- so you can get the module using json = require "dkjson".use_lpeg() 707 | end 708 | 709 | if always_try_using_lpeg then 710 | pcall (json.use_lpeg) 711 | end 712 | 713 | return json 714 | 715 | -------------------------------------------------------------------------------- /src/memory_adresses.lua: -------------------------------------------------------------------------------- 1 | adresses = { 2 | global = { 3 | -- [byte][read/write] hex value is the decimal display 4 | character_select_timer = 0x020154FB, 5 | }, 6 | players = { 7 | { 8 | -- [byte][read/write] from 0 to 6 9 | character_select_row = 0x020154CF, 10 | 11 | -- [byte][read/write] from 0 to 2 12 | character_select_col = 0x0201566B, 13 | 14 | -- [byte][read] from 0 to 2 15 | character_select_sa = 0x020154D3, 16 | 17 | -- [byte][read] from 0 to 6 18 | character_select_color = 0x02015683, 19 | 20 | -- [byte][read] from 0 to 5 21 | -- - 0 is no player 22 | -- - 1 is intro anim 23 | -- - 2 is character select 24 | -- - 3 is SA intro anim 25 | -- - 4 is SA select 26 | -- - 5 is locked SA 27 | -- Will always stay at 5 after that and during the match 28 | character_select_state = 0x0201553D, 29 | 30 | -- [byte] used to overwrite shin gouki id 31 | character_select_id = 0x02011387, 32 | 33 | -- [byte] number of legs pressed fur Chun's Hyakuretsu Kyaku 34 | kyaku_l_count = 0x02025A03, 35 | kyaku_m_count = 0x02025A05, 36 | kyaku_h_count = 0x02025A07, 37 | 38 | -- [byte] time before Hyakuretsu Kyaku button count reset 39 | kyaku_reset_time = 0x020259f3, 40 | }, 41 | { 42 | character_select_row = 0x020154D1, 43 | character_select_col = 0x0201566D, 44 | character_select_sa = 0x020154D5, 45 | character_select_color = 0x02015684, 46 | character_select_state = 0x02015545, 47 | character_select_id = 0x02011388, 48 | 49 | kyaku_l_count = 0x02026023, 50 | kyaku_m_count = 0x02026025, 51 | kyaku_h_count = 0x02026027, 52 | kyaku_reset_time = 0x02026013, 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/menu_widgets.lua: -------------------------------------------------------------------------------- 1 | text_default_color = 0xF7FFF7FF 2 | text_default_border_color = 0x101008FF 3 | text_selected_color = 0xFF0000FF 4 | text_disabled_color = 0x999999FF 5 | 6 | gui_box_bg_color = 0x293139FF 7 | gui_box_outline_color = 0x840000FF 8 | 9 | menu_y_interval = 10 10 | 11 | function gauge_menu_item(_name, _object, _property_name, _unit, _fill_color, _gauge_max, _subdivision_count) 12 | local _o = {} 13 | _o.name = _name 14 | _o.object = _object 15 | _o.property_name = _property_name 16 | _o.player_id = _player_id 17 | _o.autofire_rate = 1 18 | _o.unit = _unit or 2 19 | _o.gauge_max = _gauge_max or 0 20 | _o.subdivision_count = _subdivision_count or 1 21 | _o.fill_color = _fill_color or 0x0000FFFF 22 | 23 | function _o:draw(_x, _y, _selected) 24 | local _c = text_default_color 25 | local _prefix = "" 26 | local _suffix = "" 27 | if _selected then 28 | _c = text_selected_color 29 | _prefix = "< " 30 | _suffix = " >" 31 | end 32 | gui.text(_x, _y, _prefix..self.name.." : ", _c, text_default_border_color) 33 | 34 | local _box_width = self.gauge_max / self.unit 35 | local _box_top = _y + 1 36 | local _box_left = _x + get_text_width("< "..self.name.." : ") - 1 37 | local _box_right = _box_left + _box_width 38 | local _box_bottom = _box_top + 4 39 | gui.box(_box_left, _box_top, _box_right, _box_bottom, text_default_color, text_default_border_color) 40 | local _content_width = self.object[self.property_name] / self.unit 41 | gui.box(_box_left, _box_top, _box_left + _content_width, _box_bottom, self.fill_color, 0x00000000) 42 | for _i = 1, self.subdivision_count - 1 do 43 | local _line_x = _box_left + _i * self.gauge_max / (self.subdivision_count * self.unit) 44 | gui.line(_line_x, _box_top, _line_x, _box_bottom, text_default_border_color) 45 | end 46 | 47 | gui.text(_box_right + 2, _y, _suffix, _c, text_default_border_color) 48 | end 49 | 50 | function _o:left() 51 | self.object[self.property_name] = math.max(self.object[self.property_name] - self.unit, 0) 52 | end 53 | 54 | function _o:right() 55 | self.object[self.property_name] = math.min(self.object[self.property_name] + self.unit, self.gauge_max) 56 | end 57 | 58 | function _o:reset() 59 | self.object[self.property_name] = 0 60 | end 61 | 62 | function _o:legend() 63 | return "MP: Reset to default" 64 | end 65 | 66 | return _o 67 | end 68 | 69 | available_characters = { 70 | " ", 71 | "A", 72 | "B", 73 | "C", 74 | "D", 75 | "E", 76 | "F", 77 | "G", 78 | "H", 79 | "I", 80 | "J", 81 | "K", 82 | "L", 83 | "M", 84 | "N", 85 | "O", 86 | "P", 87 | "Q", 88 | "R", 89 | "S", 90 | "T", 91 | "U", 92 | "V", 93 | "X", 94 | "Y", 95 | "Z", 96 | "0", 97 | "1", 98 | "2", 99 | "3", 100 | "4", 101 | "5", 102 | "6", 103 | "7", 104 | "8", 105 | "9", 106 | "-", 107 | "_", 108 | } 109 | 110 | function textfield_menu_item(_name, _object, _property_name, _default_value, _max_length) 111 | _default_value = _default_value or "" 112 | _max_length = _max_length or 16 113 | local _o = {} 114 | _o.name = _name 115 | _o.object = _object 116 | _o.property_name = _property_name 117 | _o.default_value = _default_value 118 | _o.max_length = _max_length 119 | _o.edition_index = 0 120 | _o.is_in_edition = false 121 | _o.content = {} 122 | 123 | function _o:sync_to_var() 124 | local _str = "" 125 | for i = 1, #self.content do 126 | _str = _str..available_characters[self.content[i]] 127 | end 128 | self.object[self.property_name] = _str 129 | end 130 | 131 | function _o:sync_from_var() 132 | self.content = {} 133 | for i = 1, #self.object[self.property_name] do 134 | local _c = self.object[self.property_name]:sub(i,i) 135 | for j = 1, #available_characters do 136 | if available_characters[j] == _c then 137 | table.insert(self.content, j) 138 | break 139 | end 140 | end 141 | end 142 | end 143 | 144 | function _o:crop_char_table() 145 | local _last_empty_index = 0 146 | for i = 1, #self.content do 147 | if self.content[i] == 1 then 148 | _last_empty_index = i 149 | else 150 | _last_empty_index = 0 151 | end 152 | end 153 | 154 | if _last_empty_index > 0 then 155 | for i = _last_empty_index, #self.content do 156 | table.remove(self.content, _last_empty_index) 157 | end 158 | end 159 | end 160 | 161 | function _o:draw(_x, _y, _selected) 162 | local _c = text_default_color 163 | local _prefix = "" 164 | local _suffix = "" 165 | if self.is_in_edition then 166 | _c = 0xFFFF00FF 167 | elseif _selected then 168 | _c = text_selected_color 169 | end 170 | 171 | local _value = self.object[self.property_name] 172 | 173 | if self.is_in_edition then 174 | local _cycle = 100 175 | if ((frame_number % _cycle) / _cycle) < 0.5 then 176 | gui.text(_x + (#self.name + 3 + #self.content - 1) * 4, _y + 2, "_", _c, text_default_border_color) 177 | end 178 | end 179 | 180 | gui.text(_x, _y, _prefix..self.name.." : ".._value.._suffix, _c, text_default_border_color) 181 | end 182 | 183 | function _o:left() 184 | if self.is_in_edition then 185 | self:reset() 186 | end 187 | end 188 | 189 | function _o:right() 190 | if self.is_in_edition then 191 | self:validate() 192 | end 193 | end 194 | 195 | function _o:up() 196 | if self.is_in_edition then 197 | self.content[self.edition_index] = self.content[self.edition_index] + 1 198 | if self.content[self.edition_index] > #available_characters then 199 | self.content[self.edition_index] = 1 200 | end 201 | self:sync_to_var() 202 | return true 203 | else 204 | return false 205 | end 206 | end 207 | 208 | function _o:down() 209 | if self.is_in_edition then 210 | self.content[self.edition_index] = self.content[self.edition_index] - 1 211 | if self.content[self.edition_index] == 0 then 212 | self.content[self.edition_index] = #available_characters 213 | end 214 | self:sync_to_var() 215 | return true 216 | else 217 | return false 218 | end 219 | end 220 | 221 | function _o:validate() 222 | if not self.is_in_edition then 223 | self:sync_from_var() 224 | if #self.content < self.max_length then 225 | table.insert(self.content, 1) 226 | end 227 | self.edition_index = #self.content 228 | self.is_in_edition = true 229 | else 230 | if self.content[self.edition_index] ~= 1 then 231 | if #self.content < self.max_length then 232 | table.insert(self.content, 1) 233 | self.edition_index = #self.content 234 | end 235 | end 236 | end 237 | self:sync_to_var() 238 | end 239 | 240 | function _o:reset() 241 | if not self.is_in_edition then 242 | _o.content = {} 243 | self.edition_index = 0 244 | else 245 | if #self.content > 1 then 246 | table.remove(self.content, #self.content) 247 | self.edition_index = #self.content 248 | else 249 | self.content[1] = 1 250 | end 251 | end 252 | self:sync_to_var() 253 | end 254 | 255 | function _o:cancel() 256 | if self.is_in_edition then 257 | self:crop_char_table() 258 | self:sync_to_var() 259 | self.is_in_edition = false 260 | end 261 | end 262 | 263 | function _o:legend() 264 | if self.is_in_edition then 265 | return "LP/Right: Next MP/Left: Previous LK: Leave edition" 266 | else 267 | return "LP: Edit MP: Reset to default" 268 | end 269 | end 270 | 271 | _o:sync_from_var() 272 | return _o 273 | end 274 | 275 | function checkbox_menu_item(_name, _object, _property_name, _default_value) 276 | if _default_value == nil then _default_value = false end 277 | local _o = {} 278 | _o.name = _name 279 | _o.object = _object 280 | _o.property_name = _property_name 281 | _o.default_value = _default_value 282 | 283 | function _o:draw(_x, _y, _selected) 284 | local _c = text_default_color 285 | local _prefix = "" 286 | local _suffix = "" 287 | if _selected then 288 | _c = text_selected_color 289 | _prefix = "< " 290 | _suffix = " >" 291 | end 292 | 293 | local _value = "" 294 | if self.object[self.property_name] then 295 | _value = "yes" 296 | else 297 | _value = "no" 298 | end 299 | gui.text(_x, _y, _prefix..self.name.." : ".._value.._suffix, _c, text_default_border_color) 300 | end 301 | 302 | function _o:left() 303 | self.object[self.property_name] = not self.object[self.property_name] 304 | end 305 | 306 | function _o:right() 307 | self.object[self.property_name] = not self.object[self.property_name] 308 | end 309 | 310 | function _o:reset() 311 | self.object[self.property_name] = self.default_value 312 | end 313 | 314 | function _o:legend() 315 | return "MP: Reset to default" 316 | end 317 | 318 | return _o 319 | end 320 | 321 | function list_menu_item(_name, _object, _property_name, _list, _default_value) 322 | if _default_value == nil then _default_value = 1 end 323 | local _o = {} 324 | _o.name = _name 325 | _o.object = _object 326 | _o.property_name = _property_name 327 | _o.list = _list 328 | _o.default_value = _default_value 329 | 330 | function _o:draw(_x, _y, _selected) 331 | local _c = text_default_color 332 | local _prefix = "" 333 | local _suffix = "" 334 | if _selected then 335 | _c = text_selected_color 336 | _prefix = "< " 337 | _suffix = " >" 338 | end 339 | gui.text(_x, _y, _prefix..self.name.." : "..tostring(self.list[self.object[self.property_name]]).._suffix, _c, text_default_border_color) 340 | end 341 | 342 | function _o:left() 343 | self.object[self.property_name] = self.object[self.property_name] - 1 344 | if self.object[self.property_name] == 0 then 345 | self.object[self.property_name] = #self.list 346 | end 347 | end 348 | 349 | function _o:right() 350 | self.object[self.property_name] = self.object[self.property_name] + 1 351 | if self.object[self.property_name] > #self.list then 352 | self.object[self.property_name] = 1 353 | end 354 | end 355 | 356 | function _o:reset() 357 | self.object[self.property_name] = self.default_value 358 | end 359 | 360 | function _o:legend() 361 | return "MP: Reset to default" 362 | end 363 | 364 | return _o 365 | end 366 | 367 | function integer_menu_item(_name, _object, _property_name, _min, _max, _loop, _default_value, _autofire_rate) 368 | if _default_value == nil then _default_value = _min end 369 | local _o = {} 370 | _o.name = _name 371 | _o.object = _object 372 | _o.property_name = _property_name 373 | _o.min = _min 374 | _o.max = _max 375 | _o.loop = _loop 376 | _o.default_value = _default_value 377 | _o.autofire_rate = _autofire_rate 378 | 379 | function _o:draw(_x, _y, _selected) 380 | local _c = text_default_color 381 | local _prefix = "" 382 | local _suffix = "" 383 | if _selected then 384 | _c = text_selected_color 385 | _prefix = "< " 386 | _suffix = " >" 387 | end 388 | gui.text(_x, _y, _prefix..self.name.." : "..tostring(self.object[self.property_name]).._suffix, _c, text_default_border_color) 389 | end 390 | 391 | function _o:left() 392 | self.object[self.property_name] = self.object[self.property_name] - 1 393 | if self.object[self.property_name] < self.min then 394 | if self.loop then 395 | self.object[self.property_name] = self.max 396 | else 397 | self.object[self.property_name] = self.min 398 | end 399 | end 400 | end 401 | 402 | function _o:right() 403 | self.object[self.property_name] = self.object[self.property_name] + 1 404 | if self.object[self.property_name] > self.max then 405 | if self.loop then 406 | self.object[self.property_name] = self.min 407 | else 408 | self.object[self.property_name] = self.max 409 | end 410 | end 411 | end 412 | 413 | function _o:reset() 414 | self.object[self.property_name] = self.default_value 415 | end 416 | 417 | function _o:legend() 418 | return "MP: Reset to default" 419 | end 420 | 421 | return _o 422 | end 423 | 424 | function map_menu_item(_name, _object, _property_name, _map_object, _map_property) 425 | local _o = {} 426 | _o.name = _name 427 | _o.object = _object 428 | _o.property_name = _property_name 429 | _o.map_object = _map_object 430 | _o.map_property = _map_property 431 | 432 | function _o:draw(_x, _y, _selected) 433 | local _c = text_default_color 434 | local _prefix = "" 435 | local _suffix = "" 436 | if _selected then 437 | _c = text_selected_color 438 | _prefix = "< " 439 | _suffix = " >" 440 | end 441 | 442 | local _str = string.format("%s%s : %s%s", _prefix, self.name, self.object[self.property_name], _suffix) 443 | gui.text(_x, _y, _str, _c, text_default_border_color) 444 | end 445 | 446 | function _o:left() 447 | if self.map_property == nil or self.map_object == nil or self.map_object[self.map_property] == nil then 448 | return 449 | end 450 | 451 | if self.object[self.property_name] == "" then 452 | for _key, _value in pairs(self.map_object[self.map_property]) do 453 | self.object[self.property_name] = _key 454 | end 455 | else 456 | local _previous_key = "" 457 | for _key, _value in pairs(self.map_object[self.map_property]) do 458 | if _key == self.object[self.property_name] then 459 | self.object[self.property_name] = _previous_key 460 | return 461 | end 462 | _previous_key = _key 463 | end 464 | self.object[self.property_name] = "" 465 | end 466 | end 467 | 468 | function _o:right() 469 | if self.map_property == nil or self.map_object == nil or self.map_object[self.map_property] == nil then 470 | return 471 | end 472 | 473 | if self.object[self.property_name] == "" then 474 | for _key, _value in pairs(self.map_object[self.map_property]) do 475 | self.object[self.property_name] = _key 476 | return 477 | end 478 | else 479 | local _previous_key = "" 480 | for _key, _value in pairs(self.map_object[self.map_property]) do 481 | if _previous_key == self.object[self.property_name] then 482 | self.object[self.property_name] = _key 483 | return 484 | end 485 | _previous_key = _key 486 | end 487 | self.object[self.property_name] = "" 488 | end 489 | end 490 | 491 | function _o:reset() 492 | self.object[self.property_name] = "" 493 | end 494 | 495 | function _o:legend() 496 | return "MP: Reset to default" 497 | end 498 | 499 | return _o 500 | end 501 | 502 | function button_menu_item(_name, _validate_function) 503 | local _o = {} 504 | _o.name = _name 505 | _o.validate_function = _validate_function 506 | _o.last_frame_validated = 0 507 | 508 | function _o:draw(_x, _y, _selected) 509 | local _c = text_default_color 510 | if _selected then 511 | _c = text_selected_color 512 | 513 | if self.last_frame_validated > frame_number then 514 | self.last_frame_validated = 0 515 | end 516 | 517 | if (frame_number - self.last_frame_validated < 5 ) then 518 | _c = 0xFFFF00FF 519 | end 520 | end 521 | 522 | gui.text(_x, _y,self.name, _c, text_default_border_color) 523 | end 524 | 525 | function _o:validate() 526 | self.last_frame_validated = frame_number 527 | if self.validate_function then 528 | self.validate_function() 529 | end 530 | end 531 | 532 | function _o:legend() 533 | return "LP: Validate" 534 | end 535 | 536 | return _o 537 | end 538 | 539 | -- # Menus 540 | menu_stack = {} 541 | 542 | function menu_stack_push(_menu) 543 | table.insert(menu_stack, _menu) 544 | end 545 | 546 | function menu_stack_pop(_menu) 547 | for _i, _m in ipairs(menu_stack) do 548 | if _m == _menu then 549 | table.remove(menu_stack, _i) 550 | break 551 | end 552 | end 553 | end 554 | 555 | function menu_stack_top() 556 | return menu_stack[#menu_stack] 557 | end 558 | 559 | function menu_stack_clear() 560 | menu_stack = {} 561 | end 562 | 563 | function menu_stack_update(_input) 564 | if #menu_stack == 0 then 565 | return 566 | end 567 | 568 | local _last_menu = menu_stack[#menu_stack] 569 | _last_menu:update(_input) 570 | end 571 | 572 | function menu_stack_draw() 573 | for _i, _menu in ipairs(menu_stack) do 574 | _menu:draw() 575 | end 576 | end 577 | 578 | function make_multitab_menu(_left, _top, _right, _bottom, _content, _on_toggle_entry, _additional_draw) 579 | local _m = {} 580 | _m.left = _left 581 | _m.top = _top 582 | _m.right = _right 583 | _m.bottom = _bottom 584 | _m.content = _content 585 | 586 | _m.is_main_menu_selected = true 587 | _m.main_menu_selected_index = 1 588 | _m.sub_menu_selected_index = 1 589 | 590 | _m.on_toggle_entry = _on_toggle_entry 591 | _m.additional_draw = _additional_draw 592 | 593 | function _m:update(_input) 594 | multitab_menu_update(self, _input) 595 | end 596 | 597 | function _m:draw() 598 | multitab_menu_draw(self) 599 | end 600 | 601 | function _m:current_entry() 602 | if self.is_main_menu_selected then 603 | return nil 604 | else 605 | return self.content[self.main_menu_selected_index].entries[self.sub_menu_selected_index] 606 | end 607 | end 608 | 609 | return _m 610 | end 611 | 612 | function multitab_menu_update(_menu, _input) 613 | 614 | if _input.down then 615 | repeat 616 | if _menu.is_main_menu_selected then 617 | _menu.is_main_menu_selected = false 618 | _menu.sub_menu_selected_index = 1 619 | else 620 | _menu.sub_menu_selected_index = _menu.sub_menu_selected_index + 1 621 | if _menu.sub_menu_selected_index > #_menu.content[_menu.main_menu_selected_index].entries then 622 | _menu.is_main_menu_selected = true 623 | end 624 | end 625 | until ( 626 | _menu.is_main_menu_selected or 627 | _menu.content[_menu.main_menu_selected_index].entries[_menu.sub_menu_selected_index].is_disabled == nil or 628 | not _menu.content[_menu.main_menu_selected_index].entries[_menu.sub_menu_selected_index].is_disabled() 629 | ) 630 | end 631 | 632 | if _input.up then 633 | repeat 634 | if _menu.is_main_menu_selected then 635 | _menu.is_main_menu_selected = false 636 | _menu.sub_menu_selected_index = #_menu.content[_menu.main_menu_selected_index].entries 637 | else 638 | _menu.sub_menu_selected_index = _menu.sub_menu_selected_index - 1 639 | if _menu.sub_menu_selected_index == 0 then 640 | _menu.is_main_menu_selected = true 641 | end 642 | end 643 | until ( 644 | _menu.is_main_menu_selected or 645 | _menu.content[_menu.main_menu_selected_index].entries[_menu.sub_menu_selected_index].is_disabled == nil or 646 | not _menu.content[_menu.main_menu_selected_index].entries[_menu.sub_menu_selected_index].is_disabled() 647 | ) 648 | end 649 | 650 | local _current_entry = _menu.content[_menu.main_menu_selected_index].entries[_menu.sub_menu_selected_index] 651 | 652 | if _input.left then 653 | if _menu.is_main_menu_selected then 654 | _menu.main_menu_selected_index = _menu.main_menu_selected_index - 1 655 | if _menu.main_menu_selected_index == 0 then 656 | _menu.main_menu_selected_index = #_menu.content 657 | end 658 | elseif _current_entry ~= nil then 659 | if _current_entry.left ~= nil then 660 | _current_entry:left() 661 | if _menu.on_toggle_entry ~= nil then 662 | _menu.on_toggle_entry(_menu) 663 | end 664 | end 665 | end 666 | end 667 | 668 | if _input.right then 669 | if _menu.is_main_menu_selected then 670 | _menu.main_menu_selected_index = _menu.main_menu_selected_index + 1 671 | if _menu.main_menu_selected_index > #_menu.content then 672 | _menu.main_menu_selected_index = 1 673 | end 674 | elseif _current_entry ~= nil then 675 | if _current_entry.right ~= nil then 676 | _current_entry:right() 677 | if _menu.on_toggle_entry ~= nil then 678 | _menu.on_toggle_entry(_menu) 679 | end 680 | end 681 | end 682 | end 683 | 684 | if _input.validate then 685 | if is_main_menu_selected then 686 | elseif _current_entry ~= nil then 687 | if _current_entry.validate then 688 | _current_entry:validate() 689 | if _menu.on_toggle_entry ~= nil then 690 | _menu.on_toggle_entry(_menu) 691 | end 692 | end 693 | end 694 | end 695 | 696 | if _input.reset then 697 | if is_main_menu_selected then 698 | elseif _current_entry ~= nil then 699 | if _current_entry.reset then 700 | _current_entry:reset() 701 | if _menu.on_toggle_entry ~= nil then 702 | _menu.on_toggle_entry(_menu) 703 | end 704 | end 705 | end 706 | end 707 | 708 | if _input.cancel then 709 | if is_main_menu_selected then 710 | elseif _current_entry ~= nil then 711 | if _current_entry.cancel then 712 | _current_entry:cancel() 713 | if _menu.on_toggle_entry ~= nil then 714 | _menu.on_toggle_entry(_menu) 715 | end 716 | end 717 | end 718 | end 719 | end 720 | 721 | function multitab_menu_draw(_menu) 722 | gui.box(_menu.left, _menu.top, _menu.right, _menu.bottom, gui_box_bg_color, gui_box_outline_color) 723 | 724 | local _bar_x = _menu.left + 10 725 | local _bar_y = _menu.top + 6 726 | local _base_offset = 0 727 | 728 | for i = 1, #_menu.content do 729 | local _offset = 0 730 | local _c = text_disabled_color 731 | local _t = _menu.content[i].name 732 | if _menu.is_main_menu_selected and i == _menu.main_menu_selected_index then 733 | _t = "< ".._t.." >" 734 | _c = text_selected_color 735 | elseif i == _menu.main_menu_selected_index then 736 | _c = text_default_color 737 | _offset = 8 738 | else 739 | _offset = 8 740 | end 741 | gui.text(_bar_x + _offset + _base_offset, _bar_y, _t, _c, text_default_border_color) 742 | _base_offset = _base_offset + (#_menu.content[i].name + 5) * 4 743 | end 744 | 745 | local _menu_x = _menu.left + 10 746 | local _menu_y = _menu.top + 23 747 | local _draw_index = 0 748 | local _is_focused = _menu == menu_stack_top() 749 | for i = 1, #_menu.content[_menu.main_menu_selected_index].entries do 750 | if _menu.content[_menu.main_menu_selected_index].entries[i].is_disabled == nil or not _menu.content[_menu.main_menu_selected_index].entries[i].is_disabled() then 751 | _menu.content[_menu.main_menu_selected_index].entries[i]:draw(_menu_x, _menu_y + menu_y_interval * _draw_index, not _menu.is_main_menu_selected and _is_focused and _menu.sub_menu_selected_index == i) 752 | _draw_index = _draw_index + 1 753 | end 754 | end 755 | 756 | if not _menu.is_main_menu_selected then 757 | if _menu.content[_menu.main_menu_selected_index].entries[_menu.sub_menu_selected_index].legend then 758 | gui.text(_menu_x, _menu.bottom - 12, _menu.content[_menu.main_menu_selected_index].entries[_menu.sub_menu_selected_index]:legend(), text_disabled_color, text_default_border_color) 759 | end 760 | end 761 | 762 | if _menu.additional_draw ~= nil then 763 | _menu.additional_draw(_menu) 764 | end 765 | 766 | end 767 | 768 | function make_menu(_left, _top, _right, _bottom, _content, _on_toggle_entry, _draw_legend) 769 | local _m = {} 770 | _m.left = _left 771 | _m.top = _top 772 | _m.right = _right 773 | _m.bottom = _bottom 774 | _m.content = _content 775 | 776 | _m.selected_index = 1 777 | _m.on_toggle_entry = _on_toggle_entry 778 | if _draw_legend ~= nil then 779 | _m.draw_legend = _draw_legend 780 | else 781 | _m.draw_legend = true 782 | end 783 | 784 | function _m:update(_input) 785 | menu_update(self, _input) 786 | end 787 | 788 | function _m:draw() 789 | menu_draw(self) 790 | end 791 | 792 | function _m:current_entry() 793 | return self.content[self.selected_index] 794 | end 795 | 796 | return _m 797 | end 798 | 799 | function menu_update(_menu, _input) 800 | 801 | if _input.up then 802 | if _menu.content[_menu.selected_index].is_in_edition then 803 | _menu.content[_menu.selected_index]:up() 804 | else 805 | repeat 806 | _menu.selected_index = _menu.selected_index - 1 807 | if _menu.selected_index == 0 then 808 | _menu.selected_index = #_menu.content 809 | end 810 | until _menu.content[_menu.selected_index].is_disabled == nil or not _menu.content[_menu.selected_index].is_disabled() 811 | end 812 | end 813 | 814 | if _input.down then 815 | if _menu.content[_menu.selected_index].is_in_edition then 816 | _menu.content[_menu.selected_index]:down() 817 | else 818 | repeat 819 | _menu.selected_index = _menu.selected_index + 1 820 | if _menu.selected_index == #_menu.content + 1 then 821 | _menu.selected_index = 1 822 | end 823 | until _menu.content[_menu.selected_index].is_disabled == nil or not _menu.content[_menu.selected_index].is_disabled() 824 | end 825 | end 826 | 827 | _current_entry = _menu.content[_menu.selected_index] 828 | 829 | if _input.left then 830 | if _current_entry.left then 831 | _current_entry:left() 832 | if _menu.on_toggle_entry ~= nil then 833 | _menu.on_toggle_entry(_menu) 834 | end 835 | end 836 | end 837 | 838 | if _input.right then 839 | if _current_entry.right then 840 | _current_entry:right() 841 | if _menu.on_toggle_entry ~= nil then 842 | _menu.on_toggle_entry(_menu) 843 | end 844 | end 845 | end 846 | 847 | if _input.validate then 848 | if _current_entry.validate then 849 | _current_entry:validate() 850 | if _menu.on_toggle_entry ~= nil then 851 | _menu.on_toggle_entry(_menu) 852 | end 853 | end 854 | end 855 | 856 | if _input.reset then 857 | if _current_entry.reset then 858 | _current_entry:reset() 859 | if _menu.on_toggle_entry ~= nil then 860 | _menu.on_toggle_entry(_menu) 861 | end 862 | end 863 | end 864 | 865 | if _input.cancel then 866 | if _current_entry.cancel then 867 | _current_entry:cancel() 868 | if _menu.on_toggle_entry ~= nil then 869 | _menu.on_toggle_entry(_menu) 870 | end 871 | end 872 | end 873 | end 874 | 875 | function menu_draw(_menu) 876 | gui.box(_menu.left, _menu.top, _menu.right, _menu.bottom, gui_box_bg_color, gui_box_outline_color) 877 | 878 | local _menu_x = _menu.left + 10 879 | local _menu_y = _menu.top + 9 880 | local _draw_index = 0 881 | 882 | for i = 1, #_menu.content do 883 | if _menu.content[i].is_disabled == nil or not _menu.content[i].is_disabled() then 884 | _menu.content[i]:draw(_menu_x, _menu_y + menu_y_interval * _draw_index, _menu.selected_index == i) 885 | _draw_index = _draw_index + 1 886 | end 887 | end 888 | 889 | if _menu.draw_legend then 890 | if _menu.content[_menu.selected_index].legend then 891 | gui.text(_menu_x, _menu.bottom - 12, _menu.content[_menu.selected_index]:legend(), text_disabled_color, text_default_border_color) 892 | end 893 | end 894 | end 895 | -------------------------------------------------------------------------------- /src/startup.lua: -------------------------------------------------------------------------------- 1 | game_name = "Street Fighter III 3rd Strike (Japan 990512)" 2 | script_version = "v0.10 dev" 3 | fc_version = "v2.0.97.44" 4 | saved_path = "saved/" 5 | rom_name = emu.romname() 6 | is_4rd_strike = false 7 | 8 | if rom_name == "sfiii3nr1" then 9 | -- NOP 10 | elseif rom_name == "sfiii4n" then 11 | game_name = "Street Fighter III 3rd Strike - 4rd Arrange Edition 2013 (990608)" 12 | is_4rd_strike = true 13 | else 14 | print("-----------------------------") 15 | print("WARNING: You are not using a rom supported by this script. Some of the features might not be working correctly.") 16 | print("-----------------------------") 17 | rom_name = "sfiii3nr1" 18 | end 19 | -------------------------------------------------------------------------------- /src/tools.lua: -------------------------------------------------------------------------------- 1 | assert_enabled = developer_mode 2 | function t_assert(_condition, _msg) 3 | _msg = _msg or "Assertion failed" 4 | if assert_enabled and not _condition then 5 | error(_msg, 2) 6 | end 7 | end 8 | 9 | function string:split(sep) 10 | local sep, fields = sep or ":", {} 11 | local pattern = string.format("([^%s]+)", sep) 12 | self:gsub(pattern, function(c) fields[#fields+1] = c end) 13 | return fields 14 | end 15 | 16 | function string_hash(_str) 17 | if #_str == 0 then 18 | return 0 19 | end 20 | 21 | local _DJB2_INIT = 5381; 22 | local _hash = _DJB2_INIT 23 | for _i = 1, #_str do 24 | local _c = string.byte(_str,_i) 25 | _hash = bit.lshift(_hash, 5) + _hash + _c 26 | end 27 | return _hash 28 | end 29 | 30 | function string_to_color(_str) 31 | local _HRange = { 0.0, 360.0 } 32 | local _SRange = { 0.8, 1.0 } 33 | local _LRange = { 0.7, 1.0 } 34 | 35 | local _HAmplitude = _HRange[2] - _HRange[1]; 36 | local _SAmplitude = _SRange[2] - _SRange[1]; 37 | local _LAmplitude = _LRange[2] - _LRange[1]; 38 | 39 | local _hash = string_hash(_str) 40 | 41 | local _HI = bit.rshift(bit.band(_hash, 0xFF000000), 24) 42 | local _SI = bit.rshift(bit.band(_hash, 0x00FF0000), 16) 43 | local _LI = bit.rshift(bit.band(_hash, 0x0000FF00), 8) 44 | local _base = bit.lshift(1, 8) 45 | 46 | local _H = _HRange[1] + (_HI / _base) * _HAmplitude; 47 | local _S = _SRange[1] + (_SI / _base) * _SAmplitude; 48 | local _L = _LRange[1] + (_LI / _base) * _LAmplitude; 49 | 50 | local _HDiv60 = _H / 60.0 51 | local _HDiv60_Floor = math.floor(_HDiv60); 52 | local _HDiv60_Fraction = _HDiv60 - _HDiv60_Floor; 53 | 54 | local _RGBValues = { 55 | _L, 56 | _L * (1.0 - _S), 57 | _L * (1.0 - (_HDiv60_Fraction * _S)), 58 | _L * (1.0 - ((1.0 - _HDiv60_Fraction) * _S)) 59 | } 60 | 61 | local _RGBSwizzle = { 62 | {1, 4, 2}, 63 | {3, 1, 2}, 64 | {2, 1, 4}, 65 | {2, 3, 1}, 66 | {4, 2, 1}, 67 | {1, 2, 3}, 68 | } 69 | local _SwizzleIndex = (_HDiv60_Floor % 6) + 1 70 | local _R = _RGBValues[_RGBSwizzle[_SwizzleIndex][1]] 71 | local _G = _RGBValues[_RGBSwizzle[_SwizzleIndex][2]] 72 | local _B = _RGBValues[_RGBSwizzle[_SwizzleIndex][3]] 73 | 74 | --print(string.format("H:%.1f, S:%.1f, L:%.1f | R:%.1f, G:%.1f, B:%.1f", _H, _S, _L, _R, _G, _B)) 75 | 76 | local _color = bit.lshift(math.floor(_R * 255), 24) + bit.lshift(math.floor(_G * 255), 16) + bit.lshift(math.floor(_B * 255), 8) + 0xFF 77 | return _color 78 | end 79 | 80 | function to_bit(_bool) 81 | if _bool then 82 | return 1 83 | else 84 | return 0 85 | end 86 | end 87 | 88 | function memory_readword_reverse(_addr) 89 | local _1 = memory.readbyte(_addr) 90 | local _2 = memory.readbyte(_addr + 1) 91 | return bit.bor(bit.lshift(_2, 8), _1) 92 | end 93 | 94 | function clamp01(_number) 95 | return math.max(math.min(_number, 1.0), 0.0) 96 | end 97 | 98 | function check_input_down_autofire(_player_object, _input, _autofire_rate, _autofire_time) 99 | _autofire_rate = _autofire_rate or 4 100 | _autofire_time = _autofire_time or 23 101 | if _player_object.input.pressed[_input] or (_player_object.input.down[_input] and _player_object.input.state_time[_input] > _autofire_time and (_player_object.input.state_time[_input] % _autofire_rate) == 0) then 102 | return true 103 | end 104 | return false 105 | end 106 | 107 | -- json tools 108 | local json = require ("src/libs/dkjson") 109 | 110 | function read_object_from_json_file(_file_path) 111 | local _f = io.open(_file_path, "r") 112 | if _f == nil then 113 | return nil 114 | end 115 | 116 | local _object 117 | local _pos, _err 118 | _object, _pos, _err = json.decode(_f:read("*all")) 119 | _f:close() 120 | 121 | if (err) then 122 | print(string.format("Failed to read json file \"%s\" : %s", _file_path, _err)) 123 | end 124 | 125 | return _object 126 | end 127 | 128 | function write_object_to_json_file(_object, _file_path) 129 | local _f, _error, _code = io.open(_file_path, "w") 130 | if _f == nil then 131 | print(string.format("Error %d: %s", _code, _error)) 132 | return false 133 | end 134 | 135 | local _str = json.encode(_object, { indent = true }) 136 | _f:write(_str) 137 | _f:close() 138 | 139 | return true 140 | end 141 | 142 | -- log 143 | log_enabled = false 144 | log_categories_display = {} 145 | 146 | logs = {} 147 | log_sections = { 148 | global = 1, 149 | P1 = 2, 150 | P2 = 3, 151 | } 152 | log_categories = {} 153 | log_recording_on = false 154 | log_category_count = 0 155 | current_entry = 1 156 | log_size_max = 80 157 | log_line_count_max = 25 158 | log_line_offset = 0 159 | 160 | function log(_section_name, _category_name, _event_name) 161 | if not log_enabled then return end 162 | 163 | if log_categories_display[_category_name] and log_categories_display[_category_name].print then 164 | print(string.format("%d - [%s][%s] %s", frame_number, _section_name, _category_name, _event_name)) 165 | end 166 | 167 | if not log_recording_on then return end 168 | 169 | _event_name = _event_name or "" 170 | _category_name = _category_name or "" 171 | _section_name = _section_name or "global" 172 | if log_sections[_section_name] == nil then _section_name = "global" end 173 | 174 | if not log_categories_display[_category_name] or not log_categories_display[_category_name].history then return end 175 | 176 | -- Add category if it does not exists 177 | if log_categories[_category_name] == nil then 178 | log_categories[_category_name] = log_category_count 179 | log_category_count = log_category_count + 1 180 | end 181 | 182 | -- Insert frame if it does not exists 183 | if #logs == 0 or logs[#logs].frame ~= frame_number then 184 | table.insert(logs, { 185 | frame = frame_number, 186 | events = {} 187 | }) 188 | end 189 | 190 | -- Remove overflowing logs frame 191 | while #logs > log_size_max do 192 | table.remove(logs, 1) 193 | end 194 | 195 | local _current_frame = logs[#logs] 196 | table.insert(_current_frame.events, { 197 | name = _event_name, 198 | section = _section_name, 199 | category = _category_name, 200 | color = string_to_color(_event_name) 201 | }) 202 | end 203 | 204 | log_filtered = {} 205 | log_start_locked = false 206 | function log_update() 207 | log_filtered = {} 208 | if not log_enabled then return end 209 | 210 | -- compute filtered logs 211 | for _i = 1, #logs do 212 | local _frame = logs[_i] 213 | local _filtered_frame = { frame = _frame.frame, events = {}} 214 | for _j, _event in ipairs(_frame.events) do 215 | if log_categories_display[_event.category] and log_categories_display[_event.category].history then 216 | table.insert(_filtered_frame.events, _event) 217 | end 218 | end 219 | 220 | if #_filtered_frame.events > 0 then 221 | table.insert(log_filtered, _filtered_frame) 222 | end 223 | end 224 | 225 | -- process input 226 | if player.input.down.start then 227 | if player.input.pressed.HP then 228 | log_start_locked = true 229 | log_recording_on = not log_recording_on 230 | if log_recording_on then 231 | log_line_offset = 0 232 | end 233 | end 234 | if player.input.pressed.HK then 235 | log_start_locked = true 236 | log_line_offset = 0 237 | logs = {} 238 | end 239 | 240 | if check_input_down_autofire(player, "up", 4) then 241 | log_start_locked = true 242 | log_line_offset = log_line_offset - 1 243 | log_line_offset = math.max(log_line_offset, 0) 244 | end 245 | if check_input_down_autofire(player, "down", 4) then 246 | log_start_locked = true 247 | log_line_offset = log_line_offset + 1 248 | log_line_offset = math.min(log_line_offset, math.max(#log_filtered - log_line_count_max - 1, 0)) 249 | end 250 | end 251 | 252 | if not player.input.down.start and not player.input.released.start then 253 | log_start_locked = false 254 | end 255 | end 256 | 257 | function log_draw() 258 | local _log = log_filtered 259 | 260 | if #_log == 0 then return end 261 | 262 | local _line_background = { 0x333333CC, 0x555555CC } 263 | local _separator_color = 0xAAAAAAFF 264 | local _width = emu.screenwidth() - 10 265 | local _height = emu.screenheight() - 10 266 | local _x_start = 5 267 | local _y_start = 5 268 | local _line_height = 8 269 | local _current_line = 0 270 | local _columns_start = { 0, 20, 200 } 271 | local _box_size = 6 272 | local _box_margin = 2 273 | gui.box(_x_start, _y_start , _x_start + _width, _y_start, 0x00000000, _separator_color) 274 | local _last_displayed_frame = 0 275 | for _i = 0, log_line_count_max do 276 | local _frame_index = #_log - (_i + log_line_offset) 277 | if _frame_index < 1 then 278 | break 279 | end 280 | local _frame = _log[_frame_index] 281 | local _events = {{}, {}, {}} 282 | for _j, _event in ipairs(_frame.events) do 283 | if log_categories_display[_event.category] and log_categories_display[_event.category].history then 284 | table.insert(_events[log_sections[_event.section]], _event) 285 | end 286 | end 287 | 288 | local _y = _y_start + _current_line * _line_height 289 | gui.box(_x_start, _y, _x_start + _width, _y + _line_height, _line_background[(_i % 2) + 1], 0x00000000) 290 | for _section_i = 1, 3 do 291 | local _box_x = _x_start + _columns_start[_section_i] 292 | local _box_y = _y + 1 293 | for _j, _event in ipairs(_events[_section_i]) do 294 | gui.box(_box_x, _box_y, _box_x + _box_size, _box_y + _box_size, _event.color, 0x00000000) 295 | gui.box(_box_x + 1, _box_y + 1, _box_x + _box_size - 1, _box_y + _box_size - 1, 0x00000000, 0x00000022) 296 | gui.text(_box_x + _box_size + _box_margin, _box_y, _event.name, text_default_color, 0x00000000) 297 | _box_x = _box_x + _box_size + _box_margin + get_text_width(_event.name) + _box_margin 298 | end 299 | end 300 | 301 | if _frame_index > 1 then 302 | local _frame_diff = _frame.frame - _log[_frame_index - 1].frame 303 | gui.text(_x_start + 2, _y + 1, string.format("%d", _frame_diff), text_default_color, 0x00000000) 304 | end 305 | gui.box(_x_start, _y + _line_height, _x_start + _width, _y + _line_height, 0x00000000, _separator_color) 306 | _current_line = _current_line + 1 307 | _last_displayed_frame = _frame_index 308 | end 309 | end 310 | 311 | function print_memory_line(_addr) 312 | _addr = _addr - _addr % 0x10 313 | 314 | print(string.format("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", 315 | memory.readbyte(_addr + 0x0), 316 | memory.readbyte(_addr + 0x1), 317 | memory.readbyte(_addr + 0x2), 318 | memory.readbyte(_addr + 0x3), 319 | memory.readbyte(_addr + 0x4), 320 | memory.readbyte(_addr + 0x5), 321 | memory.readbyte(_addr + 0x6), 322 | memory.readbyte(_addr + 0x7), 323 | memory.readbyte(_addr + 0x8), 324 | memory.readbyte(_addr + 0x9), 325 | memory.readbyte(_addr + 0xA), 326 | memory.readbyte(_addr + 0xB), 327 | memory.readbyte(_addr + 0xC), 328 | memory.readbyte(_addr + 0xD), 329 | memory.readbyte(_addr + 0xE), 330 | memory.readbyte(_addr + 0xF) 331 | )) 332 | end 333 | 334 | function log_state(_obj, _names) 335 | local _str = "" 336 | for _i, _name in ipairs(_names) do 337 | if _i > 0 then 338 | _str = _str..", " 339 | end 340 | _str = _str.._name..":" 341 | local _value = _obj[_name] 342 | local _type = type(_value) 343 | if _type == "boolean" then 344 | _str = _str..string.format("%d", to_bit(_value)) 345 | elseif _type == "number" then 346 | _str = _str..string.format("%d", _value) 347 | end 348 | end 349 | print(_str) 350 | end --------------------------------------------------------------------------------