Compare commits

...

147 commits

Author SHA1 Message Date
Eidolon
c2c3ae63a7 Merge branch 'wallspike-crash-fix' into 'master'
Add player pointer check in wall spike bounce

Closes #397

See merge request kart-krew-dev/ring-racers!78
2025-10-26 20:59:39 -05:00
Eidolon
721d5630a1 Add player pointer check in wall spike bounce 2025-10-26 20:37:51 -05:00
Eidolon
200e7b4014 Update modversion for RC9/final just in case 2025-10-26 19:48:56 -05:00
Eidolon
d4c2235e92 Update hashes 2025-10-26 19:26:36 -05:00
Ashnal
b9df8963cd Merge branch 'tally-idle-exp-grade-fix' into 'master'
Fixes grading for 0 EXP finishes

See merge request kart-krew-dev/ring-racers-internal!2973
2025-10-27 00:20:23 +00:00
Ashnal
719923e0cf Fixes grading for 0 EXP finishes 2025-10-27 00:20:22 +00:00
Eidolon
e864ba5255 Merge branch 'team-assist' into 'master'
Significantly more active Speed Assist in teamplay

See merge request kart-krew-dev/ring-racers-internal!2969
2025-10-26 18:51:49 -05:00
Eidolon
b0e1b14c9b Merge remote-tracking branch 'public/master' 2025-10-26 18:07:56 -05:00
Eidolon
15f66de4af Merge branch 'battle-exp-queue' into 'master'
Implement score-to-EXP scaling for Battle and Duel Race when Mobiums are not enabled.

See merge request kart-krew-dev/ring-racers!69
2025-10-26 18:00:03 -05:00
Freaky Mutant Man
6d344ad118 Implement score-to-EXP scaling for Battle and Duel Race when Mobiums are not enabled. 2025-10-26 18:00:03 -05:00
Eidolon
5f0121cca8 Merge branch 'eid-sonic-boom-color' into 'master'
Fix sonic boom colorization

Closes ring-racers#378

See merge request kart-krew-dev/ring-racers-internal!2971
2025-10-26 16:50:22 -05:00
Eidolon
36de3285bd Merge branch 'eid-battle-crash-fix' into 'master'
Use a different patch for striking levels in duel vote

Closes ring-racers#388

See merge request kart-krew-dev/ring-racers-internal!2972
2025-10-26 16:50:16 -05:00
Eidolon
ecb1927cf8 Merge branch 'shorter-caltrops' into 'master'
Set short fuse on "ring burn" caltrops

See merge request kart-krew-dev/ring-racers-internal!2970
2025-10-26 15:11:04 -05:00
Antonio Martinez
c28733f2f5 Speed Assist: Store team multiplier up front 2025-10-26 15:57:23 -04:00
Eidolon
e63ad4a681 Use a different patch for striking levels in duel vote 2025-10-26 14:17:38 -05:00
Eidolon
c087c8bacc Fix sonic boom colorization 2025-10-25 21:08:23 -05:00
Eidolon
8ce90ff211 Merge public master 2025-10-25 20:43:09 -05:00
Eidolon
b4c597161b Merge branch 'node0Bot' into 'master'
Fix node 0 being occupied by a bot in dedicated servers for clients

Closes #385

See merge request kart-krew-dev/ring-racers!77
2025-10-25 20:40:00 -05:00
JugadorXEI
2b7e1384ed Fix node 0 being occupied by a bot in dedicated servers for clients 2025-10-25 20:39:59 -05:00
Eidolon
176713d243 Merge branch 'cachedlevels-lua-why-broke' into 'master'
Fix cup->cachedlevels Lua getter to return entire cachedlevels list.

