From bd5542c182723b211a49b3382dbf26ec75171b1b Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sat, 5 Aug 2023 20:14:44 -0300 Subject: [PATCH] Implement adding new teams --- src/deh_lua.c | 29 ++++ src/deh_soc.c | 51 +++++- src/doomstat.h | 13 +- src/g_game.c | 81 +++++++-- src/g_game.h | 2 + src/hu_stuff.c | 12 +- src/lua_baselib.c | 41 ++++- src/lua_infolib.c | 371 ++++++++++++++++++++++++++++++++++++++++- src/lua_libs.h | 2 + src/lua_script.c | 30 +++- src/m_menu.c | 15 +- src/netcode/d_netcmd.c | 2 +- src/p_inter.c | 34 +++- src/p_saveg.c | 10 -- 14 files changed, 628 insertions(+), 65 deletions(-) diff --git a/src/deh_lua.c b/src/deh_lua.c index feb312095..3571212f7 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -131,6 +131,22 @@ static inline int lib_freeslot(lua_State *L) if (i == NUMCOLORFREESLOTS) CONS_Alert(CONS_WARNING, "Ran out of free skincolor slots!\n"); } + else if (fastcmp(type, "TEAM")) + { + UINT8 i; + for (i = 0; i < MAXTEAMS; i++) + if (!teamnames[i]) { + CONS_Printf("Team TEAM_%s allocated.\n",word); + teamnames[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL); + strcpy(teamnames[i],word); + lua_pushinteger(L, i); + numteams++; + r++; + break; + } + if (i == MAXTEAMS) + CONS_Alert(CONS_WARNING, "Ran out of free team slots!\n"); + } else if (fastcmp(type, "SPR2")) { // Search if we already have an SPR2 by that name... @@ -561,6 +577,19 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) } return luaL_error(L, "skincolor '%s' could not be found.\n", word); } + else if (fastncmp("TEAM_",word,5)) { + p = word+5; + for (i = 0; i < MAXTEAMS; i++) + { + if (!teamnames[i]) + break; + if (fastcmp(p, teamnames[i])) { + CacheAndPushConstant(L, word, i); + return 1; + } + } + return luaL_error(L, "team '%s' could not be found.\n", word); + } else if (fastncmp("GRADE_",word,6)) { p = word+6; diff --git a/src/deh_soc.c b/src/deh_soc.c index d37ff11cf..0130b9a7a 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -473,6 +473,16 @@ void readfreeslots(MYFILE *f) break; } } + else if (fastcmp(type, "TEAM")) + { + for (i = 0; i < MAXTEAMS; i++) + if (!teamnames[i]) { + teamnames[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL); + strcpy(teamnames[i],word); + numteams++; + break; + } + } else if (fastcmp(type, "SPR2")) { // Search if we already have an SPR2 by that name... @@ -1139,15 +1149,20 @@ void readgametype(MYFILE *f, INT32 num) INT32 i, j; char *gtname = gametypes[num].name; - char gtconst[32]; + char gtconst[MAXLINELEN]; char gtdescription[441]; + UINT8 teamcount = 0; + UINT8 teamlist[MAXTEAMS]; + UINT8 newgtleftcolor, newgtrightcolor; boolean has_desc_colors[2] = { false, false }; memset(gtconst, 0, sizeof(gtconst)); memset(gtdescription, 0, sizeof(gtconst)); + strcpy(gtdescription, "???"); + do { if (myfgets(s, MAXLINELEN, f)) @@ -1307,6 +1322,35 @@ void readgametype(MYFILE *f, INT32 num) gametypes[num].typeoflevel = tol; } } + // Teams + else if (fastcmp(word, "TEAMLIST")) + { + tmp = strtok(word2,","); + do { + if (teamcount == MAXTEAMS) + { + deh_warning("readgametype %s: too many teams\n", gtname); + break; + } + UINT8 team_id = TEAM_NONE; + for (i = 1; i < MAXTEAMS; i++) + { + if (!teamnames[i]) + break; + if (fasticmp(tmp, teamnames[i])) + { + team_id = i; + break; + } + } + if (team_id == TEAM_NONE) + deh_warning("readgametype %s: unknown team %s\n", gtname, tmp); + else + { + teamlist[teamcount++] = team_id; + } + } while((tmp = strtok(NULL,",")) != NULL); + } // This SOC probably provided gametype rules as words, instead of using the RULES keyword. // (For example, "NOSPECTATORSPAWN = TRUE") else @@ -1339,6 +1383,11 @@ void readgametype(MYFILE *f, INT32 num) if (has_desc_colors[1]) G_SetGametypeDescriptionRightColor(num, newgtrightcolor); + // Copy the teams + gametypes[num].teams.num = teamcount; + if (teamcount) + memcpy(gametypes[num].teams.list, teamlist, sizeof(teamlist[0]) * teamcount); + // Write the constant name. if (gametypes[num].constant_name == NULL) { diff --git a/src/doomstat.h b/src/doomstat.h index a0cc83c1a..5b30f4bb3 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -407,7 +407,7 @@ typedef struct { char *name; char *flag_name; - UINT8 flag; + UINT16 flag; UINT32 flag_mobj_type; UINT16 color; UINT16 weapon_color; @@ -418,6 +418,8 @@ typedef struct extern team_t teams[MAXTEAMS]; extern UINT8 numteams; +extern char *teamnames[MAXTEAMS]; + #define NUMGAMETYPEFREESLOTS 128 // Gametypes @@ -485,6 +487,12 @@ enum RANKINGS_RACE }; +typedef struct +{ + UINT8 num; + UINT8 list[MAXTEAMS]; +} teamlist_t; + typedef struct { char *name; @@ -495,8 +503,7 @@ typedef struct INT16 rankings_type; INT32 pointlimit; INT32 timelimit; - UINT8 numteams; - UINT8 teams[MAXTEAMS]; + teamlist_t teams; } gametype_t; extern gametype_t gametypes[NUMGAMETYPES]; diff --git a/src/g_game.c b/src/g_game.c index 5bf91c1b9..ad7866c8a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3460,8 +3460,10 @@ gametype_t gametypes[NUMGAMETYPES] = { // default settings for match: timelimit 10 mins, no pointlimit .timelimit = 10, .pointlimit = 0, - .numteams = 2, - .teams = { TEAM_RED, TEAM_BLUE } + .teams = { + .num = 2, + .list = { TEAM_RED, TEAM_BLUE } + } }, // GT_TAG { @@ -3494,8 +3496,10 @@ gametype_t gametypes[NUMGAMETYPES] = { // default settings for CTF: no timelimit, pointlimit 5 .timelimit = 0, .pointlimit = 5, - .numteams = 2, - .teams = { TEAM_RED, TEAM_BLUE } + .teams = { + .num = 2, + .list = { TEAM_RED, TEAM_BLUE } + } }, }; @@ -3520,6 +3524,8 @@ team_t teams[MAXTEAMS] = { } }; +char *teamnames[MAXTEAMS]; + static void G_InitTeams(void) { numteams = 3; @@ -3527,6 +3533,7 @@ static void G_InitTeams(void) teams[TEAM_NONE].name = Z_StrDup("None"); teams[TEAM_NONE].flag_name = Z_StrDup("Thingmabob"); + teamnames[TEAM_NONE] = Z_StrDup("NONE"); teams[TEAM_RED].name = Z_StrDup("Red"); teams[TEAM_RED].flag_name = Z_StrDup("Red Flag"); @@ -3534,6 +3541,7 @@ static void G_InitTeams(void) teams[TEAM_RED].icons[TEAM_ICON_FLAG] = Z_StrDup("RFLAGICO"); teams[TEAM_RED].icons[TEAM_ICON_GOT_FLAG] = Z_StrDup("GOTRFLAG"); teams[TEAM_RED].icons[TEAM_ICON_MISSING_FLAG] = Z_StrDup("NONICON2"); + teamnames[TEAM_RED] = Z_StrDup("RED"); teams[TEAM_BLUE].name = Z_StrDup("Blue"); teams[TEAM_BLUE].flag_name = Z_StrDup("Blue Flag"); @@ -3541,6 +3549,7 @@ static void G_InitTeams(void) teams[TEAM_BLUE].icons[TEAM_ICON_FLAG] = Z_StrDup("BFLAGICO"); teams[TEAM_BLUE].icons[TEAM_ICON_GOT_FLAG] = Z_StrDup("GOTBFLAG"); teams[TEAM_BLUE].icons[TEAM_ICON_MISSING_FLAG] = Z_StrDup("NONICON"); + teamnames[TEAM_BLUE] = Z_StrDup("BLUE"); G_UpdateTeamSelection(); } @@ -3567,11 +3576,20 @@ void G_UpdateTeamSelection(void) i++; } - for (UINT8 j = 1; j < teamsingame; j++, i++) + if (G_GametypeHasTeams()) { - UINT8 team = G_GetTeam(j); - dummyteam_cons_t[i].value = team; - dummyteam_cons_t[i].strvalue = teams[team].name; + for (UINT8 j = 1; j < teamsingame; j++, i++) + { + UINT8 team = G_GetTeam(j); + dummyteam_cons_t[i].value = team; + dummyteam_cons_t[i].strvalue = teams[team].name; + } + } + else + { + dummyteam_cons_t[i].value = 1; + dummyteam_cons_t[i].strvalue = "Playing"; + i++; } dummyteam_cons_t[i].value = 0; @@ -3579,6 +3597,7 @@ void G_UpdateTeamSelection(void) cv_dummyteam.defaultvalue = dummyteam_cons_t[0].strvalue; cv_dummyteam.value = 0; + cv_dummyteam.string = cv_dummyteam.defaultvalue; } // @@ -3590,9 +3609,9 @@ void G_SetGametype(INT16 gtype) gametyperules = gametypes[gametype].rules; if (G_GametypeHasTeams()) - teamsingame = gametypes[gametype].numteams + 1; + teamsingame = gametypes[gametype].teams.num + 1; else - teamsingame = 3; + teamsingame = 0; G_UpdateTeamSelection(); } @@ -3803,7 +3822,10 @@ boolean G_GametypeUsesCoopStarposts(void) // boolean G_GametypeHasTeams(void) { - return (gametyperules & GTR_TEAMS); + if (gametyperules & GTR_TEAMS) + return gametypes[gametype].teams.num > 0; + + return false; } // @@ -3884,10 +3906,10 @@ UINT32 G_TOLFlag(INT32 pgametype) UINT8 G_GetGametypeTeam(UINT8 gtype, UINT8 team) { - if (team == TEAM_NONE || team >= gametypes[gtype].numteams + 1) + if (team == TEAM_NONE || team >= gametypes[gtype].teams.num + 1) return TEAM_NONE; - return gametypes[gtype].teams[team - 1] % MAXTEAMS; + return gametypes[gtype].teams.list[team - 1] % MAXTEAMS; } UINT8 G_GetTeam(UINT8 team) @@ -3981,6 +4003,39 @@ boolean G_HasTeamIcon(UINT8 team, UINT8 icon_type) return true; } +void G_SetTeamIcon(UINT8 team, UINT8 icon_type, const char *icon) +{ + if (team >= numteams || icon_type >= TEAM_ICON_MAX) + return; + + Z_Free(teams[team].icons[icon_type]); + teams[team].icons[icon_type] = NULL; + if (icon) + teams[team].icons[icon_type] = Z_StrDup(icon); +} + +void G_FreeTeamData(UINT8 team) +{ + if (team >= numteams) + return; + + team_t *team_ptr = &teams[team]; + + if (team_ptr->name) + Z_Free(team_ptr->name); + if (team_ptr->flag_name) + Z_Free(team_ptr->flag_name); + + for (UINT8 i = 0; i < TEAM_ICON_MAX; i++) + { + Z_Free(team_ptr->icons[i]); + team_ptr->icons[i] = NULL; + } + + team_ptr->name = NULL; + team_ptr->flag_name = NULL; +} + /** Select a random map with the given typeoflevel flags. * If no map has those flags, this arbitrarily gives you map 1. * \param tolflags The typeoflevel flags to insist on. Other bits may diff --git a/src/g_game.h b/src/g_game.h index 0f5533f89..cb97de6ff 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -234,6 +234,8 @@ UINT16 G_GetTeamWeaponColor(UINT8 team); UINT16 G_GetTeamMissileColor(UINT8 team); const char *G_GetTeamIcon(UINT8 team, UINT8 icon_type); boolean G_HasTeamIcon(UINT8 team, UINT8 icon_type); +void G_SetTeamIcon(UINT8 team, UINT8 icon_type, const char *icon); +void G_FreeTeamData(UINT8 team); void G_Ticker(boolean run); boolean G_Responder(event_t *ev); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 8649cc550..4df833e54 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2294,13 +2294,13 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer) greycheck = greycheckdef; supercheck = supercheckdef; - if (tab[i].team == TEAM_RED) //red + if (tab[i].team == G_GetTeam(1)) //red { redplayers++; x = 14 + (BASEVIDWIDTH/2); y = (redplayers * 9) + 20; } - else if (tab[i].team == TEAM_BLUE) //blue + else if (tab[i].team == G_GetTeam(2)) //blue { blueplayers++; x = 14; @@ -2380,7 +2380,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer) if (players[tab[i].num].spectator) continue; //ignore them. - if (tab[i].team == TEAM_RED) //red + if (tab[i].team == G_GetTeam(1)) //red { if (redplayers++ > 8) { @@ -2388,7 +2388,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer) break; // don't make more loops than we need to. } } - else if (tab[i].team == TEAM_BLUE) //blue + else if (tab[i].team == G_GetTeam(2)) //blue { if (blueplayers++ > 8) { @@ -2419,14 +2419,14 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer) if (players[tab[i].num].spectator) continue; //ignore them. - if (tab[i].team == TEAM_RED) //red + if (tab[i].team == G_GetTeam(1)) //red { if (redplayers++ > 8) continue; x = 32 + (BASEVIDWIDTH/2); y = (redplayers * 16) + 16; } - else if (tab[i].team == TEAM_BLUE) //blue + else if (tab[i].team == G_GetTeam(2)) //blue { if (blueplayers++ > 8) continue; diff --git a/src/lua_baselib.c b/src/lua_baselib.c index e4aae50a8..824202273 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -156,6 +156,8 @@ static const struct { {META_SKINCOLOR, "skincolor_t"}, {META_COLORRAMP, "skincolor_t.ramp"}, {META_GAMETYPE, "gametype_t"}, + {META_TEAM, "team_t"}, + {META_TEAMLIST, "teamlist_t"}, {META_SPRITEINFO, "spriteinfo_t"}, {META_PIVOTLIST, "spriteframepivot_t[]"}, {META_FRAMEPIVOT, "spriteframepivot_t"}, @@ -3545,7 +3547,6 @@ static int lib_sResumeMusic(lua_State *L) // G_GAME //////////// -// Copypasted from lib_cvRegisterVar :] static int lib_gAddGametype(lua_State *L) { const char *k; @@ -3562,6 +3563,8 @@ static int lib_gAddGametype(lua_State *L) UINT8 newgtrightcolor = 54; INT16 newgtrankingstype = RANKINGS_DEFAULT; int newgtinttype = 0; + UINT8 teamcount = 0; + UINT8 teamlist[MAXTEAMS]; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one. @@ -3587,7 +3590,6 @@ static int lib_gAddGametype(lua_State *L) else if (lua_isstring(L, 2)) k = lua_tostring(L, 2); - // Sorry, no gametype rules as key names. if (i == 1 || (k && fasticmp(k, "name"))) { if (!lua_isstring(L, 3)) TYPEERROR("name", LUA_TSTRING) @@ -3632,6 +3634,36 @@ static int lib_gAddGametype(lua_State *L) if (!lua_isnumber(L, 3)) TYPEERROR("headerrightcolor", LUA_TNUMBER) newgtrightcolor = (UINT8)lua_tointeger(L, 3); + } else if (i == 12 || (k && fasticmp(k, "teams"))) { + if (lua_istable(L, 3)) + { + lua_pushnil(L); + + while (lua_next(L, 3)) { + lua_Integer idx = luaL_checkinteger(L, -2) - 1; + if (idx >= 0 && idx < MAXTEAMS) + { + int team_index = luaL_checkinteger(L, -1); + if (team_index < 0 || team_index >= numteams) + luaL_error(L, "team index %d out of range (0 - %d)", team_index, numteams-1); + + teamlist[idx] = (UINT8)team_index; + + if ((lua_Integer)teamcount < idx + 1) + teamcount = (UINT8)idx + 1; + } + + lua_pop(L, 1); + } + } + else if (lua_isuserdata(L, 3)) + { + teamlist_t *tl = *((teamlist_t **)luaL_checkudata(L, 3, META_TEAMLIST)); + teamcount = tl->num; + memcpy(teamlist, tl->list, sizeof(teamlist[0]) * teamcount); + } + else + TYPEERROR("teams", LUA_TTABLE) // Key name specified } else if ((!i) && (k && fasticmp(k, "headercolor"))) { if (!lua_isnumber(L, 3)) @@ -3667,6 +3699,11 @@ static int lib_gAddGametype(lua_State *L) gt->name = gtname; + // Copy the teams + gt->teams.num = teamcount; + if (teamcount) + memcpy(gt->teams.list, teamlist, sizeof(teamlist[0]) * teamcount); + if (gtconst) G_AddGametypeConstant(gtype, gtconst); else diff --git a/src/lua_infolib.c b/src/lua_infolib.c index 08f7f8600..4f62e50cb 100644 --- a/src/lua_infolib.c +++ b/src/lua_infolib.c @@ -1933,7 +1933,8 @@ enum gametype_e gametype_intermission_type, gametype_rankings_type, gametype_pointlimit, - gametype_timelimit + gametype_timelimit, + gametype_teams }; const char *const gametype_opt[] = { @@ -1944,6 +1945,7 @@ const char *const gametype_opt[] = { "rankings_type", "point_limit", "time_limit", + "teams", NULL, }; @@ -1980,6 +1982,11 @@ static int gametype_get(lua_State *L) case gametype_timelimit: lua_pushinteger(L, gt->timelimit); break; + case gametype_teams: + LUA_PushUserdata(L, >->teams, META_TEAMLIST); + break; + default: + return luaL_error(L, LUA_QL("gametype_t") " has no field named " LUA_QS, gametype_opt[field]); } return 1; } @@ -1989,6 +1996,8 @@ static int gametype_set(lua_State *L) gametype_t *gt = *((gametype_t **)luaL_checkudata(L, 1, META_GAMETYPE)); enum gametype_e field = Lua_optoption(L, 2, -1, gametype_fields_ref); + if (!lua_lumploading) + return luaL_error(L, "Do not alter gametype data from within a hook or coroutine!"); if (hud_running) return luaL_error(L, "Do not alter gametype data in HUD rendering code!"); if (hook_cmd_running) @@ -1997,6 +2006,8 @@ static int gametype_set(lua_State *L) I_Assert(gt != NULL); I_Assert(gt >= gametypes); + INT16 gametype_id = gt - gametypes; + switch (field) { case gametype_name: @@ -2022,6 +2033,52 @@ static int gametype_set(lua_State *L) case gametype_timelimit: gt->timelimit = luaL_checkinteger(L, 3); break; + case gametype_teams: + if (lua_istable(L, 3)) + { + gt->teams.num = 0; + memset(gt->teams.list, TEAM_NONE, sizeof(gt->teams.list)); + + lua_pushnil(L); + + while (lua_next(L, 3)) { + lua_Integer i = luaL_checkinteger(L, -2) - 1; + if (i >= 0 && i < MAXTEAMS) + { + int team_index = luaL_checkinteger(L, -1); + if (team_index < 0 || team_index >= numteams) + luaL_error(L, "team index %d out of range (0 - %d)", team_index, numteams-1); + + gt->teams.list[i] = (UINT8)team_index; + + if ((lua_Integer)gt->teams.num < i + 1) + gt->teams.num = (UINT8)i + 1; + } + + lua_pop(L, 1); + } + + if (gametype == gametype_id) + { + teamsingame = gt->teams.num; + G_UpdateTeamSelection(); + } + } + else + { + teamlist_t *teamlist = *((teamlist_t **)luaL_checkudata(L, 3, META_TEAMLIST)); + + memcpy(>->teams, teamlist, sizeof(teamlist_t)); + + if (gametype == gametype_id) + { + teamsingame = gt->teams.num; + G_UpdateTeamSelection(); + } + } + break; + default: + return luaL_error(L, LUA_QL("gametype_t") " has no field named " LUA_QS, gametype_opt[field]); } return 0; } @@ -2037,6 +2094,313 @@ static int gametype_num(lua_State *L) return 1; } +/////////// +// TEAMS // +/////////// + +enum team_e +{ + team_name, + team_flag_name, + team_flag, + team_flag_mobj_type, + team_color, + team_weapon_color, + team_missile_color, + team_icon, + team_icon_flag, + team_icon_got_flag, + team_icon_missing_flag +}; + +const char *const team_opt[] = { + "name", + "flag_name", + "flag", + "flag_mobj_type", + "color", + "weapon_color", + "missile_color", + "icon", + "flag_icon", + "captured_flag_icon", + "missing_flag_icon", + NULL, +}; + +static int team_fields_ref = LUA_NOREF; + +static int lib_getTeams(lua_State *L) +{ + INT16 i; + lua_remove(L, 1); + + i = luaL_checkinteger(L, 1); + if (i < 0 || i >= numteams) + return luaL_error(L, "teams[] index %d out of range (0 - %d)", i, numteams-1); + LUA_PushUserdata(L, &teams[i], META_GAMETYPE); + return 1; +} + +static int set_team_field(lua_State *L, team_t *team, enum team_e field) +{ + switch (field) + { + case team_name: + Z_Free(team->name); + team->name = Z_StrDup(luaL_checkstring(L, 3)); + G_UpdateTeamSelection(); + break; + case team_flag_name: + Z_Free(team->flag_name); + team->flag_name = Z_StrDup(luaL_checkstring(L, 3)); + break; + case team_flag: + team->flag = (UINT16)luaL_checkinteger(L, 3); + break; + case team_flag_mobj_type: + { + mobjtype_t type = luaL_checkinteger(L, 3); + if (type >= NUMMOBJTYPES) + { + luaL_error(L, "mobj type %d out of range (0 - %d)", type, NUMMOBJTYPES-1); + return 0; + } + team->flag_mobj_type = type; + break; + } + case team_color: + { + UINT16 newcolor = (UINT16)luaL_checkinteger(L, 3); + if (newcolor >= numskincolors) + { + luaL_error(L, "skincolor %d out of range (0 - %d).", newcolor, numskincolors-1); + return 0; + } + team->color = newcolor; + break; + } + case team_weapon_color: + { + UINT16 newcolor = (UINT16)luaL_checkinteger(L, 3); + if (newcolor >= numskincolors) + { + luaL_error(L, "skincolor %d out of range (0 - %d).", newcolor, numskincolors-1); + return 0; + } + team->weapon_color = newcolor; + break; + } + case team_missile_color: + { + UINT16 newcolor = (UINT16)luaL_checkinteger(L, 3); + if (newcolor >= numskincolors) + { + luaL_error(L, "skincolor %d out of range (0 - %d).", newcolor, numskincolors-1); + return 0; + } + team->missile_color = newcolor; + break; + } + case team_icon: + G_SetTeamIcon(team - teams, TEAM_ICON, luaL_checkstring(L, 3)); + ST_LoadTeamIcons(); + break; + case team_icon_flag: + G_SetTeamIcon(team - teams, TEAM_ICON_FLAG, luaL_checkstring(L, 3)); + ST_LoadTeamIcons(); + break; + case team_icon_got_flag: + G_SetTeamIcon(team - teams, TEAM_ICON_GOT_FLAG, luaL_checkstring(L, 3)); + ST_LoadTeamIcons(); + break; + case team_icon_missing_flag: + G_SetTeamIcon(team - teams, TEAM_ICON_MISSING_FLAG, luaL_checkstring(L, 3)); + ST_LoadTeamIcons(); + break; + default: + return -1; + } + return 1; +} + +static int lib_setTeams(lua_State *L) +{ + UINT32 teamnum; + team_t *team; + lua_remove(L, 1); + { + teamnum = luaL_checkinteger(L, 1); + if (teamnum >= numteams) + return luaL_error(L, "teams[] index %d out of range (0 - %d)", teamnum, numteams-1); + team = &teams[teamnum]; + } + luaL_checktype(L, 2, LUA_TTABLE); + lua_remove(L, 1); + lua_settop(L, 1); + + if (hud_running) + return luaL_error(L, "Do not alter team data in HUD rendering code!"); + if (hook_cmd_running) + return luaL_error(L, "Do not alter team data in CMD building code!"); + + G_FreeTeamData(teamnum); + + memset(team, 0, sizeof(team_t)); + + lua_pushnil(L); + while (lua_next(L, 1)) { + const char *str = luaL_checkstring(L, 2); + int field = -1; + + for (int i = 0; team_opt[i]; i++) { + if (fastcmp(str, team_opt[i])) + { + field = i; + break; + } + } + + if (field != -1) + set_team_field(L, team, field); + else + luaL_error(L, LUA_QL("team_t") " has no field named " LUA_QS, str); + + lua_pop(L, 1); + } + return 0; +} + +// #teams -> numteams +static int lib_teamslen(lua_State *L) +{ + lua_pushinteger(L, numteams); + return 1; +} + +static int team_get(lua_State *L) +{ + team_t *team = *((team_t **)luaL_checkudata(L, 1, META_GAMETYPE)); + enum team_e field = Lua_optoption(L, 2, team_name, team_fields_ref); + + I_Assert(team != NULL); + I_Assert(team >= teams); + + switch (field) + { + case team_name: + lua_pushstring(L, team->name); + break; + case team_flag_name: + lua_pushstring(L, team->flag_name); + break; + case team_flag: + lua_pushinteger(L, team->flag); + break; + case team_flag_mobj_type: + lua_pushinteger(L, team->flag_mobj_type); + break; + case team_color: + lua_pushinteger(L, team->color); + break; + case team_weapon_color: + lua_pushinteger(L, team->weapon_color); + break; + case team_missile_color: + lua_pushinteger(L, team->missile_color); + break; + case team_icon: + if (G_HasTeamIcon(team - teams, TEAM_ICON)) + lua_pushstring(L, G_GetTeamIcon(team - teams, TEAM_ICON)); + else + lua_pushnil(L); + break; + case team_icon_flag: + if (G_HasTeamIcon(team - teams, TEAM_ICON_FLAG)) + lua_pushstring(L, G_GetTeamIcon(team - teams, TEAM_ICON_FLAG)); + else + lua_pushnil(L); + break; + case team_icon_got_flag: + if (G_HasTeamIcon(team - teams, TEAM_ICON_GOT_FLAG)) + lua_pushstring(L, G_GetTeamIcon(team - teams, TEAM_ICON_GOT_FLAG)); + else + lua_pushnil(L); + break; + case team_icon_missing_flag: + if (G_HasTeamIcon(team - teams, TEAM_ICON_MISSING_FLAG)) + lua_pushstring(L, G_GetTeamIcon(team - teams, TEAM_ICON_MISSING_FLAG)); + else + lua_pushnil(L); + break; + default: + return luaL_error(L, LUA_QL("team_t") " has no field named " LUA_QS, lua_tostring(L, 2)); + } + return 1; +} + +static int team_set(lua_State *L) +{ + team_t *team = *((team_t **)luaL_checkudata(L, 1, META_GAMETYPE)); + enum team_e field = Lua_optoption(L, 2, -1, team_fields_ref); + + if (!lua_lumploading) + return luaL_error(L, "Do not alter team data from within a hook or coroutine!"); + if (hud_running) + return luaL_error(L, "Do not alter team data in HUD rendering code!"); + if (hook_cmd_running) + return luaL_error(L, "Do not alter team data in CMD building code!"); + + I_Assert(team != NULL); + I_Assert(team >= teams); + + if (set_team_field(L, team, field) < 0) + return luaL_error(L, LUA_QL("team_t") " has no field named " LUA_QS, lua_tostring(L, 2)); + + return 0; +} + +static int team_num(lua_State *L) +{ + team_t *team = *((team_t **)luaL_checkudata(L, 1, META_GAMETYPE)); + + I_Assert(team != NULL); + I_Assert(team >= teams); + + lua_pushinteger(L, team-teams); + return 1; +} + +static int teamlist_len(lua_State *L) +{ + teamlist_t *teamlist = *((teamlist_t **)luaL_checkudata(L, 1, META_TEAMLIST)); + lua_pushinteger(L, teamlist->num); + return 1; +} + +static int teamlist_get(lua_State *L) +{ + teamlist_t *teamlist = *((teamlist_t **)luaL_checkudata(L, 1, META_TEAMLIST)); + int i = luaL_checkinteger(L, 2); + if (i < 0 || i > teamlist->num) + return luaL_error(L, "list index %d out of range (1 - %d)", i, teamlist->num); + lua_pushinteger(L, teamlist->list[i - 1]); + return 1; +} + +static int teamlist_set(lua_State *L) +{ + teamlist_t *teamlist = *((teamlist_t **)luaL_checkudata(L, 1, META_TEAMLIST)); + int i = luaL_checkinteger(L, 2); + if (i < 0 || i > teamlist->num) + return luaL_error(L, "list index %d out of range (1 - %d)", i, teamlist->num); + int team = luaL_checkinteger(L, 3); + if (team < 0 || team >= numteams) + return luaL_error(L, "team index %d out of range (0 - %d)", i, numteams - 1); + teamlist->list[i - 1] = (UINT8)team; + return 0; +} + ////////////////////////////// // // Now push all these functions into the Lua state! @@ -2055,6 +2419,8 @@ int LUA_InfoLib(lua_State *L) LUA_RegisterUserdataMetatable(L, META_STATE, state_get, state_set, state_num); LUA_RegisterUserdataMetatable(L, META_MOBJINFO, mobjinfo_get, mobjinfo_set, mobjinfo_num); LUA_RegisterUserdataMetatable(L, META_GAMETYPE, gametype_get, gametype_set, gametype_num); + LUA_RegisterUserdataMetatable(L, META_TEAM, team_get, team_set, team_num); + LUA_RegisterUserdataMetatable(L, META_TEAMLIST, teamlist_get, teamlist_set, teamlist_len); LUA_RegisterUserdataMetatable(L, META_SKINCOLOR, skincolor_get, skincolor_set, skincolor_num); LUA_RegisterUserdataMetatable(L, META_COLORRAMP, colorramp_get, colorramp_set, colorramp_len); LUA_RegisterUserdataMetatable(L, META_SFXINFO, sfxinfo_get, sfxinfo_set, sfxinfo_num); @@ -2064,6 +2430,7 @@ int LUA_InfoLib(lua_State *L) LUA_RegisterUserdataMetatable(L, META_LUABANKS, lib_getluabanks, lib_setluabanks, lib_luabankslen); mobjinfo_fields_ref = Lua_CreateFieldTable(L, mobjinfo_opt); + team_fields_ref = Lua_CreateFieldTable(L, team_opt); LUA_RegisterGlobalUserdata(L, "sprnames", lib_getSprname, NULL, lib_sprnamelen); LUA_RegisterGlobalUserdata(L, "spr2names", lib_getSpr2name, NULL, lib_spr2namelen); @@ -2071,6 +2438,8 @@ int LUA_InfoLib(lua_State *L) LUA_RegisterGlobalUserdata(L, "states", lib_getState, lib_setState, lib_statelen); LUA_RegisterGlobalUserdata(L, "mobjinfo", lib_getMobjInfo, lib_setMobjInfo, lib_mobjinfolen); LUA_RegisterGlobalUserdata(L, "gametypes", lib_getGametypes, NULL, lib_gametypeslen); + LUA_RegisterGlobalUserdata(L, "teams", lib_getTeams, lib_setTeams, lib_teamslen); + // LUA_RegisterGlobalUserdata(L, "gametypes", lib_getGametypes, NULL, lib_gametypeslen); LUA_RegisterGlobalUserdata(L, "skincolors", lib_getSkinColor, lib_setSkinColor, lib_skincolorslen); LUA_RegisterGlobalUserdata(L, "spriteinfo", lib_getSpriteInfo, lib_setSpriteInfo, lib_spriteinfolen); LUA_RegisterGlobalUserdata(L, "sfxinfo", lib_getSfxInfo, lib_setSfxInfo, lib_sfxlen); diff --git a/src/lua_libs.h b/src/lua_libs.h index 41e119fcc..8a6af9f8b 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -30,6 +30,8 @@ extern boolean ignoregameinputs; #define META_COLORRAMP "SKINCOLOR_T*RAMP" #define META_SPRITEINFO "SPRITEINFO_T*" #define META_GAMETYPE "GAMETYPE_T*" +#define META_TEAM "TEAM_T*" +#define META_TEAMLIST "TEAMLIST_T*" #define META_PIVOTLIST "SPRITEFRAMEPIVOT_T[]" #define META_FRAMEPIVOT "SPRITEFRAMEPIVOT_T*" diff --git a/src/lua_script.c b/src/lua_script.c index 40c6af3e0..084b9ce6d 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -214,10 +214,10 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushboolean(L, paused); return 1; } else if (fastcmp(word,"bluescore")) { - lua_pushinteger(L, teamscores[TEAM_BLUE]); + lua_pushinteger(L, teamscores[G_GetTeam(2)]); return 1; } else if (fastcmp(word,"redscore")) { - lua_pushinteger(L, teamscores[TEAM_RED]); + lua_pushinteger(L, teamscores[G_GetTeam(1)]); return 1; } else if (fastcmp(word,"timelimit")) { lua_pushinteger(L, cv_timelimit.value); @@ -226,16 +226,16 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushinteger(L, cv_pointlimit.value); return 1; } else if (fastcmp(word, "redflag")) { - LUA_PushUserdata(L, flagmobjs[TEAM_RED], META_MOBJ); + LUA_PushUserdata(L, flagmobjs[G_GetTeam(1)], META_MOBJ); return 1; } else if (fastcmp(word, "blueflag")) { - LUA_PushUserdata(L, flagmobjs[TEAM_BLUE], META_MOBJ); + LUA_PushUserdata(L, flagmobjs[G_GetTeam(2)], META_MOBJ); return 1; } else if (fastcmp(word, "rflagpoint")) { - LUA_PushUserdata(L, flagpoints[TEAM_RED], META_MAPTHING); + LUA_PushUserdata(L, flagpoints[G_GetTeam(1)], META_MAPTHING); return 1; } else if (fastcmp(word, "bflagpoint")) { - LUA_PushUserdata(L, flagpoints[TEAM_BLUE], META_MAPTHING); + LUA_PushUserdata(L, flagpoints[G_GetTeam(2)], META_MAPTHING); return 1; // begin map vars } else if (fastcmp(word,"spstage_start")) { @@ -271,6 +271,12 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"tutorialmode")) { lua_pushboolean(L, tutorialmode); return 1; + } else if (fastcmp(word,"numteams")) { + lua_pushinteger(L, max(numteams - 1, 0)); + return 1; + } else if (fastcmp(word,"teamsingame")) { + lua_pushinteger(L, max(teamsingame - 1, 0)); + return 1; // end map vars // begin CTF colors } else if (fastcmp(word,"skincolor_redteam")) { @@ -992,6 +998,7 @@ enum ARCH_MOUSE, ARCH_SKIN, ARCH_GAMETYPE, + ARCH_TEAM, ARCH_TEND=0xFF, }; @@ -1022,6 +1029,7 @@ static const struct { {META_MOUSE, ARCH_MOUSE}, {META_SKIN, ARCH_SKIN}, {META_GAMETYPE, ARCH_GAMETYPE}, + {META_TEAM, ARCH_TEAM}, {NULL, ARCH_NULL} }; @@ -1357,6 +1365,13 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex) WRITEUINT8(save_p, gt - gametypes); break; } + case ARCH_TEAM: + { + team_t *team = *((team_t **)lua_touserdata(gL, myindex)); + WRITEUINT8(save_p, ARCH_TEAM); + WRITEUINT8(save_p, team - teams); + break; + } default: WRITEUINT8(save_p, ARCH_NULL); return 2; @@ -1609,6 +1624,9 @@ static UINT8 UnArchiveValue(int TABLESINDEX) case ARCH_GAMETYPE: LUA_PushUserdata(gL, &gametypes[READUINT8(save_p)], META_GAMETYPE); break; + case ARCH_TEAM: + LUA_PushUserdata(gL, &teams[READUINT8(save_p)], META_TEAM); + break; case ARCH_TEND: return 1; } diff --git a/src/m_menu.c b/src/m_menu.c index 26366adb7..136e3e843 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -465,7 +465,7 @@ static CV_PossibleValue_t dummymares_cons_t[] = { {-1, "END"}, {0,"Overall"}, {1,"Mare 1"}, {2,"Mare 2"}, {3,"Mare 3"}, {4,"Mare 4"}, {5,"Mare 5"}, {6,"Mare 6"}, {7,"Mare 7"}, {8,"Mare 8"}, {0,NULL} }; -CV_PossibleValue_t dummyteam_cons_t[MAXTEAMS + 1]; +CV_PossibleValue_t dummyteam_cons_t[MAXTEAMS + 2]; consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDEN, dummyteam_cons_t, NULL); @@ -6954,18 +6954,7 @@ static void M_ConfirmTeamChange(INT32 choice) M_ClearMenus(true); - switch (cv_dummyteam.value) - { - case 0: - COM_ImmedExecute("changeteam spectator"); - break; - case 1: - COM_ImmedExecute("changeteam red"); - break; - case 2: - COM_ImmedExecute("changeteam blue"); - break; - } + COM_ImmedExecute(va("changeteam %d", cv_dummyteam.value)); } static void M_Options(INT32 choice) diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c index e9ec1dbd8..bf694425d 100644 --- a/src/netcode/d_netcmd.c +++ b/src/netcode/d_netcmd.c @@ -4208,7 +4208,7 @@ void D_GameTypeChanged(INT32 lastgametype) // When swapping to a gametype that supports spectators, // make everyone a spectator initially. // Averted with GTR_NOSPECTATORSPAWN. - if (!splitscreen && (G_GametypeHasSpectators())) + if (!splitscreen && G_GametypeHasSpectators()) { INT32 i; for (i = 0; i < MAXPLAYERS; i++) diff --git a/src/p_inter.c b/src/p_inter.c index 09ac61d0d..88baf3de6 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2283,8 +2283,20 @@ void P_CheckTimeLimit(void) } else { - //In team match and CTF, determining a tie is much simpler. =P - if (teamscores[TEAM_RED] == teamscores[TEAM_BLUE]) + boolean is_tied = true; + UINT32 lastscore = teamscores[G_GetTeam(1)]; + + for (UINT8 j = 2; j < teamsingame; j++) + { + if (teamscores[G_GetTeam(j)] != lastscore) + { + is_tied = false; + break; + } + lastscore = teamscores[G_GetTeam(j)]; + } + + if (is_tied) return; } } @@ -2304,7 +2316,8 @@ void P_CheckTimeLimit(void) */ void P_CheckPointLimit(void) { - INT32 i; + if (!server) + return; if (!cv_pointlimit.value) return; @@ -2315,19 +2328,22 @@ void P_CheckPointLimit(void) if (!(gametyperules & GTR_POINTLIMIT)) return; - // pointlimit is nonzero, check if it's been reached by this player if (G_GametypeHasTeams()) { - // Just check both teams - if ((UINT32)cv_pointlimit.value <= teamscores[TEAM_RED] || (UINT32)cv_pointlimit.value <= teamscores[TEAM_BLUE]) + for (UINT8 i = 1; i < teamsingame; i++) { - if (server) - D_SendExitLevel(false); + if (teamscores[G_GetTeam(i)] >= (UINT32)cv_pointlimit.value) + { + if (server) + D_SendExitLevel(false); + return; + } } } else { - for (i = 0; i < MAXPLAYERS; i++) + // pointlimit is nonzero, check if it's been reached by this player + for (INT32 i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; diff --git a/src/p_saveg.c b/src/p_saveg.c index f2b32f299..a92a0fa31 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4340,12 +4340,7 @@ static void P_NetArchiveMisc(boolean resending) WRITEUINT8(save_p, teamsingame); for (i = 0; i < MAXTEAMS; i++) - { WRITEUINT32(save_p, teamscores[i]); - WRITEUINT16(save_p, teams[i].color); - WRITEUINT16(save_p, teams[i].weapon_color); - WRITEUINT16(save_p, teams[i].missile_color); - } WRITEINT32(save_p, modulothing); @@ -4440,12 +4435,7 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) teamsingame = READUINT8(save_p); for (i = 0; i < MAXTEAMS; i++) - { teamscores[i] = READUINT32(save_p); - teams[i].color = READUINT16(save_p); - teams[i].weapon_color = READUINT16(save_p); - teams[i].missile_color = READUINT16(save_p); - } modulothing = READINT32(save_p);