Merge public master

This commit is contained in:
Eidolon 2025-10-20 14:36:42 -05:00
commit 131ce41657
15 changed files with 1523 additions and 66 deletions

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

@ -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;
@ -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();
}

View file

@ -4212,6 +4212,7 @@ boolean G_CheckDemoStatus(void)
if (modeattacking || demo.willsave)
{
demo.willsave = false;
if (demobuf.p)
{
G_SaveDemo();

View file

@ -5083,7 +5083,10 @@ void G_AfterIntermission(void)
return;
}
else if (demo.recording && (modeattacking || demo.willsave))
{
demo.willsave = false;
G_SaveDemo();
}
else if (demo.recording)
G_ResetDemoRecording();

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

@ -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

@ -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}
};
@ -3997,6 +4004,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));
@ -5276,6 +5361,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));
@ -5300,6 +5427,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));
@ -6792,7 +6944,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},
@ -6901,10 +7059,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},

969
src/lua_grandprixlib.c Normal file
View file

@ -0,0 +1,969 @@
// 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);
}
//return UNIMPLEMENTED;
break;
case cup_cachedlevels:
lua_createtable(L, ((cup->numlevels) + (cup->numbonus)), 0);
for (size_t i = 0; i < ((cup->numlevels) + (cup->numbonus)); i++)
{
lua_pushnumber(L, cup->cachedlevels[i]);
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

@ -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

@ -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

@ -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)
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

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