See merge request kart-krew-dev/ring-racers!73
2025-10-25 20:26:31 -05:00
Freaky Mutant Man
d363c71007 Fix cup->cachedlevels Lua getter to return entire cachedlevels list. 2025-10-25 20:26:31 -05:00
Eidolon
fd27994c48 Merge branch 'unarchiveLuaVarsAfterMapLoad' into 'master'
Unarchive Lua variables *after* loading a level in a demo (fixes #284)

Closes #284

See merge request kart-krew-dev/ring-racers!72
2025-10-25 20:16:52 -05:00
JugadorXEI
9244b9148e Unarchive Lua variables *after* loading a level in a demo (fixes #284) 2025-10-25 20:16:52 -05:00
Eidolon
5d697e378f Merge branch 'fix-long-message-crash' into 'master'
Fixed crash when sending a max character message

See merge request kart-krew-dev/ring-racers!68
2025-10-25 20:01:35 -05:00
Eidolon
0e431cd334 Merge branch 'reducevfx-battle' into 'master'
Disable overtime barrier animation/flashing when reducevfx is enabled.

See merge request kart-krew-dev/ring-racers!74
2025-10-25 19:57:12 -05:00
Eidolon
21a3489bcb Merge branch 'jesus-christ-music-remap-still-broke' into 'master'
Fix anti-Stereo Mode remap check in Lua music remapping function.

See merge request kart-krew-dev/ring-racers!75
2025-10-25 19:46:00 -05:00
Freaky Mutant Man
039ba6c3dc Fix anti-Stereo Mode remap check in Lua music remapping function. 2025-10-25 19:46:00 -05:00
Eidolon
de0fc7d3be Merge branch 'ring-bail-gp-life-loss' into 'master'
Fix unintended life loss in GP when finishing a race with -20 rings.

See merge request kart-krew-dev/ring-racers!76
2025-10-25 19:38:14 -05:00
Freaky Mutant Man
fcb7d38dce Fix unintended life loss in GP when finishing a race with -20 rings. 2025-10-25 19:38:13 -05:00
Eidolon
f984ecc5d7 Merge branch 'queue-cup-crash-fix-please' into 'master'
Fix crash when trying to queue cup while highlighting an empty square.

Closes #377

See merge request kart-krew-dev/ring-racers!71
2025-10-25 19:07:30 -05:00
Eidolon
4c2e540451 Merge branch 'chatclosefix' into 'master'
Fix softlocks surrounding chat input changes

Closes #373

See merge request kart-krew-dev/ring-racers!70
2025-10-25 19:06:27 -05:00
zander3312
59830a4ff0 Fix softlocks surrounding chat input changes 2025-10-25 19:06:26 -05:00
Antonio Martinez
af61408029 Set short fuse on "ring burn" caltrops 2025-10-25 20:03:56 -04:00
Antonio Martinez
668072c285 Significantly more active Speed Assist in teamplay 2025-10-25 19:56:38 -04:00
FreakyMutantMan
687bfb0e1d Disable overtime barrier animation/flashing when reducevfx is enabled. 2025-10-23 23:12:36 -07:00
Oni VelocitOni
121ac80b43 Merge branch 'fix-eggmark-burst' into 'master'
Fix eggmark bail occuring when last roulette was eggmark

Closes ring-racers#379 and ring-racers#384

See merge request kart-krew-dev/ring-racers-internal!2968
2025-10-24 04:11:58 +00:00
Oni VelocitOni
c9dce813e3 Merge branch 'tripwire-speed-reflect' into 'master'
Lower tripwire unstuck threshold (faster)

See merge request kart-krew-dev/ring-racers-internal!2967
2025-10-24 04:11:23 +00:00
Antonio Martinez
e6b21388bd Fix eggmark bail occuring when last roulette was eggmark 2025-10-22 18:35:31 -04:00
Antonio Martinez
7ac5a45979 Lower tripwire unstuck threshold (faster) 2025-10-22 17:52:13 -04:00
FreakyMutantMan
ea8acf33aa Fix crash when trying to queue cup while highlighting an empty square. 2025-10-22 11:20:32 -07:00
Eidolon
add76b17aa Update modversion for rc8/2.4 final 2025-10-21 20:47:53 -05:00
Eidolon
ba8bbc643c Update hashes 2025-10-21 20:09:21 -05:00
Eidolon
9087914dca Update hashes 2025-10-21 19:55:49 -05:00
Eidolon
f0f14ae91e Add 2x tripwire texture 2025-10-21 19:54:24 -05:00
Gunla
b10b845a05 RC7 Asset Hashes 2025-10-22 00:51:54 +00:00
Eidolon
8a55463587 Merge branch 'real-shorter-shoe' into 'master'
Actually shorten Stone Shoe

See merge request kart-krew-dev/ring-racers-internal!2965
2025-10-21 19:45:23 -05:00
Eidolon
c2f68ba92b Merge branch 'no-bungee-items' into 'master'
No item usage on bungees

See merge request kart-krew-dev/ring-racers-internal!2964
2025-10-21 19:44:57 -05:00
Eidolon
5b0351524f Merge branch 'addons-repair' into 'master'
Addons repair

Closes ring-racers#177 and ring-racers#172

See merge request kart-krew-dev/ring-racers-internal!2966
2025-10-21 19:39:47 -05:00
Eidolon
3e058ed7cf Merge branch 'retune-amps' into 'master'
Retune stat-based amp gains

See merge request kart-krew-dev/ring-racers-internal!2961
2025-10-21 19:34:59 -05:00
Eidolon
c42de4ec8c in lieu of a case-insensitive startswith 2025-10-21 19:31:12 -05:00
Antonio Martinez
046afb223d Actually shorten Stone Shoe 2025-10-21 16:17:16 -04:00
toaster
35da317d62 Don't send a netxcmd when using addfile outside of netgame
Was supposed to be happening already, but `multiplayer` is a useless variable...
2025-10-21 20:48:22 +01:00
toaster
2f189ca6f0 Rewind W_VerifyNMUSlumps file handle for PK3, too (resolves kart-krew-dev/ring-racers#177) 2025-10-21 20:46:00 +01:00
toaster
5a622e6ce0 Single period for tooltip for Addons case sensitivity (resolves kart-krew-dev/ring-racers#172) 2025-10-21 20:22:30 +01:00
Eidolon
3d5639c864 Merge public master 2025-10-21 12:06:48 -05:00
Eidolon
7491e40e0f Merge branch 'acs-playerrealname' into 'master'
Add PlayerSkinRealName as an ACS function

See merge request kart-krew-dev/ring-racers!13
2025-10-21 11:53:39 -05:00
Frivolous Aqua
aef864345f Add PlayerSkinRealName as an ACS function 2025-10-21 11:53:39 -05:00
Antonio Martinez
07f80f05df No item usage on bungees 2025-10-21 00:20:29 -04:00
Eidolon
c73c02aac9 Merge branch 'tripwire-texture-swap' into 'master'
Tripwire texture swap

See merge request kart-krew-dev/ring-racers-internal!2950
2025-10-20 18:33:28 -05:00
Ashnal
f09950ddd5 Tripwire texture swap 2025-10-20 18:33:28 -05:00
Eidolon
70675476e5 Merge branch 'fix-defensive-overdrive' into 'master'
Don't unset Overdrive leniency after auto Overdrive

See merge request kart-krew-dev/ring-racers-internal!2963
2025-10-20 18:24:33 -05:00
Eidolon
68413fde44 Merge branch 'rewind-double' into 'master'
Demo rewind doubled to 10s

See merge request kart-krew-dev/ring-racers-internal!2960
2025-10-20 18:23:06 -05:00
Eidolon
0b841b486f Merge branch 'hyuu-ritalin' into 'master'
Leader-penalty counter

See merge request kart-krew-dev/ring-racers-internal!2958
2025-10-20 18:22:54 -05:00
Eidolon
c009236107 Merge branch 'bot-turns' into 'master'
IMMEDIATELY reduce bot rubberbanding when cornering

See merge request kart-krew-dev/ring-racers-internal!2955
2025-10-20 18:20:34 -05:00
Eidolon
2928f84d9a Merge branch 'replay-prefix-cleanup' into 'master'
M_GetRecordMode

See merge request kart-krew-dev/ring-racers-internal!2959
2025-10-20 18:19:47 -05:00
Eidolon
09c6ee99f2 Merge branch 'fix-botscanvote' into 'master'
Fix map voting/striking for bots

Closes ring-racers#333

See merge request kart-krew-dev/ring-racers-internal!2949
2025-10-20 18:18:06 -05:00
Eidolon
3244e42177 Merge branch 'attenuating-spikes' into 'master'
Less spike softlock (resolves ring-racers#65)

Closes ring-racers#65

See merge request kart-krew-dev/ring-racers-internal!2953
2025-10-20 18:15:22 -05:00
Eidolon
a1e903fc72 Merge public master 2025-10-20 16:59:53 -05:00
Eidolon
fa8c2aac86 Fix incorrect ACS MusicRemap tune check 2025-10-20 16:57:02 -05:00
Antonio Martinez
d0938e1dc9 Don't unset Overdrive leniency after auto Overdrive 2025-10-20 16:46:09 -04:00
AJ Martinez
537a44871a Revert "Don't unset Overdrive leniency after auto Overdrive"
This reverts commit 71a1252ec5.
2025-10-20 16:45:35 -04:00
Antonio Martinez
71a1252ec5 Don't unset Overdrive leniency after auto Overdrive 2025-10-20 16:44:54 -04:00
Skirlez
4bb035c7a5 Fixed crash when sending a max character message 2025-10-20 22:54:26 +03:00
Eidolon
131ce41657 Merge public master 2025-10-20 14:36:42 -05:00
Eidolon
f7afea7abb Merge branch 'queue-cup' into 'master'
Add ability to queue all maps in a cup for netgames and local matches by pressing Action on cup select.

See merge request kart-krew-dev/ring-racers!11
2025-10-20 14:24:16 -05:00
Freaky Mutant Man
188d168ace Add ability to queue all maps in a cup for netgames and local matches by pressing Action on cup select. 2025-10-20 14:24:16 -05:00
Eidolon
66a7f40800 Merge branch 'expAmpLuaFuncs' into 'master'
Expose Exp/Amp-related functions to Lua

See merge request kart-krew-dev/ring-racers!66
2025-10-20 14:09:54 -05:00
JugadorXEI
114b01a516 Expose Exp/Amp-related functions to Lua 2025-10-20 14:09:54 -05:00
Eidolon
17ff0fea89 Merge branch 'profiledefault' into 'master'
Add profile color and followercolor to Default color rotation on character select screen

See merge request kart-krew-dev/ring-racers!34
2025-10-20 14:04:06 -05:00
zander3312
17556495f6 Add profile color and followercolor to Default color rotation on character select screen 2025-10-20 14:04:05 -05:00
Eidolon
93918b7bf2 Merge branch 'grandprixinfo-lua' into 'master'
Expose multiple Grand Prix, cup and round queue related structs to Lua.

See merge request kart-krew-dev/ring-racers!39
2025-10-20 14:01:27 -05:00
Freaky Mutant Man
3dc57d5908 Expose multiple Grand Prix, cup and round queue related structs to Lua. 2025-10-20 14:01:27 -05:00
Eidolon
303401aea5 Merge branch 'allow-replay-saving-map-change' into 'master'
Save replay if map abruptly changes

Closes #316, #276, and #242

See merge request kart-krew-dev/ring-racers!63
2025-10-20 13:55:23 -05:00
skirlez
fda4e6542e Save replay if map abruptly changes 2025-10-20 13:55:23 -05:00
Eidolon
f67c53e130 Merge branch 'me-when-i-am-string-and-dancing' into 'master'
Fix inline `V_STRINGDANCE` (hex 0x02) in strings not resetting y offset

See merge request kart-krew-dev/ring-racers!65
2025-10-20 13:50:31 -05:00
Eidolon
2a7b4c1396 Merge branch 'eid-hyudoro-sonicboom' into 'master'
Don't play sonic boom sounds in hyudoro either

See merge request kart-krew-dev/ring-racers-internal!2962
2025-10-19 20:35:45 -05:00
Eidolon
feb9787187 Don't play sonic boom sounds in hyudoro either 2025-10-19 18:57:48 -05:00
Antonio Martinez
8194b0ee8d Amp stat stuff closer to old values in most cases 2025-10-19 19:05:00 -04:00
Antonio Martinez
653393e5a9 Retune stat-based amp gains 2025-10-19 19:03:05 -04:00
toaster
b7a50f1123 Demo rewind doubled to 10s
Got a lot of feedback over time that 5s was too little for how frequently you need to rewind to go back through an extended interaction. Probably a better minimum
2025-10-19 23:40:25 +01:00
Gunla
54e9534615 Merge branch 'eggmark-bail' into 'master'
No bailing Eggmark, 30% speed -> 45%

See merge request kart-krew-dev/ring-racers-internal!2957
2025-10-19 18:28:59 +00:00
toaster
151b91f204 M_GetRecordMode
Simplifies a lot of copypasted "modeprefix" evaluations into one simple call.
Only G_UpdateRecordReplays is seperate.
Resolves two issues:
- Guest replay ghosts have been broken for a while and nobody noticed (untracked)
- Cannot watch back hivolt replays (#1656)
In addition, makes it way easier to add new modeprefixes later.
2025-10-19 17:58:18 +01:00
toaster
cb48d53f02 Leader-penalty counter
Allows for penalising 1st in a positiondelay-friendly way.
Does not increment outside of circuit, with only one player, in Sealed Stars or Cooperative contexts.
Currently applies to Hyudoro only, since that one's *really* instant in terms of giving the frontrunner an inadvisable tool for single-tic position flickers.
2025-10-19 16:34:03 +01:00
Antonio Martinez
c224b38685 Instaexplode bail if eggmark is rolling OR counting 2025-10-18 18:22:52 -04:00
Eidolon
19600c2589 Merge branch 'encore-demap' into 'master'
More encore/tweak remapping limits

Closes #1520 and #1284

See merge request kart-krew-dev/ring-racers-internal!2954
2025-10-18 10:11:13 -05:00
Eidolon
f59dc1e3a2 Merge branch 'seperate-best-times' into 'master'
Resolve ring-racers#112

Closes ring-racers#112

See merge request kart-krew-dev/ring-racers-internal!2952
2025-10-18 10:10:47 -05:00
Eidolon
b8769f14de Merge branch 'eid-more-reducevfx' into 'master'
More reducevfx

See merge request kart-krew-dev/ring-racers-internal!2951
2025-10-18 10:10:05 -05:00
Antonio Martinez
725882ab76 No bailing Eggmark, 30% speed -> 45% 2025-10-18 06:12:22 -04:00
haya
5019203872 fix: inline string dance not resetting y offset 2025-10-18 10:46:55 +08:00
Antonio Martinez
773736da46 IMMEDIATELY reduce bot rubberbanding when cornering 2025-10-17 19:23:17 -04:00
Eidolon
b7437a6565 Merge public master 2025-10-17 15:50:53 -05:00
Eidolon
6fcfd452aa Merge branch 'chatclose' into 'master'
Improved netgame chat input eating

See merge request kart-krew-dev/ring-racers!40
2025-10-17 15:46:10 -05:00
zander3312
3e1311473d Improved netgame chat input eating 2025-10-17 15:46:09 -05:00
toaster
1737db47db Prevent encore/tweak remapping of:
- Flybot 767 (resolves #1520)
- Trick Charge (resolves #1284)
- MT_BLOCKRING, MT_BLOCKBODY
- Amps and EXP
- MT_AMPRING, MT_AMPBODY, MT_AMPAURA, MT_AMPBURST
- Sonic Boom
- MT_TRIPWIREOK, MT_TRIPWIRELOCKOUT
- GOT IT!
- Rainbow Dash Ring
- Adventure Air Booster
- CDSS1 UFO
- Follower Bubble overlay
- Powerup Aura
- Script Question-Mark (from hint ring)
- Ball Switch
2025-10-17 18:59:29 +01:00
toaster
65f388de3a Less spike softlock (resolves ring-racers#65)
- Less strength in the first place
- Attenuates strength on repeated pokes
- Always stumbles since that seemed to be what was happening previously
2025-10-17 18:18:05 +01:00
toaster
e11751ddf7 Resolve ring-racers#112 2025-10-17 12:53:17 +01:00
toaster
4faf85eab4 Fix code provided by ring-racers!51
It just straight up wasn't functional, and ERRORMODE pointed me to it
Make the provideable amount use INT32 so the max/min macros actually do something
(but also don't use nested min-maxes just to reduce reliance on macros)
2025-10-17 11:25:04 +01:00
AJ Martinez
d3163d73cf Edit Default.md 2025-10-17 10:05:32 +00:00
Eidolon
06cf1b914a Merge public master 2025-10-16 21:22:21 -05:00
Eidolon
3e185efa44 Don't flicker drift electricity with reducevfx 2025-10-16 20:54:25 -05:00
Eidolon
56533a1876 Use std::string_view::npos in EggTVData 2025-10-16 20:06:57 -05:00
Eidolon
d37590f84c Merge branch 'fix-eggtv-names-and-crash' into 'master'
Fix Egg TV replay names being wrong, fix crash if your replay name was 1 character

Closes #267 and #110

See merge request kart-krew-dev/ring-racers!64
2025-10-16 20:01:21 -05:00
skirlez
c40c9a87d3 Fix Egg TV replay names being wrong, fix crash if your replay name was 1 character 2025-10-16 20:01:21 -05:00
Eidolon
eaf2e34efb Make <150% sonic boom flash transparent instead in reducevfx 2025-10-16 17:45:37 -05:00
Eidolon
99542a5b00 Disable invinc sprite flicker, MT_INVULNFLASH in reducevfx 2025-10-16 16:44:12 -05:00
SteelT
7e618b888e Fix bot voting being done under the context of human players 2025-10-15 23:52:20 -04:00
Eidolon
507a4b6c91 Fix unintended ternary bad parse 2025-10-15 22:11:30 -05:00
Eidolon
fd248e7e58 Merge public master 2025-10-15 22:10:08 -05:00
Ashnal
2fe37dc996 Merge branch 'insecticide' into 'master'
Immediately disable tether when holding completed Insta-Whip charge

See merge request kart-krew-dev/ring-racers-internal!2942
2025-10-16 03:00:09 +00:00
Ashnal
bfa4b11b6d Merge branch 'eid-more-vfx-reductions' into 'master'
Reduce invinc rainbow, grow/shrink, ring boost, countdown flashing in reducevfx

See merge request kart-krew-dev/ring-racers-internal!2944
2025-10-16 02:58:40 +00:00
Eidolon
1d3eda5926 Merge branch '309-fix' into 'master'
Resolved Issue #309 by Detecting Overflows & Underflows in Set Instructions to p->itemAmount and p->backupItemAmount

Closes #309

See merge request kart-krew-dev/ring-racers!51
2025-10-15 21:58:08 -05:00
SpringEThing
2ddc4c4867 Resolved Issue #309 by Detecting Overflows & Underflows in Set Instructions to p->itemAmount and p->backupItemAmount 2025-10-15 21:58:08 -05:00
Ashnal
e6da621dec Merge branch 'no-startline-in-special' into 'master'
No start awards in Special

See merge request kart-krew-dev/ring-racers-internal!2941
2025-10-16 02:56:44 +00:00
Ashnal
7f568779d8 Merge branch 'fix-tox-bot-amps' into 'master'
PvPAmpReward: always award at least 1 amp

See merge request kart-krew-dev/ring-racers-internal!2945
2025-10-16 02:55:38 +00:00
Ashnal
3befb83abf Merge branch 'amp-sound' into 'master'
Play amp sounds at player mo

Closes ring-racers#317

See merge request kart-krew-dev/ring-racers-internal!2946
2025-10-16 02:54:57 +00:00
Ashnal
d5154314e5 Merge branch 'goner-photosensitivity-brake' into 'master'
Photosensitivity warning is just informational now

See merge request kart-krew-dev/ring-racers-internal!2943
2025-10-16 02:53:36 +00:00
Ashnal
bbe55cda4b Photosensitivity warning is just informational now 2025-10-16 02:53:35 +00:00
Ashnal
3b0e3ec75c Merge branch 'orbinaut-waterski-new' into 'master'
orbinaut waterski is now based on owner waterski at the time of throw

See merge request kart-krew-dev/ring-racers-internal!2940
2025-10-16 02:53:18 +00:00
Ashnal
3cadaf70cc Merge branch 'no-hyu-clash' into 'master'
Hyudoro doesn't clash

Closes ring-racers#357

See merge request kart-krew-dev/ring-racers-internal!2947
2025-10-16 02:48:17 +00:00
Ashnal
3150c1f62e Merge branch 'reflected-item-fix' into 'master'
droptarget reflected items are dangerous to the thower again

See merge request kart-krew-dev/ring-racers-internal!2948
2025-10-16 02:47:28 +00:00
Eidolon
15413bf6d0 Merge branch 'fix-snatch-deployed-edgecase' into 'master'
Don't move backup item into main slot if player has item deployed

See merge request kart-krew-dev/ring-racers!59
2025-10-15 21:39:10 -05:00
skirlez
90ec96a2b0 Don't move backup item into main slot if player has item deployed 2025-10-15 21:39:10 -05:00
Eidolon
50d83e4b6d Merge branch 'fix-bot-replacement-negative-points' into 'master'
Fix replacement bot in last place keeping negative points from previous bot

See merge request kart-krew-dev/ring-racers!61
2025-10-15 21:36:12 -05:00
Superstarxalien
6182b83518 Fix replacement bot in last place keeping negative points from previous bot 2025-10-15 21:36:12 -05:00
Eidolon
6fa09928c8 Merge branch 'tune-reset' into 'master'
Prevent non-dynamic tunes remapped by ACS from remaining remapped after map ends.

See merge request kart-krew-dev/ring-racers!24
2025-10-15 21:28:49 -05:00
Freaky Mutant Man
9fb3719a8d Prevent non-dynamic tunes remapped by ACS from remaining remapped after map ends. 2025-10-15 21:28:48 -05:00
Eidolon
a02f1f7506 Merge branch 'fixMapDementia' into 'master'
Fix map vote dementia

See merge request kart-krew-dev/ring-racers!60
2025-10-15 21:19:41 -05:00
JugadorXEI
3425727ca6 Fix map vote dementia 2025-10-15 21:19:41 -05:00
Ashnal
0aad29ff68 droptarget reflected items are dangerous to the thower again 2025-10-15 20:07:47 -04:00
Antonio Martinez
83a0ca6a1f Hyudoro doesn't clash 2025-10-15 02:01:12 -04:00
Antonio Martinez
c7d137de5c Play amp sounds at player mo 2025-10-14 06:13:03 -04:00
Antonio Martinez
78e4724e2d PvPAmpReward: always award at least 1 amp 2025-10-14 03:31:31 -04:00
Eidolon
7e0fa35b7f Reduce invinc rainbow, grow/shrink, ring boost flashing 2025-10-13 21:13:29 -05:00
Antonio Martinez
71dc52d732 Immediately cut tether when completing Insta-Whip charge 2025-10-13 06:11:50 -04:00
Antonio Martinez
14c82a9f5a No start awards in Special 2025-10-13 01:36:42 -04:00
Ashnal
d782a6fdb7 orbinaut waterski is now based on owner waterski at the time of throw 2025-10-13 01:26:38 -04:00
Eidolon
2270813eee Bump modversion for 2.4 rc7 2025-10-12 19:28:24 -05:00
71 changed files with 2397 additions and 411 deletions

View file

@ -1,3 +1,7 @@
# Have you already searched the issue tracker for issues similar to yours?
(Use the search bar on https://gitlab.com/kart-krew-dev/ring-racers/-/issues - duplicate reports make it tough for us to keep track of things.)
# What version of Ring Racers are you playing?
(Replace this text with the version number. You can see this on the title screen at the bottom-left corner.)

View file

@ -130,6 +130,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
lua_itemroulettelib.c
lua_respawnvarslib.c
lua_waypointslib.c
lua_grandprixlib.c
lua_profile.cpp
k_kart.c
k_respawn.c

View file

@ -1654,6 +1654,32 @@ bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::
return false;
}
/*--------------------------------------------------
bool CallFunc_PlayerSkinRealName(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Returns the activating player's skin real name.
--------------------------------------------------*/
bool CallFunc_PlayerSkinRealName(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
Environment *env = &ACSEnv;
auto info = &static_cast<Thread *>(thread)->info;
(void)argV;
(void)argC;
if ((info != NULL)
&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
&& (info->mo->player != NULL))
{
UINT16 skin = info->mo->player->skin;
thread->dataStk.push(~env->getString( skins[skin]->realname )->idx);
return false;
}
thread->dataStk.push(0);
return false;
}
/*--------------------------------------------------
bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
@ -2409,6 +2435,8 @@ bool CallFunc_MusicStopAll(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM
bool CallFunc_MusicRemap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
ACSVM::MapScope *map = thread->scopeMap;
ACSVM::String *tuneStr = nullptr;
const char *tune = nullptr;
// 0: str tune - id for the tune to play
// 1: str song - lump name for the song to map to
@ -2419,6 +2447,16 @@ bool CallFunc_MusicRemap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::
return false;
}
tuneStr = map->getString(argV[0]);
tune = tuneStr->str;
// Do not allow ACS to remap Stereo Mode tunes.
if (strlen(tune) > 5
&& toupper(tune[0]) == 'S' && toupper(tune[1]) == 'T' && toupper(tune[2]) == 'E' && toupper(tune[3]) == 'R' && toupper(tune[4]) == 'E')
{
return false;
}
Music_Remap(map->getString(argV[0])->str, map->getString(argV[1])->str);
return false;

View file

@ -76,6 +76,7 @@ bool CallFunc_CountPushables(ACSVM::Thread *thread, const ACSVM::Word *argV, ACS
bool CallFunc_HaveUnlockableTrigger(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_HaveUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PlayerSkinRealName(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PlayerLosing(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PlayerExiting(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);

View file

@ -177,6 +177,7 @@ Environment::Environment()
addFuncDataACS0( 318, addCallFunc(CallFunc_CheckTutorialChallenge));
addFuncDataACS0( 319, addCallFunc(CallFunc_PlayerLosing));
addFuncDataACS0( 320, addCallFunc(CallFunc_PlayerExiting));
addFuncDataACS0( 321, addCallFunc(CallFunc_PlayerSkinRealName));
addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait));
addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition));

View file

@ -1008,7 +1008,7 @@ consvar_t cv_dummyprofilerumble = MenuDummy("dummyprofilerumble", "On").on_off()
consvar_t cv_dummyscramble = MenuDummy("dummyscramble", "Random").values({{0, "Random"}, {1, "Points"}});
void CV_SPBAttackChanged(void);
consvar_t cv_dummyspbattack = MenuDummy("dummyspbattack", "Off").on_off().onchange(CV_SPBAttackChanged);
consvar_t cv_dummyspbattack = MenuDummy("dummyspbattack", "Off").on_off().onchange_noinit(CV_SPBAttackChanged);
consvar_t cv_dummyspectate = MenuDummy("dummyspectate", "Spectator").values({{0, "Spectator"}, {1, "Playing"}});

View file

@ -105,6 +105,7 @@ boolean server = true; // true or false but !server == client
#define client (!server)
boolean nodownload = false;
boolean serverrunning = false;
boolean connectedtodedicated = false;
INT32 serverplayer = 0;
char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
@ -1271,6 +1272,7 @@ static boolean SV_SendServerConfig(INT32 node)
netbuffer->u.servercfg.gamestate = (UINT8)gamestate;
netbuffer->u.servercfg.gametype = (UINT8)gametype;
netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame;
netbuffer->u.servercfg.dedicated = (boolean)dedicated;
netbuffer->u.servercfg.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value));
netbuffer->u.servercfg.allownewplayer = cv_allownewplayer.value;
@ -2247,7 +2249,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
#ifdef HAVE_THREADS
I_lock_mutex(&k_menu_mutex);
#endif
M_UpdateMenuCMD(0, true);
M_UpdateMenuCMD(0, true, false);
if (cl_mode == CL_CONFIRMCONNECT)
{
@ -2502,6 +2504,7 @@ static void Command_connect(void)
// we don't request a restart unless the filelist differs
server = false;
connectedtodedicated = false;
// Get the server node.
if (netgame)
@ -2729,6 +2732,19 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
K_CheckBumpers();
P_CheckRacers();
// Reset map headers' justPlayed and anger records
// when there are no players in a dedicated server.
// Otherwise maps get angry at newly-joined players
// that don't deserve it.
if (dedicated && D_NumPlayers() == 0)
{
for (INT32 i = 0; i < nummapheaders; i++)
{
mapheaderinfo[i]->justPlayed = 0;
mapheaderinfo[i]->anger = 0;
}
}
}
void CL_Reset(void)
@ -2748,6 +2764,7 @@ void CL_Reset(void)
multiplayer = false;
servernode = 0;
server = true;
connectedtodedicated = false;
doomcom->numnodes = 1;
doomcom->numslots = 1;
SV_StopServer();
@ -4261,6 +4278,11 @@ boolean Playing(void)
return (server && serverrunning) || (client && cl_mode == CL_CONNECTED);
}
boolean InADedicatedServer(void)
{
return Playing() && (dedicated || connectedtodedicated);
}
boolean SV_SpawnServer(void)
{
#ifdef TESTERS
@ -4363,6 +4385,7 @@ void SV_StartSinglePlayerServer(INT32 dogametype, boolean donetgame)
{
INT32 lastgametype = gametype;
server = true;
connectedtodedicated = false;
multiplayer = (modeattacking == ATTACKING_NONE);
joinedIP[0] = '\0'; // Make sure to empty this so that we don't save garbage when we start our own game. (because yes we use this for netgames too....)
@ -4989,6 +5012,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
G_SetGametype(netbuffer->u.servercfg.gametype);
modifiedgame = netbuffer->u.servercfg.modifiedgame;
connectedtodedicated = netbuffer->u.servercfg.dedicated;
memcpy(server_context, netbuffer->u.servercfg.server_context, 8);

View file

@ -231,6 +231,7 @@ struct serverconfig_pak
UINT8 gametype;
UINT8 modifiedgame;
boolean dedicated;
char server_context[8]; // Unique context id, generated at server startup.
@ -407,7 +408,7 @@ struct resultsall_pak
struct say_pak
{
char message[HU_MAXMSGLEN];
char message[HU_MAXMSGLEN + 1];
UINT8 target;
UINT8 flags;
UINT8 source;
@ -465,7 +466,7 @@ struct doomdata_t
client3cmd_pak client3pak; // 264 bytes(?)
client4cmd_pak client4pak; // 324 bytes(?)
servertics_pak serverpak; // 132495 bytes (more around 360, no?)
serverconfig_pak servercfg; // 773 bytes
serverconfig_pak servercfg; // 777 bytes
UINT8 textcmd[MAXTEXTCMD+2]; // 66049 bytes (wut??? 64k??? More like 258 bytes...)
char filetxpak[sizeof (filetx_pak)];// 139 bytes
char fileack[sizeof (fileack_pak)];
@ -558,6 +559,7 @@ extern boolean server;
extern boolean serverrunning;
#define client (!server)
extern boolean dedicated; // For dedicated server
extern boolean connectedtodedicated; // Client that is connected to a dedicated server.
extern UINT16 software_MAXPACKETLENGTH;
extern boolean acceptnewnode;
extern SINT8 servernode;
@ -670,6 +672,7 @@ void CL_UpdateServerList(void);
void CL_TimeoutServerList(void);
// Is there a game running
boolean Playing(void);
boolean InADedicatedServer(void);
// Advance client-to-client pubkey verification flow
void UpdateChallenges(void);

View file

@ -107,17 +107,17 @@ extern "C" consvar_t cv_lua_profile, cv_menuframeskip;
/* Manually defined asset hashes
*/
#define ASSET_HASH_BIOS_PK3 "5f9093a5c6abfb77ab518ac530312564"
#define ASSET_HASH_BIOS_PK3 "36201c4690256d133dff7d3879436dff"
#define ASSET_HASH_SCRIPTS_PK3 "56be3c47192870c3265f19cf024e860e"
#define ASSET_HASH_GFX_PK3 "24a59ebaa74f253dbec55b00328accb9"
#define ASSET_HASH_TEXTURES_GENERAL_PK3 "609b683d3efc291ea28dd4e50d731f34"
#define ASSET_HASH_TEXTURES_SEGAZONES_PK3 "9c39dfc868680ffd5f44a7269971e419"
#define ASSET_HASH_TEXTURES_ORIGINALZONES_PK3 "6c49b9b6e273efd79c51c25fed85b290"
#define ASSET_HASH_GFX_PK3 "9e91306851cb6619124b37533cfbf029"
#define ASSET_HASH_TEXTURES_GENERAL_PK3 "3b81c0645b9e0580c1675f2eb70c4250"
#define ASSET_HASH_TEXTURES_SEGAZONES_PK3 "2e87cb9dddae7d32656932fdad32b22f"
#define ASSET_HASH_TEXTURES_ORIGINALZONES_PK3 "f15f974dbd17c9ce1b60bf31cf12d246"
#define ASSET_HASH_CHARS_PK3 "5c8c34c5623acf984e3f654da4509126"
#define ASSET_HASH_FOLLOWERS_PK3 "4b61428e5f2ec806de398de8a5fba5f0"
#define ASSET_HASH_MAPS_PK3 "bafab7e445c762de568294016ff6eefb"
#define ASSET_HASH_MAPS_PK3 "d744ac9747e078220a986ab295721182"
#define ASSET_HASH_UNLOCKS_PK3 "a4de35ba9f83829ced44dfc1316ba33e"
#define ASSET_HASH_STAFFGHOSTS_PK3 "a05c77ba7cfabfa453fe7b1784926ebd"
#define ASSET_HASH_STAFFGHOSTS_PK3 "4248d1fb6eb14c6b359f739c118249cc"
#define ASSET_HASH_SHADERS_PK3 "bc0b47744d457956db2ee9ea00f59eff"
#ifdef USE_PATCH_FILE
#define ASSET_HASH_PATCH_PK3 "00000000000000000000000000000000"
@ -343,7 +343,7 @@ void D_ProcessEvents(boolean callresponders)
// Update menu CMD
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
M_UpdateMenuCMD(i, false);
M_UpdateMenuCMD(i, false, chat_keydown);
}
}
@ -864,6 +864,7 @@ void D_SRB2Loop(void)
if (dedicated)
server = true;
connectedtodedicated = dedicated;
// Pushing of + parameters is now done back in D_SRB2Main, not here.
@ -1597,6 +1598,7 @@ void D_SRB2Main(void)
// for dedicated server
dedicated = M_CheckParm("-dedicated") != 0;
connectedtodedicated = dedicated;
if (dedicated)
{
usedTourney = true;

View file

@ -3015,9 +3015,19 @@ static void Got_Mapcmd(const UINT8 **cp, INT32 playernum)
CON_LogMessage(M_GetText("Speeding off to level...\n"));
}
if (demo.playback && !demo.timing)
precache = false;
// Save demo in case map change happened after level finish
// (either manually with the map command, or with a redo vote)
// Isn't needed for time attack (and would also cause issues, as there
// G_RecordDemo (which sets demo.recording to true) is called before this runs)
if (demo.recording && modeattacking == ATTACKING_NONE)
G_CheckDemoStatus();
demo.willsave = (cv_recordmultiplayerdemos.value == 2);
demo.savebutton = 0;
@ -4474,7 +4484,7 @@ static void Command_Addfile(void)
}
// Add file on your client directly if it is trivial, or you aren't in a netgame.
if (!(netgame || multiplayer) || musiconly)
if (!netgame || musiconly)
{
P_AddWadFile(fn);
addedfiles[numfilesadded++] = fn;
@ -5719,6 +5729,11 @@ static void Got_SetupVotecmd(const UINT8 **cp, INT32 playernum)
memcpy(g_voteLevels, tempVoteLevels, sizeof(g_voteLevels));
// admin can force vote state whenever
// so we have to save this replay if it needs to be saved
if (demo.recording)
G_CheckDemoStatus();
G_SetGamestate(GS_VOTING);
Y_StartVote();
}
@ -6090,7 +6105,7 @@ static void Got_Cheat(const UINT8 **cp, INT32 playernum)
K_StopRoulette(&player->itemRoulette);
player->itemtype = item;
player->itemamount = amt;
K_SetPlayerItemAmount(player, amt);
if (amt == 0)
{

View file

@ -738,6 +738,7 @@ struct player_t
UINT8 position; // Used for Kart positions, mostly for deterministic stuff
UINT8 oldposition; // Used for taunting when you pass someone
UINT8 positiondelay; // Used for position number, so it can grow when passing
UINT8 leaderpenalty; // Used for penalising 1st in a positiondelay-friendly way
UINT8 teamposition; // Position, but only against other teams -- not your own.
UINT8 teamimportance; // Opposite of team position x2, with +1 for being in 1st.
@ -769,6 +770,7 @@ struct player_t
UINT8 wipeoutslow; // Timer before you slowdown when getting wiped out
UINT8 justbumped; // Prevent players from endlessly bumping into each other
UINT8 noEbrakeMagnet; // Briefly disable 2.2 responsive ebrake if you're bumped by another player.
UINT8 wallSpikeDampen; // 2.4 wallspikes can softlock in closed quarters... attenuate their violence
UINT8 tumbleBounces;
UINT16 tumbleHeight; // In *mobjscaled* fracunits, or mfu, not raw fu
UINT16 stunned; // Number of tics during which rings cannot be picked up

View file

@ -220,6 +220,7 @@ extern boolean imcontinuing; // Temporary flag while continuing
#define ATTACKING_LAP (1<<1)
#define ATTACKING_SPB (1<<2)
extern UINT8 modeattacking;
const char *M_GetRecordMode(void);
// menu demo things
extern UINT8 numDemos;

View file

@ -239,6 +239,7 @@ enum {false = 0, true = 1};
#endif
#define ATTRUNUSED __attribute__((unused))
#define ATTRUNOPTIMIZE __attribute__((optimize("O0")))
#elif defined (_MSC_VER)
#define ATTRNORETURN __declspec(noreturn)
#define ATTRINLINE __forceinline

View file

@ -3517,15 +3517,7 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
}
// end of player read (the 0xFF marker)
// so this is where we are to read our lua variables (if possible!)
if (demoflags & DF_LUAVARS) // again, used for compability, lua shit will be saved to replays regardless of if it's even been loaded
{
if (!gL) // No Lua state! ...I guess we'll just start one...
LUA_ClearState();
// No modeattacking check, DF_LUAVARS won't be present here.
LUA_UnArchive(&demobuf, false);
}
// see the DF_LUAVARS if later, though.
splitscreen = 0;
@ -3547,6 +3539,18 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
G_InitNew((demoflags & DF_ENCORE) != 0, gamemap, true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer.
// so this is where we are to read our lua variables (if possible!)
// we read it here because Lua player variables can have mobj references,
// and not having the map loaded causes crashes if that's the case.
if (demoflags & DF_LUAVARS) // again, used for compability, lua shit will be saved to replays regardless of if it's even been loaded
{
if (!gL) // No Lua state! ...I guess we'll just start one...
LUA_ClearState();
// No modeattacking check, DF_LUAVARS won't be present here.
LUA_UnArchive(&demobuf, false);
}
for (i = 0; i < numslots; i++)
{
UINT8 j;
@ -4212,6 +4216,7 @@ boolean G_CheckDemoStatus(void)
if (modeattacking || demo.willsave)
{
demo.willsave = false;
if (demobuf.p)
{
G_SaveDemo();

View file

@ -496,7 +496,11 @@ bademblem:
if (!gonnadrawtime && showownrecord)
{
stickermedalinfo.timetoreach = G_GetBestTime(map);
stickermedalinfo.timetoreach = (encoremode == true)
? mapheaderinfo[map]->records.spbattack.time
: mapheaderinfo[map]->records.timeattack.time;
if (!stickermedalinfo.timetoreach)
stickermedalinfo.timetoreach = UINT32_MAX;
}
if (stickermedalinfo.timetoreach != UINT32_MAX)
@ -584,6 +588,7 @@ static void G_UpdateRecordReplays(void)
char lastdemo[256], bestdemo[256];
const char *modeprefix = "";
// See also M_GetRecordMode
if (encoremode)
{
modeprefix = "spb-";
@ -1413,6 +1418,7 @@ boolean G_Responder(event_t *ev)
if (HU_Responder(ev))
{
hu_keystrokes = true;
chat_keydown = true;
return true; // chat ate the event
}
}
@ -1522,6 +1528,7 @@ boolean G_Responder(event_t *ev)
return true;
case ev_keyup:
chat_keydown = false; // prevents repeat inputs from inputs made with chat open
return false; // always let key up events filter down
case ev_mouse:
@ -2685,7 +2692,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
// SRB2kart
p->itemtype = itemtype;
p->itemamount = itemamount;
K_SetPlayerItemAmount(p, itemamount);
p->growshrinktimer = growshrinktimer;
p->karmadelay = 0;
p->eggmanblame = -1;
@ -4064,12 +4071,91 @@ UINT16 G_RandMap(UINT32 tolflags, UINT16 pprevmap, boolean ignoreBuffers, boolea
void G_AddMapToBuffer(UINT16 map)
{
#if 0
// DEBUG: make nearly everything but four race levels full justPlayed
// to look into what happens when a dedicated runs for seven million years.
INT32 justplayedvalue = TOLMaps(gametype) - VOTE_NUM_LEVELS;
UINT32 tolflag = G_TOLFlag(gametype);
// Find all the maps that are ok
INT32 i;
for (i = 0; i < nummapheaders; i++)
{
if (mapheaderinfo[i] == NULL)
{
continue;
}
if (mapheaderinfo[i]->lumpnum == LUMPERROR)
{
continue;
}
if ((mapheaderinfo[i]->typeoflevel & tolflag) == 0)
{
continue;
}
if (mapheaderinfo[i]->menuflags & LF2_HIDEINMENU)
{
// Don't include hidden
continue;
}
// Only care about restrictions if the host is a listen server.
if (!dedicated)
{
if (!(mapheaderinfo[i]->menuflags & LF2_NOVISITNEEDED)
&& !(mapheaderinfo[i]->records.mapvisited & MV_VISITED)
&& !(
mapheaderinfo[i]->cup
&& mapheaderinfo[i]->cup->cachedlevels[0] == i
))
{
// Not visited OR head of cup
continue;
}
if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[i]->records.mapvisited & MV_BEATEN))
{
// Not completed
continue;
}
}
if (M_MapLocked(i + 1) == true)
{
// We haven't earned this one.
continue;
}
mapheaderinfo[i]->justPlayed = justplayedvalue;
justplayedvalue -= 1;
if (justplayedvalue <= 0)
break;
}
#else
if (dedicated && D_NumPlayers() == 0)
return;
const size_t upperJustPlayedLimit = TOLMaps(gametype) - VOTE_NUM_LEVELS - 1;
if (mapheaderinfo[map]->justPlayed == 0) // Started playing a new map.
{
// Decrement every maps' justPlayed value.
INT32 i;
for (i = 0; i < nummapheaders; i++)
{
// If the map's justPlayed value is higher
// than what it should be, clamp it.
// (Usually a result of SOC files
// manipulating which levels are hidden.)
if (mapheaderinfo[i]->justPlayed > upperJustPlayedLimit)
{
mapheaderinfo[i]->justPlayed = upperJustPlayedLimit;
}
if (mapheaderinfo[i]->justPlayed > 0)
{
mapheaderinfo[i]->justPlayed--;
@ -4078,8 +4164,9 @@ void G_AddMapToBuffer(UINT16 map)
}
// Set our map's justPlayed value.
mapheaderinfo[map]->justPlayed = TOLMaps(gametype) - VOTE_NUM_LEVELS;
mapheaderinfo[map]->justPlayed = upperJustPlayedLimit;
mapheaderinfo[map]->anger = 0; // Reset voting anger now that we're playing it
#endif
}
//
@ -4997,7 +5084,10 @@ void G_AfterIntermission(void)
return;
}
else if (demo.recording && (modeattacking || demo.willsave))
{
demo.willsave = false;
G_SaveDemo();
}
else if (demo.recording)
G_ResetDemoRecording();
@ -5135,6 +5225,9 @@ static void G_DoContinued(void)
// when something new is added.
void G_EndGame(void)
{
// Clean up ACS music remaps.
Music_TuneReset();
// Handle voting
if (nextmap == NEXTMAP_VOTING)
{

View file

@ -81,6 +81,8 @@ extern struct menuqueue
UINT8 size;
UINT8 sending;
UINT8 anchor;
boolean clearing;
boolean cupqueue;
roundentry_t entries[ROUNDQUEUE_MAX];
} menuqueue;

View file

@ -1386,8 +1386,18 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
grTex = HWR_GetTexture(gl_midtexture, gl_sidedef->midtexture);
wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
wallVerts[0].t = wallVerts[1].t = (h - l + texturevpeg) * grTex->scaleY;
// Check if we should flip tripwire texture vertically for unpegged tripwires
if (R_ShouldFlipTripWire(gl_linedef))
{
// Flip texture coordinates vertically
wallVerts[0].t = wallVerts[1].t = texturevpeg * grTex->scaleY;
wallVerts[3].t = wallVerts[2].t = (h - l + texturevpeg) * grTex->scaleY;
}
else
{
wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
wallVerts[0].t = wallVerts[1].t = (h - l + texturevpeg) * grTex->scaleY;
}
wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
}
@ -1433,8 +1443,19 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
texturevpeg = textureheight[gl_sidedef->midtexture]*repeats - h + polybottom;
else
texturevpeg = polytop - h;
wallVerts[2].t = texturevpeg * grTex->scaleY;
wallVerts[1].t = (h - l + texturevpeg) * grTex->scaleY;
// Apply tripwire flipping for slope correction as well
if (R_ShouldFlipTripWire(gl_linedef))
{
// Flip texture coordinates vertically
wallVerts[1].t = texturevpeg * grTex->scaleY;
wallVerts[2].t = (h - l + texturevpeg) * grTex->scaleY;
}
else
{
wallVerts[2].t = texturevpeg * grTex->scaleY;
wallVerts[1].t = (h - l + texturevpeg) * grTex->scaleY;
}
}
wallVerts[2].y = FIXED_TO_FLOAT(h);
@ -1531,8 +1552,18 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
grTex = HWR_GetTexture(gl_midtexture, gl_sidedef->midtexture);
wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * grTex->scaleY;
// Check if we should flip tripwire texture vertically for single-sided lines too
if (R_ShouldFlipTripWire(gl_linedef))
{
// Flip texture coordinates vertically
wallVerts[0].t = wallVerts[1].t = texturevpeg * grTex->scaleY;
wallVerts[3].t = wallVerts[2].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * grTex->scaleY;
}
else
{
wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * grTex->scaleY;
}
wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;

View file

@ -85,6 +85,7 @@ patch_t *frameslash; // framerate stuff. Used in screen.c
static player_t *plr;
boolean hu_keystrokes; // :)
boolean chat_keydown;
boolean chat_on; // entering a chat message?
boolean g_voicepushtotalk_on; // holding PTT?
static char w_chat[HU_MAXMSGLEN + 1];

View file

@ -124,6 +124,9 @@ typedef enum
// some functions
void HU_AddChatText(const char *text, boolean playsound);
// set true when key is pressed while chat is open
extern boolean chat_keydown;
// set true when entering a chat message
extern boolean chat_on;

View file

@ -1229,6 +1229,7 @@ boolean I_InitTcpNetwork(void)
if (M_CheckParm("-server") || dedicated)
{
server = true;
connectedtodedicated = dedicated;
// If a number of clients (i.e. nodes) is specified, the server will wait for the clients
// to connect before starting.

View file

@ -13780,7 +13780,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -13807,7 +13807,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -13915,7 +13915,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -13942,7 +13942,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -13969,7 +13969,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -13996,7 +13996,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14023,7 +14023,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14050,7 +14050,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14077,7 +14077,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14104,7 +14104,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14131,7 +14131,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14158,7 +14158,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14185,7 +14185,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14212,7 +14212,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14239,7 +14239,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -14266,7 +14266,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -17561,7 +17561,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_SPECIAL, // flags
MF_NOGRAVITY|MF_SPECIAL|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -17588,7 +17588,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_SPECIAL, // flags
MF_NOGRAVITY|MF_SPECIAL|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -17615,7 +17615,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_supert, // activesound
MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -17642,7 +17642,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOCLIPHEIGHT|MF_SPECIAL|MF_NOGRAVITY, // flags
MF_NOCLIPHEIGHT|MF_SPECIAL|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -17669,7 +17669,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -17858,7 +17858,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SPECIAL|MF_SHOOTABLE, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SPECIAL|MF_SHOOTABLE|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -19020,7 +19020,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // damage
sfx_None, // activesound
MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT, // flags
MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL, // raisestate
},
@ -19048,7 +19048,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // damage
sfx_None, // activesound
MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT, // flags
MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL, // raisestate
},
@ -19076,7 +19076,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // damage
sfx_None, // activesound
MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT, // flags
MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL, // raisestate
},
@ -19481,7 +19481,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTPUNT, // flags
MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTPUNT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -19508,7 +19508,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -19643,7 +19643,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -19697,7 +19697,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -19724,7 +19724,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -20129,7 +20129,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -20156,7 +20156,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -22764,7 +22764,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_EXP
@ -22790,7 +22790,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING, // flags
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_FLYBOT767
@ -22816,7 +22816,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING, // flags
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_STONESHOE

View file

@ -740,8 +740,11 @@ static void K_SpawnOvertimeLaser(fixed_t x, fixed_t y, fixed_t scale)
case 0:
P_SetMobjState(mo, S_OVERTIME_BULB1);
if (leveltime & 1)
mo->frame += 1;
if (!cv_reducevfx.value)
{
if (leveltime & 1)
mo->frame += 1;
}
//P_SetScale(mo, mapobjectscale);
zpos += 35 * mo->scale * flip;
@ -749,10 +752,13 @@ static void K_SpawnOvertimeLaser(fixed_t x, fixed_t y, fixed_t scale)
case 1:
P_SetMobjState(mo, S_OVERTIME_LASER);
if (leveltime & 1)
mo->frame += 3;
else
mo->frame += (leveltime / 2) % 3;
if (!cv_reducevfx.value)
{
if (leveltime & 1)
mo->frame += 3;
else
mo->frame += (leveltime / 2) % 3;
}
//P_SetScale(mo, scale);
zpos += 346 * mo->scale * flip;
@ -763,8 +769,11 @@ static void K_SpawnOvertimeLaser(fixed_t x, fixed_t y, fixed_t scale)
case 2:
P_SetMobjState(mo, S_OVERTIME_BULB2);
if (leveltime & 1)
mo->frame += 1;
if (!cv_reducevfx.value)
{
if (leveltime & 1)
mo->frame += 1;
}
//P_SetScale(mo, mapobjectscale);
break;
@ -1029,6 +1038,8 @@ boolean K_EndBattleRound(player_t *victor)
// exiting, the round has already ended.
return false;
}
UINT32 topscore = 0;
if (gametyperules & GTR_POINTLIMIT)
{
@ -1037,7 +1048,27 @@ boolean K_EndBattleRound(player_t *victor)
// TODO: a "won the round" bool used for sorting
// position / intermission, so we aren't completely
// clobbering the individual scoring.
victor->roundscore = 100;
// This isn't quite the above TODO but it's something?
// For purposes of score-to-EXP conversion, we need to not lock the winner to an arbitrarily high score.
// Instead, let's find the highest score, and if they're not the highest scoring player,
// give them a bump so they *are* the highest scoring player.
for (INT32 i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
if ((&players[i])->roundscore > topscore)
{
topscore = (&players[i])->roundscore;
}
}
if (victor->roundscore <= topscore)
{
victor->roundscore = topscore + 3;
}
if (G_GametypeHasTeams() == true && victor->team != TEAM_UNASSIGNED)
{

View file

@ -227,7 +227,7 @@ void K_UpdateMatchRaceBots(void)
{
const UINT16 defaultbotskin = R_BotDefaultSkin();
UINT8 difficulty;
UINT8 pmax = (dedicated ? MAXPLAYERS-1 : MAXPLAYERS);
UINT8 pmax = (InADedicatedServer() ? MAXPLAYERS-1 : MAXPLAYERS);
UINT8 numplayers = 0;
UINT8 numbots = 0;
UINT8 numwaiting = 0;
@ -343,12 +343,7 @@ void K_UpdateMatchRaceBots(void)
if (numbots < wantedbots)
{
// We require MORE bots!
UINT8 newplayernum = 0;
if (dedicated)
{
newplayernum = 1;
}
UINT8 newplayernum = InADedicatedServer() ? 1 : 0;
// Rearrange usable bot skins list to prevent gaps for randomised selection
if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)

View file

@ -721,6 +721,11 @@ boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2)
t2->angle += ANGLE_180;
if (t2->type == MT_JAWZ)
P_SetTarget(&t2->tracer, t2->target); // Back to the source!
// Reflected item becomes owned by the DT owner, so it becomes dangerous the the thrower
if (t1->target && !P_MobjWasRemoved(t1->target))
P_SetTarget(&t2->target, t1->target);
t2->threshold = 10;
}

View file

@ -6865,7 +6865,13 @@ static void K_drawKartStartCountdown(void)
}
}
if ((leveltime % (2*5)) / 5) // blink
int flashrate = 5;
if (cv_reducevfx.value)
{
flashrate = 35;
}
if ((leveltime % (2*flashrate)) / flashrate) // blink
pnum += 5;
if (r_splitscreen) // splitscreen
pnum += 10;

View file

@ -72,6 +72,38 @@
// comeback is Battle Mode's karma comeback, also bool
// mapreset is set when enough players fill an empty server
UINT8 K_SetPlayerItemAmount(player_t *player, INT32 amount)
{
if (amount & ~UINT8_MAX)
{
// having bits outside of valid range means time to cap
amount = (amount < 0) ? 0 : UINT8_MAX;
}
return (player->itemamount = amount);
}
UINT8 K_SetPlayerBackupItemAmount(player_t *player, INT32 amount)
{
if (amount & ~UINT8_MAX)
{
// having bits outside of valid range means time to cap
amount = (amount < 0) ? 0 : UINT8_MAX;
}
return (player->backupitemamount = amount);
}
UINT8 K_AdjustPlayerItemAmount(player_t *player, INT32 amount)
{
return K_SetPlayerItemAmount(player, player->itemamount + amount);
}
UINT8 K_AdjustPlayerBackupItemAmount(player_t *player, INT32 amount)
{
return K_SetPlayerBackupItemAmount(player, player->backupitemamount + amount);
}
void K_PopBubbleShield(player_t *player)
{
if (player->curshield != KSHIELD_BUBBLE)
@ -81,7 +113,7 @@ void K_PopBubbleShield(player_t *player)
player->curshield = KSHIELD_NONE;
player->itemtype = 0;
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
player->itemflags &= ~(IF_ITEMOUT|IF_EGGMANOUT);
K_AddHitLag(player->mo, 4, false);
@ -1243,12 +1275,50 @@ boolean K_KartSolidBounce(mobj_t *bounceMobj, mobj_t *solidMobj)
if (solidMobj->type == MT_WALLSPIKE)
{
if (bounceMobj->player && bounceMobj->hitlag)
{
bounceMobj->player->justbumped = bumptime;
return false;
}
//CONS_Printf("%sattenuation is %d\n", (leveltime & 1 ? "" : " "), bounceMobj->player->wallSpikeDampen);
// Always thrust out towards the tip
// (...don't try to roll our own bad calculations,
// just make this behave like a wallspring...)
P_DoSpringEx(bounceMobj, mapobjectscale, 0, solidMobj->info->damage,
solidMobj->angle, SKINCOLOR_NONE);
fixed_t spikeforce = solidMobj->info->damage;
fixed_t deflection = 0;
if (bounceMobj->player && !G_CompatLevel(0x0011))
{
// Okay no we need to use bad calculations just to
// prevent softlocks -- repeated touches attenuate
UINT8 atten = bounceMobj->player->wallSpikeDampen;
deflection = atten * FRACUNIT;
if (bounceMobj->angle - solidMobj->angle >= ANGLE_180)
deflection = -deflection;
K_StumblePlayer(bounceMobj->player);
bounceMobj->player->tumbleBounces = TUMBLEBOUNCES; // Only one
atten++;
while (atten)
{
// We want a power relationship - towards zero but not quite reaching it.
spikeforce = (2 * spikeforce) / 3;
atten--;
}
if (bounceMobj->player->wallSpikeDampen < UINT8_MAX
&& bounceMobj->player->justbumped < bumptime-2)
bounceMobj->player->wallSpikeDampen++;
}
P_DoSpringEx(bounceMobj, mapobjectscale, 0, spikeforce,
solidMobj->angle + R_PointToAngle2(0, 0, spikeforce, deflection),
SKINCOLOR_NONE);
K_PlayerJustBumped(bounceMobj->player);
@ -3931,7 +4001,7 @@ static void K_GetKartBoostPower(player_t *player)
if (player->eggmanexplode) // Ready-to-explode
{
ADDBOOST(6*FRACUNIT/20, FRACUNIT, 0); // + 30% top speed, + 100% acceleration, +0% handling
ADDBOOST(9*FRACUNIT/20, FRACUNIT, 0); // + 45% top speed, + 100% acceleration, +0% handling
}
if (player->vortexBoost) // Holding wavedash vortex (assigned in K_UpdateWavedashIndicator!)
@ -4007,7 +4077,12 @@ static void K_GetKartBoostPower(player_t *player)
// This should always remain the last boost stack before tethering
if (player->botvars.rubberband > FRACUNIT && K_PlayerUsesBotMovement(player) == true)
{
ADDBOOST(player->botvars.rubberband - FRACUNIT, 0, 0);
fixed_t rubber = player->botvars.rubberband - FRACUNIT;
if (!G_CompatLevel(0x0011))
rubber = FixedRescale(player->botvars.recentDeflection, 0, BOTMAXDEFLECTION, Easing_Linear, rubber, 8*rubber/10);
ADDBOOST(rubber, 0, 0);
}
if (player->draftpower > 0) // Drafting
@ -4500,6 +4575,9 @@ boolean K_PvPAmpReward(UINT32 award, player_t *attacker, player_t *defender)
if (!K_PlayerUsesBotMovement(attacker) && K_PlayerUsesBotMovement(defender))
award /= 2;
if (award == 0)
award = 1;
return award;
}
@ -4516,7 +4594,13 @@ void K_SpawnAmps(player_t *player, UINT8 amps, mobj_t *impact)
UINT32 itemdistance = min(FRACUNIT-1, K_GetItemRouletteDistance(player, D_NumPlayersInRace())); // cap this to FRACUNIT-1, so it doesn't wrap when turning it into fixed_t
fixed_t itemdistmult = FRACUNIT + min(FRACUNIT, (itemdistance<<FRACBITS) / MAXAMPSCALINGDIST);
UINT16 scaledamps = min(amps, amps * (10 + (9-player->kartspeed) - (9-player->kartweight)) / 10);
INT32 weighting = player->kartweight - player->kartspeed;
INT32 minweight = 1 - 9;
INT32 maxweight = 9 - 1;
UINT16 scaledamps = FixedRescale(weighting, minweight, maxweight, Easing_Linear, amps/2, 5*amps/4);
// Debug print for scaledamps calculation
// CONS_Printf("K_SpawnAmps: player=%s, amps=%d, kartspeed=%d, kartweight=%d, itemdistance=%d, itemdistmult=%0.2f, statscaledamps=%d, distscaledamps=%d\n",
// player_names[player-players], amps, player->kartspeed, player->kartweight,
@ -4939,7 +5023,6 @@ boolean K_Overdrive(player_t *player)
player->amps = 0;
player->overdriveready = 0;
player->overdrivelenient = false;
return true;
}
@ -6772,6 +6855,14 @@ static void K_SpawnDriftSparks(player_t *player)
I_Assert(player->mo != NULL);
I_Assert(!P_MobjWasRemoved(player->mo));
if (player->driftcharge >= dsthree)
{
if (cv_reducevfx.value || leveltime % 2 == 0)
{
K_SpawnDriftElectricity(player);
}
}
if (leveltime % 2 == 1)
return;
@ -6902,11 +6993,6 @@ static void K_SpawnDriftSparks(player_t *player)
P_SetTarget(&spark->owner, player->mo);
spark->renderflags |= RF_REDUCEVFX;
}
if (player->driftcharge >= dsthree)
{
K_SpawnDriftElectricity(player);
}
}
static void K_SpawnAIZDust(player_t *player)
@ -7698,7 +7784,7 @@ void K_PuntMine(mobj_t *origMine, mobj_t *punter)
if (mineOwner->player->itemamount)
{
mineOwner->player->itemamount--;
K_AdjustPlayerItemAmount(mineOwner->player, -1);
}
if (!mineOwner->player->itemamount)
@ -8268,7 +8354,7 @@ void K_PopPlayerShield(player_t *player)
player->curshield = KSHIELD_NONE;
player->itemtype = KITEM_NONE;
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
K_UnsetItemOut(player);
}
@ -8516,9 +8602,9 @@ void K_DropHnextList(player_t *player)
player->itemflags &= ~IF_EGGMANOUT;
}
else if ((player->itemflags & IF_ITEMOUT)
&& (dropall || (--player->itemamount <= 0)))
&& (dropall || (K_AdjustPlayerItemAmount(player, -1) <= 0)))
{
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
K_UnsetItemOut(player);
player->itemtype = KITEM_NONE;
}
@ -8773,7 +8859,7 @@ void K_RepairOrbitChain(mobj_t *orbit)
}
if (orbit->target && !P_MobjWasRemoved(orbit->target) && orbit->target->player->itemamount != num)
orbit->target->player->itemamount = num;
K_SetPlayerItemAmount(orbit->target->player, num);
}
}
@ -8926,7 +9012,7 @@ static void K_MoveHeldObjects(player_t *player)
}
else if (player->itemflags & IF_ITEMOUT)
{
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
K_UnsetItemOut(player);
player->itemtype = KITEM_NONE;
}
@ -8945,7 +9031,7 @@ static void K_MoveHeldObjects(player_t *player)
}
else if (player->itemflags & IF_ITEMOUT)
{
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
K_UnsetItemOut(player);
player->itemtype = KITEM_NONE;
}
@ -9171,12 +9257,12 @@ static void K_MoveHeldObjects(player_t *player)
// If we can move our backup item into main slots, do so.
static void K_TryMoveBackupItem(player_t *player)
{
if (player->itemtype && player->itemtype == player->backupitemtype)
if (player->itemtype && player->itemtype == player->backupitemtype && !(player->itemflags & IF_ITEMOUT))
{
player->itemamount += player->backupitemamount;
K_AdjustPlayerItemAmount(player, player->backupitemamount);
player->backupitemtype = 0;
player->backupitemamount = 0;
K_SetPlayerBackupItemAmount(player, 0);
S_StartSound(player->mo, sfx_mbs54);
}
@ -9184,10 +9270,10 @@ static void K_TryMoveBackupItem(player_t *player)
if (player->itemtype == KITEM_NONE && player->backupitemtype && P_CanPickupItem(player, PICKUP_PAPERITEM))
{
player->itemtype = player->backupitemtype;
player->itemamount = player->backupitemamount;
K_SetPlayerItemAmount(player, player->backupitemamount);
player->backupitemtype = 0;
player->backupitemamount = 0;
K_SetPlayerBackupItemAmount(player, 0);
S_StartSound(player->mo, sfx_mbs54);
}
@ -10412,7 +10498,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
{
player->rings = 0;
player->itemtype = 0;
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
player->itemRoulette.active = false;
}
}
@ -10424,12 +10510,18 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
UINT8 counted = 0;
UINT32 firstRaw = 0;
// We want the average position of the back quarter...
UINT32 requiredPosition = ((D_NumPlayersInRace()*3)/4) - 1;
// ...except in teamplay, where we want the true average.
if (g_teamplay)
requiredPosition = 1;
for (UINT8 i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] == false || players[i].spectator == true || players[i].exiting)
continue;
if (players[i].position != 1 && players[i].position >= ((D_NumPlayersInRace()*3)/4) - 1) // Not in 1st, but also back quarter of the average (-1 guy, for leeway)
if (players[i].position != 1 && players[i].position >= requiredPosition)
{
counted++;
average += K_UndoMapScaling(players[i].distancetofinish);
@ -10451,6 +10543,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
UINT32 TOO_CLOSE = average + 6500; // Start gaining here, lose if closer
UINT32 WAY_TOO_CLOSE = average + 5500; // Lose at max rate here
fixed_t comeback = K_TeamComebackMultiplier(player);
if (comeback > FRACUNIT)
{
REALLY_FAR = FixedDiv(REALLY_FAR, comeback);
TOO_CLOSE = FixedDiv(TOO_CLOSE, comeback);
WAY_TOO_CLOSE = FixedDiv(WAY_TOO_CLOSE, comeback);
}
fixed_t MAX_GAIN_PER_SEC = FRACUNIT/20; // % assist to gain per sec when REALLY_FAR
fixed_t MAX_LOSS_PER_SEC = FRACUNIT/5; // % assist to lose per sec when WAY_TOO_CLOSE
@ -10945,6 +11046,11 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
P_StartQuakeFromMobj(7, 50 * player->mo->scale, 2048 * player->mo->scale, player->mo);
player->bailhitlag = false;
/*
if (player->markedfordeath)
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
*/
}
if ((!P_PlayerInPain(player) && player->bailcharge >= 5) || player->bailcharge >= BAIL_MAXCHARGE)
@ -10952,6 +11058,13 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAIL);
P_SetTarget(&bail->target, player->mo);
if ((player->itemRoulette.active && player->itemRoulette.eggman) || player->eggmanexplode > 0)
{
player->markedfordeath = true;
player->eggmanexplode = 1;
player->rings = -20;
}
if (player->itemRoulette.active)
{
player->itemRoulette.active = false;
@ -10961,7 +11074,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
K_DeleteHnextList(player);
K_DropItems(player);
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
player->itemtype = 0;
player->rocketsneakertimer = 0;
@ -10969,7 +11082,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->itemamount)
{
K_DropPaperItem(player, player->itemtype, player->itemamount);
player->itemtype = player->itemamount = 0;
player->itemtype = K_SetPlayerItemAmount(player, 0);
}
*/
@ -11027,7 +11140,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->preventfailsafe)
player->preventfailsafe--;
if (player->tripwireUnstuck > 150)
UINT8 unstuckthreshold = (onground) ? 80 : 40;
if (player->tripwireUnstuck > unstuckthreshold)
{
player->tripwireUnstuck = 0;
K_DoIngameRespawn(player);
@ -11037,12 +11152,12 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
{
if (P_IsDisplayPlayer(player))
{
S_StartSoundAtVolume(NULL, sfx_mbs43, 255);
S_StartSoundAtVolume(NULL, sfx_mbs43, 255);
S_StartSoundAtVolume(player->mo, sfx_mbs43, 255);
S_StartSoundAtVolume(player->mo, sfx_mbs43, 255);
}
else
{
S_StartSoundAtVolume(NULL, sfx_mbs43, 127);
S_StartSoundAtVolume(player->mo, sfx_mbs43, 127);
}
player->amppickup--;
}
@ -11616,7 +11731,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
// activate a mine while you're out of its radius,
// the SAME tic it sets your itemamount to 0
// ...:dumbestass:
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_PlayAttackTaunt(player->mo);
player->botvars.itemconfirm = 0;
}
@ -11789,9 +11904,15 @@ void K_KartResetPlayerColor(player_t *player)
fullbright = true;
int invinc_rotation_delay = 2;
if (cv_reducevfx.value)
{
invinc_rotation_delay = 8;
}
if (player->invincibilitytimer > defaultTime)
{
player->mo->color = K_RainbowColor(leveltime / 2);
player->mo->color = K_RainbowColor(leveltime / invinc_rotation_delay);
player->mo->colorized = true;
skip = true;
}
@ -11800,7 +11921,7 @@ void K_KartResetPlayerColor(player_t *player)
flicker += (defaultTime - player->invincibilitytimer) / TICRATE / 2;
}
if (leveltime % flicker == 0)
if (leveltime % flicker == 0 && !cv_reducevfx.value)
{
player->mo->color = SKINCOLOR_INVINCFLASH;
player->mo->colorized = true;
@ -11815,7 +11936,8 @@ void K_KartResetPlayerColor(player_t *player)
if (player->growshrinktimer) // Ditto, for grow/shrink
{
if (player->growshrinktimer % 5 == 0)
if ((!cv_reducevfx.value && player->growshrinktimer % 5 == 0) || (cv_reducevfx.value && player->growshrinktimer % 35 < 12))
{
player->mo->colorized = true;
player->mo->color = (player->growshrinktimer < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE);
@ -11846,13 +11968,18 @@ void K_KartResetPlayerColor(player_t *player)
}
boolean allowflashing = true;
int flashingrate = 1;
if (demo.playback && cv_reducevfx.value && !R_CanShowSkinInDemo(player->skin))
{
// messy condition stack for, specifically, disabling flashing effects when viewing a staff ghost replay of a currently hidden character
allowflashing = false;
}
if (cv_reducevfx.value)
{
flashingrate = 4;
}
if (player->overdrive && (leveltime & 1) && allowflashing)
if (player->overdrive && ((leveltime / flashingrate) & 1) && allowflashing)
{
player->mo->colorized = true;
fullbright = true;
@ -11867,7 +11994,7 @@ void K_KartResetPlayerColor(player_t *player)
goto finalise;
}
if (player->ringboost && (leveltime & 1) && allowflashing) // ring boosting
if (player->ringboost && ((leveltime / flashingrate) & 1) && allowflashing) // ring boosting
{
player->mo->colorized = true;
fullbright = true;
@ -13451,7 +13578,7 @@ void K_KartUpdatePosition(player_t *player)
{
// Ensure these are reset for spectators
player->position = 0;
player->positiondelay = 0;
player->positiondelay = player->leaderpenalty = 0;
player->teamposition = 0;
player->teamimportance = 0;
return;
@ -13578,33 +13705,48 @@ void K_KartUpdatePosition(player_t *player)
}
/* except in FREE PLAY */
if (player->curshield == KSHIELD_TOP &&
(gametyperules & GTR_CIRCUIT) &&
if ((gametyperules & GTR_CIRCUIT) &&
realplayers > 1 &&
!specialstageinfo.valid
&& !K_Cooperative())
{
/* grace period so you don't fall off INSTANTLY */
if (K_GetItemRouletteDistance(player, 8) < 2000 && player->topinfirst < 2*TICRATE) // "Why 8?" Literally no reason, but since we intend for constant-ish distance we choose a fake fixed playercount.
if (position == 1)
{
player->topinfirst++;
// Hyuu and other leader-penalty
if (player->leaderpenalty < POS_DELAY_TIME + 4)
player->leaderpenalty++;
}
else
else if (player->leaderpenalty != 0)
player->leaderpenalty--;
if (player->curshield == KSHIELD_TOP)
{
if (position == 1)
/* grace period so you don't fall off INSTANTLY */
if (K_GetItemRouletteDistance(player, 8) < 2000 && player->topinfirst < 2*TICRATE) // "Why 8?" Literally no reason, but since we intend for constant-ish distance we choose a fake fixed playercount.
{
Obj_GardenTopThrow(player);
player->topinfirst++;
}
else
{
if (player->topinfirst && (leveltime%3 == 0))
player->topinfirst--;
if (position == 1)
{
Obj_GardenTopThrow(player);
}
else
{
if (player->topinfirst && (leveltime%3 == 0))
player->topinfirst--;
}
}
}
else
{
player->topinfirst = 0;
}
}
else
{
player->topinfirst = 0;
player->leaderpenalty = player->topinfirst = 0;
}
player->position = position;
@ -13687,11 +13829,11 @@ void K_StripItems(player_t *player)
K_DropRocketSneaker(player);
K_DropKitchenSink(player);
player->itemtype = KITEM_NONE;
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
player->itemflags &= ~(IF_ITEMOUT|IF_EGGMANOUT);
player->backupitemtype = KITEM_NONE;
player->backupitemamount = 0;
K_SetPlayerBackupItemAmount(player, 0);
if (player->itemRoulette.eggman == false)
{
@ -15193,7 +15335,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
player->momentboost += 3;
angle_t flingangle = player->mo->angle + ((P_RandomByte(PR_ITEM_RINGS) & 1) ? -ANGLE_90 : ANGLE_90);
P_FlingBurst(player, flingangle, MT_DEBTSPIKE, 0, 3 * FRACUNIT / 2, 20, 4*FRACUNIT);
P_FlingBurst(player, flingangle, MT_DEBTSPIKE, TICRATE/2, 3 * FRACUNIT / 2, 20, 4*FRACUNIT);
S_StartSound(player->mo, sfx_gshae);
}
@ -15255,6 +15397,10 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
K_UnsetItemOut(player);
}
else if (player->bungee)
{
// michael_jordan.mov
}
else
{
switch (player->itemtype)
@ -15264,7 +15410,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
K_DoSneaker(player, 1);
K_PlayBoostTaunt(player->mo);
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
player->botvars.itemconfirm = 0;
}
break;
@ -15282,7 +15428,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
//K_DoSneaker(player, 2);
player->rocketsneakertimer = (itemtime*3);
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_UpdateHnextList(player, true);
for (moloop = 0; moloop < 2; moloop++)
@ -15313,7 +15459,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
max(7u * TICRATE + behindScaled, player->invincibilitytimer + 5u*TICRATE));
K_PlayPowerGloatSound(player->mo);
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
player->botvars.itemconfirm = 0;
}
break;
@ -15333,7 +15479,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD);
if (!mo)
{
player->itemamount = moloop;
K_SetPlayerItemAmount(player, moloop);
break;
}
mo->flags |= MF_NOCLIPTHING;
@ -15350,7 +15496,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_ThrowKartItem(player, false, MT_BANANA, -1, 0, 0);
K_PlayAttackTaunt(player->mo);
K_UpdateHnextList(player, false);
@ -15361,7 +15507,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
mobj_t *mo;
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
player->itemflags |= IF_EGGMANOUT;
S_StartSound(player->mo, sfx_s254);
mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD);
@ -15397,7 +15543,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD);
if (!mo)
{
player->itemamount = moloop;
K_SetPlayerItemAmount(player, moloop);
break;
}
mo->flags |= MF_NOCLIPTHING;
@ -15416,7 +15562,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0, 0);
K_PlayAttackTaunt(player->mo);
K_UpdateHnextList(player, false);
@ -15441,7 +15587,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD);
if (!mo)
{
player->itemamount = moloop;
K_SetPlayerItemAmount(player, moloop);
break;
}
mo->flags |= MF_NOCLIPTHING;
@ -15459,7 +15605,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_ThrowKartItem(player, true, MT_JAWZ, 1, 0, 0);
K_PlayAttackTaunt(player->mo);
K_UpdateHnextList(player, false);
@ -15487,7 +15633,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT))
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_ThrowKartItem(player, false, MT_SSMINE, 1, 1, 0);
K_PlayAttackTaunt(player->mo);
player->itemflags &= ~IF_ITEMOUT;
@ -15498,7 +15644,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
case KITEM_LANDMINE:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
if (player->throwdir > 0)
{
K_ThrowKartItem(player, true, MT_LANDMINE, -1, 0, 0);
@ -15533,7 +15679,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT))
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
mobj_t *drop = K_ThrowKartItem(player, (player->throwdir > 0), MT_DROPTARGET, -1, 0, 0);
P_SetTarget(&drop->tracer, player->mo);
K_PlayAttackTaunt(player->mo);
@ -15631,8 +15777,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
P_SetObjectMomZ(player->mo, -50*FRACUNIT, true);
}
*/
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
player->botvars.itemconfirm = 0;
player->ballhogcharge = 0;
player->ballhogburst = 0;
@ -15647,8 +15792,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
if (numhogs > 0) // no tapfire scams
{
K_SetItemOut(player); // need this to set itemscale
player->itemamount -= numhogs;
K_AdjustPlayerItemAmount(player, -numhogs);
K_PlayAttackTaunt(player->mo);
K_DoBallhogAttack(player, numhogs);
@ -15678,7 +15822,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
case KITEM_SPB:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_SetItemOut(player);
K_ThrowKartItem(player, true, MT_SPB, 1, 0, 0);
K_UnsetItemOut(player);
@ -15718,7 +15862,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
S_StartSound(player->mo, sfx_kc5a);
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
player->botvars.itemconfirm = 0;
}
break;
@ -15726,7 +15870,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
K_DoShrink(player);
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_PlayPowerGloatSound(player->mo);
player->botvars.itemconfirm = 0;
}
@ -15841,7 +15985,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
if (player->bubbleblowup > bubbletime*2)
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
if (player->throwdir == -1)
{
@ -15947,7 +16091,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->flamemeter = 0;
player->flamelength = 0;
player->itemflags &= ~IF_HOLDREADY;
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
}
}
else
@ -15981,7 +16125,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
case KITEM_HYUDORO:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
//K_DoHyudoroSteal(player); // yes. yes they do.
Obj_HyudoroDeploy(player->mo);
K_PlayAttackTaunt(player->mo);
@ -15994,7 +16138,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
K_PlayBoostTaunt(player->mo);
//K_DoPogoSpring(player->mo, 32<<FRACBITS, 2);
P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_POGOSPRING);
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
player->botvars.itemconfirm = 0;
}
break;
@ -16037,7 +16181,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
// player->strongdriftboost += TICRATE;
// player->driftboost += TICRATE;
K_AwardPlayerRings(player, 20*player->itemamount, true);
player->itemamount = 0;
K_SetPlayerItemAmount(player, 0);
player->botvars.itemconfirm = 0;
}
break;
@ -16062,7 +16206,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_ThrowKartItem(player, false, MT_SINK, 1, 2, 0);
K_PlayAttackTaunt(player->mo);
player->itemflags &= ~IF_ITEMOUT;
@ -16073,7 +16217,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
case KITEM_GACHABOM:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_SetItemOut(player); // need this to set itemscale
K_ThrowKartItem(player, true, MT_GACHABOM, 0, 0, 0);
K_UnsetItemOut(player);
@ -16111,7 +16255,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
K_UnsetItemOut(player);
}
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_PlayAttackTaunt(player->mo);
K_UpdateHnextList(player, false);
player->botvars.itemconfirm = 0;
@ -16127,7 +16271,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
K_UnsetItemOut(player);
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
K_PlayAttackTaunt(player->mo);
player->botvars.itemconfirm = 0;
}
@ -16137,7 +16281,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
&& !player->sadtimer)
{
player->sadtimer = stealtime;
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
player->botvars.itemconfirm = 0;
}
break;
@ -17553,20 +17697,20 @@ static boolean K_PickUp(player_t *player, mobj_t *picked)
if (player->itemtype == type && player->itemamount && !(player->itemflags & IF_ITEMOUT))
{
// We have this item in main slot but not deployed, just add it
player->itemamount += amount;
K_SetPlayerItemAmount(player, player->itemamount + amount);
}
else if (player->backupitemamount && player->backupitemtype)
{
// We already have a backup item, stack it if it can be stacked or discard it
if (player->backupitemtype == type)
{
player->backupitemamount += amount;
K_AdjustPlayerBackupItemAmount(player, amount);
}
else
{
K_DropPaperItem(player, player->backupitemtype, player->backupitemamount);
player->backupitemtype = type;
player->backupitemamount = amount;
K_SetPlayerBackupItemAmount(player, amount);
S_StartSound(player->mo, sfx_kc65);
}
}
@ -17574,7 +17718,7 @@ static boolean K_PickUp(player_t *player, mobj_t *picked)
{
// We have no backup item, load one up
player->backupitemtype = type;
player->backupitemamount = amount;
K_SetPlayerBackupItemAmount(player, amount);
}
S_StartSound(player->mo, sfx_aple);

