// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh. // Copyright (C) 2012-2016 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file m_cond.c /// \brief Unlockable condition system for SRB2 version 2.1 #include "m_cond.h" #include "doomstat.h" #include "z_zone.h" #include "hu_stuff.h" // CEcho #include "v_video.h" // video flags #include "g_game.h" // record info #include "r_things.h" // numskins #include "r_draw.h" // R_GetColorByName // Map triggers for linedef executors // 32 triggers, one bit each UINT32 unlocktriggers; // The meat of this system lies in condition sets conditionset_t conditionSets[MAXCONDITIONSETS]; // Default Emblem locations emblem_t emblemlocations[MAXEMBLEMS] = { // GREEN FLOWER 1 // --- {0, 8156, 6936, 129, 1, 'A', SKINCOLOR_BLUE, 0, "Go get your feet wet\n" "to find this, the first emblem.\n" "Yes, it's very deep.", 0}, {0, 3184, 1812, 928, 1, 'B', SKINCOLOR_LAVENDER, 0, "There are many rings,\n" "but this one's not what you think.\n" "There lies the emblem.", 0}, {0, 9024, 6716, 769, 1, 'C', SKINCOLOR_RED, 0, "Right next to a lake,\n" "a ledge has been constructed.\n" "Near there is the goal.", 0}, {0, 2080, -384, 512, 1, 'D', SKINCOLOR_ORANGE, 0, "Streams come to an end\n" "where they can no longer fall.\n" "But if you went up...", 0}, {0, -336, 2064, 195, 1, 'E', SKINCOLOR_EMERALD, 0, "This one's in plain sight.\n" "Why haven't you claimed it?\n" "Surely you saw it.", 0}, {ET_SCORE, 0,0,0, 1, 'S', SKINCOLOR_BROWN, 125000, "", 0}, {ET_TIME, 0,0,0, 1, 'T', SKINCOLOR_GREY, 20*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 1, 'R', SKINCOLOR_GOLD, 200, "", 0}, // GREEN FLOWER 2 // --- {0, 2624, -6816, 1332, 2, 'A', SKINCOLOR_BLUE, 0, "Near the giant lake\n" "lies a cave with a 1-Up.\n" "An emblem's there, too!", 0}, {0, -5728, -2848, 2848, 2, 'B', SKINCOLOR_LAVENDER, 0, "Near the final lake,\n" "a higher lake falls on in.\n" "Three platforms await.", 0}, {0, 3648, 6464, 2576, 2, 'C', SKINCOLOR_RED, 0, "Near the level's start,\n" "a bridge crosses a river.\n" "What's that river's source?", 0}, {0, -2032,-10048, 986, 2, 'D', SKINCOLOR_ORANGE, 0, "Near the level's end,\n" "another bridge spans a lake.\n" "What could be under...?", 0}, {0, -170, 491, 3821, 2, 'E', SKINCOLOR_EMERALD, 0, "An ivied tunnel\n" "has a corner that's sunlit.\n" "Go reach for the sky!", 0}, {ET_SCORE, 0,0,0, 2, 'S', SKINCOLOR_BROWN, 150000, "", 0}, {ET_TIME, 0,0,0, 2, 'T', SKINCOLOR_GREY, 40*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 2, 'R', SKINCOLOR_GOLD, 200, "", 0}, // GREEN FLOWER 3 // --- {ET_TIME, 0,0,0, 3, 'T', SKINCOLOR_GREY, 30*TICRATE, "", 0}, // TECHNO HILL 1 // --- {0, -5664, -5072, 2396, 4, 'A', SKINCOLOR_BLUE, 0, "Three pipes reside near\n" "where our heroes' paths split off.\n" "You'll have to look up!", 0}, {0, -784,-13968, 2888, 4, 'B', SKINCOLOR_LAVENDER, 0, "Climbing yields great range.\n" "Yet, on a path for climbers,\n" "flying is the key.", 0}, {0, 4160, -5824, 3776, 4, 'C', SKINCOLOR_RED, 0, "That's sure lots of slime.\n" "Say, do you ever wonder\n" "what's dumping it all?", 0}, {0, 6400, -8352, 1764, 4, 'D', SKINCOLOR_ORANGE, 0, "Spinning through small gaps\n" "can slip you into a cave.\n" "In that cave's first stretch...", 0}, {0, 2848, -9088, 488, 4, 'E', SKINCOLOR_EMERALD, 0, "The slime lake is deep,\n" "but reaching the floor takes height.\n" "Scream \"Geronimo!\"...", 0}, {ET_SCORE, 0,0,0, 4, 'S', SKINCOLOR_BROWN, 75000, "", 0}, {ET_TIME, 0,0,0, 4, 'T', SKINCOLOR_GREY, 75*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 4, 'R', SKINCOLOR_GOLD, 300, "", 0}, // TECHNO HILL 2 // --- {0,-19138, -2692, 688, 5, 'A', SKINCOLOR_BLUE, 0, "Near the first checkpoint,\n" "a bridge crosses a slime pool.\n" "(Sensing a pattern?)", 0}, {0,-13120, 8062, 1248, 5, 'B', SKINCOLOR_LAVENDER, 0, "Behind the windows,\n" "near crushers, ever smashing\n" "a conveyor belt.", 0}, {0, 580, 4552, 1344, 5, 'C', SKINCOLOR_RED, 0, "A pipe drops onto\n" "a half-outdoors conveyor.\n" "But is it empty?", 0}, {0, 192, -8768, 24, 5, 'D', SKINCOLOR_ORANGE, 0, "There is a hallway\n" "that a button floods with slime.\n" "Go through it again!", 0}, {0, -2468,-12128, 1312, 5, 'E', SKINCOLOR_EMERALD, 0, "Jumping on turtles\n" "will send you springing skyward.\n" "Now, do that six times...", 0}, {ET_SCORE, 0,0,0, 5, 'S', SKINCOLOR_BROWN, 100000, "", 0}, {ET_TIME, 0,0,0, 5, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 5, 'R', SKINCOLOR_GOLD, 600, "", 0}, // TECHNO HILL 3 // --- {ET_TIME, 0,0,0, 6, 'T', SKINCOLOR_GREY, 40*TICRATE, "", 0}, // DEEP SEA 1 // --- {0,-16224, -2880, 3530, 7, 'A', SKINCOLOR_BLUE, 0, "Climb up two maze walls.\n" "Break the roof, then a corner.\n" "There, glide, but stay dry.", 0}, {0, -8224, 896, 1056, 7, 'B', SKINCOLOR_LAVENDER, 0, "Follow the left path.\n" "A square green button lurks deep.\n" "Weight it down, somehow.", 0}, {0, 4992, -5072, 4136, 7, 'C', SKINCOLOR_RED, 0, "A certain path holds\n" "many gargoyle puzzles.\n" "Victors reach a \"V\".", 0}, {0, 4576, 5168, 2660, 7, 'D', SKINCOLOR_ORANGE, 0, "A caved-in hallway?\n" "The floor falls; the path goes down.\n" "But those rocks looked weak...", 0}, {0, 12576, 16096, -992, 7, 'E', SKINCOLOR_EMERALD, 0, "The end is quite dry.\n" "Some rocks dam the water in.\n" "Knuckles can fix that...", 0}, {ET_SCORE, 0,0,0, 7, 'S', SKINCOLOR_BROWN, 75000, "", 0}, {ET_TIME, 0,0,0, 7, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 7, 'R', SKINCOLOR_GOLD, 400, "", 0}, // DEEP SEA 2 // --- {0,-15040, 6976, 2016, 8, 'A', SKINCOLOR_BLUE, 0, "A waterfall lands\n" "near a starpost in a cave.\n" "It's dark up there, but...", 0}, {0, 4288, 2912, 544, 8, 'B', SKINCOLOR_LAVENDER, 0, "So many blocks here!\n" "Take five; bathe in the fountain.\n" "Hmmm? A hidden path...?", 0}, {0, -5696, 16992, 791, 8, 'C', SKINCOLOR_RED, 0, "An ornate dragon\n" "faces a secret passage.\n" "Knuckles! Don't get crushed!", 0}, {0,-13344, 18688, 1034, 8, 'D', SKINCOLOR_ORANGE, 0, "In the current maze\n" "hides a dark room of columns.\n" "Find it, then look up.", 0}, {0, 3104, 16192, 2408, 8, 'E', SKINCOLOR_EMERALD, 0, "That same dragon's eye\n" "hides another secret room.\n" "There, solve its riddle.", 0}, {ET_SCORE, 0,0,0, 8, 'S', SKINCOLOR_BROWN, 50000, "", 0}, {ET_TIME, 0,0,0, 8, 'T', SKINCOLOR_GREY, 150*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 8, 'R', SKINCOLOR_GOLD, 250, "", 0}, // DEEP SEA 3 // --- {ET_TIME, 0,0,0, 9, 'T', SKINCOLOR_GREY, 90*TICRATE, "", 0}, // CASTLE EGGMAN 1 // --- {0, -6176, -5184, -128, 10, 'A', SKINCOLOR_BLUE, 0, "A drain feeds the lake.\n" "Water rushes quickly through.\n" "Go against the flow.", 0}, {0, 3648,-15296, -992, 10, 'B', SKINCOLOR_LAVENDER, 0, "The left starting path\n" "goes atop a large wood deck.\n" "Checked underneath yet?", 0}, {0, 11712, 21312, 5472, 10, 'C', SKINCOLOR_RED, 0, "At last, the castle!\n" "Hold up! Don't just barge right in!\n" "What's the facade hold...?", 0}, {0, 20224, 13344, 3104, 10, 'D', SKINCOLOR_ORANGE, 0, "The final approach!\n" "A tower holds the emblem\n" "near a ring arrow.", 0}, {0, 9472, -5890, 710, 10, 'E', SKINCOLOR_EMERALD, 0, "The right starting path\n" "hides this near a canopy,\n" "high, where two trees meet.", 0}, {ET_SCORE, 0,0,0, 10, 'S', SKINCOLOR_BROWN, 50000, "", 0}, {ET_TIME, 0,0,0, 10, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 10, 'R', SKINCOLOR_GOLD, 200, "", 0}, // CASTLE EGGMAN 2 // --- {0, 832,-15168, 7808, 11, 'A', SKINCOLOR_BLUE, 0, "Find a trick bookcase\n" "that hides a darkened hallway.\n" "There, climb a tower.", 0}, {0,-18460,-22180, 2416, 11, 'B', SKINCOLOR_LAVENDER, 0, "Down in the dungeon,\n" "a cracked wall hides secret paths.\n" "Echidnas only!", 0}, {0, -6144,-11792, 3232, 11, 'C', SKINCOLOR_RED, 0, "A room you can flood!\n" "A brown grate's near its exit.\n" "Knuckles can break it...", 0}, {0, 4608, -7024, 4256, 11, 'D', SKINCOLOR_ORANGE, 0, "Some of these bookshelves\n" "are not flush against the walls.\n" "Wonder why that is?", 0}, {0, 12708,-13536, 4768, 11, 'E', SKINCOLOR_EMERALD, 0, "The ending's towers\n" "are hiding a small alcove.\n" "Check around outside.", 0}, {ET_SCORE, 0,0,0, 11, 'S', SKINCOLOR_BROWN, 400000, "", 0}, {ET_TIME, 0,0,0, 11, 'T', SKINCOLOR_GREY, 210*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 11, 'R', SKINCOLOR_GOLD, 600, "", 0}, // CASTLE EGGMAN 3 // --- {ET_TIME, 0,0,0, 12, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0}, // ARID CANYON 1 // --- {0, 3488, 2208, 3072, 13, 'A', SKINCOLOR_BLUE, 0, "A rather large gap\n" "must be crossed by way of tram.\n" "At its end, jump left.", 0}, {0, -7552, 10464, 4094, 13, 'B', SKINCOLOR_LAVENDER, 0, "Crushers that go up!\n" "Mind your step; if they're triggered,\n" "they'll block this emblem.", 0}, {0,-12093, 14575, 5752, 13, 'C', SKINCOLOR_RED, 0, "There's an oil lake\n" "that you can sink deep into.\n" "Drain it, and explore.", 0}, {0, 512, -7136, 4640, 13, 'D', SKINCOLOR_ORANGE, 0, "Not far from the start,\n" "if you climb toward the sky,\n" "the cliffs hide something.", 0}, {0, 12504, 6848, 3424, 13, 'E', SKINCOLOR_EMERALD, 0, "Right by the exit,\n" "an emblem lies on a cliff.\n" "Ride ropes to reach it.", 0}, {ET_SCORE, 0,0,0, 13, 'S', SKINCOLOR_BROWN, 50000, "", 0}, {ET_TIME, 0,0,0, 13, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 13, 'R', SKINCOLOR_GOLD, 300, "", 0}, // RED VOLCANO 1 // --- {0,-13184, 11424, 3080, 16, 'A', SKINCOLOR_BLUE, 0, "Look around the room,\n" "just before you clear the stage;\n" "something's hidden there!", 0}, {0, -2816, 3120, 3044, 16, 'B', SKINCOLOR_LAVENDER, 0, "Ever look upwards\n" "when you're traversing across\n" "collapsing platforms?", 0}, {0, 6720, 6784, 1452, 16, 'C', SKINCOLOR_RED, 0, "Check out a corner\n" "of a lake of magma near\n" "spinning jets of flame.", 0}, {0, -5504, 9824, 800, 16, 'D', SKINCOLOR_ORANGE, 0, "Where once a bridge stood,\n" "now magma falls from above.\n" "The bridge dropped something...", 0}, {0, 8287,-11043, 1328, 16, 'E', SKINCOLOR_EMERALD, 0, "A lake of magma\n" "ebbs and flows unendingly.\n" "Wait for its nadir.", 0}, {ET_SCORE, 0,0,0, 16, 'S', SKINCOLOR_BROWN, 30000, "", 0}, {ET_TIME, 0,0,0, 16, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 16, 'R', SKINCOLOR_GOLD, 100, "", 0}, // EGG ROCK 1 // --- {0,-10976, -7328, 1584, 22, 'A', SKINCOLOR_BLUE, 0, "Vanishing platforms,\n" "then collapsing ones herald\n" "a last-second jump.", 0}, {0, -6592,-11200, 2208, 22, 'B', SKINCOLOR_LAVENDER, 0, "What is this red stuff?\n" "You can't breathe it in, but look!\n" "It can't reach up there...", 0}, {0, 6816, 832, 936, 22, 'C', SKINCOLOR_RED, 0, "The team's paths diverge.\n" "Should Tails run the crusher path?\n" "No! Fly outside it!", 0}, {0, 6942, -8902, 2080, 22, 'D', SKINCOLOR_ORANGE, 0, "Don't jump too high here!\n" "No conveyor will catch you;\n" "you'd fall to your death.", 0}, {0, -6432, -6192, 584, 22, 'E', SKINCOLOR_EMERALD, 0, "Conveyors! Magma!\n" "What an intense room this is!\n" "But, what brought you here?", 0}, {ET_SCORE, 0,0,0, 22, 'S', SKINCOLOR_BROWN, 25000, "", 0}, {ET_TIME, 0,0,0, 22, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 22, 'R', SKINCOLOR_GOLD, 150, "", 0}, // EGG ROCK 2 // --- {0, -6672, 7792, 352, 23, 'A', SKINCOLOR_BLUE, 0, "Walk on the ceiling;\n" "resist the urge to flip back!\n" "Find the cyan path...", 0}, {0,-12256, 15136, -288, 23, 'B', SKINCOLOR_LAVENDER, 0, "X marks the spot? Nope!\n" "Try standing further away\n" "when the timer flips.", 0}, {0, 1536, 16224, 1144, 23, 'C', SKINCOLOR_RED, 0, "There is more than one\n" "elevator inside the\n" "elevator shaft...", 0}, {0,-15968, 14192, 3152, 23, 'D', SKINCOLOR_ORANGE, 0, "Gears with missing teeth\n" "can hide a clever secret!\n" "Think Green Hill Zone boss.", 0}, {0, 1920, 20608, 1064, 23, 'E', SKINCOLOR_EMERALD, 0, "Just before you reach\n" "the defective cargo bay,\n" "fly under a bridge.", 0}, {ET_SCORE, 0,0,0, 23, 'S', SKINCOLOR_BROWN, 60000, "", 0}, {ET_TIME, 0,0,0, 23, 'T', SKINCOLOR_GREY, 300*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 23, 'R', SKINCOLOR_GOLD, 250, "", 0}, // EGG ROCK 3 // --- /* Just in case, I'll leave these here in the source. {0, 848, -3584, 592, 24, 'A', SKINCOLOR_BLUE, 0, "[PH] Hiding at the end of the first hallway.", 0}, {0,-10368, -2816, 144, 24, 'B', SKINCOLOR_LAVENDER, 0, "Directions are meaningless.", 0}, {0, -8160, -5952, 560, 24, 'C', SKINCOLOR_RED, 0, "[PH] In the ceiling of the conveyor belt + laser hallway.", 0}, {0,-13728,-13728, 1552, 24, 'D', SKINCOLOR_ORANGE, 0, "[PH] On top of the platform with rows of spikes in reverse gravity.", 0}, {0,-14944, 768, 1232, 24, 'E', SKINCOLOR_EMERALD, 0, "Follow the leader.", 0}, */ {ET_SCORE, 0,0,0, 24, 'S', SKINCOLOR_BROWN, 14000, "", 0}, {ET_TIME, 0,0,0, 24, 'T', SKINCOLOR_GREY, 210*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 24, 'R', SKINCOLOR_GOLD, 100, "", 0}, // EGG ROCK CORE // --- {ET_TIME, 0,0,0, 25, 'T', SKINCOLOR_GREY, 100*TICRATE, "", 0}, // PIPE TOWERS // --- {0, 3182, 5040, 3008, 30, 'A', SKINCOLOR_BLUE, 0, "A pipe in the roof\n" "eternally drops water.\n" "Something's stuck up there.", 0}, {0, -2400, 5984, 2752, 30, 'B', SKINCOLOR_LAVENDER, 0, "Pushing a red switch\n" "raises the water level;\n" "from there, can't miss it.", 0}, {0, 6112, 7008, 4032, 30, 'C', SKINCOLOR_RED, 0, "A high-up passage\n" "hides near the second checkpoint.\n" "Climb in; then, climb more.", 0}, {0, 11424, -4832, 1376, 30, 'D', SKINCOLOR_ORANGE, 0, "The underground room\n" "with platforms that fall and rise\n" "only LOOKS empty...", 0}, {0 , 4960, -6112, 1312, 30, 'E', SKINCOLOR_EMERALD, 0, "This one's straightforward.\n" "What comes to mind when I say:\n" "\"WELCOME TO WARP ZONE!\"?", 0}, {ET_SCORE, 0,0,0, 30, 'S', SKINCOLOR_BROWN, 75000, "", 0}, {ET_TIME, 0,0,0, 30, 'T', SKINCOLOR_GREY, 100*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 30, 'R', SKINCOLOR_GOLD, 300, "", 0}, // AERIAL GARDEN // --- {0, 10176,-14304, 1796, 40, 'A', SKINCOLOR_BLUE, 0, "A central tower,\n" "one with many waterfalls,\n" "hides a secret room.", 0}, {0, 480, 17696, 6496, 40, 'B', SKINCOLOR_LAVENDER, 0, "Hidden off the path\n" "lies a skyscraping tower.\n" "A lake's at the top.", 0}, {0, -8896, 13248, 3362, 40, 'C', SKINCOLOR_RED, 0, "Find all four buttons\n" "that sink when you stand on them.\n" "They'll open a door...", 0}, {0, -8896, -9952, 2480, 40, 'D', SKINCOLOR_ORANGE, 0, "Much like the last one,\n" "you need to find some switches.\n" "Only two, this time.", 0}, {0, 13184, 18880, 6672, 40, 'E', SKINCOLOR_EMERALD, 0, "The inner sanctum!\n" "Teleport to its switches;\n" "then, check near the goal.", 0}, {ET_SCORE, 0,0,0, 40, 'S', SKINCOLOR_BROWN, 300000, "", 0}, {ET_TIME, 0,0,0, 40, 'T', SKINCOLOR_GREY, 240*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 40, 'R', SKINCOLOR_GOLD, 1200, "", 0}, // AZURE TEMPLE // --- {0, -2400, 7552, 1120, 41, 'A', SKINCOLOR_BLUE, 0, "For those who can swim,\n" "a long tunnel hides rewards.\n" "Do mind the Buzzes!", 0}, {0, -64, 14016, 2072, 41, 'B', SKINCOLOR_LAVENDER, 0, "So many skylights!\n" "A markedly large one hides\n" "behind a starpost...", 0}, {0, 2976, 13920, -32, 41, 'C', SKINCOLOR_RED, 0, "When you reach gauntlets\n" "of diagonal fire,\n" "check out the corners.", 0}, {0, 2176, 22592, 1376, 41, 'D', SKINCOLOR_ORANGE, 0, "A room of currents;\n" "most of them are marked by spikes.\n" "This one? A corner.", 0}, {0, -4128, 21344, 1120, 41, 'E', SKINCOLOR_EMERALD, 0, "The only way to hit\n" "all those gems at once is with\n" "a radial blast.", 0}, {ET_SCORE, 0,0,0, 41, 'S', SKINCOLOR_BROWN, 425000, "", 0}, {ET_TIME, 0,0,0, 41, 'T', SKINCOLOR_GREY, 240*TICRATE, "", 0}, {ET_RINGS, 0,0,0, 41, 'R', SKINCOLOR_GOLD, 300, "", 0}, // FLORAL FIELD // --- {0, 5394, -996, 160, 50, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 50, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 50, 'T', SKINCOLOR_GREY, 40*TICRATE, "", 0}, // TOXIC PLATEAU // --- {0, 780, -1664, 32, 51, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 51, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 51, 'T', SKINCOLOR_GREY, 50*TICRATE, "", 0}, // FLOODED COVE // --- {0, 1824, -1888, 2448, 52, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 52, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 52, 'T', SKINCOLOR_GREY, 90*TICRATE, "", 0}, // CAVERN FORTRESS // --- {0, -3089, -431, 1328, 53, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 53, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 53, 'T', SKINCOLOR_GREY, 75*TICRATE, "", 0}, // DUSTY WASTELAND // --- {0, 957, 924, 2956, 54, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 54, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 54, 'T', SKINCOLOR_GREY, 65*TICRATE, "", 0}, // MAGMA CAVES // --- {0, -2752, 3104, 1800, 55, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 55, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 55, 'T', SKINCOLOR_GREY, 80*TICRATE, "", 0}, // EGG SATELLITE // --- {0, 5334, -609, 3426, 56, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 56, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 56, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0}, // BLACK HOLE // --- {0, 2108, 3776, 32, 57, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 57, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 57, 'T', SKINCOLOR_GREY, 150*TICRATE, "", 0}, // SPRING HILL // --- {0, -1840, -1024, 1644, 58, 'N', SKINCOLOR_RUST, 0, "", 0}, {ET_NGRADE, 0,0,0, 58, 'Q', SKINCOLOR_TEAL, GRADE_A, "", 0}, {ET_NTIME, 0,0,0, 58, 'T', SKINCOLOR_GREY, 60*TICRATE, "", 0}, }; // Default Extra Emblems extraemblem_t extraemblems[MAXEXTRAEMBLEMS] = { {"Game Complete", "Complete 1P Mode", 10, 'X', SKINCOLOR_BLUE, 0}, {"All Emeralds", "Complete 1P Mode with all Emeralds", 11, 'V', SKINCOLOR_GREY, 0}, {"Perfect Bonus", "Perfect Bonus on a non-secret stage", 30, 'P', SKINCOLOR_GOLD, 0}, {"PLACEHOLDER", "PLACEHOLDER", 0, 'O', SKINCOLOR_RUST, 0}, {"NiGHTS Mastery", "Show your mastery of NiGHTS!", 22, 'W', SKINCOLOR_TEAL, 0}, }; // Default Unlockables unlockable_t unlockables[MAXUNLOCKABLES] = { // Name, Objective, Menu Height, ConditionSet, Unlock Type, Variable, NoCecho, NoChecklist /* 01 */ {"Record Attack", "/", 0, 1, SECRET_RECORDATTACK, 0, true, true, 0}, /* 02 */ {"NiGHTS Mode", "/", 0, 2, SECRET_NIGHTSMODE, 0, true, true, 0}, /* 03 */ {"Play Credits", "/", 30, 10, SECRET_CREDITS, 0, true, true, 0}, /* 04 */ {"Sound Test", "/", 40, 10, SECRET_SOUNDTEST, 0, false, false, 0}, /* 05 */ {"EXTRA LEVELS", "/", 58, 0, SECRET_HEADER, 0, true, true, 0}, /* 06 */ {"Aerial Garden Zone", "/", 70, 11, SECRET_WARP, 40, false, false, 0}, /* 07 */ {"Azure Temple Zone", "/", 80, 20, SECRET_WARP, 41, false, false, 0}, /* 08 */ {"BONUS LEVELS", "/", 98, 0, SECRET_HEADER, 0, true, true, 0}, /* 09 */ {"PLACEHOLDER", "/", 0, 0, SECRET_NONE, 0, true, true, 0}, /* 10 */ {"Mario Koopa Blast", "/", 110, 42, SECRET_WARP, 30, false, false, 0}, /* 11 */ {"PLACEHOLDER", "/", 0, 0, SECRET_NONE, 0, true, true, 0}, /* 12 */ {"Spring Hill Zone", "/", 0, 44, SECRET_NONE, 0, false, false, 0}, /* 13 */ {"Black Hole", "Get grade A in all Special Stages", 0, 50, SECRET_NONE, 0, false, true, 0}, /* 14 */ {"Emblem Hints", "/", 0, 41, SECRET_EMBLEMHINTS, 0, false, true, 0}, /* 15 */ {"Emblem Radar", "/", 0, 43, SECRET_ITEMFINDER, 0, false, true, 0}, /* 16 */ {"Pandora's Box", "/", 0, 45, SECRET_PANDORA, 0, false, false, 0}, /* 17 */ {"Level Select", "/", 20, 45, SECRET_LEVELSELECT, 1, false, true, 0}, }; // Default number of emblems and extra emblems INT32 numemblems = 155; INT32 numextraemblems = 5; // DEFAULT CONDITION SETS FOR SRB2 2.1: void M_SetupDefaultConditionSets(void) { memset(conditionSets, 0, sizeof(conditionSets)); // -- 1: Complete GFZ1 M_AddRawCondition(1, 1, UC_MAPBEATEN, 1, 0, 0); // -- 2: Complete SS1 M_AddRawCondition(2, 1, UC_MAPBEATEN, 50, 0, 0); // -- 10: Complete the game M_AddRawCondition(10, 1, UC_GAMECLEAR, 1, 0, 0); // -- 11: Complete the game with all emeralds M_AddRawCondition(11, 1, UC_ALLEMERALDS, 1, 0, 0); // -- 20: Beat AGZ M_AddRawCondition(20, 1, UC_MAPBEATEN, 40, 0, 0); // -- 22: Beat Black Hole M_AddRawCondition(22, 1, UC_MAPBEATEN, 57, 0, 0); // -- 30: Perfect Bonus M_AddRawCondition(30, 1, UC_MAPPERFECT, 1, 0, 0); M_AddRawCondition(30, 2, UC_MAPPERFECT, 2, 0, 0); M_AddRawCondition(30, 3, UC_MAPPERFECT, 4, 0, 0); M_AddRawCondition(30, 4, UC_MAPPERFECT, 5, 0, 0); M_AddRawCondition(30, 5, UC_MAPPERFECT, 7, 0, 0); M_AddRawCondition(30, 6, UC_MAPPERFECT, 8, 0, 0); M_AddRawCondition(30, 7, UC_MAPPERFECT, 10, 0, 0); M_AddRawCondition(30, 8, UC_MAPPERFECT, 11, 0, 0); M_AddRawCondition(30, 9, UC_MAPPERFECT, 13, 0, 0); M_AddRawCondition(30, 10, UC_MAPPERFECT, 16, 0, 0); M_AddRawCondition(30, 11, UC_MAPPERFECT, 22, 0, 0); M_AddRawCondition(30, 12, UC_MAPPERFECT, 23, 0, 0); M_AddRawCondition(30, 13, UC_MAPPERFECT, 24, 0, 0); M_AddRawCondition(30, 14, UC_MAPPERFECT, 40, 0, 0); M_AddRawCondition(30, 15, UC_MAPPERFECT, 41, 0, 0); // -- 40: Find 20 emblems M_AddRawCondition(40, 1, UC_TOTALEMBLEMS, 20, 0, 0); // -- 41: Find 40 emblems M_AddRawCondition(41, 1, UC_TOTALEMBLEMS, 40, 0, 0); // -- 42: Find 60 emblems M_AddRawCondition(42, 1, UC_TOTALEMBLEMS, 60, 0, 0); // -- 43: Find 80 emblems M_AddRawCondition(43, 1, UC_TOTALEMBLEMS, 80, 0, 0); // -- 44: Find 100 emblems M_AddRawCondition(44, 1, UC_TOTALEMBLEMS, 100, 0, 0); // -- 45: Find 160 (all) emblems M_AddRawCondition(45, 1, UC_TOTALEMBLEMS, 160, 0, 0); // -- 50: A rank all NiGHTS special stages M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 50, 0); M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 51, 0); M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 52, 0); M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 53, 0); M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 54, 0); M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 55, 0); M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 56, 0); } void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2) { condition_t *cond; UINT32 num, wnum; I_Assert(set && set <= MAXCONDITIONSETS); wnum = conditionSets[set - 1].numconditions; num = ++conditionSets[set - 1].numconditions; conditionSets[set - 1].condition = Z_Realloc(conditionSets[set - 1].condition, sizeof(condition_t)*num, PU_STATIC, 0); cond = conditionSets[set - 1].condition; cond[wnum].id = id; cond[wnum].type = c; cond[wnum].requirement = r; cond[wnum].extrainfo1 = x1; cond[wnum].extrainfo2 = x2; } void M_ClearConditionSet(UINT8 set) { if (conditionSets[set - 1].numconditions) { Z_Free(conditionSets[set - 1].condition); conditionSets[set - 1].condition = NULL; conditionSets[set - 1].numconditions = 0; } conditionSets[set - 1].achieved = false; } // Clear ALL secrets. void M_ClearSecrets(void) { INT32 i; memset(mapvisited, 0, sizeof(mapvisited)); for (i = 0; i < MAXEMBLEMS; ++i) emblemlocations[i].collected = false; for (i = 0; i < MAXEXTRAEMBLEMS; ++i) extraemblems[i].collected = false; for (i = 0; i < MAXUNLOCKABLES; ++i) unlockables[i].unlocked = false; for (i = 0; i < MAXCONDITIONSETS; ++i) conditionSets[i].achieved = false; timesBeaten = timesBeatenWithEmeralds = timesBeatenUltimate = 0; // Re-unlock any always unlocked things M_SilentUpdateUnlockablesAndEmblems(); } // ---------------------- // Condition set checking // ---------------------- static UINT8 M_CheckCondition(condition_t *cn) { switch (cn->type) { case UC_PLAYTIME: // Requires total playing time >= x return (totalplaytime >= (unsigned)cn->requirement); case UC_GAMECLEAR: // Requires game beaten >= x times return (timesBeaten >= (unsigned)cn->requirement); case UC_ALLEMERALDS: // Requires game beaten with all 7 emeralds >= x times return (timesBeatenWithEmeralds >= (unsigned)cn->requirement); case UC_ULTIMATECLEAR: // Requires game beaten on ultimate >= x times (in other words, never) return (timesBeatenUltimate >= (unsigned)cn->requirement); case UC_OVERALLSCORE: // Requires overall score >= x return (M_GotHighEnoughScore(cn->requirement)); case UC_OVERALLTIME: // Requires overall time <= x return (M_GotLowEnoughTime(cn->requirement)); case UC_OVERALLRINGS: // Requires overall rings >= x return (M_GotHighEnoughRings(cn->requirement)); case UC_MAPVISITED: // Requires map x to be visited return ((mapvisited[cn->requirement - 1] & MV_VISITED) == MV_VISITED); case UC_MAPBEATEN: // Requires map x to be beaten return ((mapvisited[cn->requirement - 1] & MV_BEATEN) == MV_BEATEN); case UC_MAPALLEMERALDS: // Requires map x to be beaten with all emeralds in possession return ((mapvisited[cn->requirement - 1] & MV_ALLEMERALDS) == MV_ALLEMERALDS); case UC_MAPULTIMATE: // Requires map x to be beaten on ultimate return ((mapvisited[cn->requirement - 1] & MV_ULTIMATE) == MV_ULTIMATE); case UC_MAPPERFECT: // Requires map x to be beaten with a perfect bonus return ((mapvisited[cn->requirement - 1] & MV_PERFECT) == MV_PERFECT); case UC_MAPSCORE: // Requires score on map >= x return (G_GetBestScore(cn->extrainfo1) >= (unsigned)cn->requirement); case UC_MAPTIME: // Requires time on map <= x return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement); case UC_MAPRINGS: // Requires rings on map >= x return (G_GetBestRings(cn->extrainfo1) >= cn->requirement); case UC_NIGHTSSCORE: return (G_GetBestNightsScore(cn->extrainfo1, (UINT8)cn->extrainfo2) >= (unsigned)cn->requirement); case UC_NIGHTSTIME: return (G_GetBestNightsTime(cn->extrainfo1, (UINT8)cn->extrainfo2) <= (unsigned)cn->requirement); case UC_NIGHTSGRADE: return (G_GetBestNightsGrade(cn->extrainfo1, (UINT8)cn->extrainfo2) >= cn->requirement); case UC_TRIGGER: // requires map trigger set return !!(unlocktriggers & (1 << cn->requirement)); case UC_TOTALEMBLEMS: // Requires number of emblems >= x return (M_GotEnoughEmblems(cn->requirement)); case UC_EMBLEM: // Requires emblem x to be obtained return emblemlocations[cn->requirement-1].collected; case UC_EXTRAEMBLEM: // Requires extra emblem x to be obtained return extraemblems[cn->requirement-1].collected; case UC_CONDITIONSET: // requires condition set x to already be achieved return M_Achieved(cn->requirement-1); } return false; } static UINT8 M_CheckConditionSet(conditionset_t *c) { UINT32 i; UINT32 lastID = 0; condition_t *cn; UINT8 achievedSoFar = true; for (i = 0; i < c->numconditions; ++i) { cn = &c->condition[i]; // If the ID is changed and all previous statements of the same ID were true // then this condition has been successfully achieved if (lastID && lastID != cn->id && achievedSoFar) return true; // Skip future conditions with the same ID if one fails, for obvious reasons else if (lastID && lastID == cn->id && !achievedSoFar) continue; lastID = cn->id; achievedSoFar = M_CheckCondition(cn); } return achievedSoFar; } void M_CheckUnlockConditions(void) { INT32 i; conditionset_t *c; for (i = 0; i < MAXCONDITIONSETS; ++i) { c = &conditionSets[i]; if (!c->numconditions || c->achieved) continue; c->achieved = (M_CheckConditionSet(c)); } } UINT8 M_UpdateUnlockablesAndExtraEmblems(void) { INT32 i; char cechoText[992] = ""; UINT8 cechoLines = 0; if (modifiedgame && !savemoddata) return false; M_CheckUnlockConditions(); // Go through extra emblems for (i = 0; i < numextraemblems; ++i) { if (extraemblems[i].collected || !extraemblems[i].conditionset) continue; if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false) { strcat(cechoText, va(M_GetText("Got \"%s\" emblem!\\"), extraemblems[i].name)); ++cechoLines; } } // Fun part: if any of those unlocked we need to go through the // unlock conditions AGAIN just in case an emblem reward was reached if (cechoLines) M_CheckUnlockConditions(); // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) { if (unlockables[i].unlocked || !unlockables[i].conditionset) continue; if ((unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1)) != false) { if (unlockables[i].nocecho) continue; strcat(cechoText, va(M_GetText("\"%s\" unlocked!\\"), unlockables[i].name)); ++cechoLines; } } // Announce if (cechoLines) { char slashed[1024] = ""; for (i = 0; (i < 21) && (i < 24 - cechoLines); ++i) slashed[i] = '\\'; slashed[i] = 0; strcat(slashed, cechoText); HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8); HU_SetCEchoDuration(6); HU_DoCEcho(slashed); return true; } return false; } // Used when loading gamedata to make sure all unlocks are synched with conditions void M_SilentUpdateUnlockablesAndEmblems(void) { INT32 i; boolean checkAgain = false; // Just in case they aren't to sync M_CheckUnlockConditions(); M_CheckLevelEmblems(); // Go through extra emblems for (i = 0; i < numextraemblems; ++i) { if (extraemblems[i].collected || !extraemblems[i].conditionset) continue; if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false) checkAgain = true; } // check again if extra emblems unlocked, blah blah, etc if (checkAgain) M_CheckUnlockConditions(); // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) { if (unlockables[i].unlocked || !unlockables[i].conditionset) continue; unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1); } } // Emblem unlocking shit UINT8 M_CheckLevelEmblems(void) { INT32 i; INT32 valToReach; INT16 levelnum; UINT8 res; UINT8 somethingUnlocked = 0; // Update Score, Time, Rings emblems for (i = 0; i < numemblems; ++i) { if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || emblemlocations[i].collected) continue; levelnum = emblemlocations[i].level; valToReach = emblemlocations[i].var; switch (emblemlocations[i].type) { case ET_SCORE: // Requires score on map >= x res = (G_GetBestScore(levelnum) >= (unsigned)valToReach); break; case ET_TIME: // Requires time on map <= x res = (G_GetBestTime(levelnum) <= (unsigned)valToReach); break; case ET_RINGS: // Requires rings on map >= x res = (G_GetBestRings(levelnum) >= valToReach); break; case ET_NGRADE: // Requires NiGHTS grade on map >= x res = (G_GetBestNightsGrade(levelnum, 0) >= valToReach); break; case ET_NTIME: // Requires NiGHTS time on map <= x res = (G_GetBestNightsTime(levelnum, 0) <= (unsigned)valToReach); break; default: // unreachable but shuts the compiler up. continue; } emblemlocations[i].collected = res; if (res) ++somethingUnlocked; } return somethingUnlocked; } UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough. { INT32 i; INT32 embtype; INT16 levelnum; UINT8 res; UINT8 somethingUnlocked = 0; UINT8 flags; for (i = 0; i < numemblems; ++i) { if (emblemlocations[i].type != ET_MAP || emblemlocations[i].collected) continue; levelnum = emblemlocations[i].level; embtype = emblemlocations[i].var; flags = MV_BEATEN; if (embtype & ME_ALLEMERALDS) flags |= MV_ALLEMERALDS; if (embtype & ME_ULTIMATE) flags |= MV_ULTIMATE; if (embtype & ME_PERFECT) flags |= MV_PERFECT; res = ((mapvisited[levelnum - 1] & flags) == flags); emblemlocations[i].collected = res; if (res) ++somethingUnlocked; } return somethingUnlocked; } // ------------------- // Quick unlock checks // ------------------- UINT8 M_AnySecretUnlocked(void) { INT32 i; for (i = 0; i < MAXUNLOCKABLES; ++i) { if (!unlockables[i].nocecho && unlockables[i].unlocked) return true; } return false; } UINT8 M_SecretUnlocked(INT32 type) { INT32 i; for (i = 0; i < MAXUNLOCKABLES; ++i) { if (unlockables[i].type == type && unlockables[i].unlocked) return true; } return false; } UINT8 M_MapLocked(INT32 mapnum) { if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0) return false; if (!unlockables[mapheaderinfo[mapnum-1]->unlockrequired].unlocked) return true; return false; } INT32 M_CountEmblems(void) { INT32 found = 0, i; for (i = 0; i < numemblems; ++i) { if (emblemlocations[i].collected) found++; } for (i = 0; i < numextraemblems; ++i) { if (extraemblems[i].collected) found++; } return found; } // -------------------------------------- // Quick functions for calculating things // -------------------------------------- // Theoretically faster than using M_CountEmblems() // Stops when it reaches the target number of emblems. UINT8 M_GotEnoughEmblems(INT32 number) { INT32 i, gottenemblems = 0; for (i = 0; i < numemblems; ++i) { if (emblemlocations[i].collected) if (++gottenemblems >= number) return true; } for (i = 0; i < numextraemblems; ++i) { if (extraemblems[i].collected) if (++gottenemblems >= number) return true; } return false; } UINT8 M_GotHighEnoughScore(INT32 tscore) { INT32 mscore = 0; INT32 i; for (i = 0; i < NUMMAPS; ++i) { if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK)) continue; if (!mainrecords[i]) continue; if ((mscore += mainrecords[i]->score) > tscore) return true; } return false; } UINT8 M_GotLowEnoughTime(INT32 tictime) { INT32 curtics = 0; INT32 i; for (i = 0; i < NUMMAPS; ++i) { if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK)) continue; if (!mainrecords[i] || !mainrecords[i]->time) return false; else if ((curtics += mainrecords[i]->time) > tictime) return false; } return true; } UINT8 M_GotHighEnoughRings(INT32 trings) { INT32 mrings = 0; INT32 i; for (i = 0; i < NUMMAPS; ++i) { if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK)) continue; if (!mainrecords[i]) continue; if ((mrings += mainrecords[i]->rings) > trings) return true; } return false; } // ---------------- // Misc Emblem shit // ---------------- // Returns pointer to an emblem if an emblem exists for that level. // Pass -1 mapnum to continue from last emblem. // NULL if not found. // note that this goes in reverse!! emblem_t *M_GetLevelEmblems(INT32 mapnum) { static INT32 map = -1; static INT32 i = -1; if (mapnum > 0) { map = mapnum; i = numemblems; } while (--i >= 0) { if (emblemlocations[i].level == map) return &emblemlocations[i]; } return NULL; } skincolors_t M_GetEmblemColor(emblem_t *em) { if (!em || em->color >= MAXSKINCOLORS) return SKINCOLOR_NONE; return em->color; } const char *M_GetEmblemPatch(emblem_t *em) { static char pnamebuf[7] = "GOTITn"; I_Assert(em->sprite >= 'A' && em->sprite <= 'Z'); pnamebuf[5] = em->sprite; return pnamebuf; } skincolors_t M_GetExtraEmblemColor(extraemblem_t *em) { if (!em || em->color >= MAXSKINCOLORS) return SKINCOLOR_NONE; return em->color; } const char *M_GetExtraEmblemPatch(extraemblem_t *em) { static char pnamebuf[7] = "GOTITn"; I_Assert(em->sprite >= 'A' && em->sprite <= 'Z'); pnamebuf[5] = em->sprite; return pnamebuf; }