|
| 1 | +-- Pizza Tower Auto Splitter |
| 2 | +-- By Penaz |
| 3 | +-- Based on the original work of ccarl |
| 4 | +process("PizzaTower.exe") |
| 5 | + |
| 6 | +-- The variable that will contain the address for the beginning speedrun section of the memory |
| 7 | +local sig = nil |
| 8 | + |
| 9 | +-- This variable is used to lock the auto splitter from splitting when it should not |
| 10 | +local can_split = false |
| 11 | + |
| 12 | +-- This is an array containing the name of the rooms where the level exits are located |
| 13 | +local full_game_split_rooms = { |
| 14 | + tower_tutorial1 = true, |
| 15 | + tower_tutorial1N = true, |
| 16 | + entrance_1 = true, |
| 17 | + medieval_1 = true, |
| 18 | + ruin_1 = true, |
| 19 | + dungeon_1 = true, |
| 20 | + badland_1 = true, |
| 21 | + graveyard_1 = true, |
| 22 | + farm_2 = true, |
| 23 | + saloon_1 = true, |
| 24 | + plage_entrance = true, |
| 25 | + forest_1 = true, |
| 26 | + minigolf_1 = true, |
| 27 | + space_1 = true, |
| 28 | + street_intro = true, |
| 29 | + sewer_1 = true, |
| 30 | + industrial_1 = true, |
| 31 | + freezer_1 = true, |
| 32 | + chateau_1 = true, |
| 33 | + kidsparty_1 = true, |
| 34 | + war_13 = true, |
| 35 | + boss_pepperman = true, |
| 36 | + boss_vigilante = true, |
| 37 | + boss_noise = true, |
| 38 | + boss_fakepepkey = true, |
| 39 | + boss_pizzaface = true, |
| 40 | + boss_pizzafacefinale = true, |
| 41 | + tower_entrancehall = true, |
| 42 | + rank_room = true, |
| 43 | +} |
| 44 | + |
| 45 | +-- This array contains the names of the rooms where we can unlock the splitting |
| 46 | +-- usually these are the rooms containing Pillar John (which starts Pizza Time) |
| 47 | +local split_unlock_rooms = { |
| 48 | + tower_tutorial10 = true, |
| 49 | + tower_tutorial3N = true, |
| 50 | + entrance_10 = true, |
| 51 | + medieval_10 = true, |
| 52 | + ruin_11 = true, |
| 53 | + dungeon_10 = true, |
| 54 | + badland_9 = true, |
| 55 | + graveyard_6 = true, |
| 56 | + farm_11 = true, |
| 57 | + saloon_6 = true, |
| 58 | + plage_cavern2 = true, |
| 59 | + forest_john = true, |
| 60 | + space_9 = true, |
| 61 | + minigolf_8 = true, |
| 62 | + street_john = true, |
| 63 | + sewer_8 = true, |
| 64 | + industrial_5 = true, |
| 65 | + freezer_escape1 = true, |
| 66 | + chateau_9 = true, |
| 67 | + kidsparty_john = true, |
| 68 | + war_1 = true, |
| 69 | + boss_pepperman = true, |
| 70 | + boss_vigilante = true, |
| 71 | + boss_noise = true, |
| 72 | + boss_fakepepkey = true, |
| 73 | + boss_pizzaface = true, |
| 74 | + tower_finalhallway = true, |
| 75 | +} |
| 76 | + |
| 77 | + |
| 78 | +-- The current state of the game |
| 79 | +local current = { |
| 80 | + file_minutes = nil, |
| 81 | + file_seconds = nil, |
| 82 | + level_minutes = nil, |
| 83 | + level_seconds = nil, |
| 84 | + room = nil, |
| 85 | + parsed_room= "Unknown", |
| 86 | + eol_fade_exists = false, |
| 87 | + boss_hp = nil, |
| 88 | +} |
| 89 | + |
| 90 | +-- The state of the game in the previous read |
| 91 | +local old = { |
| 92 | + file_minutes = nil, |
| 93 | + file_seconds = nil, |
| 94 | + level_minutes = nil, |
| 95 | + level_seconds = nil, |
| 96 | + room = nil, |
| 97 | + parsed_room= "Unknown", |
| 98 | + eol_fade_exists = false, |
| 99 | + boss_hp = nil, |
| 100 | +} |
| 101 | + |
| 102 | +-- Function to copy over data from one table to another |
| 103 | +function shallow_copy_tbl(t) |
| 104 | + local t2 = {} |
| 105 | + for k,v in pairs(t) do |
| 106 | + t2[k] = v |
| 107 | + end |
| 108 | + return t2 |
| 109 | +end |
| 110 | + |
| 111 | +-- Function used to print tables, useful for debugging purposes |
| 112 | +function print_tbl(t) |
| 113 | + for k,v in pairs(t) do |
| 114 | + print(k, " -> ", v) |
| 115 | + end |
| 116 | +end |
| 117 | + |
| 118 | +-- Standard startup function, 60FPS, uses internal game time |
| 119 | +function startup() |
| 120 | + refreshRate = 60 |
| 121 | + useGameTime = true |
| 122 | +end |
| 123 | + |
| 124 | +function state() |
| 125 | + -- If the address is nil, either we just started up the auto splitter or we couldn't |
| 126 | + -- find the address reporting the signature. |
| 127 | + if sig == nil then |
| 128 | + print("Scanning...") |
| 129 | + -- C2 5A 17 ... Marks the beginning of the "speedrun section" of memory, provided it exists. |
| 130 | + sig = sig_scan("C2 5A 17 65 BE 4D DF D6 F2 1C D1 3B A7 A6 1F C3", 0) |
| 131 | + if sig == nil then |
| 132 | + -- If we didn't find it, it was either not allocated yet (better luck next loop) or PT was not started |
| 133 | + -- with the "-livesplit" option. |
| 134 | + print("Signature not found, make sure Pizza Tower is run with the -livesplit option") |
| 135 | + end |
| 136 | + else |
| 137 | + -- First of all, before updating the current state, copy it over to the previous one |
| 138 | + -- so we can tell the difference |
| 139 | + old = shallow_copy_tbl(current) |
| 140 | + -- At 0x80 (128) bytes after the beginning of the signature, we find the minutes spent on the savefile |
| 141 | + -- It is a double-precision float |
| 142 | + current.file_minutes = readAddress("double", sig + 0x80) |
| 143 | + -- At 0x88 we find the seconds |
| 144 | + current.file_seconds = readAddress("double", sig + 0x88) |
| 145 | + -- 0x90 -> Minutes spent on the level |
| 146 | + current.level_minutes = readAddress("double", sig + 0x90) |
| 147 | + -- 0x98 -> Minutes spent on the level |
| 148 | + current.level_seconds = readAddress("double", sig + 0x98) |
| 149 | + -- 0xA0 -> A 64-character string telling us the name of the room we're in (including cutscenes) |
| 150 | + current.room = readAddress("string64", sig + 0xA0) |
| 151 | + -- 0xE0 -> A boolean telling us if we're fading to the end-of-level |
| 152 | + current.eol_fade_exists = readAddress("bool", sig + 0xE0) |
| 153 | + -- 0xE1 -> A small integer telling us the remaining boss health |
| 154 | + current.boss_hp = readAddress("byte", sig + 0xE1) |
| 155 | + end |
| 156 | +end |
| 157 | + |
| 158 | +function start() |
| 159 | + -- Start from new file. If we transition from the Intro to the Entrance, start the timer. |
| 160 | + if old.room == "Finalintro" and current.room == "tower_entrancehall" then |
| 161 | + return true |
| 162 | + end |
| 163 | + -- Start from loaded file. If we transition from the loading screen to the Entrance, start the timer. |
| 164 | + if old.room == "hub_loadingscreen" and current.room == "tower_entrancehall" then |
| 165 | + return true |
| 166 | + end |
| 167 | + -- If none of the above applies. Do nothing. |
| 168 | + return false |
| 169 | +end |
| 170 | + |
| 171 | +function split() |
| 172 | + -- If we cannot split yet, check if the current room can "unlock" the split lock. |
| 173 | + -- If it is already unlocked, don't do anything (or it may "re-lock") |
| 174 | + if not can_split then |
| 175 | + can_split = split_unlock_rooms[current.room] ~= nil |
| 176 | + end |
| 177 | + -- If we changed room |
| 178 | + if old.room ~= current.room then |
| 179 | + -- And we can split |
| 180 | + if can_split then |
| 181 | + -- Then parse the current room name into something that makes more sense (there are many hubs in the tower, for instance) |
| 182 | + current.parsed_room = getCurrentLevel(current.room, old.parsed_room) |
| 183 | + -- If the room we just passed contains an exit... |
| 184 | + if full_game_split_rooms[old.room] ~= nil then |
| 185 | + -- And we ended in the Results Screen (normal levels) or a room in the Tower (for the boss outro skip) |
| 186 | + if current.parsed_room == "ResultsScreen" or current.parsed_room == "Hub" then |
| 187 | + -- And the boss HP is depleted (which is always true on normal levels) |
| 188 | + if old.boss_hp == 0 then |
| 189 | + -- Re-lock the split lock for the next time |
| 190 | + can_split = false |
| 191 | + -- Allow Libresplit to split |
| 192 | + return true |
| 193 | + end |
| 194 | + end |
| 195 | + end |
| 196 | + end |
| 197 | + end |
| 198 | + -- Frame perfect End of Run split. This is very specific for the the of "The Crumbling Tower of Pizza" |
| 199 | + -- Where it would not split "frame-perfectly" otherwise. |
| 200 | + if (current.eol_fade_exists and not old.eol_fade_exists) and current.room == "tower_entrancehall" then |
| 201 | + return true |
| 202 | + end |
| 203 | + return false |
| 204 | +end |
| 205 | + |
| 206 | +function reset() |
| 207 | + -- Reset on new save |
| 208 | + if current.room == "Finalintro" and old.room ~= "Finalintro" then |
| 209 | + return true |
| 210 | + end |
| 211 | + -- Reset on loading save |
| 212 | + if current.room ~= old.room then |
| 213 | + if current.room == "hub_loadingscreen" then |
| 214 | + return true |
| 215 | + end |
| 216 | + end |
| 217 | + return false |
| 218 | +end |
| 219 | + |
| 220 | +function gameTime() |
| 221 | + -- Since we have the file minutes and seconds, we just chuck them into the game time |
| 222 | + -- if not null |
| 223 | + if (current.file_minutes ~= nil and current.file_seconds ~= nil) then |
| 224 | + return current.file_minutes * 60000 + current.file_seconds * 1000 |
| 225 | + end |
| 226 | + return 0 |
| 227 | +end |
| 228 | + |
| 229 | +-- A translation function for the level names into something that makes more sense |
| 230 | +-- For instance every room starting with "tower_" is a Hub room, unless we started a CTOP run. |
| 231 | +-- Or if we started a "Secrets of the world" run, all secret levels will be part of SOTW, instead of "normal secrets" |
| 232 | +function getCurrentLevel(room_name, prev_level) |
| 233 | + if prev_level == "F5CrumblingTower" and string.match(room_name, "tower_") and not string.match(room_name, "tower_pizzafacehall") then |
| 234 | + return "F5CrumblingTower" |
| 235 | + end |
| 236 | + if prev_level == "SecretsOfTheWorld" and string.match(room_name, "secret") then |
| 237 | + return "SecretsOfTheWorld" |
| 238 | + end |
| 239 | + if string.match(room_name, "tower_finalhallway") then |
| 240 | + return "F5CrumblingTower" |
| 241 | + end |
| 242 | + if room_name=="tower_tutorial1N" then |
| 243 | + return "F1TutorialNoise" |
| 244 | + end |
| 245 | + if room_name=="tower_tutorial2N" then |
| 246 | + return "F1TutorialNoise" |
| 247 | + end |
| 248 | + if room_name=="tower_tutorial3N" then |
| 249 | + return "F1TutorialNoise" |
| 250 | + end |
| 251 | + if string.match(room_name, "tower_tutorial") then |
| 252 | + return "F1Tutorial" |
| 253 | + end |
| 254 | + if string.match(room_name, "tower_") then |
| 255 | + return "Hub" |
| 256 | + end |
| 257 | + if string.match(room_name, "boss_pizzafacehub") then |
| 258 | + return "Hub" |
| 259 | + end |
| 260 | + if string.match(room_name, "entrance_") then |
| 261 | + return "F1JohnGutter" |
| 262 | + end |
| 263 | + if string.match(room_name, "medieval_") then |
| 264 | + return "F1Pizzascape" |
| 265 | + end |
| 266 | + if string.match(room_name, "ruin_") then |
| 267 | + return "F1AncientCheese" |
| 268 | + end |
| 269 | + if string.match(room_name, "dungeon_") then |
| 270 | + return "F1BloodsauceDungeon" |
| 271 | + end |
| 272 | + if room_name == "boss_pepperman" then |
| 273 | + return "Pepperman" |
| 274 | + end |
| 275 | + if string.match(room_name, "badland_") then |
| 276 | + return "F2OreganoDesert" |
| 277 | + end |
| 278 | + if string.match(room_name, "graveyard_") then |
| 279 | + return "F2Wasteyard" |
| 280 | + end |
| 281 | + if string.match(room_name, "farm_") then |
| 282 | + return "F2FunFarm" |
| 283 | + end |
| 284 | + if string.match(room_name, "saloon_") then |
| 285 | + return "F2FastfoodSaloon" |
| 286 | + end |
| 287 | + if room_name == "boss_vigilante" then |
| 288 | + return "Vigilante" |
| 289 | + end |
| 290 | + if string.match(room_name, "plage_") then |
| 291 | + return "F3CrustCove" |
| 292 | + end |
| 293 | + if string.match(room_name, "forest_") then |
| 294 | + return "F3GnomeForest" |
| 295 | + end |
| 296 | + if string.match(room_name, "space_") then |
| 297 | + return "F3DeepDish9" |
| 298 | + end |
| 299 | + if string.match(room_name, "minigolf_") then |
| 300 | + return "F3Golf" |
| 301 | + end |
| 302 | + if room_name == "boss_noise" then |
| 303 | + return "Noise" |
| 304 | + end |
| 305 | + if string.match(room_name, "street_") then |
| 306 | + return "F4ThePigCity" |
| 307 | + end |
| 308 | + if string.match(room_name, "industrial_") then |
| 309 | + return "F4PeppibotFactory" |
| 310 | + end |
| 311 | + if string.match(room_name, "sewer_") then |
| 312 | + return "F4OhShit" |
| 313 | + end |
| 314 | + if string.match(room_name, "freezer_") then |
| 315 | + return "F4Refrigerator" |
| 316 | + end |
| 317 | + if string.match(room_name, "boss_fakepep") then |
| 318 | + return "Fake" |
| 319 | + end |
| 320 | + if string.match(room_name, "secret_entrance") then |
| 321 | + return "SecretsOfTheWorld" |
| 322 | + end |
| 323 | + if string.match(room_name, "trickytreat") then |
| 324 | + return "TrickyTreat" |
| 325 | + end |
| 326 | + if string.match(room_name, "cheateau_") then |
| 327 | + return "F5Pizzascare" |
| 328 | + end |
| 329 | + if string.match(room_name, "kidsparty_") then |
| 330 | + return "F5DMAS" |
| 331 | + end |
| 332 | + if string.match(room_name, "war_") then |
| 333 | + return "F5War" |
| 334 | + end |
| 335 | + if room_name == "boss_pizzaface" then |
| 336 | + return "Pizzaface" |
| 337 | + end |
| 338 | + if room_name == "rank_room" then |
| 339 | + return "ResultsScreen" |
| 340 | + end |
| 341 | + return "Unknown" |
| 342 | +end |
0 commit comments