View file

@ -41,7 +41,7 @@ Make sure this matches the actual number of states
#define INSTAWHIP_RINGDRAINEVERY (TICRATE/2)
#define INSTAWHIP_HOLD_DELAY (TICRATE*2)
// MUST be longer or equal to INSTAWHIP_CHARGETIME.
#define INSTAWHIP_TETHERBLOCK (TICRATE*4)
#define INSTAWHIP_TETHERBLOCK (3*TICRATE/4)
#define PUNISHWINDOW (G_CompatLevel(0x0010) ? 7*TICRATE/10 : 10*TICRATE/10)
#define BAIL_MAXCHARGE (84) // tics to bail when in painstate nad in air, on ground is half, if you touch this, also update Obj_BailChargeThink synced animation logic
@ -125,6 +125,11 @@ Make sure this matches the actual number of states
#define AUTORESPAWN_TIME (10*TICRATE)
#define AUTORESPAWN_THRESHOLD (7*TICRATE)
UINT8 K_SetPlayerItemAmount(player_t *player, INT32 amount);
UINT8 K_SetPlayerBackupItemAmount(player_t *player, INT32 amount);
UINT8 K_AdjustPlayerItemAmount(player_t *player, INT32 amount);
UINT8 K_AdjustPlayerBackupItemAmount(player_t *player, INT32 amount);
angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed);
void K_PopBubbleShield(player_t *player);

