"when cursed with Eggmark, blow up the racer responsible"
Just a simple condition. Either it happens or it doesn't.
MT_SPBEXPLOSION now tracks whether it was made by KITEM_EGGMAN or KITEM_SPB, too!
Break a target (Prison Egg/UFO) using exactly one Gachabom again and again
Becomes invalid if an Insta-Whip collides with a target, or you throw two at the same time
`Condition1 = UfoAttackMethod [type]`
- "smash a UFO Catcher using only [type]"
- Combine with `Prefix_SealedStar` or `Prefix_IsMap [special stage stage]`
- Shows up as "???"
- Types supported:
- `Boost` - "boost power" (sneakers)
- `Whip` - "Insta-Whip"
- `Banana` - "Bananas"
- `Orbinaut`- "Orbinauts"
- `Jawz` - "Jawz"
- `SPB` - "Self Propelled Bombs"
- Other types could be added on request, these were just the easy ones
In addition, the prototype for P_MobjWasRemoved was moved to `p_mobj.h`.
It's EXTREMELY important that we're able to safely check mobj pointers anywhere a mobj_t is possible to observe, without including the full `p_local.h`...
A series of 100 booleans on the roundconditions struct, one per possible lap.
Allows for a full suite of track hazard touching conditions - see the following examples.
- `Condition1 = Prefix_IsMap RR_MOTOBUGMOTORWAY
- `Condition1 = TrackHazard No`
- `Condition1 = IsMap RR_MOTOBUGMOTORWAY`
- "MOTOBUG MOTORWAY: Don't touch any track hazard"
- `Condition1 = Prefix_GrandPrix`
- `Condition1 = IsMap RR_HARDBOILEDSTADIUM`
- `Condition1 = TrackHazard Yes`
- `Condition1 = And`
- `Condition1 = FinishPlace 1`
- "GRAND PRIX: On HARD-BOILED STADIUM, touch a track hazard every lap & finish in 1st"
- `Condition1 = Prefix IsMap RR_DEATHEGG`
- `Condition1 = Trackhazard No 8`
- "DEATH EGG ZONE: Don't touch any track hazard on lap 8"
- `Condition1 = Prefix_IsMap RR_SPEEDHIGHWAY
- `Condition1 = TrackHazard No Final`
- "SPEED HIGHWAY: Don't touch any track hazard on the final lap"
- `UnlockPercent 40 AltMusic` - "Get 40% of alternate music"
- `UnlockPercent 1 Color` - "Get 1% of Spray Cans"
- `UnlockPercent 20 Map` - "Get 20% of Courses"
- `UnlockPercent 55 Cup` - "Get 55% of Cups"
- `UnlockPercent 100` - "Get 100% completion"
- Provide a percentage and, optionally, an Unlockable type
- This only works for Unlockable Types where there are expected to be more than one per board
- "DescriptionOverride" (new!)
- Provide a full description in place of the params and this will be used
- Any conditions that are before it will not be wiped, so you can prefix it if you need to
- Can be used with any other set of Conditions
- `Condition1 = DescriptionOverride Complete the sentence: "ring racers (???)"`
- "Password"
- Now supports passwords that contain spaces
- `Condition1 = Password race as a ring!`
- "WetPlayer"
- Now supports liquids that contain spaces
- HOWEVER, it comes with the following caveats as part of the change:
- The strictness level must be provided first.
- You can't leave the strictness out. The previous default behaviour now requires STANDARD to be explicitly written.
- `Condition1 = WetPlayer Standard Mega Mack` can be used, now.
Gamedata minor version was updated again.
(God this was a weirdly big amount of work and it's not even polished.)
- Condition1 = PrisonEggCD [Level that has to be unlocked]
- Approximately every 30 Prison Eggs destroyed, you get a shot at a Prison Egg Drop.
- The only Prison Egg Drop implemented right now is an Alt Music CD.
- Your [Wild Prize] is guaranteed to be selected only from conditions associated with levels that are unlocked!
- Only spawns in Grand Prix Bonus Rounds, for netsync and game design.
- The number is fuzzed. If you start the level with 0 Prison Eggs to destroy, it selects a random number of Prisons in the level to bust.
- If you miss the pickup (such as into a deathpit), you'll get another shot in the immediate next Bonus Round you play.
Also:
- The number of Chao Keys you start your gamedata with is now part of the header file, not buried in the wiping function.
- Removed the ACTUAL last object definition vestiges of the Emerald Hunt gamemode.
- `Condition1 = MakeRetire Eggrobo`
- Fires when:
- Not a cooperative context
- You've finished in good standing
- Another player has both
- PF_NOCONTEST
- The skin specified in the condition
Also makes rivalname-handling for K_InitGrandPrixBots `const char *`, since the author of this commit had to reference that code.
Just in case I don't get to do a major pass on adding a ton of extra unlockable conditions later, this is an easy, quick add featuring a stat gamedata and the statistics menu has been tracking for a while.
Instead of being specific to each level, Spray Cans are stored in a list on gamedata that will be stepped along each Spray Can you collect.
They are only assigned to a level on collection - which prevents you from farming the same easy location for every colour in the game.
In addition, this new system is one step short of dehardcoding them entirely. Now only Spray Cans specifically asked for by the existence of UC_SPRAYCAN will be put in the list, and if a secondary parameter is either "Yes" or "True", it will be put at the head of the list. This could technically support custom skincolours one day, but the author of this commit doesn't care to do the last bit of work necessary to make it happen.
There's a slight extra overhead in that skincolor_t now also holds a `cache_spraycan` (renamed from `cachedcan`, which maps had previously)
Currently, there's no safeguard against grabbing it on a custom course - you'll lose the Spray Can as soon as you load a fresh game again - but I consider that easy to fix (tomorrow) and necessary before merger, because the author of this commit does NOT want complaints on release because we forgot to protect users who keep on losing their skincolors.
Increments gamedata minor version, be aware
- M_AssignSpraycans
- Called in M_FinaliseGameData.
- Attaches a hardcoded set of colours to all race maps in cup order, stopping once we run out.
- The colours are shuffled, with some "freebies" always at the head of the list.
- Integrates partial lists pretty well.
- In DEVELOP builds, I_Errors if it produces corrupted state.
- G_LoadGameData, G_SaveGameData
- Save & Load is implemented for these assignments
Creates a central landing point where gamedata loads/creates can be finalised properly.
In addition, gamedata wipes caused by data erase or custom SOC gamedata can no longer be saved in a partway corrupted state if they were to crash midway through.
The linedef's behaviour was broken horribly by long map names, and it's not worth the effort to fix it for the following reasons.
- It was considered a security vulnerability to have free access to the console when it was written.
- The game literally had a cvar to disable running console scripts. That's "I am willingly distributing ActiveX Word Documents in 2023" levels of foolhardery.
- Anything GOOD it can do, both Lua and ACS can do better.
A much more focused replacement for Hornmod, specc'd out by Tyron and Oni working together and implemented by the author of this commit because it's pretty funny.
- Followers have `hornsound` in their SOC configuration.
- The default sound for all followers without a provided one is sfx_horn00.
- They'll play this sound if you use lookback with one following you, and there's nearby players to get the player looking all the way around.
- Only the players who are successfully considered for lookback will hear it.
- Has a v1-like visual with less randomisation, but still netsynced.
- Also controlled by the cvar `taunthorns`, which, like `tauntvoices`, takes "Tasteful" (default), "Meme", and "Off".
TODO: make the condition for horn a little delayed, so you have to hold lookback for a little bit.
Also makes gamedata save/load a little more forward compatible longterm by making a UINT32 bitfield for various once-event flags, with increased minor version
- Four evaluation modes.
- Perfect
- Currently no visual implementation
- All others have a cool set of visuals
- Multi-stage animation of a glowing threat and a Star that's Sealed
- If they're relevant, show the gems you HAVEN'T grabbed
- Three modes here
- No gems
- For Easy mode, asks you to brave a higher difficulty
- Chaos Emeralds
- Not all 7 chaos emeralds? Push your rank harder!
- Super Emeralds
- Not all 7 super emeralds? Further challenge awaits!
- `useBlackRock` to make evaluation context less specific for custom material is replaced with `useSeal` option
- M_CheckCupEmeralds(difficulty)
- Returns the Emeralds you have for that difficulty
- Obviously returns 0 for Easy
- Makes the method of checking collected Emeralds for cup contexts significantly easier
Hilariously broken due to the evaulation gamestate, but the first piece of the puzzle: gets the player into the Credits gamestate after the conclusion of a Podium, if the GP context's cup has this boolean set.
We're unlikely to utilise this, but permits creators of custom user-created cups to flex their own style without fighting to replace the default Podium.
- Now called "Grand Prix Backup"
- Filename is now `gpringsav.bkp`
- Since available in standard contexts, do a little extra guarding against unsporting behaviour:
- In non-DEVELOP builds, delete Grand Prix Backup when returning to the titlescreen/menus.
- "undo your extra work and make it more generic" - Tyron
- Sprites have directional lighting, like walls
- For normal sprites: contrast is much stronger than walls
- Papersprites look the same as walls
- SpriteBacklight option in level header weakens the contrast for sprites only
- SpriteBacklight subtracts from LightContrast
- E.g. SpriteBacklight = 0 would let it match LightContrast
- E.g. SpriteBacklight = 60 would make the contrast much weaker
- Negative values make the contrast stronger
Made sure there is more than enough headroom for our current purposes.
It should be easy to double again if necessary now that the datatypes have been increased... but that would be obscene at this point
- 1024 Unlockables and 1024 Conditions (these were always tied together in slots)
- 2048 emblems (Medals + nonmedals). If we ship with ~250 maps this permits 8 Medals per map - which is higher than we intend right now but could easily fill out in patches
Only persist the full data while unloaded if player has explicitly achieved something on that level, rather than merely visited (such as in a netgame).
Achieving something counts as:
- Getting a best time
- Getting a best lap time (Race only)
- Getting MV_SPBATTACK (completing an SPB run)
- Completing the level IF the level has LF2_FINISHNEEDED (finish level in GP/MP before you see it in Time Attack)
In the previous entry in the series, due to level header records existing in a NUMMAPS-sized table always saved and loaded in full, Time Attack times persisted even across game loads without the relevant custom levels added.
However, this was changed with the long map name system. Map records were assigned to level headers, which were only created on level load.
This new system brings Ring Racers up to parity (or better, due to the reduced incidence of header conflicts!)
- All levels currently loaded with records attached are written on gamedata save.
- This reduces gamedata size for a player who has not unlocked every level!
- On gamedata load, if a level is not loaded, store those extra records on a linked list.
- On level header creation, check the linked list to see if an associated unloaded mapheader record exists.
- If it does, write the record onto the map structure directly, and delete the "unloaded mapheader" storage struct!
- Then on the NEXT gamedata save, in addition to all loaded mapheaders, it writes the extra records kept in long term storage in exactly the same format.