diff --git a/README.md b/README.md index dbf9865be65c..fb8246503095 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Currently, the following games are supported: * A Short Hike * Yoshi's Island * Mario & Luigi: Superstar Saga +* Bomb Rush Cyberfunk For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 6a9994f5a1f6..b0f360249483 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -25,6 +25,9 @@ # Blasphemous /worlds/blasphemous/ @TRPG0 +# Bomb Rush Cyberfunk +/worlds/bomb_rush_cyberfunk/ @TRPG0 + # Bumper Stickers /worlds/bumpstik/ @FelicitusNeko diff --git a/worlds/bomb_rush_cyberfunk/Items.py b/worlds/bomb_rush_cyberfunk/Items.py new file mode 100644 index 000000000000..b8aa877205e3 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Items.py @@ -0,0 +1,553 @@ +from typing import TypedDict, List, Dict, Set +from enum import Enum + + +class BRCType(Enum): + Music = 0 + GraffitiM = 1 + GraffitiL = 2 + GraffitiXL = 3 + Skateboard = 4 + InlineSkates = 5 + BMX = 6 + Character = 7 + Outfit = 8 + REP = 9 + Camera = 10 + + +class ItemDict(TypedDict, total=False): + name: str + count: int + type: BRCType + + +base_id = 2308000 + + +item_table: List[ItemDict] = [ + # Music + {'name': "Music (GET ENUF)", + 'type': BRCType.Music}, + {'name': "Music (Chuckin Up)", + 'type': BRCType.Music}, + {'name': "Music (Spectres)", + 'type': BRCType.Music}, + {'name': "Music (You Can Say Hi)", + 'type': BRCType.Music}, + {'name': "Music (JACK DA FUNK)", + 'type': BRCType.Music}, + {'name': "Music (Feel The Funk (Computer Love))", + 'type': BRCType.Music}, + {'name': "Music (Big City Life)", + 'type': BRCType.Music}, + {'name': "Music (I Wanna Kno)", + 'type': BRCType.Music}, + {'name': "Music (Plume)", + 'type': BRCType.Music}, + {'name': "Music (Two Days Off)", + 'type': BRCType.Music}, + {'name': "Music (Scraped On The Way Out)", + 'type': BRCType.Music}, + {'name': "Music (Last Hoorah)", + 'type': BRCType.Music}, + {'name': "Music (State of Mind)", + 'type': BRCType.Music}, + {'name': "Music (AGUA)", + 'type': BRCType.Music}, + {'name': "Music (Condensed milk)", + 'type': BRCType.Music}, + {'name': "Music (Light Switch)", + 'type': BRCType.Music}, + {'name': "Music (Hair Dun Nails Dun)", + 'type': BRCType.Music}, + {'name': "Music (Precious Thing)", + 'type': BRCType.Music}, + {'name': "Music (Next To Me)", + 'type': BRCType.Music}, + {'name': "Music (Refuse)", + 'type': BRCType.Music}, + {'name': "Music (Iridium)", + 'type': BRCType.Music}, + {'name': "Music (Funk Express)", + 'type': BRCType.Music}, + {'name': "Music (In The Pocket)", + 'type': BRCType.Music}, + {'name': "Music (Bounce Upon A Time)", + 'type': BRCType.Music}, + {'name': "Music (hwbouths)", + 'type': BRCType.Music}, + {'name': "Music (Morning Glow)", + 'type': BRCType.Music}, + {'name': "Music (Chromebies)", + 'type': BRCType.Music}, + {'name': "Music (watchyaback!)", + 'type': BRCType.Music}, + {'name': "Music (Anime Break)", + 'type': BRCType.Music}, + {'name': "Music (DA PEOPLE)", + 'type': BRCType.Music}, + {'name': "Music (Trinitron)", + 'type': BRCType.Music}, + {'name': "Music (Operator)", + 'type': BRCType.Music}, + {'name': "Music (Sunshine Popping Mixtape)", + 'type': BRCType.Music}, + {'name': "Music (House Cats Mixtape)", + 'type': BRCType.Music}, + {'name': "Music (Breaking Machine Mixtape)", + 'type': BRCType.Music}, + {'name': "Music (Beastmode Hip Hop Mixtape)", + 'type': BRCType.Music}, + + # Graffiti + {'name': "Graffiti (M - OVERWHELMME)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - QUICK BING)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - BLOCKY)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - Flow)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Pora)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Teddy 4)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - BOMB BEATS)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - SPRAYTANICPANIC!)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - SHOGUN)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - EVIL DARUMA)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - TeleBinge)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - All Screws Loose)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - 0m33)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Vom'B)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Street classic)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Thick Candy)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - colorBOMB)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Zona Leste)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Stacked Symbols)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - Constellation Circle)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - B-boy Love)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Devil 68)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - pico pow)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - 8 MINUTES OF LEAN MEAN)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (L - WHOLE SIXER)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - INFINITY)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - Dynamo)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - VoodooBoy)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Fang It Up!)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - FREAKS)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Graffo Le Fou)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Lauder)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - SpawningSeason)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Moai Marathon)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Tius)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - KANI-BOZU)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - NOISY NINJA)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - Dinner On The Court)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Campaign Trail)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - skate or di3)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Jd Vila Formosa)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Messenger Mural)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - Solstice Script)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - RECORD.HEAD)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Boom)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - wild rush)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - buttercup)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - DIGITAL BLOCKBUSTER)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (XL - Gold Rush)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - WILD STRUXXA)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - VIBRATIONS)", + 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - Bevel)", + # 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - SECOND SIGHT)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Bomb Croc)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - FATE)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Web Spitter)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - MOTORCYCLE GANG)", + 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - CYBER TENGU)", + # 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - Don't Screw Around)", + # 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Deep Dive)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - MegaHood)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Gamex UPA ABL)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - BiGSHiNYBoMB)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Bomb Burner)", + 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - Astrological Augury)", + # 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Pirate's Life 4 Me)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Bombing by FireMan)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - end 2 end)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Raver Funk)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - headphones on Helmet on)", + 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - HIGH TECH WS)", + # 'type': BRCType.GraffitiXL}, + + # Skateboards + {'name': "Skateboard (Devon)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Terrence)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Maceo)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Lazer Accuracy)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Death Boogie)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Sylk)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Taiga)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Just Swell)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Mantra)", + 'type': BRCType.Skateboard}, + + # Inline Skates + {'name': "Inline Skates (Glaciers)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Sweet Royale)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Strawberry Missiles)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Ice Cold Killers)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Red Industry)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Mech Adversary)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Orange Blasters)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (ck)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Sharpshooters)", + 'type': BRCType.InlineSkates}, + + # BMX + {'name': "BMX (Mr. Taupe)", + 'type': BRCType.BMX}, + {'name': "BMX (Gum)", + 'type': BRCType.BMX}, + {'name': "BMX (Steel Wheeler)", + 'type': BRCType.BMX}, + {'name': "BMX (oyo)", + 'type': BRCType.BMX}, + {'name': "BMX (Rigid No.6)", + 'type': BRCType.BMX}, + {'name': "BMX (Ceremony)", + 'type': BRCType.BMX}, + {'name': "BMX (XXX)", + 'type': BRCType.BMX}, + {'name': "BMX (Terrazza)", + 'type': BRCType.BMX}, + {'name': "BMX (Dedication)", + 'type': BRCType.BMX}, + + # Outfits + {'name': "Outfit (Red - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Red - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Tryce - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Tryce - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Bel - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Bel - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Vinyl - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Vinyl - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Solace - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Solace - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Felix - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Felix - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Rave - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Rave - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Mesh - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Mesh - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Shine - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Shine - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Rise - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Rise - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Coil - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Coil - Winter)", + 'type': BRCType.Outfit}, + + # Characters + {'name': "Tryce", + 'type': BRCType.Character}, + {'name': "Bel", + 'type': BRCType.Character}, + {'name': "Vinyl", + 'type': BRCType.Character}, + {'name': "Solace", + 'type': BRCType.Character}, + {'name': "Rave", + 'type': BRCType.Character}, + {'name': "Mesh", + 'type': BRCType.Character}, + {'name': "Shine", + 'type': BRCType.Character}, + {'name': "Rise", + 'type': BRCType.Character}, + {'name': "Coil", + 'type': BRCType.Character}, + {'name': "Frank", + 'type': BRCType.Character}, + {'name': "Rietveld", + 'type': BRCType.Character}, + {'name': "DJ Cyber", + 'type': BRCType.Character}, + {'name': "Eclipse", + 'type': BRCType.Character}, + {'name': "DOT.EXE", + 'type': BRCType.Character}, + {'name': "Devil Theory", + 'type': BRCType.Character}, + {'name': "Flesh Prince", + 'type': BRCType.Character}, + {'name': "Futurism", + 'type': BRCType.Character}, + {'name': "Oldhead", + 'type': BRCType.Character}, + + # REP + {'name': "8 REP", + 'type': BRCType.REP}, + {'name': "16 REP", + 'type': BRCType.REP}, + {'name': "24 REP", + 'type': BRCType.REP}, + {'name': "32 REP", + 'type': BRCType.REP}, + {'name': "48 REP", + 'type': BRCType.REP}, + + # App + {'name': "Camera App", + 'type': BRCType.Camera} +] + + +group_table: Dict[str, Set[str]] = { + "graffitim": {"Graffiti (M - OVERWHELMME)", + "Graffiti (M - QUICK BING)", + "Graffiti (M - BLOCKY)", + "Graffiti (M - Pora)", + "Graffiti (M - Teddy 4)", + "Graffiti (M - BOMB BEATS)", + "Graffiti (M - SPRAYTANICPANIC!)", + "Graffiti (M - SHOGUN)", + "Graffiti (M - TeleBinge)", + "Graffiti (M - 0m33)", + "Graffiti (M - Vom'B)", + "Graffiti (M - Street classic)", + "Graffiti (M - Thick Candy)", + "Graffiti (M - colorBOMB)", + "Graffiti (M - Zona Leste)", + "Graffiti (M - Stacked Symbols)", + "Graffiti (M - B-boy Love)", + "Graffiti (M - Devil 68)", + "Graffiti (M - pico pow)"}, + "graffitil": {"Graffiti (L - WHOLE SIXER)", + "Graffiti (L - INFINITY)", + "Graffiti (L - VoodooBoy)", + "Graffiti (L - Fang It Up!)", + "Graffiti (L - FREAKS)", + "Graffiti (L - Graffo Le Fou)", + "Graffiti (L - Lauder)", + "Graffiti (L - SpawningSeason)", + "Graffiti (L - Moai Marathon)", + "Graffiti (L - Tius)", + "Graffiti (L - NOISY NINJA)", + "Graffiti (L - Campaign Trail)", + "Graffiti (L - skate or di3)", + "Graffiti (L - Jd Vila Formosa)", + "Graffiti (L - Messenger Mural)", + "Graffiti (L - RECORD.HEAD)", + "Graffiti (L - Boom)", + "Graffiti (L - wild rush)", + "Graffiti (L - buttercup)"}, + "graffitixl": {"Graffiti (XL - Gold Rush)", + "Graffiti (XL - WILD STRUXXA)", + "Graffiti (XL - VIBRATIONS)", + "Graffiti (XL - SECOND SIGHT)", + "Graffiti (XL - Bomb Croc)", + "Graffiti (XL - FATE)", + "Graffiti (XL - Web Spitter)", + "Graffiti (XL - MOTORCYCLE GANG)", + "Graffiti (XL - Deep Dive)", + "Graffiti (XL - MegaHood)", + "Graffiti (XL - Gamex UPA ABL)", + "Graffiti (XL - BiGSHiNYBoMB)", + "Graffiti (XL - Bomb Burner)", + "Graffiti (XL - Pirate's Life 4 Me)", + "Graffiti (XL - Bombing by FireMan)", + "Graffiti (XL - end 2 end)", + "Graffiti (XL - Raver Funk)", + "Graffiti (XL - headphones on Helmet on)"}, + "skateboard": {"Skateboard (Devon)", + "Skateboard (Terrence)", + "Skateboard (Maceo)", + "Skateboard (Lazer Accuracy)", + "Skateboard (Death Boogie)", + "Skateboard (Sylk)", + "Skateboard (Taiga)", + "Skateboard (Just Swell)", + "Skateboard (Mantra)"}, + "inline skates": {"Inline Skates (Glaciers)", + "Inline Skates (Sweet Royale)", + "Inline Skates (Strawberry Missiles)", + "Inline Skates (Ice Cold Killers)", + "Inline Skates (Red Industry)", + "Inline Skates (Mech Adversary)", + "Inline Skates (Orange Blasters)", + "Inline Skates (ck)", + "Inline Skates (Sharpshooters)"}, + "skates": {"Inline Skates (Glaciers)", + "Inline Skates (Sweet Royale)", + "Inline Skates (Strawberry Missiles)", + "Inline Skates (Ice Cold Killers)", + "Inline Skates (Red Industry)", + "Inline Skates (Mech Adversary)", + "Inline Skates (Orange Blasters)", + "Inline Skates (ck)", + "Inline Skates (Sharpshooters)"}, + "inline": {"Inline Skates (Glaciers)", + "Inline Skates (Sweet Royale)", + "Inline Skates (Strawberry Missiles)", + "Inline Skates (Ice Cold Killers)", + "Inline Skates (Red Industry)", + "Inline Skates (Mech Adversary)", + "Inline Skates (Orange Blasters)", + "Inline Skates (ck)", + "Inline Skates (Sharpshooters)"}, + "bmx": {"BMX (Mr. Taupe)", + "BMX (Gum)", + "BMX (Steel Wheeler)", + "BMX (oyo)", + "BMX (Rigid No.6)", + "BMX (Ceremony)", + "BMX (XXX)", + "BMX (Terrazza)", + "BMX (Dedication)"}, + "bike": {"BMX (Mr. Taupe)", + "BMX (Gum)", + "BMX (Steel Wheeler)", + "BMX (oyo)", + "BMX (Rigid No.6)", + "BMX (Ceremony)", + "BMX (XXX)", + "BMX (Terrazza)", + "BMX (Dedication)"}, + "bicycle": {"BMX (Mr. Taupe)", + "BMX (Gum)", + "BMX (Steel Wheeler)", + "BMX (oyo)", + "BMX (Rigid No.6)", + "BMX (Ceremony)", + "BMX (XXX)", + "BMX (Terrazza)", + "BMX (Dedication)"}, + "characters": {"Tryce", + "Bel", + "Vinyl", + "Solace", + "Rave", + "Mesh", + "Shine", + "Rise", + "Coil", + "Frank", + "Rietveld", + "DJ Cyber", + "Eclipse", + "DOT.EXE", + "Devil Theory", + "Flesh Prince", + "Futurism", + "Oldhead"}, + "girl": {"Bel", + "Vinyl", + "Rave", + "Shine", + "Rise", + "Futurism"} +} \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/Locations.py b/worlds/bomb_rush_cyberfunk/Locations.py new file mode 100644 index 000000000000..57d913219bfd --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Locations.py @@ -0,0 +1,785 @@ +from typing import TypedDict, List +from .Regions import Stages + + +class LocationDict(TypedDict): + name: str + stage: Stages + game_id: str + + +class EventDict(TypedDict): + name: str + stage: Stages + item: str + + +location_table: List[LocationDict] = [ + {'name': "Hideout: Half pipe CD", + 'stage': Stages.H, + 'game_id': "MusicTrack_CondensedMilk"}, + {'name': "Hideout: Garage tower CD", + 'stage': Stages.H, + 'game_id': "MusicTrack_MorningGlow"}, + {'name': "Hideout: Rooftop CD", + 'stage': Stages.H, + 'game_id': "MusicTrack_LightSwitch"}, + {'name': "Hideout: Under staircase graffiti", + 'stage': Stages.H, + 'game_id': "UnlockGraffiti_grafTex_M1"}, + {'name': "Hideout: Secret area graffiti", + 'stage': Stages.H, + 'game_id': "UnlockGraffiti_grafTex_L1"}, + {'name': "Hideout: Rear studio graffiti", + 'stage': Stages.H, + 'game_id': "UnlockGraffiti_grafTex_XL1"}, + {'name': "Hideout: Corner ledge graffiti", + 'stage': Stages.H, + 'game_id': "UnlockGraffiti_grafTex_M2"}, + {'name': "Hideout: Upper platform skateboard", + 'stage': Stages.H, + 'game_id': "SkateboardDeck3"}, + {'name': "Hideout: BMX garage skateboard", + 'stage': Stages.H, + 'game_id': "SkateboardDeck2"}, + {'name': "Hideout: Unlock phone app", + 'stage': Stages.H, + 'game_id': "camera"}, + {'name': "Hideout: Vinyl joins the crew", + 'stage': Stages.H, + 'game_id': "girl1"}, + {'name': "Hideout: Solace joins the crew", + 'stage': Stages.H, + 'game_id': "dummy"}, + + {'name': "Versum Hill: Main street Robo Post graffiti", + 'stage': Stages.VH1, + 'game_id': "UnlockGraffiti_grafTex_L4"}, + {'name': "Versum Hill: Behind glass graffiti", + 'stage': Stages.VH1, + 'game_id': "UnlockGraffiti_grafTex_L3"}, + {'name': "Versum Hill: Office room graffiti", + 'stage': Stages.VH1, + 'game_id': "UnlockGraffiti_grafTex_M4"}, + {'name': "Versum Hill: Under bridge graffiti", + 'stage': Stages.VH2, + 'game_id': "UnlockGraffiti_grafTex_XL4"}, + {'name': "Versum Hill: Train rail ledge skateboard", + 'stage': Stages.VH2, + 'game_id': "SkateboardDeck6"}, + {'name': "Versum Hill: Train station CD", + 'stage': Stages.VH2, + 'game_id': "MusicTrack_PreciousThing"}, + {'name': "Versum Hill: Billboard platform outfit", + 'stage': Stages.VH2, + 'game_id': "MetalheadOutfit3"}, + {'name': "Versum Hill: Hilltop Robo Post CD", + 'stage': Stages.VH2, + 'game_id': "MusicTrack_BounceUponATime"}, + {'name': "Versum Hill: Hill secret skateboard", + 'stage': Stages.VH2, + 'game_id': "SkateboardDeck7"}, + {'name': "Versum Hill: Rooftop CD", + 'stage': Stages.VH2, + 'game_id': "MusicTrack_NextToMe"}, + {'name': "Versum Hill: Wallrunning challenge reward", + 'stage': Stages.VH2, + 'game_id': "UnlockGraffiti_grafTex_M3"}, + {'name': "Versum Hill: Manual challenge reward", + 'stage': Stages.VH2, + 'game_id': "UnlockGraffiti_grafTex_L2"}, + {'name': "Versum Hill: Corner challenge reward", + 'stage': Stages.VH2, + 'game_id': "UnlockGraffiti_grafTex_M13"}, + {'name': "Versum Hill: Side street alley outfit", + 'stage': Stages.VH3, + 'game_id': "MetalheadOutfit4"}, + {'name': "Versum Hill: Side street secret skateboard", + 'stage': Stages.VH3, + 'game_id': "SkateboardDeck9"}, + {'name': "Versum Hill: Basketball court alley skateboard", + 'stage': Stages.VH4, + 'game_id': "SkateboardDeck5"}, + {'name': "Versum Hill: Basketball court Robo Post CD", + 'stage': Stages.VH4, + 'game_id': "MusicTrack_Operator"}, + {'name': "Versum Hill: Underground mall billboard graffiti", + 'stage': Stages.VHO, + 'game_id': "UnlockGraffiti_grafTex_XL3"}, + {'name': "Versum Hill: Underground mall vending machine skateboard", + 'stage': Stages.VHO, + 'game_id': "SkateboardDeck8"}, + {'name': "Versum Hill: BMX gate outfit", + 'stage': Stages.VH1, + 'game_id': "AngelOutfit3"}, + {'name': "Versum Hill: Glass floor skates", + 'stage': Stages.VH2, + 'game_id': "InlineSkates4"}, + {'name': "Versum Hill: Basketball court shortcut CD", + 'stage': Stages.VH4, + 'game_id': "MusicTrack_GetEnuf"}, + {'name': "Versum Hill: Rave joins the crew", + 'stage': Stages.VHO, + 'game_id': "angel"}, + {'name': "Versum Hill: Frank joins the crew", + 'stage': Stages.VH2, + 'game_id': "frank"}, + {'name': "Versum Hill: Rietveld joins the crew", + 'stage': Stages.VH4, + 'game_id': "jetpackBossPlayer"}, + {'name': "Versum Hill: Big Polo", + 'stage': Stages.VH1, + 'game_id': "PoloBuilding/Mascot_Polo_sit_big"}, + {'name': "Versum Hill: Trash Polo", + 'stage': Stages.VH1, + 'game_id': "TrashCluster (1)/Mascot_Polo_street"}, + {'name': "Versum Hill: Fruit stand Polo", + 'stage': Stages.VHO, + 'game_id': "SecretRoom/Mascot_Polo_street"}, + + {'name': "Millennium Square: Center ramp graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_L6"}, + {'name': "Millennium Square: Rooftop staircase graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_M8"}, + {'name': "Millennium Square: Toilet graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_XL6"}, + {'name': "Millennium Square: Trash graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_M5"}, + {'name': "Millennium Square: Center tower graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_M6"}, + {'name': "Millennium Square: Rooftop billboard graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_XL7"}, + {'name': "Millennium Square: Center Robo Post CD", + 'stage': Stages.MS, + 'game_id': "MusicTrack_FeelTheFunk"}, + {'name': "Millennium Square: Parking garage Robo Post CD", + 'stage': Stages.MS, + 'game_id': "MusicTrack_Plume"}, + {'name': "Millennium Square: Mall ledge outfit", + 'stage': Stages.MS, + 'game_id': "BlockGuyOutfit3"}, + {'name': "Millennium Square: Alley rooftop outfit", + 'stage': Stages.MS, + 'game_id': "BlockGuyOutfit4"}, + {'name': "Millennium Square: Alley staircase skateboard", + 'stage': Stages.MS, + 'game_id': "SkateboardDeck4"}, + {'name': "Millennium Square: Secret painting skates", + 'stage': Stages.MS, + 'game_id': "InlineSkates2"}, + {'name': "Millennium Square: Vending machine skates", + 'stage': Stages.MS, + 'game_id': "InlineSkates3"}, + {'name': "Millennium Square: Walkway roof skates", + 'stage': Stages.MS, + 'game_id': "InlineSkates5"}, + {'name': "Millennium Square: Alley ledge skates", + 'stage': Stages.MS, + 'game_id': "InlineSkates6"}, + {'name': "Millennium Square: DJ Cyber joins the crew", + 'stage': Stages.MS, + 'game_id': "dj"}, + {'name': "Millennium Square: Half pipe Polo", + 'stage': Stages.MS, + 'game_id': "propsSecretArea/Mascot_Polo_street"}, + + {'name': "Brink Terminal: Upside grind challenge reward", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_M10"}, + {'name': "Brink Terminal: Manual challenge reward", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_L8"}, + {'name': "Brink Terminal: Score challenge reward", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_M12"}, + {'name': "Brink Terminal: Under square ledge graffiti", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_L9"}, + {'name': "Brink Terminal: Bus graffiti", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_XL9"}, + {'name': "Brink Terminal: Under square Robo Post graffiti", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_M9"}, + {'name': "Brink Terminal: BMX gate graffiti", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_L7"}, + {'name': "Brink Terminal: Square tower CD", + 'stage': Stages.BT1, + 'game_id': "MusicTrack_Chapter1Mixtape"}, + {'name': "Brink Terminal: Trash CD", + 'stage': Stages.BT1, + 'game_id': "MusicTrack_HairDunNailsDun"}, + {'name': "Brink Terminal: Shop roof outfit", + 'stage': Stages.BT1, + 'game_id': "AngelOutfit4"}, + {'name': "Brink Terminal: Underground glass skates", + 'stage': Stages.BTO1, + 'game_id': "InlineSkates8"}, + {'name': "Brink Terminal: Glass roof skates", + 'stage': Stages.BT1, + 'game_id': "InlineSkates10"}, + {'name': "Brink Terminal: Mesh's skateboard", + 'stage': Stages.BTO2, + 'game_id': "SkateboardDeck10"}, # double check this one + {'name': "Brink Terminal: Underground ramp skates", + 'stage': Stages.BTO1, + 'game_id': "InlineSkates7"}, + {'name': "Brink Terminal: Rooftop halfpipe graffiti", + 'stage': Stages.BT3, + 'game_id': "UnlockGraffiti_grafTex_M11"}, + {'name': "Brink Terminal: Wire grind CD", + 'stage': Stages.BT2, + 'game_id': "MusicTrack_Watchyaback"}, + {'name': "Brink Terminal: Rooftop glass CD", + 'stage': Stages.BT3, + 'game_id': "MusicTrack_Refuse"}, + {'name': "Brink Terminal: Tower core outfit", + 'stage': Stages.BT3, + 'game_id': "SpacegirlOutfit4"}, + {'name': "Brink Terminal: High rooftop outfit", + 'stage': Stages.BT3, + 'game_id': "WideKidOutfit3"}, + {'name': "Brink Terminal: Ocean platform CD", + 'stage': Stages.BTO2, + 'game_id': "MusicTrack_ScrapedOnTheWayOut"}, + {'name': "Brink Terminal: End of dock CD", + 'stage': Stages.BTO2, + 'game_id': "MusicTrack_Hwbouths"}, + {'name': "Brink Terminal: Dock Robo Post outfit", + 'stage': Stages.BTO2, + 'game_id': "WideKidOutfit4"}, + {'name': "Brink Terminal: Control room skates", + 'stage': Stages.BTO2, + 'game_id': "InlineSkates9"}, + {'name': "Brink Terminal: Mesh joins the crew", + 'stage': Stages.BTO2, + 'game_id': "wideKid"}, + {'name': "Brink Terminal: Eclipse joins the crew", + 'stage': Stages.BT1, + 'game_id': "medusa"}, + {'name': "Brink Terminal: Behind glass Polo", + 'stage': Stages.BT1, + 'game_id': "KingFood (Bear)/Mascot_Polo_street"}, + + {'name': "Millennium Mall: Warehouse pallet graffiti", + 'stage': Stages.MM1, + 'game_id': "UnlockGraffiti_grafTex_L5"}, + {'name': "Millennium Mall: Wall alcove graffiti", + 'stage': Stages.MM1, + 'game_id': "UnlockGraffiti_grafTex_XL10"}, + {'name': "Millennium Mall: Maintenance shaft CD", + 'stage': Stages.MM1, + 'game_id': "MusicTrack_MissingBreak"}, + {'name': "Millennium Mall: Glass cylinder CD", + 'stage': Stages.MM1, + 'game_id': "MusicTrack_DAPEOPLE"}, + {'name': "Millennium Mall: Lower Robo Post outfit", + 'stage': Stages.MM1, + 'game_id': "SpacegirlOutfit3"}, + {'name': "Millennium Mall: Atrium vending machine graffiti", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_M15"}, + {'name': "Millennium Mall: Trick challenge reward", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_XL8"}, + {'name': "Millennium Mall: Slide challenge reward", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_L10"}, + {'name': "Millennium Mall: Fish challenge reward", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_L12"}, + {'name': "Millennium Mall: Score challenge reward", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_XL11"}, + {'name': "Millennium Mall: Atrium top floor Robo Post CD", + 'stage': Stages.MM2, + 'game_id': "MusicTrack_TwoDaysOff"}, + {'name': "Millennium Mall: Atrium top floor floating CD", + 'stage': Stages.MM2, + 'game_id': "MusicTrack_Spectres"}, + {'name': "Millennium Mall: Atrium top floor BMX", + 'stage': Stages.MM2, + 'game_id': "BMXBike2"}, + {'name': "Millennium Mall: Theater entrance BMX", + 'stage': Stages.MM2, + 'game_id': "BMXBike3"}, + {'name': "Millennium Mall: Atrium BMX gate BMX", + 'stage': Stages.MM2, + 'game_id': "BMXBike5"}, + {'name': "Millennium Mall: Upside down rail outfit", + 'stage': Stages.MM2, + 'game_id': "BunGirlOutfit3"}, + {'name': "Millennium Mall: Theater stage corner graffiti", + 'stage': Stages.MM3, + 'game_id': "UnlockGraffiti_grafTex_L15"}, + {'name': "Millennium Mall: Theater hanging billboards graffiti", + 'stage': Stages.MM3, + 'game_id': "UnlockGraffiti_grafTex_XL15"}, + {'name': "Millennium Mall: Theater garage graffiti", + 'stage': Stages.MM3, + 'game_id': "UnlockGraffiti_grafTex_M16"}, + {'name': "Millennium Mall: Theater maintenance CD", + 'stage': Stages.MM3, + 'game_id': "MusicTrack_WannaKno"}, + {'name': "Millennium Mall: Race track Robo Post CD", + 'stage': Stages.MMO2, + 'game_id': "MusicTrack_StateOfMind"}, + {'name': "Millennium Mall: Hanging lights CD", + 'stage': Stages.MMO1, + 'game_id': "MusicTrack_Chapter2Mixtape"}, + {'name': "Millennium Mall: Shine joins the crew", + 'stage': Stages.MM3, + 'game_id': "bunGirl"}, + {'name': "Millennium Mall: DOT.EXE joins the crew", + 'stage': Stages.MM2, + 'game_id': "eightBall"}, + + {'name': "Pyramid Island: Lower rooftop graffiti", + 'stage': Stages.PI1, + 'game_id': "UnlockGraffiti_grafTex_L18"}, + {'name': "Pyramid Island: Polo graffiti", + 'stage': Stages.PI1, + 'game_id': "UnlockGraffiti_grafTex_L16"}, + {'name': "Pyramid Island: Above entrance graffiti", + 'stage': Stages.PI1, + 'game_id': "UnlockGraffiti_grafTex_XL16"}, + {'name': "Pyramid Island: BMX gate BMX", + 'stage': Stages.PI1, + 'game_id': "BMXBike6"}, + {'name': "Pyramid Island: Quarter pipe rooftop graffiti", + 'stage': Stages.PI2, + 'game_id': "UnlockGraffiti_grafTex_M17"}, + {'name': "Pyramid Island: Supply port Robo Post CD", + 'stage': Stages.PI2, + 'game_id': "MusicTrack_Trinitron"}, + {'name': "Pyramid Island: Above gate ledge CD", + 'stage': Stages.PI2, + 'game_id': "MusicTrack_Agua"}, + {'name': "Pyramid Island: Smoke hole BMX", + 'stage': Stages.PI2, + 'game_id': "BMXBike8"}, + {'name': "Pyramid Island: Above gate rail outfit", + 'stage': Stages.PI2, + 'game_id': "VinylOutfit3"}, + {'name': "Pyramid Island: Rail loop outfit", + 'stage': Stages.PI2, + 'game_id': "BunGirlOutfit4"}, + {'name': "Pyramid Island: Score challenge reward", + 'stage': Stages.PI2, + 'game_id': "UnlockGraffiti_grafTex_XL2"}, + {'name': "Pyramid Island: Score challenge 2 reward", + 'stage': Stages.PI2, + 'game_id': "UnlockGraffiti_grafTex_L13"}, + {'name': "Pyramid Island: Quarter pipe challenge reward", + 'stage': Stages.PI2, + 'game_id': "UnlockGraffiti_grafTex_XL12"}, + {'name': "Pyramid Island: Wind turbines CD", + 'stage': Stages.PI3, + 'game_id': "MusicTrack_YouCanSayHi"}, + {'name': "Pyramid Island: Shortcut glass CD", + 'stage': Stages.PI3, + 'game_id': "MusicTrack_Chromebies"}, + {'name': "Pyramid Island: Turret jump CD", + 'stage': Stages.PI3, + 'game_id': "MusicTrack_ChuckinUp"}, + {'name': "Pyramid Island: Helipad BMX", + 'stage': Stages.PI3, + 'game_id': "BMXBike7"}, + {'name': "Pyramid Island: Pipe outfit", + 'stage': Stages.PI3, + 'game_id': "PufferGirlOutfit3"}, + {'name': "Pyramid Island: Trash outfit", + 'stage': Stages.PI3, + 'game_id': "PufferGirlOutfit4"}, + {'name': "Pyramid Island: Pyramid top CD", + 'stage': Stages.PI4, + 'game_id': "MusicTrack_BigCityLife"}, + {'name': "Pyramid Island: Pyramid top Robo Post CD", + 'stage': Stages.PI4, + 'game_id': "MusicTrack_Chapter3Mixtape"}, + {'name': "Pyramid Island: Maze outfit", + 'stage': Stages.PIO, + 'game_id': "VinylOutfit4"}, + {'name': "Pyramid Island: Rise joins the crew", + 'stage': Stages.PI4, + 'game_id': "pufferGirl"}, + {'name': "Pyramid Island: Devil Theory joins the crew", + 'stage': Stages.PI3, + 'game_id': "boarder"}, + {'name': "Pyramid Island: Polo pile 1", + 'stage': Stages.PI1, + 'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave"}, + {'name': "Pyramid Island: Polo pile 2", + 'stage': Stages.PI1, + 'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (1)"}, + {'name': "Pyramid Island: Polo pile 3", + 'stage': Stages.PI1, + 'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (2)"}, + {'name': "Pyramid Island: Polo pile 4", + 'stage': Stages.PI1, + 'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (3)"}, + {'name': "Pyramid Island: Maze glass Polo", + 'stage': Stages.PIO, + 'game_id': "Start/Mascot_Polo_sit_big (1)"}, + {'name': "Pyramid Island: Maze classroom Polo", + 'stage': Stages.PIO, + 'game_id': "PeteRoom/Mascot_Polo_sit_big_wave (1)"}, + {'name': "Pyramid Island: Maze vent Polo", + 'stage': Stages.PIO, + 'game_id': "CheckerRoom/Mascot_Polo_street"}, + {'name': "Pyramid Island: Big maze Polo", + 'stage': Stages.PIO, + 'game_id': "YellowPoloRoom/Mascot_Polo_sit_big"}, + {'name': "Pyramid Island: Maze desk Polo", + 'stage': Stages.PIO, + 'game_id': "PoloRoom/Mascot_Polo_sit_big"}, + {'name': "Pyramid Island: Maze forklift Polo", + 'stage': Stages.PIO, + 'game_id': "ForkliftRoom/Mascot_Polo_sit_big_wave"}, + + {'name': "Mataan: Robo Post graffiti", + 'stage': Stages.MA1, + 'game_id': "UnlockGraffiti_grafTex_XL17"}, + {'name': "Mataan: Secret ledge BMX", + 'stage': Stages.MA1, + 'game_id': "BMXBike9"}, + {'name': "Mataan: Highway rooftop BMX", + 'stage': Stages.MA1, + 'game_id': "BMXBike10"}, + {'name': "Mataan: Trash CD", + 'stage': Stages.MA2, + 'game_id': "MusicTrack_JackDaFunk"}, + {'name': "Mataan: Half pipe CD", + 'stage': Stages.MA2, + 'game_id': "MusicTrack_FunkExpress"}, + {'name': "Mataan: Across bull horns graffiti", + 'stage': Stages.MA2, + 'game_id': "UnlockGraffiti_grafTex_L17"}, + {'name': "Mataan: Small rooftop graffiti", + 'stage': Stages.MA2, + 'game_id': "UnlockGraffiti_grafTex_M18"}, + {'name': "Mataan: Trash graffiti", + 'stage': Stages.MA2, + 'game_id': "UnlockGraffiti_grafTex_XL5"}, + {'name': "Mataan: Deep city Robo Post CD", + 'stage': Stages.MA3, + 'game_id': "MusicTrack_LastHoorah"}, + {'name': "Mataan: Deep city tower CD", + 'stage': Stages.MA3, + 'game_id': "MusicTrack_Chapter4Mixtape"}, + {'name': "Mataan: Race challenge reward", + 'stage': Stages.MA3, + 'game_id': "UnlockGraffiti_grafTex_M14"}, + {'name': "Mataan: Wallrunning challenge reward", + 'stage': Stages.MA3, + 'game_id': "UnlockGraffiti_grafTex_L14"}, + {'name': "Mataan: Score challenge reward", + 'stage': Stages.MA3, + 'game_id': "UnlockGraffiti_grafTex_XL13"}, + {'name': "Mataan: Deep city vent jump BMX", + 'stage': Stages.MA3, + 'game_id': "BMXBike4"}, + {'name': "Mataan: Deep city side wires outfit", + 'stage': Stages.MA3, + 'game_id': "DummyOutfit3"}, + {'name': "Mataan: Deep city center island outfit", + 'stage': Stages.MA3, + 'game_id': "DummyOutfit4"}, + {'name': "Mataan: Red light rail graffiti", + 'stage': Stages.MAO, + 'game_id': "UnlockGraffiti_grafTex_XL18"}, + {'name': "Mataan: Red light side alley outfit", + 'stage': Stages.MAO, + 'game_id': "RingDudeOutfit3"}, + {'name': "Mataan: Statue hand outfit", + 'stage': Stages.MA4, + 'game_id': "RingDudeOutfit4"}, + {'name': "Mataan: Crane CD", + 'stage': Stages.MA5, + 'game_id': "MusicTrack_InThePocket"}, + {'name': "Mataan: Elephant tower glass outfit", + 'stage': Stages.MA5, + 'game_id': "LegendFaceOutfit3"}, + {'name': "Mataan: Helipad outfit", + 'stage': Stages.MA5, + 'game_id': "LegendFaceOutfit4"}, + {'name': "Mataan: Vending machine CD", + 'stage': Stages.MA5, + 'game_id': "MusicTrack_Iridium"}, + {'name': "Mataan: Coil joins the crew", + 'stage': Stages.MA5, + 'game_id': "ringdude"}, + {'name': "Mataan: Flesh Prince joins the crew", + 'stage': Stages.MA5, + 'game_id': "prince"}, + {'name': "Mataan: Futurism joins the crew", + 'stage': Stages.MA5, + 'game_id': "futureGirl"}, + {'name': "Mataan: Trash Polo", + 'stage': Stages.MA2, + 'game_id': "PropsMallArea/Mascot_Polo_street"}, + {'name': "Mataan: Shopping Polo", + 'stage': Stages.MA5, + 'game_id': "propsMarket/Mascot_Polo_street"}, + + {'name': "Tagged 5 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf5"}, + {'name': "Tagged 10 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf10"}, + {'name': "Tagged 15 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf15"}, + {'name': "Tagged 20 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf20"}, + {'name': "Tagged 25 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf25"}, + {'name': "Tagged 30 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf30"}, + {'name': "Tagged 35 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf35"}, + {'name': "Tagged 40 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf40"}, + {'name': "Tagged 45 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf45"}, + {'name': "Tagged 50 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf50"}, + {'name': "Tagged 55 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf55"}, + {'name': "Tagged 60 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf60"}, + {'name': "Tagged 65 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf65"}, + {'name': "Tagged 70 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf70"}, + {'name': "Tagged 75 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf75"}, + {'name': "Tagged 80 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf80"}, + {'name': "Tagged 85 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf85"}, + {'name': "Tagged 90 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf90"}, + {'name': "Tagged 95 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf95"}, + {'name': "Tagged 100 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf100"}, + {'name': "Tagged 105 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf105"}, + {'name': "Tagged 110 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf110"}, + {'name': "Tagged 115 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf115"}, + {'name': "Tagged 120 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf120"}, + {'name': "Tagged 125 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf125"}, + {'name': "Tagged 130 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf130"}, + {'name': "Tagged 135 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf135"}, + {'name': "Tagged 140 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf140"}, + {'name': "Tagged 145 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf145"}, + {'name': "Tagged 150 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf150"}, + {'name': "Tagged 155 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf155"}, + {'name': "Tagged 160 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf160"}, + {'name': "Tagged 165 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf165"}, + {'name': "Tagged 170 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf170"}, + {'name': "Tagged 175 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf175"}, + {'name': "Tagged 180 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf180"}, + {'name': "Tagged 185 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf185"}, + {'name': "Tagged 190 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf190"}, + {'name': "Tagged 195 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf195"}, + {'name': "Tagged 200 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf200"}, + {'name': "Tagged 205 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf205"}, + {'name': "Tagged 210 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf210"}, + {'name': "Tagged 215 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf215"}, + {'name': "Tagged 220 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf220"}, + {'name': "Tagged 225 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf225"}, + {'name': "Tagged 230 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf230"}, + {'name': "Tagged 235 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf235"}, + {'name': "Tagged 240 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf240"}, + {'name': "Tagged 245 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf245"}, + {'name': "Tagged 250 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf250"}, + {'name': "Tagged 255 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf255"}, + {'name': "Tagged 260 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf260"}, + {'name': "Tagged 265 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf265"}, + {'name': "Tagged 270 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf270"}, + {'name': "Tagged 275 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf275"}, + {'name': "Tagged 280 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf280"}, + {'name': "Tagged 285 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf285"}, + {'name': "Tagged 290 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf290"}, + {'name': "Tagged 295 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf295"}, + {'name': "Tagged 300 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf300"}, + {'name': "Tagged 305 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf305"}, + {'name': "Tagged 310 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf310"}, + {'name': "Tagged 315 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf315"}, + {'name': "Tagged 320 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf320"}, + {'name': "Tagged 325 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf325"}, + {'name': "Tagged 330 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf330"}, + {'name': "Tagged 335 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf335"}, + {'name': "Tagged 340 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf340"}, + {'name': "Tagged 345 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf345"}, + {'name': "Tagged 350 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf350"}, + {'name': "Tagged 355 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf355"}, + {'name': "Tagged 360 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf360"}, + {'name': "Tagged 365 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf365"}, + {'name': "Tagged 370 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf370"}, + {'name': "Tagged 375 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf375"}, + {'name': "Tagged 380 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf380"}, + {'name': "Tagged 385 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf385"}, + {'name': "Tagged 389 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf379"}, +] + + +event_table: List[EventDict] = [ + {'name': "Versum Hill: Complete Chapter 1", + 'stage': Stages.VH4, + 'item': "Chapter Completed"}, + {'name': "Brink Terminal: Complete Chapter 2", + 'stage': Stages.BT3, + 'item': "Chapter Completed"}, + {'name': "Millennium Mall: Complete Chapter 3", + 'stage': Stages.MM3, + 'item': "Chapter Completed"}, + {'name': "Pyramid Island: Complete Chapter 4", + 'stage': Stages.PI3, + 'item': "Chapter Completed"}, + {'name': "Defeat Faux", + 'stage': Stages.MA5, + 'item': "Victory"}, +] \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/Options.py b/worlds/bomb_rush_cyberfunk/Options.py new file mode 100644 index 000000000000..46df0014e5bb --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Options.py @@ -0,0 +1,162 @@ +from dataclasses import dataclass +from Options import Choice, Toggle, DefaultOnToggle, Range, DeathLink, PerGameCommonOptions +import typing + +if typing.TYPE_CHECKING: + from random import Random +else: + Random = typing.Any + + +class Logic(Choice): + """Choose the logic used by the randomizer.""" + display_name = "Logic" + option_glitchless = 0 + option_glitched = 1 + default = 0 + + +class SkipIntro(DefaultOnToggle): + """Skips escaping the police station. + Graffiti spots tagged during the intro will not unlock items.""" + display_name = "Skip Intro" + + +class SkipDreams(Toggle): + """Skips the dream sequences at the end of each chapter. + This can be changed later in the options menu inside the Archipelago phone app.""" + display_name = "Skip Dreams" + + +class SkipHands(Toggle): + """Skips spraying the lion statue hands after the dream in Chapter 5.""" + display_name = "Skip Statue Hands" + + +class TotalRep(Range): + """Change the total amount of REP in your world. + At least 960 REP is needed to finish the game. + Will be rounded to the nearest number divisible by 8.""" + display_name = "Total REP" + range_start = 1000 + range_end = 2000 + default = 1400 + + def round_to_nearest_step(self): + rem: int = self.value % 8 + if rem >= 5: + self.value = self.value - rem + 8 + else: + self.value = self.value - rem + + def get_rep_item_counts(self, random_source: Random, location_count: int) -> typing.List[int]: + def increment_item(item: int) -> int: + if item >= 32: + item = 48 + else: + item += 8 + return item + + items = [8]*location_count + while sum(items) < self.value: + index = random_source.randint(0, location_count-1) + while items[index] >= 48: + index = random_source.randint(0, location_count-1) + items[index] = increment_item(items[index]) + + while sum(items) > self.value: + index = random_source.randint(0, location_count-1) + while not (items[index] == 16 or items[index] == 24 or items[index] == 32): + index = random_source.randint(0, location_count-1) + items[index] -= 8 + + return [items.count(8), items.count(16), items.count(24), items.count(32), items.count(48)] + + +class EndingREP(Toggle): + """Changes the final boss to require 1000 REP instead of 960 REP to start.""" + display_name = "Extra REP Required" + + +class StartStyle(Choice): + """Choose which movestyle to start with.""" + display_name = "Starting Movestyle" + option_skateboard = 2 + option_inline_skates = 3 + option_bmx = 1 + default = 2 + + +class LimitedGraffiti(Toggle): + """Each graffiti design can only be used a limited number of times before being removed from your inventory. + In some cases, such as completing a dream, using graffiti to defeat enemies, or spraying over your own graffiti, + uses will not be counted. + If enabled, doing graffiti is disabled during crew battles, to prevent softlocking.""" + display_name = "Limited Graffiti" + + +class SGraffiti(Choice): + """Choose if small graffiti should be separate, meaning that you will need to switch characters every time you run + out, or combined, meaning that unlocking new characters will add 5 uses that any character can use. + Has no effect if Limited Graffiti is disabled.""" + display_name = "Small Graffiti Uses" + option_separate = 0 + option_combined = 1 + default = 0 + + +class JunkPhotos(Toggle): + """Skip taking pictures of Polo for items.""" + display_name = "Skip Polo Photos" + + +class DontSavePhotos(Toggle): + """Photos taken with the Camera app will not be saved. + This can be changed later in the options menu inside the Archipelago phone app.""" + display_name = "Don't Save Photos" + + +class ScoreDifficulty(Choice): + """Alters the score required to win score challenges and crew battles. + This can be changed later in the options menu inside the Archipelago phone app.""" + display_name = "Score Difficulty" + option_normal = 0 + option_medium = 1 + option_hard = 2 + option_very_hard = 3 + option_extreme = 4 + default = 0 + + +class DamageMultiplier(Range): + """Multiplies all damage received. + At 3x, most damage will OHKO the player, including falling into pits. + At 6x, all damage will OHKO the player. + This can be changed later in the options menu inside the Archipelago phone app.""" + display_name = "Damage Multiplier" + range_start = 1 + range_end = 6 + default = 1 + + +class BRCDeathLink(DeathLink): + """When you die, everyone dies. The reverse is also true. + This can be changed later in the options menu inside the Archipelago phone app.""" + + +@dataclass +class BombRushCyberfunkOptions(PerGameCommonOptions): + logic: Logic + skip_intro: SkipIntro + skip_dreams: SkipDreams + skip_statue_hands: SkipHands + total_rep: TotalRep + extra_rep_required: EndingREP + starting_movestyle: StartStyle + limited_graffiti: LimitedGraffiti + small_graffiti_uses: SGraffiti + skip_polo_photos: JunkPhotos + dont_save_photos: DontSavePhotos + score_difficulty: ScoreDifficulty + damage_multiplier: DamageMultiplier + death_link: BRCDeathLink \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/Regions.py b/worlds/bomb_rush_cyberfunk/Regions.py new file mode 100644 index 000000000000..652c1e5bb3fb --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Regions.py @@ -0,0 +1,102 @@ +from typing import Dict, List + +class Stages: + Misc = "Misc" + H = "Hideout" + VH1 = "Versum Hill" + VH2 = "Versum Hill - After Roadblock" + VHO = "Versum Hill - Underground Mall" + VH3 = "Versum Hill - Side Street" + VH4 = "Versum Hill - Basketball Court" + MS = "Millennium Square" + BT1 = "Brink Terminal" + BTO1 = "Brink Terminal - Underground" + BTO2 = "Brink Terminal - Dock" + BT2 = "Brink Terminal - Planet Plaza" + BT3 = "Brink Terminal - Tower" + MM1 = "Millennium Mall" + MMO1 = "Millennium Mall - Hanging Lights" + MM2 = "Millennium Mall - Atrium" + MMO2 = "Millennium Mall - Race Track" + MM3 = "Millennium Mall - Theater" + PI1 = "Pyramid Island - Base" + PI2 = "Pyramid Island - After Gate" + PIO = "Pyramid Island - Maze" + PI3 = "Pyramid Island - Upper Areas" + PI4 = "Pyramid Island - Top" + MA1 = "Mataan - Streets" + MA2 = "Mataan - After Smoke Wall" + MA3 = "Mataan - Deep City" + MAO = "Mataan - Red Light District" + MA4 = "Mataan - Lion Statue" + MA5 = "Mataan - Skyscrapers" + + +region_exits: Dict[str, str] = { + Stages.Misc: [Stages.H], + Stages.H: [Stages.Misc, + Stages.VH1, + Stages.MS, + Stages.MA1], + Stages.VH1: [Stages.H, + Stages.VH2], + Stages.VH2: [Stages.H, + Stages.VH1, + Stages.MS, + Stages.VHO, + Stages.VH3, + Stages.VH4], + Stages.VHO: [Stages.VH2], + Stages.VH3: [Stages.VH2], + Stages.VH4: [Stages.VH2, + Stages.VH1], + Stages.MS: [Stages.VH2, + Stages.BT1, + Stages.MM1, + Stages.PI1, + Stages.MA1], + Stages.BT1: [Stages.MS, + Stages.BTO1, + Stages.BTO2, + Stages.BT2], + Stages.BTO1: [Stages.BT1], + Stages.BTO2: [Stages.BT1], + Stages.BT2: [Stages.BT1, + Stages.BT3], + Stages.BT3: [Stages.BT1, + Stages.BT2], + Stages.MM1: [Stages.MS, + Stages.MMO1, + Stages.MM2], + Stages.MMO1: [Stages.MM1], + Stages.MM2: [Stages.MM1, + Stages.MMO2, + Stages.MM3], + Stages.MMO2: [Stages.MM2], + Stages.MM3: [Stages.MM2, + Stages.MM1], + Stages.PI1: [Stages.MS, + Stages.PI2], + Stages.PI2: [Stages.PI1, + Stages.PIO, + Stages.PI3], + Stages.PIO: [Stages.PI2], + Stages.PI3: [Stages.PI1, + Stages.PI2, + Stages.PI4], + Stages.PI4: [Stages.PI1, + Stages.PI2, + Stages.PI3], + Stages.MA1: [Stages.H, + Stages.MS, + Stages.MA2], + Stages.MA2: [Stages.MA1, + Stages.MA3], + Stages.MA3: [Stages.MA2, + Stages.MAO, + Stages.MA4], + Stages.MAO: [Stages.MA3], + Stages.MA4: [Stages.MA3, + Stages.MA5], + Stages.MA5: [Stages.MA1] +} \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/Rules.py b/worlds/bomb_rush_cyberfunk/Rules.py new file mode 100644 index 000000000000..d9bf416a3fd2 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Rules.py @@ -0,0 +1,1043 @@ +from worlds.generic.Rules import set_rule, add_rule +from BaseClasses import CollectionState +from typing import Dict +from .Regions import Stages + + +def graffitiM(state: CollectionState, player: int, limit: bool, spots: int) -> bool: + return state.count_group_exclusive("graffitim", player) * 7 >= spots if limit \ + else state.has_group("graffitim", player) + + +def graffitiL(state: CollectionState, player: int, limit: bool, spots: int) -> bool: + return state.count_group_exclusive("graffitil", player) * 6 >= spots if limit \ + else state.has_group("graffitil", player) + + +def graffitiXL(state: CollectionState, player: int, limit: bool, spots: int) -> bool: + return state.count_group_exclusive("graffitixl", player) * 4 >= spots if limit \ + else state.has_group("graffitixl", player) + + +def skateboard(state: CollectionState, player: int, movestyle: int) -> bool: + return True if movestyle == 2 else state.has_group("skateboard", player) + + +def inline_skates(state: CollectionState, player: int, movestyle: int) -> bool: + return True if movestyle == 3 else state.has_group("skates", player) + + +def bmx(state: CollectionState, player: int, movestyle: int) -> bool: + return True if movestyle == 1 else state.has_group("bmx", player) + + +def camera(state: CollectionState, player: int) -> bool: + return state.has("Camera App", player) + + +def is_girl(state: CollectionState, player: int) -> bool: + return state.has_group("girl", player) + + +def current_chapter(state: CollectionState, player: int, chapter: int) -> bool: + return state.has("Chapter Completed", player, chapter-1) + + +def versum_hill_entrance(state: CollectionState, player: int) -> bool: + return rep(state, player, 20) + + +def versum_hill_ch1_roadblock(state: CollectionState, player: int, limit: bool) -> bool: + return graffitiL(state, player, limit, 10) + + +def versum_hill_challenge1(state: CollectionState, player: int) -> bool: + return rep(state, player, 50) + + +def versum_hill_challenge2(state: CollectionState, player: int) -> bool: + return rep(state, player, 58) + + +def versum_hill_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 65) + + +def versum_hill_all_challenges(state: CollectionState, player: int) -> bool: + return versum_hill_challenge3(state, player) + + +def versum_hill_basketball_court(state: CollectionState, player: int) -> bool: + return rep(state, player, 90) + + +def versum_hill_oldhead(state: CollectionState, player: int) -> bool: + return rep(state, player, 120) + + +def versum_hill_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 90) + and graffitiM(state, player, limit, 98) + ) + else: + return ( + rep(state, player, 90) + and graffitiM(state, player, limit, 27) + ) + + +def versum_hill_rietveld(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + current_chapter(state, player, 2) + and graffitiM(state, player, limit, 114) + ) + else: + return ( + current_chapter(state, player, 2) + and graffitiM(state, player, limit, 67) + ) + + +def versum_hill_rave(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + if current_chapter(state, player, 4): + return ( + graffitiL(state, player, limit, 90) + and graffitiXL(state, player, limit, 51) + ) + elif current_chapter(state, player, 3): + return ( + graffitiL(state, player, limit, 89) + and graffitiXL(state, player, limit, 51) + ) + else: + return ( + graffitiL(state, player, limit, 85) + and graffitiXL(state, player, limit, 48) + ) + else: + return ( + graffitiL(state, player, limit, 26) + and graffitiXL(state, player, limit, 10) + ) + + +def millennium_square_entrance(state: CollectionState, player: int) -> bool: + return current_chapter(state, player, 2) + + +def brink_terminal_entrance(state: CollectionState, player: int) -> bool: + return ( + is_girl(state, player) + and rep(state, player, 180) + and current_chapter(state, player, 2) + ) + + +def brink_terminal_challenge1(state: CollectionState, player: int) -> bool: + return rep(state, player, 188) + + +def brink_terminal_challenge2(state: CollectionState, player: int) -> bool: + return rep(state, player, 200) + + +def brink_terminal_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 220) + + +def brink_terminal_all_challenges(state: CollectionState, player: int) -> bool: + return brink_terminal_challenge3(state, player) + + +def brink_terminal_plaza(state: CollectionState, player: int) -> bool: + return brink_terminal_all_challenges(state, player) + + +def brink_terminal_tower(state: CollectionState, player: int) -> bool: + return rep(state, player, 280) + + +def brink_terminal_oldhead_underground(state: CollectionState, player: int) -> bool: + return rep(state, player, 250) + + +def brink_terminal_oldhead_dock(state: CollectionState, player: int) -> bool: + return rep(state, player, 320) + + +def brink_terminal_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 280) + and graffitiL(state, player, limit, 103) + ) + else: + return ( + rep(state, player, 280) + and graffitiL(state, player, limit, 62) + ) + + +def brink_terminal_mesh(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + graffitiM(state, player, limit, 114) + and graffitiXL(state, player, limit, 45) + ) + else: + return ( + graffitiM(state, player, limit, 67) + and graffitiXL(state, player, limit, 45) + ) + + +def millennium_mall_entrance(state: CollectionState, player: int) -> bool: + return ( + rep(state, player, 380) + and current_chapter(state, player, 3) + ) + + +def millennium_mall_oldhead_ceiling(state: CollectionState, player: int, limit: bool) -> bool: + return ( + rep(state, player, 580) + or millennium_mall_theater(state, player, limit) + ) + + +def millennium_mall_switch(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + graffitiM(state, player, limit, 114) + and current_chapter(state, player, 3) + ) + else: + return ( + graffitiM(state, player, limit, 72) + and current_chapter(state, player, 3) + ) + + +def millennium_mall_big(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return millennium_mall_switch(state, player, limit, glitched) + + +def millennium_mall_oldhead_race(state: CollectionState, player: int) -> bool: + return rep(state, player, 530) + + +def millennium_mall_challenge1(state: CollectionState, player: int) -> bool: + return rep(state, player, 434) + + +def millennium_mall_challenge2(state: CollectionState, player: int) -> bool: + return rep(state, player, 442) + + +def millennium_mall_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 450) + + +def millennium_mall_challenge4(state: CollectionState, player: int) -> bool: + return rep(state, player, 458) + + +def millennium_mall_all_challenges(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return millennium_mall_challenge4(state, player, limit, glitched) + + +def millennium_mall_theater(state: CollectionState, player: int, limit: bool) -> bool: + return ( + rep(state, player, 491) + and graffitiM(state, player, limit, 78) + ) + + +def millennium_mall_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 491) + and graffitiM(state, player, limit, 114) + and graffitiL(state, player, limit, 107) + ) + else: + return ( + rep(state, player, 491) + and graffitiM(state, player, limit, 78) + and graffitiL(state, player, limit, 80) + ) + + +def pyramid_island_entrance(state: CollectionState, player: int) -> bool: + return current_chapter(state, player, 4) + + +def pyramid_island_gate(state: CollectionState, player: int) -> bool: + return rep(state, player, 620) + + +def pyramid_island_oldhead(state: CollectionState, player: int) -> bool: + return rep(state, player, 780) + + +def pyramid_island_challenge1(state: CollectionState, player: int) -> bool: + return ( + rep(state, player, 630) + and current_chapter(state, player, 4) + ) + + +def pyramid_island_race(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + pyramid_island_challenge1(state, player) + and graffitiL(state, player, limit, 108) + ) + else: + return ( + pyramid_island_challenge1(state, player) + and graffitiL(state, player, limit, 93) + ) + + +def pyramid_island_challenge2(state: CollectionState, player: int) -> bool: + return rep(state, player, 650) + + +def pyramid_island_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 660) + + +def pyramid_island_all_challenges(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + graffitiM(state, player, limit, 114) + and rep(state, player, 660) + ) + else: + return ( + graffitiM(state, player, limit, 88) + and rep(state, player, 660) + ) + + +def pyramid_island_upper_half(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return pyramid_island_all_challenges(state, player, limit, glitched) + + +def pyramid_island_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 730) + and graffitiL(state, player, limit, 108) + ) + else: + return ( + rep(state, player, 730) + and graffitiL(state, player, limit, 97) + ) + + +def pyramid_island_top(state: CollectionState, player: int) -> bool: + return current_chapter(state, player, 5) + + +def mataan_entrance(state: CollectionState, player: int) -> bool: + return current_chapter(state, player, 2) + + +def mataan_smoke_wall(state: CollectionState, player: int) -> bool: + return ( + current_chapter(state, player, 5) + and rep(state, player, 850) + ) + + +def mataan_challenge1(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + current_chapter(state, player, 5) + and rep(state, player, 864) + and graffitiL(state, player, limit, 108) + ) + else: + return ( + current_chapter(state, player, 5) + and rep(state, player, 864) + and graffitiL(state, player, limit, 98) + ) + + +def mataan_deep_city(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return mataan_challenge1(state, player, limit, glitched) + + +def mataan_oldhead(state: CollectionState, player: int) -> bool: + return rep(state, player, 935) + + +def mataan_challenge2(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 880) + and graffitiXL(state, player, limit, 59) + ) + else: + return ( + rep(state, player, 880) + and graffitiXL(state, player, limit, 57) + ) + + +def mataan_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 920) + + +def mataan_all_challenges(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return ( + mataan_challenge2(state, player, limit, glitched) + and mataan_challenge3(state, player) + ) + + +def mataan_smoke_wall2(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return ( + mataan_all_challenges(state, player, limit, glitched) + and rep(state, player, 960) + ) + + +def mataan_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + mataan_smoke_wall2(state, player, limit, glitched) + and graffitiM(state, player, limit, 122) + and graffitiXL(state, player, limit, 59) + ) + else: + return ( + mataan_smoke_wall2(state, player, limit, glitched) + and graffitiM(state, player, limit, 117) + and graffitiXL(state, player, limit, 57) + ) + + +def mataan_deepest(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return mataan_crew_battle(state, player, limit, glitched) + + +def mataan_faux(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return ( + mataan_deepest(state, player, limit, glitched) + and graffitiM(state, player, limit, 122) + ) + + +def spots_s_glitchless(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 10 + conditions: Dict[str, int] = { + "versum_hill_entrance": 1, + "versum_hill_ch1_roadblock": 11, + "chapter2": 12, + "versum_hill_oldhead": 1, + "brink_terminal_entrance": 9, + "brink_terminal_plaza": 3, + "brink_terminal_tower": 0, + "chapter3": 6, + "brink_terminal_oldhead_dock": 1, + "millennium_mall_entrance": 3, + "millennium_mall_switch": 4, + "millennium_mall_theater": 3, + "chapter4": 2, + "pyramid_island_gate": 5, + "pyramid_island_upper_half": 8, + "pyramid_island_oldhead": 2, + "mataan_smoke_wall": 3, + "mataan_deep_city": 5, + "mataan_oldhead": 3, + "mataan_deepest": 2 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = 5 + (state.count_group_exclusive("characters", player) * 5) + if total <= sprayable: + return total + else: + return sprayable + else: + return total + + +def spots_s_glitched(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 75 + conditions: Dict[str, int] = { + "brink_terminal_entrance": 13, + "chapter3": 6 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = 5 + (state.count_group_exclusive("characters", player) * 5) + if total <= sprayable: + return total + else: + return sprayable + else: + return total + + +def spots_m_glitchless(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 4 + conditions: Dict[str, int] = { + "versum_hill_entrance": 3, + "versum_hill_ch1_roadblock": 13, + "versum_hill_all_challenges": 3, + "chapter2": 16, + "versum_hill_oldhead": 4, + "brink_terminal_entrance": 13, + "brink_terminal_plaza": 4, + "brink_terminal_tower": 0, + "chapter3": 3, + "brink_terminal_oldhead_dock": 4, + "millennium_mall_entrance": 5, + "millennium_mall_big": 6, + "millennium_mall_theater": 4, + "chapter4": 2, + "millennium_mall_oldhead_ceiling": 1, + "pyramid_island_gate": 3, + "pyramid_island_upper_half": 8, + "chapter5": 2, + "pyramid_island_oldhead": 5, + "mataan_deep_city": 7, + "skateboard": 1, + "mataan_oldhead": 1, + "mataan_smoke_wall2": 1, + "mataan_deepest": 10 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + elif access_name != "skateboard": + break + + if limit: + sprayable: int = state.count_group_exclusive("graffitim", player) * 7 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitim", player): + return total + else: + return 0 + + +def spots_m_glitched(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 99 + conditions: Dict[str, int] = { + "brink_terminal_entrance": 21, + "chapter3": 3 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = state.count_group_exclusive("graffitim", player) * 7 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitim", player): + return total + else: + return 0 + + +def spots_l_glitchless(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 7 + conditions: Dict[str, int] = { + "inline_skates": 1, + "versum_hill_entrance": 2, + "versum_hill_ch1_roadblock": 13, + "versum_hill_all_challenges": 1, + "chapter2": 14, + "versum_hill_oldhead": 2, + "brink_terminal_entrance": 10, + "brink_terminal_plaza": 2, + "brink_terminal_oldhead_underground": 1, + "brink_terminal_tower": 1, + "chapter3": 4, + "brink_terminal_oldhead_dock": 4, + "millennium_mall_entrance": 3, + "millennium_mall_big": 8, + "millennium_mall_theater": 4, + "chapter4": 5, + "millennium_mall_oldhead_ceiling": 3, + "pyramid_island_gate": 4, + "pyramid_island_upper_half": 5, + "pyramid_island_crew_battle": 1, + "chapter5": 1, + "pyramid_island_oldhead": 2, + "mataan_smoke_wall": 1, + "mataan_deep_city": 2, + "skateboard": 1, + "mataan_oldhead": 2, + "mataan_deepest": 7 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + elif not (access_name == "inline_skates" or access_name == "skateboard"): + break + + if limit: + sprayable: int = state.count_group_exclusive("graffitil", player) * 6 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitil", player): + return total + else: + return 0 + + +def spots_l_glitched(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 88 + conditions: Dict[str, int] = { + "brink_terminal_entrance": 18, + "chapter3": 4, + "chapter4": 1 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = state.count_group_exclusive("graffitil", player) * 6 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitil", player): + return total + else: + return 0 + + +def spots_xl_glitchless(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 3 + conditions: Dict[str, int] = { + "versum_hill_ch1_roadblock": 6, + "versum_hill_basketball_court": 1, + "chapter2": 9, + "brink_terminal_entrance": 3, + "brink_terminal_plaza": 1, + "brink_terminal_oldhead_underground": 1, + "brink_terminal_tower": 1, + "chapter3": 3, + "brink_terminal_oldhead_dock": 2, + "millennium_mall_entrance": 2, + "millennium_mall_big": 5, + "millennium_mall_theater": 5, + "chapter4": 3, + "millennium_mall_oldhead_ceiling": 1, + "pyramid_island_upper_half": 5, + "pyramid_island_oldhead": 3, + "mataan_smoke_wall": 2, + "mataan_deep_city": 2, + "mataan_oldhead": 2, + "mataan_deepest": 2 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = state.count_group_exclusive("graffitixl", player) * 4 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitixl", player): + return total + else: + return 0 + + +def spots_xl_glitched(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 51 + conditions: Dict[str, int] = { + "brink_terminal_entrance": 7, + "chapter3": 3, + "chapter4": 1 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = state.count_group_exclusive("graffitixl", player) * 4 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitixl", player): + return total + else: + return 0 + + +def build_access_cache(state: CollectionState, player: int, movestyle: int, limit: bool, glitched: bool) -> Dict[str, bool]: + funcs: Dict[str, tuple] = { + "versum_hill_entrance": (state, player), + "versum_hill_ch1_roadblock": (state, player, limit), + "versum_hill_oldhead": (state, player), + "versum_hill_all_challenges": (state, player), + "versum_hill_basketball_court": (state, player), + "brink_terminal_entrance": (state, player), + "brink_terminal_oldhead_underground": (state, player), + "brink_terminal_oldhead_dock": (state, player), + "brink_terminal_plaza": (state, player), + "brink_terminal_tower": (state, player), + "millennium_mall_entrance": (state, player), + "millennium_mall_switch": (state, player, limit, glitched), + "millennium_mall_oldhead_ceiling": (state, player, limit), + "millennium_mall_big": (state, player, limit, glitched), + "millennium_mall_theater": (state, player, limit), + "pyramid_island_gate": (state, player), + "pyramid_island_oldhead": (state, player), + "pyramid_island_upper_half": (state, player, limit, glitched), + "pyramid_island_crew_battle": (state, player, limit, glitched), + "mataan_smoke_wall": (state, player), + "mataan_deep_city": (state, player, limit, glitched), + "mataan_oldhead": (state, player), + "mataan_smoke_wall2": (state, player, limit, glitched), + "mataan_deepest": (state, player, limit, glitched) + } + + access_cache: Dict[str, bool] = { + "skateboard": skateboard(state, player, movestyle), + "inline_skates": inline_skates(state, player, movestyle), + "chapter2": current_chapter(state, player, 2), + "chapter3": current_chapter(state, player, 3), + "chapter4": current_chapter(state, player, 4), + "chapter5": current_chapter(state, player, 5) + } + + stop: bool = False + for fname, fvars in funcs.items(): + if stop: + access_cache[fname] = False + continue + func = globals()[fname] + access: bool = func(*fvars) + access_cache[fname] = access + if not access and not "oldhead" in fname: + stop = True + + return access_cache + + +def graffiti_spots(state: CollectionState, player: int, movestyle: int, limit: bool, glitched: bool, spots: int) -> bool: + access_cache = build_access_cache(state, player, movestyle, limit, glitched) + + total: int = 0 + + if glitched: + total = spots_s_glitched(state, player, limit, access_cache) \ + + spots_m_glitched(state, player, limit, access_cache) \ + + spots_l_glitched(state, player, limit, access_cache) \ + + spots_xl_glitched(state, player, limit, access_cache) + else: + total = spots_s_glitchless(state, player, limit, access_cache) \ + + spots_m_glitchless(state, player, limit, access_cache) \ + + spots_l_glitchless(state, player, limit, access_cache) \ + + spots_xl_glitchless(state, player, limit, access_cache) + + return total >= spots + + +def rep(state: CollectionState, player: int, required: int) -> bool: + return state.has("rep", player, required) + + +def rules(brcworld): + multiworld = brcworld.multiworld + player = brcworld.player + + movestyle = brcworld.options.starting_movestyle + limit = brcworld.options.limited_graffiti + glitched = brcworld.options.logic + extra = brcworld.options.extra_rep_required + photos = not brcworld.options.skip_polo_photos + + # entrances + for e in multiworld.get_region(Stages.BT1, player).entrances: + set_rule(e, lambda state: brink_terminal_entrance(state, player)) + + if not glitched: + # versum hill + for e in multiworld.get_region(Stages.VH1, player).entrances: + set_rule(e, lambda state: versum_hill_entrance(state, player)) + for e in multiworld.get_region(Stages.VH2, player).entrances: + set_rule(e, lambda state: versum_hill_ch1_roadblock(state, player, limit)) + for e in multiworld.get_region(Stages.VHO, player).entrances: + set_rule(e, lambda state: versum_hill_oldhead(state, player)) + for e in multiworld.get_region(Stages.VH3, player).entrances: + set_rule(e, lambda state: versum_hill_all_challenges(state, player)) + for e in multiworld.get_region(Stages.VH4, player).entrances: + set_rule(e, lambda state: versum_hill_basketball_court(state, player)) + + # millennium square + for e in multiworld.get_region(Stages.MS, player).entrances: + set_rule(e, lambda state: millennium_square_entrance(state, player)) + + # brink terminal + for e in multiworld.get_region(Stages.BTO1, player).entrances: + set_rule(e, lambda state: brink_terminal_oldhead_underground(state, player)) + for e in multiworld.get_region(Stages.BTO2, player).entrances: + set_rule(e, lambda state: brink_terminal_oldhead_dock(state, player)) + for e in multiworld.get_region(Stages.BT2, player).entrances: + set_rule(e, lambda state: brink_terminal_plaza(state, player)) + for e in multiworld.get_region(Stages.BT3, player).entrances: + set_rule(e, lambda state: brink_terminal_tower(state, player)) + + # millennium mall + for e in multiworld.get_region(Stages.MM1, player).entrances: + set_rule(e, lambda state: millennium_mall_entrance(state, player)) + for e in multiworld.get_region(Stages.MMO1, player).entrances: + set_rule(e, lambda state: millennium_mall_oldhead_ceiling(state, player, limit)) + for e in multiworld.get_region(Stages.MM2, player).entrances: + set_rule(e, lambda state: millennium_mall_big(state, player, limit, glitched)) + for e in multiworld.get_region(Stages.MMO2, player).entrances: + set_rule(e, lambda state: millennium_mall_oldhead_race(state, player)) + for e in multiworld.get_region(Stages.MM3, player).entrances: + set_rule(e, lambda state: millennium_mall_theater(state, player, limit)) + + # pyramid island + for e in multiworld.get_region(Stages.PI1, player).entrances: + set_rule(e, lambda state: pyramid_island_entrance(state, player)) + for e in multiworld.get_region(Stages.PI2, player).entrances: + set_rule(e, lambda state: pyramid_island_gate(state, player)) + for e in multiworld.get_region(Stages.PIO, player).entrances: + set_rule(e, lambda state: pyramid_island_oldhead(state, player)) + for e in multiworld.get_region(Stages.PI3, player).entrances: + set_rule(e, lambda state: pyramid_island_upper_half(state, player, limit, glitched)) + for e in multiworld.get_region(Stages.PI4, player).entrances: + set_rule(e, lambda state: pyramid_island_top(state, player)) + + # mataan + for e in multiworld.get_region(Stages.MA1, player).entrances: + set_rule(e, lambda state: mataan_entrance(state, player)) + for e in multiworld.get_region(Stages.MA2, player).entrances: + set_rule(e, lambda state: mataan_smoke_wall(state, player)) + for e in multiworld.get_region(Stages.MA3, player).entrances: + set_rule(e, lambda state: mataan_deep_city(state, player, limit, glitched)) + for e in multiworld.get_region(Stages.MAO, player).entrances: + set_rule(e, lambda state: mataan_oldhead(state, player)) + for e in multiworld.get_region(Stages.MA4, player).entrances: + set_rule(e, lambda state: mataan_smoke_wall2(state, player, limit, glitched)) + for e in multiworld.get_region(Stages.MA5, player).entrances: + set_rule(e, lambda state: mataan_deepest(state, player, limit, glitched)) + + + # locations + # hideout + set_rule(multiworld.get_location("Hideout: BMX garage skateboard", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Hideout: Unlock phone app", player), + lambda state: current_chapter(state, player, 2)) + set_rule(multiworld.get_location("Hideout: Vinyl joins the crew", player), + lambda state: current_chapter(state, player, 4)) + set_rule(multiworld.get_location("Hideout: Solace joins the crew", player), + lambda state: current_chapter(state, player, 5)) + + # versum hill + set_rule(multiworld.get_location("Versum Hill: Wallrunning challenge reward", player), + lambda state: versum_hill_challenge1(state, player)) + set_rule(multiworld.get_location("Versum Hill: Manual challenge reward", player), + lambda state: versum_hill_challenge2(state, player)) + set_rule(multiworld.get_location("Versum Hill: Corner challenge reward", player), + lambda state: versum_hill_challenge3(state, player)) + set_rule(multiworld.get_location("Versum Hill: BMX gate outfit", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Versum Hill: Glass floor skates", player), + lambda state: inline_skates(state, player, movestyle)) + set_rule(multiworld.get_location("Versum Hill: Basketball court shortcut CD", player), + lambda state: current_chapter(state, player, 2)) + set_rule(multiworld.get_location("Versum Hill: Rave joins the crew", player), + lambda state: versum_hill_rave(state, player, limit, glitched)) + set_rule(multiworld.get_location("Versum Hill: Frank joins the crew", player), + lambda state: current_chapter(state, player, 2)) + set_rule(multiworld.get_location("Versum Hill: Rietveld joins the crew", player), + lambda state: versum_hill_rietveld(state, player, limit, glitched)) + if photos: + set_rule(multiworld.get_location("Versum Hill: Big Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Versum Hill: Trash Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Versum Hill: Fruit stand Polo", player), + lambda state: camera(state, player)) + + # millennium square + if photos: + set_rule(multiworld.get_location("Millennium Square: Half pipe Polo", player), + lambda state: camera(state, player)) + + # brink terminal + set_rule(multiworld.get_location("Brink Terminal: Upside grind challenge reward", player), + lambda state: brink_terminal_challenge1(state, player)) + set_rule(multiworld.get_location("Brink Terminal: Manual challenge reward", player), + lambda state: brink_terminal_challenge2(state, player)) + set_rule(multiworld.get_location("Brink Terminal: Score challenge reward", player), + lambda state: brink_terminal_challenge3(state, player)) + set_rule(multiworld.get_location("Brink Terminal: BMX gate graffiti", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Brink Terminal: Mesh's skateboard", player), + lambda state: brink_terminal_mesh(state, player, limit, glitched)) + set_rule(multiworld.get_location("Brink Terminal: Rooftop glass CD", player), + lambda state: inline_skates(state, player, movestyle)) + set_rule(multiworld.get_location("Brink Terminal: Mesh joins the crew", player), + lambda state: brink_terminal_mesh(state, player, limit, glitched)) + set_rule(multiworld.get_location("Brink Terminal: Eclipse joins the crew", player), + lambda state: current_chapter(state, player, 3)) + if photos: + set_rule(multiworld.get_location("Brink Terminal: Behind glass Polo", player), + lambda state: camera(state, player)) + + # millennium mall + set_rule(multiworld.get_location("Millennium Mall: Glass cylinder CD", player), + lambda state: inline_skates(state, player, movestyle)) + set_rule(multiworld.get_location("Millennium Mall: Trick challenge reward", player), + lambda state: millennium_mall_challenge1(state, player)) + set_rule(multiworld.get_location("Millennium Mall: Slide challenge reward", player), + lambda state: millennium_mall_challenge2(state, player)) + set_rule(multiworld.get_location("Millennium Mall: Fish challenge reward", player), + lambda state: millennium_mall_challenge3(state, player)) + set_rule(multiworld.get_location("Millennium Mall: Score challenge reward", player), + lambda state: millennium_mall_challenge4(state, player)) + set_rule(multiworld.get_location("Millennium Mall: Atrium BMX gate BMX", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Millennium Mall: Shine joins the crew", player), + lambda state: current_chapter(state, player, 4)) + set_rule(multiworld.get_location("Millennium Mall: DOT.EXE joins the crew", player), + lambda state: current_chapter(state, player, 4)) + + # pyramid island + set_rule(multiworld.get_location("Pyramid Island: BMX gate BMX", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Pyramid Island: Score challenge reward", player), + lambda state: pyramid_island_challenge1(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Score challenge 2 reward", player), + lambda state: pyramid_island_challenge2(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Quarter pipe challenge reward", player), + lambda state: pyramid_island_challenge3(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Shortcut glass CD", player), + lambda state: inline_skates(state, player, movestyle)) + set_rule(multiworld.get_location("Pyramid Island: Maze outfit", player), + lambda state: skateboard(state, player, movestyle)) + if not glitched: + add_rule(multiworld.get_location("Pyramid Island: Rise joins the crew", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Devil Theory joins the crew", player), + lambda state: current_chapter(state, player, 5)) + if photos: + set_rule(multiworld.get_location("Pyramid Island: Polo pile 1", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Polo pile 2", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Polo pile 3", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Polo pile 4", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze glass Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze classroom Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze vent Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Big maze Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze desk Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze forklift Polo", player), + lambda state: camera(state, player)) + + # mataan + set_rule(multiworld.get_location("Mataan: Race challenge reward", player), + lambda state: mataan_challenge1(state, player, limit, glitched)) + set_rule(multiworld.get_location("Mataan: Wallrunning challenge reward", player), + lambda state: mataan_challenge2(state, player, limit, glitched)) + set_rule(multiworld.get_location("Mataan: Score challenge reward", player), + lambda state: mataan_challenge3(state, player)) + if photos: + set_rule(multiworld.get_location("Mataan: Trash Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Mataan: Shopping Polo", player), + lambda state: camera(state, player)) + + # events + set_rule(multiworld.get_location("Versum Hill: Complete Chapter 1", player), + lambda state: versum_hill_crew_battle(state, player, limit, glitched)) + set_rule(multiworld.get_location("Brink Terminal: Complete Chapter 2", player), + lambda state: brink_terminal_crew_battle(state, player, limit, glitched)) + set_rule(multiworld.get_location("Millennium Mall: Complete Chapter 3", player), + lambda state: millennium_mall_crew_battle(state, player, limit, glitched)) + set_rule(multiworld.get_location("Pyramid Island: Complete Chapter 4", player), + lambda state: pyramid_island_crew_battle(state, player, limit, glitched)) + set_rule(multiworld.get_location("Defeat Faux", player), + lambda state: mataan_faux(state, player, limit, glitched)) + + if extra: + add_rule(multiworld.get_location("Defeat Faux", player), + lambda state: rep(state, player, 1000)) + + + # graffiti spots + spots: int = 0 + while spots < 385: + spots += 5 + set_rule(multiworld.get_location(f"Tagged {spots} Graffiti Spots", player), + lambda state, spots=spots: graffiti_spots(state, player, movestyle, limit, glitched, spots)) + + set_rule(multiworld.get_location("Tagged 389 Graffiti Spots", player), + lambda state: graffiti_spots(state, player, movestyle, limit, glitched, 389)) + + \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/__init__.py b/worlds/bomb_rush_cyberfunk/__init__.py new file mode 100644 index 000000000000..a6572ea28ef3 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/__init__.py @@ -0,0 +1,214 @@ +from typing import Any, Dict +from BaseClasses import MultiWorld, Region, Location, Item, Tutorial, ItemClassification, CollectionState +from worlds.AutoWorld import World, WebWorld +from .Items import base_id, item_table, group_table, BRCType +from .Locations import location_table, event_table +from .Regions import region_exits +from .Rules import rules +from .Options import BombRushCyberfunkOptions, StartStyle + + +class BombRushCyberfunkWeb(WebWorld): + theme = "ocean" + tutorials = [Tutorial( + "Multiworld Setup Guide", + "A guide to setting up Bomb Rush Cyberfunk randomizer and connecting to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["TRPG"] + )] + + +class BombRushCyberfunkWorld(World): + """Bomb Rush Cyberfunk is 1 second per second of advanced funkstyle. Battle rival crews and dispatch militarized + police to conquer the five boroughs of New Amsterdam. Become All City.""" + + game = "Bomb Rush Cyberfunk" + web = BombRushCyberfunkWeb() + + item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)} + item_name_to_type = {item["name"]: item["type"] for item in item_table} + location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)} + + item_name_groups = group_table + options_dataclass = BombRushCyberfunkOptions + options: BombRushCyberfunkOptions + + + def __init__(self, multiworld: MultiWorld, player: int): + super(BombRushCyberfunkWorld, self).__init__(multiworld, player) + self.item_classification: Dict[BRCType, ItemClassification] = { + BRCType.Music: ItemClassification.filler, + BRCType.GraffitiM: ItemClassification.progression, + BRCType.GraffitiL: ItemClassification.progression, + BRCType.GraffitiXL: ItemClassification.progression, + BRCType.Outfit: ItemClassification.filler, + BRCType.Character: ItemClassification.progression, + BRCType.REP: ItemClassification.progression_skip_balancing, + BRCType.Camera: ItemClassification.progression + } + + + def collect(self, state: "CollectionState", item: "Item") -> bool: + change = super().collect(state, item) + if change and "REP" in item.name: + rep: int = int(item.name[0:len(item.name)-4]) + state.prog_items[item.player]["rep"] += rep + return change + + + def remove(self, state: "CollectionState", item: "Item") -> bool: + change = super().remove(state, item) + if change and "REP" in item.name: + rep: int = int(item.name[0:len(item.name)-4]) + state.prog_items[item.player]["rep"] -= rep + return change + + + def set_rules(self): + rules(self) + + + def get_item_classification(self, item_type: BRCType) -> ItemClassification: + classification = ItemClassification.filler + if item_type in self.item_classification.keys(): + classification = self.item_classification[item_type] + + return classification + + + def create_item(self, name: str) -> "BombRushCyberfunkItem": + item_id: int = self.item_name_to_id[name] + item_type: BRCType = self.item_name_to_type[name] + classification = self.get_item_classification(item_type) + + return BombRushCyberfunkItem(name, classification, item_id, self.player) + + + def create_event(self, event: str) -> "BombRushCyberfunkItem": + return BombRushCyberfunkItem(event, ItemClassification.progression_skip_balancing, None, self.player) + + + def get_filler_item_name(self) -> str: + item = self.random.choice(item_table) + + while self.get_item_classification(item["type"]) == ItemClassification.progression: + item = self.random.choice(item_table) + + return item["name"] + + + def generate_early(self): + if self.options.starting_movestyle == StartStyle.option_skateboard: + self.item_classification[BRCType.Skateboard] = ItemClassification.filler + else: + self.item_classification[BRCType.Skateboard] = ItemClassification.progression + + if self.options.starting_movestyle == StartStyle.option_inline_skates: + self.item_classification[BRCType.InlineSkates] = ItemClassification.filler + else: + self.item_classification[BRCType.InlineSkates] = ItemClassification.progression + + if self.options.starting_movestyle == StartStyle.option_bmx: + self.item_classification[BRCType.BMX] = ItemClassification.filler + else: + self.item_classification[BRCType.BMX] = ItemClassification.progression + + + def create_items(self): + rep_locations: int = 87 + if self.options.skip_polo_photos: + rep_locations -= 18 + + self.options.total_rep.round_to_nearest_step() + rep_counts = self.options.total_rep.get_rep_item_counts(self.random, rep_locations) + #print(sum([8*rep_counts[0], 16*rep_counts[1], 24*rep_counts[2], 32*rep_counts[3], 48*rep_counts[4]]), \ + # rep_counts) + + pool = [] + + for item in item_table: + if "REP" in item["name"]: + count: int = 0 + + if item["name"] == "8 REP": + count = rep_counts[0] + elif item["name"] == "16 REP": + count = rep_counts[1] + elif item["name"] == "24 REP": + count = rep_counts[2] + elif item["name"] == "32 REP": + count = rep_counts[3] + elif item["name"] == "48 REP": + count = rep_counts[4] + + if count > 0: + for _ in range(count): + pool.append(self.create_item(item["name"])) + else: + pool.append(self.create_item(item["name"])) + + self.multiworld.itempool += pool + + + def create_regions(self): + multiworld = self.multiworld + player = self.player + + menu = Region("Menu", player, multiworld) + multiworld.regions.append(menu) + + for n in region_exits: + multiworld.regions += [Region(n, player, multiworld)] + + menu.add_exits({"Hideout": "New Game"}) + + for n in region_exits: + self.get_region(n).add_exits(region_exits[n]) + + for index, loc in enumerate(location_table): + if self.options.skip_polo_photos and "Polo" in loc["name"]: + continue + stage: Region = self.get_region(loc["stage"]) + stage.add_locations({loc["name"]: base_id + index}) + + for e in event_table: + stage: Region = self.get_region(e["stage"]) + event = BombRushCyberfunkLocation(player, e["name"], None, stage) + event.show_in_spoiler = False + event.place_locked_item(self.create_event(e["item"])) + stage.locations += [event] + + multiworld.completion_condition[player] = lambda state: state.has("Victory", player) + + def fill_slot_data(self) -> Dict[str, Any]: + options = self.options + + slot_data: Dict[str, Any] = { + "locations": {loc["game_id"]: (base_id + index) for index, loc in enumerate(location_table)}, + "logic": options.logic.value, + "skip_intro": bool(options.skip_intro.value), + "skip_dreams": bool(options.skip_dreams.value), + "skip_statue_hands": bool(options.skip_statue_hands.value), + "total_rep": options.total_rep.value, + "extra_rep_required": bool(options.extra_rep_required.value), + "starting_movestyle": options.starting_movestyle.value, + "limited_graffiti": bool(options.limited_graffiti.value), + "small_graffiti_uses": options.small_graffiti_uses.value, + "skip_polo_photos": bool(options.skip_polo_photos.value), + "dont_save_photos": bool(options.dont_save_photos.value), + "score_difficulty": int(options.score_difficulty.value), + "damage_multiplier": options.damage_multiplier.value, + "death_link": bool(options.death_link.value) + } + + return slot_data + + +class BombRushCyberfunkItem(Item): + game: str = "Bomb Rush Cyberfunk" + + +class BombRushCyberfunkLocation(Location): + game: str = "Bomb Rush Cyberfunk" \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md b/worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md new file mode 100644 index 000000000000..b77e183ff0fa --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md @@ -0,0 +1,29 @@ +# Bomb Rush Cyberfunk + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export +a config file. + +## What does randomization do in this game? + +The goal of Bomb Rush Cyberfunk randomizer is to defeat all rival crews in each borough of New Amsterdam. REP is no +longer earned from doing graffiti, and is instead earned by finding it randomly in the multiworld. + +Items can be found by picking up any type of collectible, unlocking characters, taking pictures of Polo, and for every +5 graffiti spots tagged. The types of items that can be found are Music, Graffiti (M), Graffiti (L), Graffiti (XL), +Skateboards, Inline Skates, BMX, Outifts, Characters, REP, and the Camera. + +Several changes have been made to the game for a better experience as a randomizer: + +- The prelude in the police station can be skipped. +- The map for each stage is always unlocked. +- The taxi is always unlocked, but you will still need to visit each stage's taxi stop before you can use them. +- No M, L, or XL graffiti is unlocked at the beginning. +- Optionally, graffiti can be depleted after a certain number of uses. +- All characters except Red are locked. +- One single REP count is used throughout the game, instead of having separate totals for each stage. REP requirements +are the same as the original game, but added together in order. At least 960 REP is needed to finish the game. + +The mod also adds two new apps to the phone, an "Encounter" app which lets you retry certain events early, and the +"Archipelago" app which lets you view chat messages and change some options while playing. \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/docs/setup_en.md b/worlds/bomb_rush_cyberfunk/docs/setup_en.md new file mode 100644 index 000000000000..0db0b292be7f --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/docs/setup_en.md @@ -0,0 +1,41 @@ +# Bomb Rush Cyberfunk Multiworld Setup Guide + +## Quick Links + +- Bomb Rush Cyberfunk: [Steam](https://store.steampowered.com/app/1353230/Bomb_Rush_Cyberfunk/) +- Archipelago Mod: [Thunderstore](https://thunderstore.io/c/bomb-rush-cyberfunk/p/TRPG/Archipelago/), +[GitHub](https://github.com/TRPG0/BRC-Archipelago/releases) + +## Setup + +To install the Archipelago mod, you can use a mod manager like +[r2modman](https://thunderstore.io/c/bomb-rush-cyberfunk/p/ebkr/r2modman/), or install manually by following these steps: + +1. Download and install [BepInEx 5.4.22 x64](https://github.com/BepInEx/BepInEx/releases/tag/v5.4.22) in your Bomb Rush +Cyberfunk root folder. *Do not use any pre-release versions of BepInEx 6.* + +2. Start Bomb Rush Cyberfunk once so that BepInEx can create its required configuration files. + +3. Download the zip archive from the [releases](https://github.com/TRPG0/BRC-Archipelago/releases) page, and extract its +contents into `BepInEx\plugins`. + +After installing Archipelago, there are some additional mods that can also be installed for a better experience: + +- [MoreMap](https://thunderstore.io/c/bomb-rush-cyberfunk/p/TRPG/MoreMap/) by TRPG + - Adds pins to the map for every type of collectible. +- [FasterLoadTimes](https://thunderstore.io/c/bomb-rush-cyberfunk/p/cspotcode/FasterLoadTimes/) by cspotcode + - Load stages faster by skipping assets that are already loaded. +- [CutsceneSkip](https://thunderstore.io/c/bomb-rush-cyberfunk/p/Jay/CutsceneSkip/) by Jay + - Makes every cutscene skippable. +- [GimmeMyBoost](https://thunderstore.io/c/bomb-rush-cyberfunk/p/Yuri/GimmeMyBoost/) by Yuri + - Retains boost when loading into a new stage. +- [DisableAnnoyingCutscenes](https://thunderstore.io/c/bomb-rush-cyberfunk/p/viliger/DisableAnnoyingCutscenes/) by viliger + - Disables the police cutscenes when increasing your heat level. +- [FastTravel](https://thunderstore.io/c/bomb-rush-cyberfunk/p/tari/FastTravel/) by tari + - Adds an app to the phone to call for a taxi from anywhere. + +## Connecting + +To connect to an Archipelago server, click one of the Archipelago buttons next to the save files. If the save file is +blank or already has randomizer save data, it will open a menu where you can enter the server address and port, your +name, and a password if necessary. Then click the check mark to connect to the server. \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/test/__init__.py b/worlds/bomb_rush_cyberfunk/test/__init__.py new file mode 100644 index 000000000000..9cd6c3a504bf --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/test/__init__.py @@ -0,0 +1,5 @@ +from test.bases import WorldTestBase + + +class BombRushCyberfunkTestBase(WorldTestBase): + game = "Bomb Rush Cyberfunk" \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py b/worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py new file mode 100644 index 000000000000..af5402323038 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py @@ -0,0 +1,284 @@ +from . import BombRushCyberfunkTestBase +from ..Rules import build_access_cache, spots_s_glitchless, spots_s_glitched, spots_m_glitchless, spots_m_glitched, \ + spots_l_glitchless, spots_l_glitched, spots_xl_glitched, spots_xl_glitchless + + +class TestSpotsGlitchless(BombRushCyberfunkTestBase): + @property + def run_default_tests(self) -> bool: + return False + + def test_spots_glitchless(self) -> None: + player = self.player + + self.collect_by_name([ + "Graffiti (M - OVERWHELMME)", + "Graffiti (L - WHOLE SIXER)", + "Graffiti (XL - Gold Rush)" + ]) + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 1 - hideout + self.assertEqual(10, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(4, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(7, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(3, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.collect_by_name("Inline Skates (Glaciers)") + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + self.assertEqual(8, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 20 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 1 - VH1-2 + self.assertEqual(22, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(20, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(23, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(9, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 65 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 1 - VH3 + self.assertEqual(23, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(24, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 90 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 1 - VH4 + self.assertEqual(10, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["Chapter Completed"] = 1 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - MS + MA1 + self.assertEqual(34, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(39, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(38, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(19, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 120 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - VHO + self.assertEqual(35, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(43, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(40, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.collect_by_name("Bel") + self.multiworld.state.prog_items[player]["rep"] = 180 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BT1 + self.assertEqual(44, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(56, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(50, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(22, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 220 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BT2 + self.assertEqual(47, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(60, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(52, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(23, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 250 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BTO1 + self.assertEqual(53, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(24, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 280 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BT3 / chapter 3 - MS + self.assertEqual(58, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(28, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 320 + self.multiworld.state.prog_items[player]["Chapter Completed"] = 2 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BTO2 / chapter 3 - MS + self.assertEqual(54, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(67, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(62, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(30, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 380 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 3 - MM1-2 + self.assertEqual(61, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(78, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(73, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(37, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 491 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 3 - MM3 + self.assertEqual(64, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(82, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(77, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(42, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["Chapter Completed"] = 3 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 4 - MS / BT / MMO1 / PI1 + self.assertEqual(66, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(85, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(85, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(46, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 620 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 4 - PI2 + self.assertEqual(71, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(88, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(89, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 660 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 4 - PI3 + self.assertEqual(79, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(96, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(94, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(51, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 730 + self.multiworld.state.prog_items[player]["Chapter Completed"] = 4 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - PI4 + self.assertEqual(98, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(96, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 780 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - PIO + self.assertEqual(81, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(103, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(98, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(54, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 850 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - MA2 + self.assertEqual(84, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(99, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(56, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 864 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - MA3 + self.assertEqual(89, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(111, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(102, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(58, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 935 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - MAO + self.assertEqual(92, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(112, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(104, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(60, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 960 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - MA4-5 + self.assertEqual(94, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(123, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(111, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(62, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + +class TestSpotsGlitched(BombRushCyberfunkTestBase): + options = { + "logic": "glitched" + } + + @property + def run_default_tests(self) -> bool: + return False + + def test_spots_glitched(self) -> None: + player = self.player + + self.collect_by_name([ + "Graffiti (M - OVERWHELMME)", + "Graffiti (L - WHOLE SIXER)", + "Graffiti (XL - Gold Rush)" + ]) + access_cache = build_access_cache(self.multiworld.state, player, 2, False, True) + + self.assertEqual(75, spots_s_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(99, spots_m_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(88, spots_l_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(51, spots_xl_glitched(self.multiworld.state, player, False, access_cache)) + + + self.collect_by_name("Bel") + self.multiworld.state.prog_items[player]["Chapter Completed"] = 1 + self.multiworld.state.prog_items[player]["rep"] = 180 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, True) + + # brink terminal + self.assertEqual(88, spots_s_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(120, spots_m_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(106, spots_l_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(58, spots_xl_glitched(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["Chapter Completed"] = 2 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, True) + + # chapter 3 + self.assertEqual(94, spots_s_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(123, spots_m_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(110, spots_l_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(61, spots_xl_glitched(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["Chapter Completed"] = 3 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, True) + + # chapter 4 + self.assertEqual(111, spots_l_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(62, spots_xl_glitched(self.multiworld.state, player, False, access_cache)) \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/test/test_options.py b/worlds/bomb_rush_cyberfunk/test/test_options.py new file mode 100644 index 000000000000..7640700dc06f --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/test/test_options.py @@ -0,0 +1,29 @@ +from . import BombRushCyberfunkTestBase + + +class TestRegularGraffitiGlitchless(BombRushCyberfunkTestBase): + options = { + "logic": "glitchless", + "limited_graffiti": False + } + + +class TestLimitedGraffitiGlitchless(BombRushCyberfunkTestBase): + options = { + "logic": "glitchless", + "limited_graffiti": True + } + + +class TestRegularGraffitiGlitched(BombRushCyberfunkTestBase): + options = { + "logic": "glitched", + "limited_graffiti": False + } + + +class TestLimitedGraffitiGlitched(BombRushCyberfunkTestBase): + options = { + "logic": "glitched", + "limited_graffiti": True + } \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/test/test_rep_items.py b/worlds/bomb_rush_cyberfunk/test/test_rep_items.py new file mode 100644 index 000000000000..61272a3f0977 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/test/test_rep_items.py @@ -0,0 +1,45 @@ +from . import BombRushCyberfunkTestBase +from typing import List + + +rep_item_names: List[str] = [ + "8 REP", + "16 REP", + "24 REP", + "32 REP", + "48 REP" +] + + +class TestCollectAndRemoveREP(BombRushCyberfunkTestBase): + @property + def run_default_tests(self) -> bool: + return False + + def test_default_rep_total(self) -> None: + self.collect_by_name(rep_item_names) + self.assertEqual(1400, self.multiworld.state.prog_items[self.player]["rep"]) + + new_total = 1400 + + if self.count("8 REP") > 0: + new_total -= 8 + self.remove(self.get_item_by_name("8 REP")) + + if self.count("16 REP") > 0: + new_total -= 16 + self.remove(self.get_item_by_name("16 REP")) + + if self.count("24 REP") > 0: + new_total -= 24 + self.remove(self.get_item_by_name("24 REP")) + + if self.count("32 REP") > 0: + new_total -= 32 + self.remove(self.get_item_by_name("32 REP")) + + if self.count("48 REP") > 0: + new_total -= 48 + self.remove(self.get_item_by_name("48 REP")) + + self.assertEqual(new_total, self.multiworld.state.prog_items[self.player]["rep"]) \ No newline at end of file