View file

@ -748,7 +748,7 @@ void M_SetMenuDelay(UINT8 i);
void M_SortServerList(void);
void M_UpdateMenuCMD(UINT8 i, boolean bailrequired);
void M_UpdateMenuCMD(UINT8 i, boolean bailrequired, boolean chat_open);
boolean M_Responder(event_t *ev);
boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt);
boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt);
@ -961,6 +961,10 @@ void M_MenuToLevelPreamble(UINT8 ssplayers, boolean nowipe);
void M_LevelSelected(INT16 add, boolean menuupdate);
boolean M_LevelSelectCupSwitch(boolean next, boolean skipones);
void M_LevelConfirmHandler(void);
void M_ClearQueueHandler(void);
void M_CupQueueHandler(cupheader_t *cup);
// dummy consvars for GP & match race setup
extern consvar_t cv_dummygpdifficulty;
extern consvar_t cv_dummykartspeed;

View file

@ -3340,6 +3340,21 @@ void M_DrawCupSelect(void)
M_DrawCupPreview(y, &templevelsearch);
M_DrawCupTitle(120 - ty, &templevelsearch);
const char *worktext = "Undo";
if (menuqueue.size)
worktext = "Undo";
else if (roundqueue.size)
worktext = "Clear Queue";
if (levellist.canqueue)
{
K_DrawGameControl(BASEVIDWIDTH/2, 6-ty, 0, va("%s Queue Cup<white> %s %s",
(templevelsearch.cup && templevelsearch.cup != &dummy_lostandfound && !roundqueue.size) ? "<z_animated>" : "<z_pressed><gray>",
(roundqueue.size || menuqueue.size) ? "<c_animated>" : "<c_pressed><gray>",
worktext), 1, TINY_FONT, 0);
}
if (templevelsearch.grandprix == false && templevelsearch.cup != NULL)
{

View file

@ -1084,7 +1084,7 @@ void M_SetMenuDelay(UINT8 i)
}
}
void M_UpdateMenuCMD(UINT8 i, boolean bailrequired)
void M_UpdateMenuCMD(UINT8 i, boolean bailrequired, boolean chat_open)
{
UINT8 mp = max(1, setup_numplayers);
@ -1097,6 +1097,10 @@ void M_UpdateMenuCMD(UINT8 i, boolean bailrequired)
menucmd[i].buttonsHeld = menucmd[i].buttons;
menucmd[i].buttons = 0;
// Eat inputs made when chat is open
if (chat_open && pausemenu.closing)
return;
if (G_PlayerInputDown(i, gc_screenshot, mp)) { menucmd[i].buttons |= MBT_SCREENSHOT; }
if (G_PlayerInputDown(i, gc_startmovie, mp)) { menucmd[i].buttons |= MBT_STARTMOVIE; }
if (G_PlayerInputDown(i, gc_startlossless, mp)) { menucmd[i].buttons |= MBT_STARTLOSSLESS; }

View file

@ -1956,7 +1956,8 @@ void K_KartGetItemResult(player_t *const player, kartitems_t getitem)
UINT8 itemamount = K_ItemResultToAmount(getitem, &player->itemRoulette);
if (cv_kartdebugitem.value != KITEM_NONE && cv_kartdebugitem.value == player->itemtype && cv_kartdebugamount.value > 1)
itemamount = cv_kartdebugamount.value;
player->itemamount = itemamount;
K_SetPlayerItemAmount(player, itemamount);
if (player->itemtype == KITEM_SPB)
Obj_SPBEradicateCapsules();

View file

@ -345,13 +345,10 @@ void level_tally_t::Init(player_t *player)
}
}
if ((gametypes[gt]->rules & GTR_CIRCUIT) == GTR_CIRCUIT)
if ((gametypes[gt]->rules & GTR_CIRCUIT) == GTR_CIRCUIT && K_GetNumGradingPoints() > 0) // EXP should be a rule type, but here we are
{
if (player->exp)
{
exp = player->exp;
totalExp = EXP_TARGET;
}
exp = static_cast<UINT16>(std::max<fixed_t>(player->exp, 0)); // The scoring calc doesn't subtract anymore, so using 0 is okay and will not wrap
totalExp = EXP_TARGET;
}
if (battleprisons)

View file

@ -1845,7 +1845,7 @@ static void Y_TickVoteStageStrike(void)
static void Y_TickVoteSelection(void)
{
boolean everyone_voted = true;/* the default condition */
INT32 i;
INT32 i, j;
if (vote.tic < 3*(NEWTICRATE/7)) // give it some time before letting you control it :V
{
@ -1872,7 +1872,6 @@ static void Y_TickVoteSelection(void)
if (vote.players[i].sentTimeOutVote == false)
{
// Move off of striked stages for the timeout vote.
INT32 j;
for (j = 0; j < VOTE_NUM_LEVELS; j++)
{
if (g_votes_striked[vote.players[i].selection] == false)
@ -1944,9 +1943,9 @@ static void Y_TickVoteSelection(void)
{
// bots vote randomly
INT32 rng = M_RandomKey(VOTE_NUM_LEVELS);
for (i = 0; i < VOTE_NUM_LEVELS; i++)
for (j = 0; j < VOTE_NUM_LEVELS; j++)
{
if (g_votes_striked[i] == false)
if (g_votes_striked[j] == false)
{
break;
}
@ -2095,7 +2094,7 @@ static void Y_InitVoteDrawing(void)
INT32 i = 0, j = 0;
vote_draw.ruby_icon = W_CachePatchName("RUBYICON", PU_STATIC);
vote_draw.strike_icon = W_CachePatchName("K_NOBLNS", PU_STATIC);
vote_draw.strike_icon = W_CachePatchName("VT_LSTRK", PU_STATIC);
for (i = 0; i < PLANET_FRAMES; i++)
{

View file

@ -46,6 +46,7 @@
#include "k_hitlag.h"
#include "music.h" // music functions necessary for lua integration
#include "k_terrain.h"
#include "k_grandprix.h"
#include "lua_script.h"
#include "lua_libs.h"
@ -256,6 +257,12 @@ static const struct {
{META_POWERUPVARS, "powerupvars_t"},
{META_ICECUBEVARS, "icecubevars_t"},
{META_SKYBOX, "skybox_t"},
{META_CUP, "cupheader_t"},
{META_GPRANK, "gprank_t"},
{META_GPRANKLEVEL, "gprank_level_t"},
{META_GPRANKLEVELPERPLAYER, "gprank_level_perplayer_t"},
{META_ROUNDENTRY, "roundentry_t"},
{NULL, NULL}
};
@ -480,6 +487,13 @@ static int lib_mMusicRemap(lua_State *L)
{
return LUA_ErrNoTune(L, tune_id);
}
// Do not allow Lua to remap Stereo Mode tunes.
if (strlen(tune_id) > 5
&& toupper(tune_id[0]) == 'S' && toupper(tune_id[1]) == 'T' && toupper(tune_id[2]) == 'E' && toupper(tune_id[3]) == 'R' && toupper(tune_id[4]) == 'E')
{
return LUA_ErrStereo(L, tune_id);
}
Music_Remap(tune_id, music_name);
@ -3991,6 +4005,84 @@ static int lib_kMomentumAngle(lua_State *L)
return 1;
}
static int lib_kPvPAmpReward(lua_State *L)
{
UINT32 award = luaL_checkinteger(L, 1);
player_t *attacker = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
player_t *defender = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
NOHUD
INLEVEL
if (!attacker || !defender)
return LUA_ErrInvalid(L, "player_t");
lua_pushinteger(L, K_PvPAmpReward(award, attacker, defender));
return 1;
}
static int lib_kSpawnAmps(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
UINT8 amps = luaL_checkinteger(L, 2);
mobj_t *impact = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (!impact)
return LUA_ErrInvalid(L, "mobj_t");
K_SpawnAmps(player, amps, impact);
return 0;
}
static int lib_kSpawnEXP(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
UINT8 exp = luaL_checkinteger(L, 2);
mobj_t *impact = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (!impact)
return LUA_ErrInvalid(L, "mobj_t");
K_SpawnEXP(player, exp, impact);
return 0;
}
static int lib_kAwardPlayerAmps(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
UINT8 amps = luaL_checkinteger(L, 2);
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
K_AwardPlayerAmps(player, amps);
return 0;
}
static int lib_kOverdrive(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushboolean(L, K_Overdrive(player));
return 1;
}
static int lib_kDefensiveOverdrive(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushboolean(L, K_DefensiveOverdrive(player));
return 1;
}
static int lib_kDoInstashield(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@ -5270,6 +5362,48 @@ static int lib_kPlayerCanUseItem(lua_State *L)
return 1;
}
static int lib_kGetGradingFactorAdjustment(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
UINT32 gradingpoint = luaL_checkinteger(L, 2);
INLEVEL
NOHUD
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushfixed(L, K_GetGradingFactorAdjustment(player, gradingpoint));
return 1;
}
static int lib_kGetGradingFactorMinMax(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
boolean max = luaL_checkboolean(L, 2);
INLEVEL
NOHUD
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushfixed(L, K_GetGradingFactorMinMax(player, max));
return 1;
}
static int lib_kGetEXP(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INLEVEL
NOHUD
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushinteger(L, K_GetEXP(player));
return 1;
}
static int lib_kGetNumGradingPoints(lua_State *L)
{
INLEVEL
lua_pushinteger(L, K_GetNumGradingPoints());
return 1;
}
static int lib_kEggmanTransfer(lua_State *L)
{
player_t *source = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@ -5294,6 +5428,31 @@ static int lib_kSetTireGrease(lua_State *L)
return 0;
}
static int lib_kApplyStun(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
mobj_t *inflictor = NULL;
mobj_t *source = NULL;
INT32 damage = luaL_optinteger(L, 4, 0);
UINT8 damagetype = luaL_optinteger(L, 5, 0);
INLEVEL
NOHUD
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (!lua_isnil(L, 2) && lua_isuserdata(L, 2)) {
inflictor = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
if (!inflictor)
return LUA_ErrInvalid(L, "mobj_t");
}
if (!lua_isnil(L, 3) && lua_isuserdata(L, 3)) {
source = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
}
K_ApplyStun(player, inflictor, source, damage, damagetype);
return 0;
}
static int lib_kGetCollideAngle(lua_State *L)
{
mobj_t *t1 = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@ -6786,7 +6945,13 @@ static luaL_Reg lib[] = {
{"K_MomentumAngleEx",lib_kMomentumAngleEx},
{"K_MomentumAngleReal",lib_kMomentumAngleReal},
{"K_MomentumAngle",lib_kMomentumAngle},
{"K_PvPAmpReward",lib_kPvPAmpReward},
{"K_SpawnAmps",lib_kSpawnAmps},
{"K_SpawnEXP",lib_kSpawnEXP},
{"K_AwardPlayerAmps",lib_kAwardPlayerAmps},
{"K_AwardPlayerRings",lib_kAwardPlayerRings},
{"K_Overdrive",lib_kOverdrive},
{"K_DefensiveOverdrive",lib_kDefensiveOverdrive},
{"K_DoInstashield",lib_kDoInstashield},
{"K_DoPowerClash",lib_kDoPowerClash},
{"K_DoGuardBreak",lib_kDoGuardBreak},
@ -6895,10 +7060,15 @@ static luaL_Reg lib[] = {
{"K_BumperInflate",lib_kBumperInflate},
{"K_ThunderDome",lib_kThunderDome},
{"K_PlayerCanUseItem",lib_kPlayerCanUseItem},
{"K_GetGradingFactorAdjustment",lib_kGetGradingFactorAdjustment},
{"K_GetGradingFactorMinMax",lib_kGetGradingFactorMinMax},
{"K_GetEXP",lib_kGetEXP},
{"K_GetNumGradingPoints",lib_kGetNumGradingPoints},
{"K_PlayerGuard",lib_kPlayerGuard},
{"K_FastFallBounce",lib_kFastFallBounce},
{"K_EggmanTransfer",lib_kEggmanTransfer},
{"K_SetTireGrease",lib_kSetTireGrease},
{"K_ApplyStun",lib_kApplyStun},
{"K_GetCollideAngle",lib_kGetCollideAngle},

968
src/lua_grandprixlib.c Normal file
View file

@ -0,0 +1,968 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by Freaky Mutant Man.
// Copyright (C) 2025 by Kart Krew.
// Copyright (C) 2020 by Sonic Team Junior.
// Copyright (C) 2016 by John "JTE" Muniz.
//
// 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 lua_grandprixlib.c
/// \brief Grand Prix, cup and rank info for Lua scripting.
#include "doomdef.h"
#include "fastcmp.h"
#include "doomstat.h"
#include "k_grandprix.h"
#include "k_rank.h"
#include "g_game.h"
#include "lua_script.h"
#include "lua_libs.h"
#define UNIMPLEMENTED luaL_error(L, LUA_QL("cupheader_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", cup_opt[field])
#define RNOFIELDGP luaL_error(L, LUA_QL("grandprixinfo") " has no field named " LUA_QS, field)
#define RNOFIELDCH luaL_error(L, LUA_QL("cupheader_t") " has no field named " LUA_QS, field)
#define RNOFIELDGR luaL_error(L, LUA_QL("gprank_t") " has no field named " LUA_QS, field)
#define RNOFIELDGRL luaL_error(L, LUA_QL("gprank_level_t") " has no field named " LUA_QS, field)
#define RNOFIELDGRLP luaL_error(L, LUA_QL("gprank_level_perplayer_t") " has no field named " LUA_QS, field)
#define RNOFIELDRQ luaL_error(L, LUA_QL("roundqueue") " has no field named " LUA_QS, field)
#define RNOFIELDRE luaL_error(L, LUA_QL("roundentry_t") " has no field named " LUA_QS, field)
#define GPERR luaL_error(L, LUA_QL("grandprixinfo") " field " LUA_QS " cannot be accessed while grandprixinfo.gp is false.", grandprix_opt[field])
#define ROUNDCUEERR luaL_error(L, LUA_QL("roundqueue") " field " LUA_QS " cannot be accessed while roundqueue.size is 0.", grandprix_opt[field])
enum grandprix {
grandprix_gp = 0,
grandprix_cup,
grandprix_gamespeed,
grandprix_encore,
grandprix_masterbots,
grandprix_initialize,
grandprix_initalize,
grandprix_wonround,
grandprix_eventmode,
grandprix_specialdamage,
grandprix_rank,
};
enum cup {
cup_valid = 0,
cup_id,
cup_monitor,
cup_name,
cup_namehash,
cup_realname,
cup_icon,
cup_levellist,
cup_cachedlevels,
cup_numlevels,
cup_numbonus,
cup_emeraldnum,
cup_playcredits,
cup_hintcondition,
cup_cache_cuplock,
cup_windata,
cup_next,
};
enum gprank {
gprank_valid = 0,
gprank_numplayers,
gprank_totalplayers,
gprank_position,
gprank_skin,
gprank_winpoints,
gprank_totalpoints,
gprank_exp,
gprank_totalexp,
gprank_continuesused,
gprank_prisons,
gprank_totalprisons,
gprank_rings,
gprank_totalrings,
gprank_specialwon,
gprank_scoreposition,
gprank_scoregppoints,
gprank_scoreexp,
gprank_scoreprisons,
gprank_scorerings,
gprank_scorecontinues,
gprank_scoretotal,
gprank_numlevels,
gprank_levels,
};
enum gprank_level {
gprank_level_valid = 0,
gprank_level_id,
gprank_level_event,
gprank_level_time,
gprank_level_totalexp,
gprank_level_totalprisons,
gprank_level_continues,
gprank_level_perplayer,
};
enum gprank_level_perplayer {
gprank_level_perplayer_valid = 0,
gprank_level_perplayer_position,
gprank_level_perplayer_rings,
gprank_level_perplayer_exp,
gprank_level_perplayer_prisons,
gprank_level_perplayer_gotspecialprize,
gprank_level_perplayer_grade,
};
enum roundcue { // named "roundcue" to avoid overlap with actual roundqueue struct
roundcue_size = 0, // placed first since we'll be checking this to see if the roundqueue is currently in use
roundcue_roundnum,
roundcue_position,
roundcue_netcommunicate,
roundcue_writetextmap,
roundcue_snapshotmaps,
roundcue_entries,
};
enum roundentry {
roundentry_valid = 0,
roundentry_mapnum,
roundentry_gametype,
roundentry_encore,
roundentry_rankrestricted,
roundentry_overridden,
};
static const char *const grandprix_opt[] = {
"gp",
"cup",
"gamespeed",
"encore",
"masterbots",
"initialize",
"initalize",
"wonround",
"eventmode",
"specialdamage",
"rank",
NULL
};
static const char *const cup_opt[] = {
"valid",
"id",
"monitor",
"name",
"namehash",
"realname",
"icon",
"levellist",
"cachedlevels",
"numlevels",
"numbonus",
"emeraldnum",
"playcredits",
"hintcondition",
"cache_cuplock",
"windata",
"next",
NULL
};
static const char *const gprank_opt[] = {
"valid",
"numplayers",
"totalplayers",
"position",
"skin",
"winpoints",
"totalpoints",
"exp",
"totalexp",
"continuesused",
"prisons",
"totalprisons",
"rings",
"totalrings",
"specialwon",
"scoreposition",
"scoregppoints",
"scoreexp",
"scoreprisons",
"scorerings",
"scorecontinues",
"scoretotal",
"numlevels",
"levels",
NULL
};
static const char *const gprank_level_opt[] = {
"valid",
"id",
"event",
"time",
"totalexp",
"totalprisons",
"continues",
"perplayer",
NULL
};
static const char *const gprank_level_perplayer_opt[] = {
"valid",
"position",
"rings",
"exp",
"prisons",
"gotspecialprize",
"grade",
NULL
};
static const char *const roundcue_opt[] = {
"size",
"roundnum",
"position",
"netcommunicate",
"writetextmap",
"snapshotmaps",
"entries",
NULL
};
static const char *const roundentry_opt[] = {
"valid",
"mapnum",
"gametype",
"encore",
"rankrestricted",
"overridden",
NULL
};
static int grandprix_get(lua_State *L)
{
enum grandprix field = luaL_checkoption(L, 2, grandprix_opt[0], grandprix_opt);
// Don't return any grandprixinfo values while not in a GP.
if (!grandprixinfo.gp)
{
switch (field)
{
case grandprix_gp:
lua_pushboolean(L, false);
return 1;
default:
return GPERR;
}
}
switch (field)
{
case grandprix_gp:
lua_pushboolean(L, grandprixinfo.gp);
break;
case grandprix_cup:
LUA_PushUserdata(L, grandprixinfo.cup, META_CUP);
break;
case grandprix_gamespeed:
lua_pushnumber(L, grandprixinfo.gamespeed);
break;
case grandprix_encore:
lua_pushboolean(L, grandprixinfo.encore);
break;
case grandprix_masterbots:
lua_pushboolean(L, grandprixinfo.masterbots);
break;
case grandprix_initialize:
case grandprix_initalize: // when the struct misspelled the variable...
lua_pushboolean(L, grandprixinfo.initalize);
break;
case grandprix_wonround:
lua_pushboolean(L, grandprixinfo.wonround);
break;
case grandprix_eventmode:
lua_pushnumber(L, grandprixinfo.eventmode);
break;
case grandprix_specialdamage:
lua_pushnumber(L, grandprixinfo.specialDamage);
break;
case grandprix_rank:
LUA_PushUserdata(L, &grandprixinfo.rank, META_GPRANK);
break;
default:
return RNOFIELDGP;
}
return 1;
}
static int grandprix_set(lua_State *L)
{
return luaL_error(L, LUA_QL("grandprixinfo") " struct cannot be edited by Lua.");
}
static int cup_get(lua_State *L)
{
cupheader_t *cup = *((cupheader_t **)luaL_checkudata(L, 1, META_CUP));
enum cup field = luaL_checkoption(L, 2, cup_opt[0], cup_opt);
if (!cup)
{
switch (field)
{
case cup_valid:
lua_pushboolean(L, false);
return 1;
default:
return LUA_ErrInvalid(L, "cupheader_t");
}
}
switch (field)
{
case cup_valid:
lua_pushboolean(L, true);
break;
case cup_id:
lua_pushnumber(L, cup->id);
break;
case cup_monitor:
lua_pushnumber(L, cup->monitor);
break;
case cup_name:
lua_pushstring(L, cup->name);
break;
case cup_namehash:
return UNIMPLEMENTED;
break;
case cup_realname:
lua_pushstring(L, cup->realname);
break;
case cup_icon:
lua_pushstring(L, cup->icon);
break;
case cup_levellist:
lua_createtable(L, ((cup->numlevels) + (cup->numbonus)), 0);
for (size_t i = 0; i < ((cup->numlevels) + (cup->numbonus)); i++)
{
lua_pushstring(L, cup->levellist[i]);
lua_rawseti(L, -2, 1 + i);
}
break;
case cup_cachedlevels:
lua_createtable(L, ((cup->numlevels) + (cup->numbonus)), 0);
for (size_t i = 0; i < CUPCACHE_MAX; i++)
{
lua_pushnumber(L, (cup->cachedlevels[i])+1);
lua_rawseti(L, -2, 1 + i);
}
break;
case cup_numlevels:
lua_pushnumber(L, cup->numlevels);
break;
case cup_numbonus:
lua_pushnumber(L, cup->numbonus);
break;
case cup_emeraldnum:
lua_pushnumber(L, cup->emeraldnum);
break;
case cup_playcredits:
lua_pushboolean(L, cup->playcredits);
break;
case cup_hintcondition:
lua_pushnumber(L, cup->hintcondition);
break;
case cup_cache_cuplock:
return UNIMPLEMENTED;
break;
case cup_windata:
return UNIMPLEMENTED;
break;
case cup_next:
return UNIMPLEMENTED;
break;
default:
return RNOFIELDCH;
}
return 1;
}
static int cup_set(lua_State *L)
{
return luaL_error(L, LUA_QL("cupheader_t") " struct cannot be edited by Lua.");
}
static int gprank_get(lua_State *L)
{
gpRank_t *gprank = *((gpRank_t **)luaL_checkudata(L, 1, META_GPRANK));
enum gprank field = luaL_checkoption(L, 2, gprank_opt[0], gprank_opt);
if (!gprank)
{
switch (field)
{
case gprank_valid:
lua_pushboolean(L, false);
return 1;
default:
return LUA_ErrInvalid(L, "gprank_t");
}
}
switch (field)
{
case gprank_valid:
lua_pushboolean(L, true);
break;
case gprank_numplayers:
lua_pushnumber(L, gprank->numPlayers);
break;
case gprank_totalplayers:
lua_pushnumber(L, gprank->totalPlayers);
break;
case gprank_position:
lua_pushnumber(L, gprank->position);
break;
case gprank_skin:
lua_pushnumber(L, gprank->skin);
break;
case gprank_winpoints:
lua_pushnumber(L, gprank->winPoints);
break;
case gprank_totalpoints:
lua_pushnumber(L, gprank->totalPoints);
break;
case gprank_exp:
lua_pushnumber(L, gprank->exp);
break;
case gprank_totalexp:
lua_pushnumber(L, gprank->totalExp);
break;
case gprank_continuesused:
lua_pushnumber(L, gprank->continuesUsed);
break;
case gprank_prisons:
lua_pushnumber(L, gprank->prisons);
break;
case gprank_totalprisons:
lua_pushnumber(L, gprank->totalPrisons);
break;
case gprank_rings:
lua_pushnumber(L, gprank->rings);
break;
case gprank_totalrings:
lua_pushnumber(L, gprank->totalRings);
break;
case gprank_specialwon:
lua_pushboolean(L, gprank->specialWon);
break;
case gprank_scoreposition:
lua_pushnumber(L, gprank->scorePosition);
break;
case gprank_scoregppoints:
lua_pushnumber(L, gprank->scoreGPPoints);
break;
case gprank_scoreexp:
lua_pushnumber(L, gprank->scoreExp);
break;
case gprank_scoreprisons:
lua_pushnumber(L, gprank->scorePrisons);
break;
case gprank_scorerings:
lua_pushnumber(L, gprank->scoreRings);
break;
case gprank_scorecontinues:
lua_pushnumber(L, gprank->scoreContinues);
break;
case gprank_scoretotal:
lua_pushnumber(L, gprank->scoreTotal);
break;
case gprank_numlevels:
lua_pushnumber(L, gprank->numLevels);
break;
case gprank_levels:
lua_createtable(L, ((grandprixinfo.cup->numlevels) + (grandprixinfo.cup->numbonus)), 0);
for (size_t i = 0; i < ((grandprixinfo.cup->numlevels) + (grandprixinfo.cup->numbonus)); i++)
{
LUA_PushUserdata(L, &gprank->levels[i], META_GPRANKLEVEL);
lua_rawseti(L, -2, 1 + i);
}
break;
default:
return RNOFIELDGR;
}
return 1;
}
static int gprank_set(lua_State *L)
{
return luaL_error(L, LUA_QL("gprank_t") " struct cannot be edited by Lua.");
}
static int gprank_level_get(lua_State *L)
{
gpRank_level_t *gprank_level = *((gpRank_level_t **)luaL_checkudata(L, 1, META_GPRANKLEVEL));
enum gprank_level field = luaL_checkoption(L, 2, gprank_level_opt[0], gprank_level_opt);
if (!gprank_level)
{
switch (field)
{
case gprank_level_valid:
lua_pushboolean(L, false);
return 1;
default:
return LUA_ErrInvalid(L, "gprank_level_t");
}
}
switch (field)
{
case gprank_level_valid:
lua_pushboolean(L, true);
break;
case gprank_level_id:
lua_pushnumber(L, gprank_level->id);
break;
case gprank_level_event:
lua_pushnumber(L, gprank_level->event);
break;
case gprank_level_time:
lua_pushnumber(L, gprank_level->time);
break;
case gprank_level_totalexp:
lua_pushnumber(L, gprank_level->totalExp);
break;
case gprank_level_totalprisons:
lua_pushnumber(L, gprank_level->totalPrisons);
break;
case gprank_level_continues:
lua_pushnumber(L, gprank_level->continues);
break;
case gprank_level_perplayer:
lua_createtable(L, grandprixinfo.rank.numPlayers, 0);
for (size_t i = 0; i < grandprixinfo.rank.numPlayers; i++)
{
LUA_PushUserdata(L, &gprank_level->perPlayer[i], META_GPRANKLEVELPERPLAYER);
lua_rawseti(L, -2, 1 + i);
}
break;
default:
return RNOFIELDGRL;
}
return 1;
}
static int gprank_level_set(lua_State *L)
{
return luaL_error(L, LUA_QL("gprank_level_t") " struct cannot be edited by Lua.");
}
static int gprank_level_perplayer_get(lua_State *L)
{
// "perplaya" to avoid shadowed declaration
gpRank_level_perplayer_t *gprank_level_perplaya = *((gpRank_level_perplayer_t **)luaL_checkudata(L, 1, META_GPRANKLEVELPERPLAYER));
enum gprank_level_perplayer field = luaL_checkoption(L, 2, gprank_level_perplayer_opt[0], gprank_level_perplayer_opt);
if (!gprank_level_perplaya)
{
switch (field)
{
case gprank_level_perplayer_valid:
lua_pushboolean(L, false);
return 1;
default:
return LUA_ErrInvalid(L, "gprank_level_perplayer_t");
}
}
switch (field)
{
case gprank_level_perplayer_valid:
lua_pushboolean(L, true);
break;
case gprank_level_perplayer_position:
lua_pushnumber(L, gprank_level_perplaya->position);
break;
case gprank_level_perplayer_rings:
lua_pushnumber(L, gprank_level_perplaya->rings);
break;
case gprank_level_perplayer_exp:
lua_pushnumber(L, gprank_level_perplaya->exp);
break;
case gprank_level_perplayer_prisons:
lua_pushnumber(L, gprank_level_perplaya->prisons);
break;
case gprank_level_perplayer_gotspecialprize:
lua_pushboolean(L, gprank_level_perplaya->gotSpecialPrize);
break;
case gprank_level_perplayer_grade:
lua_pushnumber(L, gprank_level_perplaya->grade);
break;
default:
return RNOFIELDGRLP;
}
return 1;
}
static int gprank_level_perplayer_set(lua_State *L)
{
return luaL_error(L, LUA_QL("gprank_level_perplayer") " struct cannot be edited by Lua.");
}
static int roundcue_get(lua_State *L)
{
enum roundcue field = luaL_checkoption(L, 2, roundcue_opt[0], roundcue_opt);
// Don't return any grandprixinfo values while not in a GP.
if (!roundqueue.size)
{
switch (field)
{
case roundcue_size:
lua_pushboolean(L, false);
return 1;
default:
return ROUNDCUEERR;
}
}
switch (field)
{
case roundcue_size:
lua_pushnumber(L, roundqueue.size);
break;
case roundcue_roundnum:
lua_pushnumber(L, roundqueue.roundnum);
break;
case roundcue_position:
lua_pushnumber(L, roundqueue.position);
break;
case roundcue_netcommunicate:
return UNIMPLEMENTED;
break;
case roundcue_writetextmap:
return UNIMPLEMENTED;
break;
case roundcue_snapshotmaps:
lua_pushboolean(L, roundqueue.snapshotmaps);
break;
case roundcue_entries:
lua_createtable(L, roundqueue.size, 0);
for (size_t i = 0; i < roundqueue.size; i++)
{
LUA_PushUserdata(L, &roundqueue.entries[i], META_ROUNDENTRY);
lua_rawseti(L, -2, 1 + i);
}
break;
default:
return RNOFIELDRQ;
}
return 1;
}
static int roundcue_set(lua_State *L)
{
return luaL_error(L, LUA_QL("roundqueue") " struct cannot be edited by Lua.");
}
static int roundentry_get(lua_State *L)
{
roundentry_t *roundentry = *((roundentry_t **)luaL_checkudata(L, 1, META_ROUNDENTRY));
enum roundentry field = luaL_checkoption(L, 2, roundentry_opt[0], roundentry_opt);
if (!roundentry)
{
switch (field)
{
case roundentry_valid:
lua_pushboolean(L, false);
return 1;
default:
return LUA_ErrInvalid(L, "roundentry_t");
}
}
switch (field)
{
case roundentry_valid:
lua_pushboolean(L, true);
break;
case roundentry_mapnum:
lua_pushnumber(L, roundentry->mapnum);
break;
case roundentry_gametype:
lua_pushnumber(L, roundentry->gametype);
break;
case roundentry_encore:
lua_pushboolean(L, roundentry->encore);
break;
case roundentry_rankrestricted:
lua_pushboolean(L, roundentry->rankrestricted);
break;
case roundentry_overridden:
lua_pushboolean(L, roundentry->overridden);
break;
default:
return RNOFIELDRE;
}
return 1;
}
static int roundentry_set(lua_State *L)
{
return luaL_error(L, LUA_QL("roundentry_t") " struct cannot be edited by Lua.");
}
#undef UNIMPLEMENTED
#undef RNOFIELDGP
#undef RNOFIELDCH
#undef RNOFIELDGR
#undef RNOFIELDGRL
#undef RNOFIELDGRLP
#undef RNOFIELDRQ
#undef RNOFIELDRE
#undef GPERR
#undef ROUNDCUEERR
static int lib_numCupheaders(lua_State *L)
{
lua_pushinteger(L, numkartcupheaders);
return 1;
}
// There was, in fact, a better thing to do here - thanks toaster
#define GETCUPERR UINT16_MAX
// copied and edited from G_MapNumber
static UINT16 LUA_GetCupByNum(UINT16 cupnum)
{
cupheader_t *checkcup;
// find by cup id
if (cupnum != GETCUPERR)
{
if (cupnum >= numkartcupheaders)
return GETCUPERR; // id outta range
for (checkcup = kartcupheaders; checkcup->id <= numkartcupheaders; checkcup = checkcup->next)
{
if (checkcup->id != cupnum)
continue;
else
break;
return GETCUPERR; // id invalid
}
return checkcup->id;
}
return GETCUPERR;
}
// copied and edited from G_MapNumber
static UINT16 LUA_GetCupByName(const char * name)
{
cupheader_t *checkcup;
UINT32 hash = quickncasehash(name, MAXCUPNAME);
// find by cup name/realname
for (checkcup = kartcupheaders; checkcup != NULL; checkcup = checkcup->next)
{
if (hash != checkcup->namehash)
continue;
if (strcasecmp(checkcup->name, name) != 0)
continue;
return checkcup->id;
}
return GETCUPERR;
}
static int lib_iterateCups(lua_State *L)
{
INT32 i = -1;
cupheader_t *tempcup = kartcupheaders;
if (lua_gettop(L) < 2)
{
lua_pushcfunction(L, lib_iterateCups);
return 1;
}
lua_settop(L, 2);
lua_remove(L, 1); // state is unused.
if (!lua_isnil(L, 1))
{
i = ((*((cupheader_t **)luaL_checkudata(L, 1, META_CUP)))->id) + 1;
}
else
i = 0;
for (tempcup = kartcupheaders; tempcup->id < numkartcupheaders; tempcup = tempcup->next)
{
if (tempcup->next == NULL)
break;
if (tempcup->id >= i)
break;
}
// cups are always valid, only added, never removed
if (i < numkartcupheaders)
{
LUA_PushUserdata(L, tempcup, META_CUP);
return 1;
}
return 0;
}
// Shamelessly copied and edited from lua_waypointslib.c (with thanks to JugadorXEI)
static int lib_getCupheader(lua_State *L)
{
const char *field;
size_t i;
cupheader_t *checkcup;
UINT16 getResult = GETCUPERR;
// find cup by id number
if (lua_type(L, 2) == LUA_TNUMBER)
{
i = luaL_checkinteger(L, 2);
if (i > numkartcupheaders)
return luaL_error(L, "cupheader_t id %d out of loaded range (0 - %d)", i, numkartcupheaders);
getResult = LUA_GetCupByNum(i);
if (getResult == GETCUPERR)
return luaL_error(L, "cupheader_t id %d invalid", i);
for (checkcup = kartcupheaders; checkcup->id < numkartcupheaders; checkcup = checkcup->next)
{
if (checkcup->id != getResult)
continue;
else
break;
return luaL_error(L, "cupheader_t id %d invalid (LUA_GetCupByNum failed?)", i);
}
LUA_PushUserdata(L, checkcup, META_CUP);
return 1;
}
field = luaL_checkstring(L, 2);
// special function iterate
if (fastcmp(field,"iterate"))
{
lua_pushcfunction(L, lib_iterateCups);
return 1;
}
if (lua_type(L, 2) == LUA_TSTRING)
{
getResult = LUA_GetCupByName(field);
if (getResult == GETCUPERR)
return luaL_error(L, "no cupheader_t with name %s", field);
}
// If, after all this...
if (getResult == GETCUPERR)
return luaL_error(L, "internal failure in lua_grandprixlib.c???");
for (checkcup = kartcupheaders; checkcup->id < numkartcupheaders; checkcup = checkcup->next)
{
if (checkcup->id != getResult)
continue;
else
break;
return luaL_error(L, "cupheader_t id %d invalid (LUA_GetCupByName failed?)", i);
}
LUA_PushUserdata(L, checkcup, META_CUP);
return 1;
}
int LUA_GrandPrixLib(lua_State *L)
{
lua_newuserdata(L, 0);
lua_createtable(L, 0, 2);
lua_pushcfunction(L, grandprix_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, grandprix_set);
lua_setfield(L, -2, "__newindex");
lua_setmetatable(L, -2);
lua_setglobal(L, "grandprixinfo");
lua_newuserdata(L, 0);
lua_createtable(L, 0, 2);
lua_pushcfunction(L, roundcue_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, roundcue_set);
lua_setfield(L, -2, "__newindex");
lua_setmetatable(L, -2);
lua_setglobal(L, "roundqueue");
luaL_newmetatable(L, META_CUP);
lua_pushcfunction(L, cup_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, cup_set);
lua_setfield(L, -2, "__newindex");
lua_pop(L,1);
luaL_newmetatable(L, META_GPRANK);
lua_pushcfunction(L, gprank_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, gprank_set);
lua_setfield(L, -2, "__newindex");
lua_pop(L,1);
luaL_newmetatable(L, META_GPRANKLEVEL);
lua_pushcfunction(L, gprank_level_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, gprank_level_set);
lua_setfield(L, -2, "__newindex");
lua_pop(L,1);
luaL_newmetatable(L, META_GPRANKLEVELPERPLAYER);
lua_pushcfunction(L, gprank_level_perplayer_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, gprank_level_perplayer_set);
lua_setfield(L, -2, "__newindex");
lua_pop(L,1);
luaL_newmetatable(L, META_ROUNDENTRY);
lua_pushcfunction(L, roundentry_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, roundentry_set);
lua_setfield(L, -2, "__newindex");
lua_pop(L,1);
lua_newuserdata(L, 0);
lua_createtable(L, 0, 2);
lua_pushcfunction(L, lib_getCupheader);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, lib_numCupheaders);
lua_setfield(L, -2, "__len");
lua_setmetatable(L, -2);
lua_setglobal(L, "cups");
return 0;
}

View file

@ -122,6 +122,12 @@ extern lua_State *gL;
#define META_ICECUBEVARS "ICECUBEVARS_T*"
#define META_SKYBOX "SKYBOX_T*"
#define META_CUP "CUPHEADER_T*"
#define META_GPRANK "GPRANK_T*"
#define META_GPRANKLEVEL "GPRANK_LEVEL_T*"
#define META_GPRANKLEVELPERPLAYER "GPRANK_LEVEL_PERPLAYER_T*"
#define META_ROUNDENTRY "ROUNDENTRY_T*"
boolean luaL_checkboolean(lua_State *L, int narg);
int LUA_EnumLib(lua_State *L);
@ -146,6 +152,7 @@ int LUA_BotVarsLib(lua_State *L);
int LUA_TerrainLib(lua_State *L);
int LUA_RespawnVarsLib(lua_State *L);
int LUA_WaypointLib(lua_State *L);
int LUA_GrandPrixLib(lua_State *L);
#ifdef __cplusplus
} // extern "C"

View file

@ -215,6 +215,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->oldposition);
else if (fastcmp(field,"positiondelay"))
lua_pushinteger(L, plr->positiondelay);
else if (fastcmp(field,"leaderpenalty"))
lua_pushinteger(L, plr->leaderpenalty);
else if (fastcmp(field,"teamposition"))
lua_pushinteger(L, plr->teamposition);
else if (fastcmp(field,"teamimportance"))
@ -255,6 +257,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->justbumped);
else if (fastcmp(field,"noebrakemagnet"))
lua_pushinteger(L, plr->noEbrakeMagnet);
else if (fastcmp(field,"wallspikedampen"))
lua_pushinteger(L, plr->wallSpikeDampen);
else if (fastcmp(field,"tumblebounces"))
lua_pushinteger(L, plr->tumbleBounces);
else if (fastcmp(field,"tumbleheight"))
@ -988,6 +992,8 @@ static int player_set(lua_State *L)
plr->oldposition = luaL_checkinteger(L, 3);
else if (fastcmp(field,"positiondelay"))
plr->positiondelay = luaL_checkinteger(L, 3);
else if (fastcmp(field,"leaderpenalty"))
plr->leaderpenalty = luaL_checkinteger(L, 3);
else if (fastcmp(field,"teamposition"))
plr->teamposition = luaL_checkinteger(L, 3);
else if (fastcmp(field,"teamimportance"))
@ -1033,6 +1039,8 @@ static int player_set(lua_State *L)
plr->justbumped = luaL_checkinteger(L, 3);
else if (fastcmp(field,"noebrakemagnet"))
plr->noEbrakeMagnet = luaL_checkinteger(L, 3);
else if (fastcmp(field,"wallspikedampen"))
plr->wallSpikeDampen = luaL_checkinteger(L, 3);
else if (fastcmp(field,"tumblebounces"))
plr->tumbleBounces = luaL_checkinteger(L, 3);
else if (fastcmp(field,"tumbleheight"))

View file

@ -66,6 +66,7 @@ static lua_CFunction liblist[] = {
LUA_TerrainLib, // t_splash_t, t_footstep_t, t_overlay_t, terrain_t
LUA_RespawnVarsLib, // respawnvars_t
LUA_WaypointLib, // waypoint_t
LUA_GrandPrixLib, // grandprixinfo, cupheader_t, gprank_t, skinrecord_t, etc.
NULL
};

View file

@ -117,6 +117,9 @@ void COM_Lua_f(void);
// Music: "No tune" error.
#define LUA_ErrNoTune(L, tune) luaL_error(L, "tune \"%s\" does not exist", tune)
// Music: "Stereo Mode" error.
#define LUA_ErrStereo(L, tune) luaL_error(L, "tune \"%s\" cannot be remapped (stereo mode)", tune)
// Deprecation warnings
// Shows once upon use. Then doesn't show again.
#define LUA_Deprecated(L,this_func,use_instead)\

View file

@ -229,7 +229,7 @@ EggTVData::Replay::Replay(Folder::Cache::ReplayRef& ref) : ref_(&ref)
const std::string_view str = info.title;
const std::size_t mid = str.find(kDelimiter);
title_ = Title(str.substr(0, mid), mid == srb2::String::npos ? "" : str.substr(mid + kDelimiter.size()));
title_ = Title(str.substr(0, mid), mid == std::string_view::npos ? "" : str.substr(mid + kDelimiter.size()));
//title_ = Title("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW", "WWWWWWWWWWWWWWWWWWWWWWWWWWW");
}

View file

@ -57,12 +57,12 @@ static void M_GonerHandStrain(INT32 ch)
static void M_GonerPhotosensitivity(INT32 ch)
{
if (ch == MA_YES)
{
CV_StealthSet(&cv_reducevfx, "Yes");
CV_StealthSet(&cv_screenshake, "Off");
CV_StealthSet(&cv_tilting, "Off");
}
// if (ch == MA_YES)
// {
// CV_StealthSet(&cv_reducevfx, "Yes");
// CV_StealthSet(&cv_screenshake, "Off");
// CV_StealthSet(&cv_tilting, "Off");
// }
#ifdef HANDSTRAIN
M_StartMessage("Hand strain warning",
@ -96,9 +96,12 @@ static void M_GonerAccessibilityTick(void)
"patterns.""\x80"" Listen to your body, and\n"
"stop playing if you feel unwell.\n"
"\n"
"There is a ""\x88""special mode""\x80"" to reduce some\n"
"visual effects. Would you like to turn it on?\n"
, &M_GonerPhotosensitivity, MM_YESNO, "Yes, reduce effects", "No thanks");
"There is a ""\x88""special mode""\x80""\n"
"to reduce some visual effects.\n"
"\n"
"You can turn it on within the\n"
"Profile Setup > Accessibility menu.\n"
, &M_GonerPhotosensitivity, MM_NOTHING, NULL, NULL);
return;
}

View file

@ -28,7 +28,7 @@ menuitem_t OPTIONS_DataAdvancedAddon[] =
{IT_STRING | IT_CVAR, "Matching", "Set where to check for the text pattern when looking up addons via name.",
NULL, {.cvar = &cv_addons_search_type}, 0, 0},
{IT_STRING | IT_CVAR, "Case Sensitivity", "Set whether to consider the case when searching for addons..",
{IT_STRING | IT_CVAR, "Case Sensitivity", "Set whether to consider the case when searching for addons.",
NULL, {.cvar = &cv_addons_search_case}, 0, 0},
};

View file

@ -1019,7 +1019,10 @@ static void M_HandleColorRotate(setup_player_t *p, UINT8 num)
{
if (p->skin >= 0)
{
p->color = SKINCOLOR_NONE;
if (p->color == SKINCOLOR_NONE)
p->color = PR_GetProfile(p->profilen)->color;
else
p->color = SKINCOLOR_NONE;
p->rotate = CSROTATETICS;
p->hitlag = true;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s
@ -1252,8 +1255,11 @@ static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num)
}
else if (M_MenuExtraPressed(num))
{
UINT16 profile_followercolor = PR_GetProfile(p->profilen)->followercolor;
if (p->followercolor == FOLLOWERCOLOR_MATCH)
p->followercolor = FOLLOWERCOLOR_OPPOSITE;
else if (p->followercolor == FOLLOWERCOLOR_OPPOSITE && profile_followercolor != FOLLOWERCOLOR_OPPOSITE && profile_followercolor != FOLLOWERCOLOR_MATCH)
p->followercolor = profile_followercolor;
else if (p->followercolor == SKINCOLOR_NONE)
p->followercolor = FOLLOWERCOLOR_MATCH;
else

View file

@ -266,6 +266,23 @@ menu_t PLAY_TAGhostsDef = {
NULL
};
// See also G_UpdateRecordReplays
const char *M_GetRecordMode(void)
{
if (cv_dummyspbattack.value)
{
return "spb-";
}
const INT32 skinid = R_SkinAvailableEx(cv_skin[0].string, false);
if (skinid >= 0 && (skins[skinid]->flags & SF_HIVOLT))
{
return "hivolt-";
}
return "";
}
void CV_SPBAttackChanged(void);
void CV_SPBAttackChanged(void)
{
@ -275,14 +292,12 @@ void CV_SPBAttackChanged(void)
{
// see also p_setup.c's P_LoadRecordGhosts
char *gpath = Z_StrDup(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(levellist.choosemap+1)));
const char *modeprefix = "";
const char *modeprefix = M_GetRecordMode();
UINT8 active;
if (!gpath)
return;
if (cv_dummyspbattack.value)
modeprefix = "spb-";
active = false;
PLAY_TimeAttack[ta_guest].status = IT_DISABLED;
@ -458,20 +473,7 @@ void M_ReplayTimeAttack(INT32 choice)
{
menudemo_t menudemo = {0};
const char *which = NULL;
const char *modeprefix = "";
if (cv_dummyspbattack.value)
{
modeprefix = "spb-";
}
else
{
const INT32 skinid = R_SkinAvailableEx(cv_skin[0].string, false);
if (skinid >= 0 && (skins[skinid]->flags & SF_HIVOLT))
{
modeprefix = "hivolt-";
}
}
const char *modeprefix = M_GetRecordMode();
switch (choice)
{
@ -528,20 +530,7 @@ static void M_WriteGuestReplay(INT32 ch)
gpath = Z_StrDup(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(levellist.choosemap+1)));
const char *modeprefix = "";
if (cv_dummyspbattack.value)
{
modeprefix = "spb-";
}
else
{
const INT32 skinid = R_SkinAvailableEx(cv_skin[0].string, false);
if (skinid >= 0 && (skins[skinid]->flags & SF_HIVOLT))
{
modeprefix = "hivolt-";
}
}
const char *modeprefix = M_GetRecordMode();
if (TA_GuestReplay_Str != NULL)
{
@ -597,20 +586,7 @@ void M_SetGuestReplay(INT32 choice)
break;
}
const char *modeprefix = "";
if (cv_dummyspbattack.value)
{
modeprefix = "spb-";
}
else
{
const INT32 skinid = R_SkinAvailableEx(cv_skin[0].string, false);
if (skinid >= 0 && (skins[skinid]->flags & SF_HIVOLT))
{
modeprefix = "hivolt-";
}
}
const char *modeprefix = M_GetRecordMode();
if (FIL_FileExists(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%sguest.lmp", srb2home, timeattackfolder, G_BuildMapName(levellist.choosemap+1), modeprefix)))
{
@ -626,7 +602,7 @@ void M_StartTimeAttack(INT32 choice)
{
char *gpath;
char nameofdemo[256];
const char *modeprefix = "";
const char *modeprefix = M_GetRecordMode();
(void)choice;
@ -649,16 +625,6 @@ void M_StartTimeAttack(INT32 choice)
{
encoremode = true; // guarantees short wipe
}
modeprefix = "spb-";
}
else
{
const INT32 skinid = R_SkinAvailableEx(cv_skin[0].string, false);
if (skinid >= 0 && (skins[skinid]->flags & SF_HIVOLT))
{
modeprefix = "hivolt-";
}
}
// DON'T SOFTLOCK

View file

@ -287,6 +287,97 @@ static void M_GPBackup(INT32 choice)
M_StartCup(UINT8_MAX);
}
static boolean M_IsCupQueueable(cupheader_t *cup)
{
levelsearch_t templevelsearch = levellist.levelsearch; // copy levellist so we don't mess with stuff I think
UINT16 ShownCount = 0;
UINT16 CupCount = 0;
UINT32 CheckGametype[2] = {TOL_RACE,TOL_BATTLE};
templevelsearch.cup = cup;
UINT8 e, i = 0;
for (e = 0; e < 2; e++)
{
templevelsearch.typeoflevel = CheckGametype[e];
ShownCount += M_CountLevelsToShowInList(&templevelsearch);
}
//CONS_Printf(M_GetText("ShownCount: %d\n"), ShownCount);
UINT16 checkmap = NEXTMAP_INVALID;
for (i = 0; i < CUPCACHE_SPECIAL; i++)
{
checkmap = templevelsearch.cup->cachedlevels[i];
if (checkmap == NEXTMAP_INVALID)
{
continue;
}
CupCount++;
}
//CONS_Printf(M_GetText("CupCount: %d\n"), CupCount);
if (ShownCount >= CupCount) // greater than is used to ensure multi-gametype maps don't accidentally cause this to return false.
return true;
return false;
}
static void M_CupStartResponse(INT32 ch)
{
if (ch != MA_YES)
return;
if (!(server || (IsPlayerAdmin(consoleplayer))))
return;
M_LevelConfirmHandler();
}
static void M_CupQueueResponse(INT32 ch)
{
if (ch != MA_YES)
return;
if (!(server || (IsPlayerAdmin(consoleplayer))))
return;
cupheader_t *queuedcup = cupgrid.builtgrid[CUPMENU_CURSORID];
M_CupQueueHandler(queuedcup);
S_StartSound(NULL, sfx_gshe2);
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (!netgame)
{
M_StartMessage("Cup Queue",
va(M_GetText(
"You just queued %s CUP.\n"
"\n"
"Do you want to start the\n"
"cup immediately?\n"
), queuedcup->realname
), &M_CupStartResponse, MM_YESNO,
"Here we go!",
"On second thought..."
);
}
else
{
M_StartMessage("Cup Queue",
va(M_GetText(
"You just queued %s CUP.\n"
"\n"
"Do you want to queue it\n"
"for everyone?\n"
), queuedcup->realname
), &M_CupStartResponse, MM_YESNO,
"Queue em up!",
"Not yet"
);
}
}
void M_CupSelectHandler(INT32 choice)
{
const UINT8 pid = 0;
@ -429,6 +520,63 @@ void M_CupSelectHandler(INT32 choice)
S_StartSound(NULL, sfx_s3k63);
}
}
// Queue a cup for match race and netgames. See levelselect.c for most of how this actually works.
else if (levellist.canqueue && M_MenuButtonPressed(pid, MBT_Z))
{
M_SetMenuDelay(pid);
if ((cupgrid.builtgrid[CUPMENU_CURSORID] == &dummy_lostandfound) || (cupgrid.builtgrid[CUPMENU_CURSORID] == NULL))
S_StartSound(NULL, sfx_gshe7);
else if (!M_IsCupQueueable(cupgrid.builtgrid[CUPMENU_CURSORID]))
{
S_StartSound(NULL, sfx_s3kb2);
M_StartMessage("Back to the Grand Prix!", "Can't queue a cup you haven't fully unlocked!", NULL, MM_NOTHING, NULL, NULL);
}
// Better to avoid any headaches here - pass the buck to the Extra button.
else if (roundqueue.size)
{
S_StartSound(NULL, sfx_s3kb2);
M_StartMessage("Queue is not empty!", "Clear the queue before trying to queue a cup!", NULL, MM_NOTHING, NULL, NULL);
return;
}
else
{
// We're not queueing Battle maps if we're in single-player Match Race.
if (!levellist.netgame && (cv_splitplayers.value == 1) && !netgame)
{
M_StartMessage("Cup Queue",
va(M_GetText(
"This will queue all Race courses in this cup.\n"
"\n"
"Any rounds already in the queue will be cleared out.\n"
"\n"
"Do you want to queue the cup?\n"
)), &M_CupQueueResponse, MM_YESNO,
"Let's do it!",
"Nah.");
}
else
{
M_StartMessage("Cup Queue",
va(M_GetText(
"This will queue the entire cup, including both Race and Battle courses.\n"
"\n"
"Any rounds already in the queue will be cleared out.\n"
"\n"
"Do you want to queue the cup?\n"
)), &M_CupQueueResponse, MM_YESNO,
"Let's do it!",
"Nah.");
}
}
}
else if (levellist.canqueue && M_MenuExtraPressed(pid))
{
M_ClearQueueHandler();
}
else if (M_MenuBackPressed(pid))
{
M_SetMenuDelay(pid);
@ -443,4 +591,6 @@ void M_CupSelectHandler(INT32 choice)
void M_CupSelectTick(void)
{
cupgrid.previewanim++;
// Shoving this here for cup queue purposes.
M_LevelSelectTick();
}

View file

@ -832,8 +832,10 @@ void M_LevelSelected(INT16 add, boolean menuupdate)
static void M_MenuQueueStopSend(INT32 ch)
{
(void)ch;
memset(&menuqueue, 0, sizeof(struct menuqueue));
menuqueue.clearing = false;
}
static void M_MenuQueueSelectedLocal(void)
@ -905,6 +907,92 @@ static void M_MenuQueueSelectedLocal(void)
}
}
// Copy-pasted and edited from G_GPCupIntoRoundQueue
void M_CupQueueHandler(cupheader_t *cup)
{
UINT8 i, levelindex = 0, bonusindex = 0;
UINT8 bonusmodulo = max(1, (cup->numlevels+1)/(cup->numbonus+1));
UINT16 cupLevelNum;
INT32 gtcheck;
// We shouldn't get to this point while there's rounds queued, but if we do, get outta there.
if (roundqueue.size)
{
return;
}
menuqueue.size = 0;
// Levels are added to the queue in the following pattern.
// For 5 Race rounds and 2 Bonus rounds, the most common case:
// race - race - BONUS - race - race - BONUS - race
// The system is flexible enough to permit other arrangements.
// However, we just want to keep the pacing even & consistent.
while (levelindex < cup->numlevels)
{
memset(menuqueue.entries+menuqueue.size, 0, sizeof(roundentry_t));
// Fill like two or three Race maps.
for (i = 0; i < bonusmodulo; i++)
{
cupLevelNum = cup->cachedlevels[levelindex];
if (cupLevelNum >= nummapheaders)
{
// Just skip the map if it's invalid.
continue;
}
if ((mapheaderinfo[cupLevelNum]->typeoflevel & TOL_RACE) == TOL_RACE)
{
gtcheck = GT_RACE;
}
else
{
gtcheck = mapheaderinfo[cupLevelNum]->typeoflevel;
}
menuqueue.entries[menuqueue.size].mapnum = cupLevelNum;
menuqueue.entries[menuqueue.size].gametype = gtcheck;
menuqueue.entries[menuqueue.size].encore = (cv_kartencore.value == 1);
menuqueue.size++;
levelindex++;
if (levelindex >= cup->numlevels)
break;
}
// Attempt to add an interstitial Battle round.
// If we're in singleplayer Match Race, just skip this.
if ((levelindex < cup->numlevels
&& bonusindex < cup->numbonus) && (levellist.netgame || (cv_splitplayers.value > 1) || netgame))
{
cupLevelNum = cup->cachedlevels[CUPCACHE_BONUS + bonusindex];
if (cupLevelNum < nummapheaders)
{
if ((mapheaderinfo[cupLevelNum]->typeoflevel & TOL_BATTLE) == TOL_BATTLE)
{
gtcheck = GT_BATTLE;
}
else
{
gtcheck = mapheaderinfo[cupLevelNum]->typeoflevel;
}
// In the case of Bonus rounds, we simply skip invalid maps.
menuqueue.entries[menuqueue.size].mapnum = cupLevelNum;
menuqueue.entries[menuqueue.size].gametype = gtcheck;
menuqueue.entries[menuqueue.size].encore = (cv_kartencore.value == 1);
menuqueue.size++;
}
bonusindex++;
}
}
}
boolean M_LevelSelectCupSwitch(boolean next, boolean skipones)
{
levelsearch_t templevelsearch = levellist.levelsearch;
@ -1002,6 +1090,40 @@ static void M_MenuQueueResponse(INT32 ch)
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
// Ripped out of LevelSelectHandler for use in cup queueing from cupselect.c
void M_LevelConfirmHandler(void)
{
// Starting immediately OR importing queue
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (!levellist.canqueue || !menuqueue.size)
{
M_LevelSelected(levellist.cursor, true);
}
else if (netgame)
{
menuqueue.anchor = roundqueue.size;
menuqueue.sending = 1;
M_StartMessage("Queueing Rounds",
va(M_GetText(
"Attempting to send %d Round%s...\n"
"\n"
"If this is taking longer than you\n"
"expect, exit out of this message.\n"
), menuqueue.size, (menuqueue.size == 1 ? "" : "s")
), &M_MenuQueueStopSend, MM_NOTHING,
NULL,
"This is taking too long..."
);
}
else
{
M_MenuQueueSelectedLocal();
}
}
static void M_ClearQueueResponse(INT32 ch)
{
@ -1012,18 +1134,56 @@ static void M_ClearQueueResponse(INT32 ch)
return;
S_StartSound(NULL, sfx_slip);
if (netgame)
if (!netgame)
memset(&roundqueue, 0, sizeof(struct roundqueue));
if (netgame && (roundqueue.size != 0))
{
if (roundqueue.size)
{
Handle_MapQueueSend(0, ROUNDQUEUE_CMD_CLEAR, false);
}
return;
menuqueue.clearing = true;
Handle_MapQueueSend(0, ROUNDQUEUE_CMD_CLEAR, false);
M_StartMessage("Clearing Rounds",
va(M_GetText(
"Attempting to clear %d Round%s...\n"
"\n"
"If this is taking longer than you\n"
"expect, exit out of this message.\n"
), roundqueue.size, (roundqueue.size == 1 ? "" : "s")
), &M_MenuQueueStopSend, MM_NOTHING,
NULL,
"This is taking too long..."
);
}
memset(&roundqueue, 0, sizeof(struct roundqueue));
}
// Ripped out of LevelSelectHandler for use in queue clearing from cupselect.c
void M_ClearQueueHandler(void)
{
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (menuqueue.size)
{
S_StartSound(NULL, sfx_shldls);
menuqueue.size--;
}
else if (roundqueue.size)
{
M_StartMessage("Queue Clearing",
va(M_GetText(
"There %s %d Round%s of play queued.\n"
"\n"
"Do you want to empty the queue?\n"
),
(roundqueue.size == 1 ? "is" : "are"),
roundqueue.size,
(roundqueue.size == 1 ? "" : "s")
), &M_ClearQueueResponse, MM_YESNO,
"Time to start fresh",
"Not right now"
);
}
}
void M_LevelSelectHandler(INT32 choice)
{
const UINT8 pid = 0;
@ -1074,38 +1234,9 @@ void M_LevelSelectHandler(INT32 choice)
if (M_MenuConfirmPressed(pid))
{
// Starting immediately OR importing queue
M_SetMenuDelay(pid);
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (!levellist.canqueue || !menuqueue.size)
{
M_LevelSelected(levellist.cursor, true);
}
else if (netgame)
{
menuqueue.anchor = roundqueue.size;
menuqueue.sending = 1;
M_StartMessage("Queueing Rounds",
va(M_GetText(
"Attempting to send %d Round%s...\n"
"\n"
"If this is taking longer than you\n"
"expect, exit out of this message.\n"
), menuqueue.size, (menuqueue.size == 1 ? "" : "s")
), &M_MenuQueueStopSend, MM_NOTHING,
NULL,
"This is taking too long..."
);
}
else
{
M_MenuQueueSelectedLocal();
}
M_LevelConfirmHandler();
}
else if (levellist.canqueue && M_MenuButtonPressed(pid, MBT_Z))
{
@ -1138,30 +1269,7 @@ void M_LevelSelectHandler(INT32 choice)
}
else if (levellist.canqueue && M_MenuExtraPressed(pid))
{
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (menuqueue.size)
{
S_StartSound(NULL, sfx_shldls);
menuqueue.size--;
}
else if (roundqueue.size)
{
M_StartMessage("Queue Clearing",
va(M_GetText(
"There %s %d Round%s of play queued.\n"
"\n"
"Do you want to empty the queue?\n"
),
(roundqueue.size == 1 ? "is" : "are"),
roundqueue.size,
(roundqueue.size == 1 ? "" : "s")
), &M_ClearQueueResponse, MM_YESNO,
"Time to start fresh",
"Not right now"
);
}
M_ClearQueueHandler();
}
else if (M_MenuBackPressed(pid))
{
@ -1176,9 +1284,20 @@ void M_LevelSelectHandler(INT32 choice)
void M_LevelSelectTick(void)
{
if (menuqueue.clearing)
{
if (roundqueue.size != 0)
return;
menuqueue.clearing = false;
if (!menuqueue.cupqueue)
M_StopMessage(MA_NONE);
else
menuqueue.cupqueue = false;
}
if (!menuqueue.sending)
return;
if ((menuqueue.sending <= menuqueue.size) // Sending
&& (roundqueue.size >= menuqueue.anchor)) // Didn't get it wiped
{

View file

@ -15,6 +15,7 @@
#include "../../d_netcmd.h"
#include "../../i_time.h"
#include "../../k_menu.h"
#include "../../hu_stuff.h"
#include "../../k_grandprix.h" // K_CanChangeRules
#include "../../m_cond.h"
#include "../../s_sound.h"
@ -125,6 +126,9 @@ void M_OpenPauseMenu(void)
pausemenu.openoffset.dist = 0;
pausemenu.closing = false;
// Fix specific input error regarding closing netgame chat with escape while a controller is connected (only on Windows?)
chat_keydown = false;
itemOn = currentMenu->lastOn = mpause_continue; // Make sure we select "RESUME GAME" by default
// Now the hilarious balancing act of deciding what options should be enabled and which ones shouldn't be!

View file

@ -39,7 +39,7 @@ menuitem_t PAUSE_PlaybackMenu[] =
{IT_CALL | IT_STRING, "Hide Menu", NULL, "M_PHIDE", {.routine = M_SelectableClearMenus}, 0, 0},
{IT_CALL | IT_STRING, "Restart", NULL, "M_PRSTRT", {.routine = M_PlaybackRewind}, 20, 0},
{IT_CALL | IT_STRING, "Rewind 5 seconds", NULL, "M_PREW", {.routine = M_PlaybackRewind}, 36, 0},
{IT_CALL | IT_STRING, "Rewind 10 seconds", NULL, "M_PREW", {.routine = M_PlaybackRewind}, 36, 0},
{IT_CALL | IT_STRING, "Pause", NULL, "M_PPAUSE", {.routine = M_PlaybackPause}, 52, 0},
{IT_CALL | IT_STRING, "Fast-Forward", NULL, "M_PFFWD", {.routine = M_PlaybackFastForward}, 68, 0},
{IT_CALL | IT_STRING, "Resume", NULL, "M_PRESUM", {.routine = M_PlaybackPause}, 52, 0},
@ -220,9 +220,9 @@ void M_PlaybackRewind(INT32 choice)
if (demo.simplerewind)
{
if (curleveltime > 5*TICRATE)
if (curleveltime > 10*TICRATE)
{
g_fast_forward = curleveltime - (5 * TICRATE);
g_fast_forward = curleveltime - (10 * TICRATE);
g_fast_forward_clock_stop = INFTICS; //I_GetTime() + 2 * TICRATE; -- maybe?
}
else

View file

@ -25,6 +25,7 @@ TuneManager g_tunes;
void Music_Init(void)
{
// Many tunes below now have their default songs set in Music_TuneReset. Check there first for changing those.
{
Tune& tune = g_tunes.insert("level");
@ -55,21 +56,21 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("battle_overtime", g_tunes.find("level"));
tune.song = "shwdwn";
tune.song = ""; // Music_TuneReset
tune.priority = 11;
}
{
Tune& tune = g_tunes.insert("battle_overtime_stress", g_tunes.find("battle_overtime"));
tune.song = "shwdn2";
tune.song = ""; // Music_TuneReset
tune.priority = 10;
}
{
Tune& tune = g_tunes.insert("grow");
tune.song = "kgrow";
tune.song = ""; // Music_TuneReset
tune.priority = 20;
tune.resume_fade_in = 200;
tune.use_level_volume = true;
@ -78,7 +79,7 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("invinc");
tune.song = "kinvnc";
tune.song = ""; // Music_TuneReset
tune.priority = 21;
tune.use_level_volume = true;
}
@ -86,7 +87,7 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("finish_silence");
tune.song = "";
tune.song = ""; // Music_TuneReset
tune.priority = 30;
}
@ -100,7 +101,7 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("comeon");
tune.song = "chalng";
tune.song = ""; // Music_TuneReset
tune.priority = 35;
tune.loop = false;
}
@ -116,7 +117,7 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("vote");
tune.song = "vote";
tune.song = ""; // Music_TuneReset
tune.priority = 50;
tune.credit = true;
}
@ -124,14 +125,14 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("vote_suspense");
tune.song = "voteea";
tune.song = ""; // Music_TuneReset
tune.priority = 51;
}
{
Tune& tune = g_tunes.insert("vote_end");
tune.song = "voteeb";
tune.song = ""; // Music_TuneReset
tune.priority = 52;
tune.loop = false;
}
@ -139,14 +140,14 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("wait");
tune.song = "WAIT2J";
tune.song = ""; // Music_TuneReset
tune.priority = 60;
}
{
Tune& tune = g_tunes.insert("title");
tune.song = "_title";
tune.song = ""; // Music_TuneReset
tune.priority = 100;
tune.resist = true;
}
@ -167,7 +168,7 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("credits_silence");
tune.song = "";
tune.song = ""; // Music_TuneReset
tune.priority = 100;
}
@ -175,7 +176,7 @@ void Music_Init(void)
Tune& tune = g_tunes.insert("credits");
tune.priority = 101;
tune.song = "_creds";
tune.song = ""; // Music_TuneReset
tune.credit = true;
}
@ -213,10 +214,12 @@ void Music_Init(void)
{
Tune& tune = g_tunes.insert("lawyer");
tune.song = "lawyer";
tune.song = ""; // Music_TuneReset
tune.priority = 35;
tune.loop = false;
}
Music_TuneReset();
}
@ -522,3 +525,21 @@ void Music_ResetLevelVolume(void)
{
g_tunes.level_volume(100, true);
}
void Music_TuneReset(void)
{
Music_Remap("battle_overtime", "shwdwn");
Music_Remap("battle_overtime_stress", "shwdn2");
Music_Remap("grow", "kgrow");
Music_Remap("invinc", "kinvnc");
Music_Remap("finish_silence", "");
Music_Remap("comeon", "chalng");
Music_Remap("vote", "vote");
Music_Remap("vote_suspense", "voteea");
Music_Remap("vote_end", "voteeb");
Music_Remap("wait", "WAIT2J");
Music_Remap("title", "_title");
Music_Remap("credits_silence", "");
Music_Remap("credits", "_creds");
Music_Remap("lawyer", "lawyer");
}

View file

@ -207,6 +207,10 @@ void Music_Tick(void);
// the music plays again when re-enabled.
void Music_Flip(void);
// Resets all non-dynamic tunes to default values.
// Keeps ACS music remapping from playing havoc after a map.
void Music_TuneReset(void);
#ifdef __cplusplus
} // extern "C"

View file

@ -19,6 +19,7 @@
#include "../r_main.h"
#include "../tables.h"
#include "../s_sound.h"
#include "../k_kart.h"
/* An object may not be visible on the same tic:
1) that it spawned
@ -85,7 +86,7 @@ bool award_target(mobj_t* mobj)
if (rebound_timer(mobj) < 1)
{
player->itemtype = KITEM_GACHABOM;
player->itemamount++;
K_AdjustPlayerItemAmount(player, 1);
if (player->roundconditions.gachabom_miser == 1)
player->roundconditions.gachabom_miser = 0;

View file

@ -612,7 +612,7 @@ Obj_GardenTopThrow (player_t *player)
}
if (player->itemamount > 0)
player->itemamount--;
K_AdjustPlayerItemAmount(player, -1);
if (player->itemamount <= 0)
player->itemtype = KITEM_NONE;

View file

@ -325,7 +325,7 @@ move_to_player (mobj_t *hyu)
// For first place only: cap hyudoro speed at 50%
// target player's kart speed
if (target->player && target->player->position == 1)
if (target->player && target->player->leaderpenalty)
{
const fixed_t normalspeed =
K_GetKartSpeed(target->player, false, false) / 2;
@ -582,7 +582,7 @@ hyudoro_patrol_hit_player
S_StartSound(toucher, sfx_s3k92);
/* do not make 1st place invisible */
if (player->position != 1)
if (player->leaderpenalty == 0)
{
player->hyudorotimer = hyudorotime;
}
@ -625,7 +625,7 @@ award_immediately (mobj_t *hyu)
if (player)
{
if (player->position == 1)
if (player->leaderpenalty)
{
return false;
}
@ -742,7 +742,7 @@ blend_hover_hyudoro (mobj_t *hyu)
/* 1st place: Hyudoro stack is unusable, so make a visual
indication */
if (player->position == 1)
if (player->leaderpenalty)
{
hyu->renderflags |= RF_MODULATE;
trail_glow(hyu);

View file

@ -325,10 +325,9 @@ void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, fixed_t dir)
{
th->color = orbinaut_owner(th)->player->skincolor;
const mobj_t *owner = orbinaut_owner(th);
const ffloor_t *rover = P_IsObjectFlipped(owner) ? owner->ceilingrover : owner->floorrover;
const boolean ownerwaterrun = K_WaterRun(orbinaut_owner(th));
if (dir >= 0 && rover && (rover->fofflags & FOF_SWIMMABLE))
if (dir >= 0 && ownerwaterrun)
{
// The owner can run on water, so we should too!
orbinaut_flags(th) |= ORBI_WATERSKI;

View file

@ -88,7 +88,7 @@ struct Shoe : Mobj
bool valid() const { return Mobj::valid(follow()) && follow()->valid() && Mobj::valid(chain()); }
Fixed minDist() const { return 200 * mapobjectscale; }
Fixed maxDist() const { return 800 * mapobjectscale; }
Fixed maxDist() const { return 500 * mapobjectscale; }
angle_t followAngle() const { return R_PointToAngle2(x, y, follow()->x, follow()->y); }
Fixed followDistance() const { return FixedHypot(x - follow()->x, y - follow()->y); }
@ -237,7 +237,7 @@ private:
}
}
thrust(a, 8 * mapobjectscale);
thrust(a, 10 * mapobjectscale);
Fixed maxSpeed = 32 * mapobjectscale;
Fixed speed = FixedHypot(momx, momy);

View file

@ -463,9 +463,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
player->itemtype = special->threshold;
if ((UINT16)(player->itemamount) + special->movecount > 255)
player->itemamount = 255;
K_SetPlayerItemAmount(player, 255);
else
player->itemamount += special->movecount;
K_AdjustPlayerItemAmount(player, special->movecount);
}
}
@ -1800,16 +1800,17 @@ void P_UpdateRemovedOrbital(mobj_t *target, mobj_t *inflictor, mobj_t *source)
{
if (target->target->hnext && !P_MobjWasRemoved(target->target->hnext))
K_KillBananaChain(target->target->hnext, inflictor, source);
target->target->player->itemamount = 0;
K_SetPlayerItemAmount(target->target->player, 0);
}
else if (target->target->player->itemamount)
target->target->player->itemamount--;
K_AdjustPlayerItemAmount(target->target->player, -1);
}
else if ((target->type == MT_ORBINAUT_SHIELD && target->target->player->itemtype == KITEM_ORBINAUT) // orbit items
|| (target->type == MT_JAWZ_SHIELD && target->target->player->itemtype == KITEM_JAWZ))
{
if (target->target->player->itemamount)
target->target->player->itemamount--;
K_AdjustPlayerItemAmount(target->target->player, -1);
if (target->lastlook != 0)
{
K_RepairOrbitChain(target);
@ -2206,15 +2207,15 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
if (target->threshold < 1 || target->threshold >= NUMKARTITEMS) // bruh moment prevention
{
player->itemtype = KITEM_SAD;
player->itemamount = 1;
K_SetPlayerItemAmount(player, 1);
}
else
{
player->itemtype = target->threshold;
if (K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE) // never give more than 1 shield
player->itemamount = 1;
K_SetPlayerItemAmount(player, 1);
else
player->itemamount = max(1, target->movecount);
K_SetPlayerItemAmount(player, max(1, target->movecount));
}
player->karthud[khud_itemblink] = TICRATE;
player->karthud[khud_itemblinkmode] = 0;
@ -3953,7 +3954,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
sfx = sfx_s3k45;
}
else if (player->hyudorotimer > 0)
;
{
clash = false;
}
else
{
invincible = false;

View file

@ -1378,12 +1378,12 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
if (g_tm.thing->z + g_tm.thing->height < thing->z)
return BMIT_CONTINUE; // underneath
if (g_tm.thing->player && g_tm.thing->player && g_tm.thing->player->tumbleBounces > 0)
if (g_tm.thing->player && g_tm.thing->player->tumbleBounces)
{
return BMIT_CONTINUE;
if (thing->type == MT_SPIKE || G_CompatLevel(0x0011))
return BMIT_CONTINUE;
}
if (!P_IsObjectOnGround(g_tm.thing) && g_tm.thing->momz * P_MobjFlip(g_tm.thing) < 0) // fell into it
else if (!P_IsObjectOnGround(g_tm.thing) && g_tm.thing->momz * P_MobjFlip(g_tm.thing) < 0) // fell into it
{
P_DamageMobj(g_tm.thing, thing, thing, 1, DMG_TUMBLE);
@ -1398,6 +1398,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
{
if (
thing->type == MT_WALLSPIKE
&& G_CompatLevel(0x0011)
&& g_tm.thing->health
&& g_tm.thing->player
&& (g_tm.thing->player->justbumped < bumptime-2)
@ -1856,6 +1857,7 @@ boolean P_IsLineTripWire(const line_t *ld)
return ld->tripwire;
}
static boolean P_UsingStepUp(mobj_t *thing)
{
if (thing->flags & MF_NOCLIP)

View file

@ -8120,9 +8120,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
mobj->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(player));
// Alright, let's just handle all the sfx down here
boolean not_invinc_or_grow = player->invincibilitytimer == 0 && player->growshrinktimer <= 0;
boolean not_perma_tripwireok = player->invincibilitytimer == 0 && player->growshrinktimer <= 0 && player->hyudorotimer == 0;
if (P_IsDisplayPlayer(player) && not_invinc_or_grow && not_respawning)
if (P_IsDisplayPlayer(player) && not_perma_tripwireok && not_respawning)
{
UINT8 MIN_VOLUME = 25;
UINT8 MAX_VOLUME = 75;
@ -8202,13 +8202,34 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
trans = NUMTRANSMAPS - trans;
if ((trans >= NUMTRANSMAPS) // not a valid visibility
|| (myspeed < (tripspeed - basespeed/2) && (leveltime & 1)) // < 150% flickering
|| (mobj->target->player->tripwirePass < TRIPWIRE_BOOST) // Not strong enough to make an aura
|| mobj->target->player->flamedash) // Flameshield dash
int invinc_rotation_delay = 2;
if (cv_reducevfx.value)
{
invinc_rotation_delay = 8;
}
boolean updatecolor = false;
if ((trans >= NUMTRANSMAPS) || mobj->target->player->flamedash || mobj->target->player->tripwirePass < TRIPWIRE_BOOST)
{
// never show for flameshield dash, below tripwire minimum or transparency invalid
mobj->renderflags &= ~RF_TRANSMASK;
mobj->renderflags |= RF_DONTDRAW;
}
else if (myspeed < (tripspeed - basespeed/2))
{
mobj->renderflags &= ~(RF_TRANSMASK|RF_DONTDRAW);
if (cv_reducevfx.value)
{
// < 150% make more transparent for reducevfx
mobj->renderflags |= RF_TRANS40;
}
else if (leveltime & 1)
{
// < 150% flickering normally
mobj->renderflags |= RF_DONTDRAW;
}
updatecolor = true;
}
else
{
boolean blastermode = (myspeed >= tripspeed) && (mobj->target->player->tripwirePass >= TRIPWIRE_BLASTER);
@ -8220,11 +8241,29 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
mobj->renderflags |= (mobj->target->renderflags & RF_DONTDRAW);
updatecolor = true;
if (blastermode == !(mobj->flags2 & MF2_AMBUSH))
{
mobj->flags2 ^= MF2_AMBUSH;
if (blastermode)
{
P_SetMobjState(mobj, (mobj->extravalue1) ? S_TRIPWIREBOOST_BLAST_BOTTOM : S_TRIPWIREBOOST_BLAST_TOP);
}
else
{
P_SetMobjState(mobj, (mobj->extravalue1) ? S_TRIPWIREBOOST_BOTTOM : S_TRIPWIREBOOST_TOP);
}
}
}
if (updatecolor)
{
if (mobj->target->player->invincibilitytimer > 0)
{
if (mobj->target->player->invincibilitytimer > itemtime+(2*TICRATE))
{
mobj->color = K_RainbowColor(leveltime / 2);
mobj->color = K_RainbowColor(leveltime / invinc_rotation_delay);
}
else
{
@ -8242,19 +8281,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
mobj->color = SKINCOLOR_NONE;
mobj->colorized = false;
}
if (blastermode == !(mobj->flags2 & MF2_AMBUSH))
{
mobj->flags2 ^= MF2_AMBUSH;
if (blastermode)
{
P_SetMobjState(mobj, (mobj->extravalue1) ? S_TRIPWIREBOOST_BLAST_BOTTOM : S_TRIPWIREBOOST_BLAST_TOP);
}
else
{
P_SetMobjState(mobj, (mobj->extravalue1) ? S_TRIPWIREBOOST_BOTTOM : S_TRIPWIREBOOST_TOP);
}
}
}
}
@ -8321,6 +8347,17 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
P_RemoveMobj(mobj);
return false;
}
// This invinc mobj flickers intensely, so don't draw it in reducevfx
if (cv_reducevfx.value && (mobj->renderflags & RF_DONTDRAW) == 0)
{
mobj->renderflags |= RF_DONTDRAW;
}
if (!cv_reducevfx.value && (mobj->renderflags & RF_DONTDRAW) != 0)
{
mobj->renderflags ^= RF_DONTDRAW;
}
P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
break;
case MT_BRAKEDRIFT:

View file

@ -455,6 +455,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].position);
WRITEUINT8(save->p, players[i].oldposition);
WRITEUINT8(save->p, players[i].positiondelay);
WRITEUINT8(save->p, players[i].leaderpenalty);
WRITEUINT8(save->p, players[i].teamposition);
WRITEUINT8(save->p, players[i].teamimportance);
WRITEUINT32(save->p, players[i].distancetofinish);
@ -479,6 +480,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].wipeoutslow);
WRITEUINT8(save->p, players[i].justbumped);
WRITEUINT8(save->p, players[i].noEbrakeMagnet);
WRITEUINT8(save->p, players[i].wallSpikeDampen);
WRITEUINT8(save->p, players[i].tumbleBounces);
WRITEUINT16(save->p, players[i].tumbleHeight);
WRITEUINT16(save->p, players[i].stunned);
@ -1140,6 +1142,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].position = READUINT8(save->p);
players[i].oldposition = READUINT8(save->p);
players[i].positiondelay = READUINT8(save->p);
players[i].leaderpenalty = READUINT8(save->p);
players[i].teamposition = READUINT8(save->p);
players[i].teamimportance = READUINT8(save->p);
players[i].distancetofinish = READUINT32(save->p);
@ -1164,6 +1167,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].wipeoutslow = READUINT8(save->p);
players[i].justbumped = READUINT8(save->p);
players[i].noEbrakeMagnet = READUINT8(save->p);
players[i].wallSpikeDampen = READUINT8(save->p);
players[i].tumbleBounces = READUINT8(save->p);
players[i].tumbleHeight = READUINT16(save->p);
players[i].stunned = READUINT16(save->p);

View file

@ -3375,6 +3375,23 @@ static boolean P_CheckLineSideTripWire(line_t *ld, int p)
terrain = K_GetTerrainForTextureNum(sda->midtexture);
tripwire = terrain && (terrain->flags & TRF_TRIPWIRE);
// If we are texture TRIPWIRE and have the ML_MIDTEXINVISWALL, the replace texture with TRIPWLOW
if (tripwire && (ld->flags & ML_MIDTEXINVISWALL)) // if we do backwards compat, update this to also swap for older custom maps without the flag
{
if (sda->midtexture == R_TextureNumForName("TRIPWIRE"))
{
sda->midtexture = R_TextureNumForName("TRIPWLOW");
}
else if (sda->midtexture == R_TextureNumForName("2RIPWIRE"))
{
sda->midtexture = R_TextureNumForName("2RIPWLOW");
}
else if (sda->midtexture == R_TextureNumForName("4RIPWIRE"))
{
sda->midtexture = R_TextureNumForName("4RIPWLOW");
}
}
if (tripwire)
{
// copy midtexture to other side
@ -7841,24 +7858,11 @@ static void P_LoadRecordGhosts(void)
{
// see also /menus/play-local-race-time-attack.c's M_PrepareTimeAttack
char *gpath;
const char *modeprefix = "";
const char *modeprefix = M_GetRecordMode();
INT32 i;
gpath = Z_StrDup(va("%s" PATHSEP "media" PATHSEP "replay" PATHSEP "%s" PATHSEP "%s", srb2home, timeattackfolder, G_BuildMapName(gamemap)));
if (encoremode)
{
modeprefix = "-spb";
}
else
{
const INT32 skinid = R_SkinAvailableEx(cv_skin[0].string, false);
if (skinid >= 0 && (skins[skinid]->flags & SF_HIVOLT))
{
modeprefix = "-hivolt";
}
}
enum
{
kTime = 1 << 0,
@ -7883,7 +7887,7 @@ static void P_LoadRecordGhosts(void)
auto add_ghosts = [gpath](const srb2::String& base, UINT8 bits)
{
auto load = [base](const char* suffix) { P_TryAddExternalGhost(fmt::format("{}-{}.lmp", base, suffix).c_str()); };
auto load = [base](const char* suffix) { P_TryAddExternalGhost(fmt::format("{}{}.lmp", base, suffix).c_str()); };
if (bits & kTime)
load("time-best");
@ -7901,7 +7905,7 @@ static void P_LoadRecordGhosts(void)
if (allGhosts)
{
for (i = 0; i < numskins; ++i)
add_ghosts(fmt::format("{}-{}{}", gpath, skins[i]->name, modeprefix), allGhosts);
add_ghosts(fmt::format("{}-{}-{}", gpath, skins[i]->name, modeprefix), allGhosts);
}
if (sameGhosts)
@ -7909,7 +7913,7 @@ static void P_LoadRecordGhosts(void)
INT32 skin = R_SkinAvailableEx(cv_skin[0].string, false);
if (skin < 0 || !R_SkinUsable(-1, skin, false))
skin = 0; // use default skin
add_ghosts(fmt::format("{}-{}{}", gpath, skins[skin]->name, modeprefix), sameGhosts);
add_ghosts(fmt::format("{}-{}-{}", gpath, skins[skin]->name, modeprefix), sameGhosts);
}
// Guest ghost
@ -9100,6 +9104,8 @@ void P_PostLoadLevel(void)
marathonmode = static_cast<marathonmode_t>(marathonmode & ~MA_INIT);
}
Music_TuneReset(); // Placed before ACS scripts to allow remaps to occur on level start.
ACS_RunLevelStartScripts();
LUA_HookInt(gamemap, HOOK(MapLoad));
@ -9565,8 +9571,8 @@ void Command_Platinums(void)
else
{
CONS_Printf(", %s (+%d:%02d:%02d)", stafftime.second.c_str(),
G_TicsToMinutes(stafftime.first - platinumtime, true),
G_TicsToSeconds(stafftime.first - platinumtime),
G_TicsToMinutes(stafftime.first - platinumtime, true),
G_TicsToSeconds(stafftime.first - platinumtime),
G_TicsToCentiseconds(stafftime.first - platinumtime));
}

View file

@ -2067,7 +2067,7 @@ static void K_HandleLapIncrement(player_t *player)
K_UpdateAllPlayerPositions(); // P_DoPlayerExit calls this
}
if (!G_TimeAttackStart() && player->laps == 1 && lapisfresh)
if (!G_TimeAttackStart() && !(gametyperules & GTR_ROLLINGSTART) && player->laps == 1 && lapisfresh)
{
if (rainbowstartavailable)
{

View file

@ -1330,7 +1330,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
if (grandprixinfo.gp == true
&& grandprixinfo.eventmode != GPEVENT_SPECIAL
&& player->bot == false && losing == false)
&& player->bot == false && losing == false && player->hudrings > 0)
{
const UINT8 lifethreshold = 20;
@ -4647,6 +4647,11 @@ void P_PlayerThink(player_t *player)
player->flashing--;
}
if (!player->flashing && !P_PlayerInPain(player))
{
player->wallSpikeDampen = 0;
}
if (player->nocontrol && player->nocontrol < UINT16_MAX)
{
player->nocontrol--;

View file

@ -429,6 +429,13 @@ boolean R_IsDebugLine(seg_t *line)
return false;
}
boolean R_ShouldFlipTripWire(const line_t *ld)
{
// Flip tripwire textures when they are unpegged
// so the energy flows downward instead of upward, matching collision behavior
return (ld->tripwire && !(ld->flags & ML_MIDPEG));
}
//
// R_AddLine
// Clips the given segment and adds any visible pieces to the line list.

View file

@ -61,6 +61,7 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
INT32 *ceilinglightlevel, boolean back);
boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back);
boolean R_IsDebugLine(seg_t *line);
boolean R_ShouldFlipTripWire(const line_t *ld);
INT32 R_GetPlaneLight(sector_t *sector, fixed_t planeheight, boolean underside);
void R_Prep3DFloors(sector_t *sector);

View file

@ -696,7 +696,7 @@ void R_RenderMaskedSegRange(drawseg_t *drawseg, INT32 x1, INT32 x2)
// are not stored per-column with post info in SRB2
if (textures[texnum]->holes)
{
if (textures[texnum]->flip & 2) // vertically flipped?
if ((textures[texnum]->flip & 2) || R_ShouldFlipTripWire(ldef)) // vertically flipped?
{
colfunc_2s = R_DrawFlippedMaskedColumn;
lengthcol = textures[texnum]->height;
@ -706,8 +706,16 @@ void R_RenderMaskedSegRange(drawseg_t *drawseg, INT32 x1, INT32 x2)
}
else
{
colfunc_2s = R_Render2sidedMultiPatchColumn; // render multipatch with no holes (no post_t info)
lengthcol = textures[texnum]->height;
if (R_ShouldFlipTripWire(ldef)) // Check for tripwire flip even for non-holey textures
{
colfunc_2s = R_DrawFlippedMaskedColumn;
lengthcol = textures[texnum]->height;
}
else
{
colfunc_2s = R_Render2sidedMultiPatchColumn; // render multipatch with no holes (no post_t info)
lengthcol = textures[texnum]->height;
}
}
maskedtexturecol = drawseg->maskedtexturecol;

View file

@ -358,6 +358,7 @@ boolean I_InitNetwork(void)
if (M_CheckParm("-server") || dedicated)
{
server = true;
connectedtodedicated = dedicated;
// If a number of clients (i.e. nodes) is specified, the server will wait for the clients
// to connect before starting.

View file

@ -2796,6 +2796,7 @@ void V_DrawStringScaled(
if (nodanceoverride)
{
dance = false;
cyoff = 0;
}
}
else if (c == V_STRINGDANCE)

View file

@ -4,7 +4,7 @@
// it's only for detection of the version the player is using so the MS can alert them of an update.
// Only set it higher, not lower, obviously.
// Note that we use this to help keep internal testing in check; this is why v2.0 is not version "2".
#define MODVERSION 9
#define MODVERSION 12
// Define this as a prerelease version suffix
#define BETAVERSION "RC6"
#define BETAVERSION "RC9"

View file

@ -2535,12 +2535,12 @@ int W_VerifyNMUSlumps(const char *filename, FILE *handle, boolean exit_on_error)
&& stricmp(&filename[strlen(filename) - 4], ".lua"))
{
status = W_VerifyWAD(handle, NMUSlist, false);
// repair file handle in this specific case
fseek(handle, 0, SEEK_SET);
}
}
// repair file handle so we don't have to open a new one
fseek(handle, 0, SEEK_SET);
if (status == -1)
W_InitFileError(filename, exit_on_error);

View file

@ -260,6 +260,7 @@ static inline VOID OpenTextConsole(void)
HANDLE ci, co;
const BOOL tco = M_CheckParm("-console") != 0;
dedicated = M_CheckParm("-dedicated") != 0;
connectedtodedicated = dedicated;
if (!(dedicated || tco))
return;
FreeConsole();

View file

@ -162,6 +162,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
boolean completed[MAXPLAYERS];
INT32 numplayersingame = 0;
boolean getmainplayer = false;
UINT32 topscore = 0, btopemeralds = 0;
// Initialize variables
if (rankingsmode > 1)
@ -190,6 +191,27 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
data.increase[i] = INT16_MIN;
continue;
}
// for getting the proper maximum value for score-to-EXP conversion
if (gametype == GT_BATTLE)
{
if ((&players[i])->roundscore > topscore)
{
topscore = (&players[i])->roundscore;
}
if (K_NumEmeralds(&players[i]) > btopemeralds)
{
btopemeralds = K_NumEmeralds(&players[i]); // necessary so non-emerald wins can still get max EXP if no one else is holding more emeralds
}
}
if (K_InRaceDuel() == true)
{
if (((UINT32)(&players[i])->duelscore) > topscore)
{
topscore = (&players[i])->duelscore;
}
}
if (!rankingsmode)
data.increase[i] = INT16_MIN;
@ -294,10 +316,33 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
if (powertype == PWRLV_DISABLED)
{
UINT8 pointgetters = numplayersingame + spectateGriefed;
UINT32 scoreconversion = 0;
UINT32 pscore = 0;
if (data.pos[data.numplayers] <= pointgetters)
// accept players that nocontest, but not bots
if (data.pos[data.numplayers] <= pointgetters &&
!((players[i].pflags & PF_NOCONTEST) && players[i].bot))
{
data.increase[i] = K_CalculateGPRankPoints((&players[i])->exp, data.pos[data.numplayers], pointgetters);
if (gametype == GT_BATTLE)
{
pscore = (&players[i])->roundscore + K_NumEmeralds(&players[i]);
scoreconversion = FixedRescale(pscore, 0, topscore + btopemeralds, Easing_Linear, EXP_MIN, EXP_MAX);
data.increase[i] = K_CalculateGPRankPoints(scoreconversion, data.pos[data.numplayers], pointgetters);
}
else
{
// For Duel scoring, convert duelscore into EXP.
if (K_InRaceDuel())
{
pscore = (&players[i])->duelscore;
scoreconversion = FixedRescale(pscore, 0, topscore, Easing_Linear, EXP_MIN, EXP_MAX);
data.increase[i] = K_CalculateGPRankPoints(scoreconversion, data.pos[data.numplayers], pointgetters);
}
else
{
data.increase[i] = K_CalculateGPRankPoints((&players[i])->exp, data.pos[data.numplayers], pointgetters);
}
}
if (data.winningteam != TEAM_UNASSIGNED)
{